• WebGL之绘制三维地球


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

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

    内容大纲

    1. 构建网格

    2. 编写着色器

    3. 实现3D地球

    构建网格

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

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

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

    经纬球

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

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

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

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

    编写着色器

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

    // 顶点着色器
    attribute vec4 aPosition;
    attribute vec4 aNormal;
    attribute vec2 aTexcoord;
    uniform mat4 modelMatrix;
    uniform mat4 vpMatrix;
    varying vec3 fragPos;
    varying vec3 fragNor;
    varying vec2 texcoord;
    
    void main() {
        gl_Position = vpMatrix * modelMatrix * aPosition;
        fragPos= vec3(modelMatrix * aPosition);
        fragNor = vec3(modelMatrix * aNormal);
        texcoord = aTexcoord;
    }
    
    // 片段着色器
    precision mediump float;
    uniform vec3 viewPos;
    uniform vec3 lightPos;
    uniform vec3 lightColor;
    uniform vec3 ambientColor;
    uniform sampler2D diffMap;
    varying vec3 fragPos;
    varying vec3 fragNor;
    varying vec2 texcoord;
    
    void main() {
        vec3 normal = normalize(fragNor);
        vec3 color = texture2D(diffMap, texcoord).rgb;
    	
        // 光线方向
        vec3 lightDir = normalize(lightPos - fragPos);
        // 光线方向和法向量夹角
        float cosTheta = max(dot(lightDir, normal), 0.0);
        // 漫反射
        vec3 diffuse = lightColor * color * cosTheta;
    	
        // 环境光
        // ...
        // 高光
        // ...
    
        gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
    }
    

    实现3D地球

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

    地图

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

    //...
    const vpMatrix = m4.identity();
    const uniforms = {
       modelMatrix: m4.identity(),
       lightPos: [20, 0, -20],
       lightColor: [1, 1, 1],
       ambientColor: [0.5, 0.5, 0.5],
    };
    
    gl.clearColor(0.1, 0.1, 0.1, 1);
    gl.enable(gl.DEPTH_TEST);//深度测试
    gl.enable(gl.CULL_FACE);//背面剔除
    gl.viewport(0, 0, canvas.width, canvas.height); //设置绘图区域
    gl.useProgram(program.program);
    
    function animate() {
       gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
       m4.multiply(projection, m4.inverse(m4.lookAt(eye, [0, 0, 0], [0, 1, 0])), vpMatrix);
       setBuffersAndAttributes(gl, vao);
       setUniforms(program, { vpMatrix });
       drawBufferInfo(gl, vao);
       gl.bindVertexArray(null);
    
       requestAnimationFrame(animate);
    };
    
    //加载贴图后执行
    createTexture(gl, { src: '/img/earth.jpg', flipY: true }, texture => {
       uniforms.diffMap = texture;
       setUniforms(program, uniforms );
       animate();
    }); 
    
  • 相关阅读:
    C#中的Singleton模式
    CodeLib
    Google Chats 居然和Gmail集成了...
    Windows中OSG环境搭建
    Socket中winsock.h和winsock2.h的不同
    高斯日记 蓝桥杯
    MATLAB矩阵处理
    马虎的算式 蓝桥杯
    MATLAB基础
    矩阵相乘的一维数组实现
  • 原文地址:https://www.cnblogs.com/edwardloveyou/p/14700684.html
Copyright © 2020-2023  润新知