经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
WebGL之绘制三维地球
来源:cnblogs  作者:Jeff.Zhong  时间:2021/5/6 17:55:32  对本文有异议

通过Three.js也许可以很方便的展示出3D模型,但是你知道它是怎么一步一步从构建网格到贴图到最终渲染出3D模型的吗?现在我们直接使用底层的webgl加上一点点的数学知识就可以实现它。

本节实现的效果: WebGL三维地球
WebGL三维地球

内容大纲

  1. 构建网格

  2. 编写着色器

  3. 实现3D地球

构建网格

首先我们要建立球体的三维模型,三维网格模型包括如下属性(不熟悉请复习webgl教程):

  • 顶点(position)
  • 法线(normal)
  • 贴图坐标(uv)
  • 顶点索引(indices)

最后要构建出如下所示的经纬球模型

经纬球

首先可以从xy平面构建圆形,接着再从xz平面将圆形转化为圆球,这其中只需使用到三角函数而已,是不是非常简单。

  • 法线使用的是顶点坐标,因为法线与顶点其实方向是一致的
  • 顶点索引为6个点,是因为每个面由两个三角形构成
  • 贴图uv坐标不需要深度信息,它对应上贴图的xy坐标即可

下面就是构建网格模型的基本逻辑:

  1. const radius = 8;//半径
  2. const n = 20;//经纬度格数
  3. const position = [];//顶点
  4. const normal = [];//法线
  5. const texcoord = [];//uv坐标
  6. const indices = [];//顶点索引
  7. let x, y, z;
  8. for (let i = 0; i < n; i++) {
  9. const rad = Math.PI / n * i - Math.PI / 2;//从-90度开始计算
  10. const r = radius * Math.cos(rad);
  11. y = radius * Math.sin(rad);
  12. for (let j = 0; j < n; j++) {
  13. x = r * Math.sin(xRadian * j);
  14. z = r * Math.cos(xRadian * j);
  15. position.push(x, y, z);
  16. texcoord.push(j / n, i / n);
  17. normal.push(x, y, z); //顶点作为法线,法线从圆心360度放射
  18. const c = i * (n + 1) + j
  19. indices.push(c, c + 1, c + l + 1, c, c + l + 1, c + l);//平面的索引
  20. }
  21. }

编写着色器

和普通着色器相比,只是增加了uv坐标,uv直接通过顶点着色器差值透传到片段着色器即可,在片段着色器使用texture2D函数获取uv坐标对应的颜色,整体上也是比较基础。

  1. // 顶点着色器
  2. attribute vec4 aPosition;
  3. attribute vec4 aNormal;
  4. attribute vec2 aTexcoord;
  5. uniform mat4 modelMatrix;
  6. uniform mat4 vpMatrix;
  7. varying vec3 fragPos;
  8. varying vec3 fragNor;
  9. varying vec2 texcoord;
  10. void main() {
  11. gl_Position = vpMatrix * modelMatrix * aPosition;
  12. fragPos= vec3(modelMatrix * aPosition);
  13. fragNor = vec3(modelMatrix * aNormal);
  14. texcoord = aTexcoord;
  15. }
  16. // 片段着色器
  17. precision mediump float;
  18. uniform vec3 viewPos;
  19. uniform vec3 lightPos;
  20. uniform vec3 lightColor;
  21. uniform vec3 ambientColor;
  22. uniform sampler2D diffMap;
  23. varying vec3 fragPos;
  24. varying vec3 fragNor;
  25. varying vec2 texcoord;
  26. void main() {
  27. vec3 normal = normalize(fragNor);
  28. vec3 color = texture2D(diffMap, texcoord).rgb;
  29. // 光线方向
  30. vec3 lightDir = normalize(lightPos - fragPos);
  31. // 光线方向和法向量夹角
  32. float cosTheta = max(dot(lightDir, normal), 0.0);
  33. // 漫反射
  34. vec3 diffuse = lightColor * color * cosTheta;
  35. // 环境光
  36. // ...
  37. // 高光
  38. // ...
  39. gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
  40. }

实现3D地球

最后实现部分就和之前的webgl基本逻辑一致,不过要准备好地球贴图

地图

图片加载完将构建好的贴图sampler传入着色器即可,其他都是基础业务逻辑,不再详述,这样我们就将三维地球实现了

  1. //...
  2. const vpMatrix = m4.identity();
  3. const uniforms = {
  4. modelMatrix: m4.identity(),
  5. lightPos: [20, 0, -20],
  6. lightColor: [1, 1, 1],
  7. ambientColor: [0.5, 0.5, 0.5],
  8. };
  9. gl.clearColor(0.1, 0.1, 0.1, 1);
  10. gl.enable(gl.DEPTH_TEST);//深度测试
  11. gl.enable(gl.CULL_FACE);//背面剔除
  12. gl.viewport(0, 0, canvas.width, canvas.height); //设置绘图区域
  13. gl.useProgram(program.program);
  14. function animate() {
  15. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  16. m4.multiply(projection, m4.inverse(m4.lookAt(eye, [0, 0, 0], [0, 1, 0])), vpMatrix);
  17. setBuffersAndAttributes(gl, vao);
  18. setUniforms(program, { vpMatrix });
  19. drawBufferInfo(gl, vao);
  20. gl.bindVertexArray(null);
  21. requestAnimationFrame(animate);
  22. };
  23. //加载贴图后执行
  24. createTexture(gl, { src: '/img/earth.jpg', flipY: true }, texture => {
  25. uniforms.diffMap = texture;
  26. setUniforms(program, uniforms );
  27. animate();
  28. });

原文链接:http://www.cnblogs.com/edwardloveyou/p/14700684.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号