2022-10-28-----webgl绘制地球

126 阅读1分钟

一、原理

image.png

球面坐标公式: image.png

当然按照y轴向上的习惯,x,y,z与图上不一致,转换过来即 x=r*sin(θ)cos(φ) y=r*cos(θ) z=r*sin(θ)sin(φ)

注意:索引数目较多需要用Uint16Array,相应地绘制时也需要修改对应类型

gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);

二、效果:

20221028_103740.gif

三、代码

 //顶点着色器
    var VSHADER_SOURCE = /*glsl*/`
        attribute vec2 a_Uv;
        attribute vec4 a_Position;
        uniform mat4 u_ModelViewMatrix; 
        varying vec2 v_Uv;
        void main(){ 
           gl_Position = u_ModelViewMatrix*a_Position;
            v_Uv=a_Uv;
        }`;

    //片元着色器
    var FSHADER_SOURCE = /*glsl*/`
        #ifdef GL_ES
        precision mediump float;
        #endif
        uniform sampler2D u_Texture;
        varying vec2 v_Uv;
        void main(){
        
           gl_FragColor = texture2D(u_Texture,v_Uv);
        }`;

    //声明js需要的相关变量
    var canvas = document.getElementById("canvas");
    var gl = getWebGLContext(canvas);

    async function main() {
        if (!gl) {
            console.log("你的浏览器不支持WebGL");
            return;
        }

        //初始化着色器
        if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
            console.log("无法初始化着色器");
            return;
        }


        // var n = initVertexBuffers(gl);
        var n = drawSphere(gl)
        if (n < 0) {
            console.log("无法创建缓冲区");
            return;
        }

        const texture = await initTexture(gl, './image/earth.jpg', 0)
        //设置底色
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        var currentAngle = 0
        var tick = function () {
            //计算角度
            currentAngle -= 0.3
            draw(gl, currentAngle, n)
            requestAnimationFrame(tick)
        }
        tick()
        //进入场景初始化
        //draw(gl, n, u_ModelViewMatrix, u_ModelMatrix, u_NormalMatrix);
    }

    function draw(gl, currentAngle, n) {
        //设置视角矩阵的相关信息(视点,视线,上方向)
        var viewMatrix = new Matrix4();
        viewMatrix.setLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);

        //设置模型矩阵的相关信息
        var modelMatrix = new Matrix4();
        modelMatrix.setRotate(-23.5, 0, 0, 1);
        modelMatrix.rotate(currentAngle, 0, 1, 0);


        //设置透视投影矩阵
        var projMatrix = new Matrix4();
        projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);

        //计算出模型视图矩阵 viewMatrix.multiply(modelMatrix)相当于在着色器里面u_ViewMatrix * u_ModelMatrix
        var modeViewMatrix = projMatrix.multiply(viewMatrix.multiply(modelMatrix));

        //设置视角矩阵的相关信息
        var u_ModelViewMatrix = gl.getUniformLocation(gl.program, "u_ModelViewMatrix");
        if (u_ModelViewMatrix < 0) {
            console.log("无法获取mvp矩阵变量的存储位置");
            return;
        }

        //将试图矩阵传给u_ViewMatrix变量
        gl.uniformMatrix4fv(u_ModelViewMatrix, false, modeViewMatrix.elements);

        //开启隐藏面清除
        gl.enable(gl.DEPTH_TEST);

        //清空颜色和深度缓冲区
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


        //绘制图形
        // gl.drawElements(gl.LINES, n, gl.UNSIGNED_SHORT, 0);
        gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);


    }




    function drawSphere(gl) {
        const maxLat = 30, maxLon = 30
        const radius = 1
        const position = []
        const uv = []
        //计算所有的索引值
        const indexArr = []
        for (let lat = 0; lat <= maxLat; lat++) {
            //     //计算占据的百分比并转为弧度(0->PI之间)
            // let lat=15
            const theta = lat / maxLat * Math.PI
            const y = radius * Math.cos(theta)
            for (let lon = 0; lon < maxLon; lon++) {
                //计算横向占据的百分比(0-2*PI之间)
                const phi = lon * 2 * Math.PI / (maxLon - 1)
                const x = radius * Math.sin(theta) * Math.cos(phi)
                const z = radius * Math.sin(theta) * Math.sin(phi)
                position.push(x, y, z)
                uv.push(1 - lon / (maxLon - 1), 1 - lat / maxLat)


                //计算当前索引个数
                if (lat < maxLat) {
                    const current = lon + lat * maxLon
                    if (lon === maxLon - 1) {
                        continue;
                    } else {
                        indexArr.push(current, current + maxLon, current + 1)
                        indexArr.push(current + 1, current + maxLon, current + maxLon + 1)

                    }
                }


            }

        }
        const vertices = new Float32Array(position)
        const indices = new Uint16Array(indexArr)
        const uvs = new Float32Array(uv)
        //创建缓冲区对象
        initArrayBuffer(gl, vertices, 3, gl.FLOAT, "a_Position");
        initArrayBuffer(gl, uvs, 2, gl.FLOAT, "a_Uv");
        //将顶点索引数据写入缓冲区对象
        var indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
        return indices.length

    }

    function initArrayBuffer(gl, data, num, type, attribute) {
        //创建缓冲区对象
        var buffer = gl.createBuffer();
        if (!buffer) {
            console.log("无法创建缓冲区对象");
            return -1;
        }

        //绑定缓冲区对象并写入数据
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

        //获取顶点位置变量位置
        var a_attribue = gl.getAttribLocation(gl.program, attribute);
        if (a_attribue < 0) {
            console.log("无法获取顶点位置的存储变量" + attribute);
            return -1;
        }

        //对位置的顶点数据进行分配,并开启
        gl.vertexAttribPointer(a_attribue, num, type, false, 0, 0);
        gl.enableVertexAttribArray(a_attribue);
    }