如何从 gltf 2.0 加载立方体并在纯 WebGL 中绘制它

How to load a cube from gltf 2.0 and draw it in pure WebGL

我将一个默认立方体从 Blender 3.0 导出到 gltf+bin。我尝试用纯 WebGL 绘制它。

这只是一个非常简单的例子。您将在此示例中看到幻数,例如:

const normalData = new Float32Array(binData, 288, 288 / Float32Array.BYTES_PER_ELEMENT);

这些数字是 byteLengthbyteOffset。我直接从 BoxBlender3.gltf 文件中获取这些数字。

沙盒演示(由 Rabbid76 解决):https://plnkr.co/edit/BYseznZdUBTrUmRa?preview

源代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Loading a cube from gltf 2.0. WebGL, JavaScript</title>
    <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>
</head>

<body>
    <canvas id="renderCanvas" width="400" height="400"></canvas>
    <script>
        loadFile("assets/BoxBlender3.gltf", (content) =>
        {
            const gltf = JSON.parse(content);

            loadBin("assets/BoxBlender3.bin", (binData) =>
            {
                const canvas = document.getElementById("renderCanvas");
                const gl = canvas.getContext("webgl");

                gl.enable(gl.DEPTH_TEST);

                const vertShaderSource =
                    `attribute vec4 aPosition;
                    attribute vec4 aNormal;
                    uniform mat4 uMvpMatrix;
                    uniform mat4 uModelMatrix;
                    uniform mat4 uNormalMatrix;
                    varying vec3 vPosition;
                    varying vec3 vNormal;
                    void main()
                    {
                        gl_Position = uMvpMatrix * aPosition;
                        vPosition = vec3(uModelMatrix * aPosition);
                        vNormal = normalize(vec3(uNormalMatrix * aNormal));
                    }`;

                const fragShaderSource =
                    `precision mediump float;
                    const vec3 lightColor = vec3(1.0, 1.0, 1.0);
                    const vec3 ambientLight = vec3(0.2, 0.2, 0.2);
                    uniform vec3 uLightPosition;
                    varying vec3 vPosition;
                    varying vec3 vNormal;
                    void main()
                    {
                        vec4 color = vec4(0.5, 1.0, 0.5, 1.0);
                        vec3 normal = normalize(vNormal);
                        vec3 lightDirection = normalize(uLightPosition - vPosition);
                        float nDotL = max(dot(lightDirection, normal), 0.0);
                        vec3 diffuse = lightColor * color.rgb * nDotL;
                        vec3 ambient = ambientLight * color.rgb;
                        gl_FragColor = vec4(diffuse + ambient, color.a);
                    }`;

                const vShader = gl.createShader(gl.VERTEX_SHADER);
                gl.shaderSource(vShader, vertShaderSource);
                gl.compileShader(vShader);
                let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
                if (!ok) { console.log("vert: " + gl.getShaderInfoLog(vShader)); };

                const fShader = gl.createShader(gl.FRAGMENT_SHADER);
                gl.shaderSource(fShader, fragShaderSource);
                gl.compileShader(fShader);
                ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
                if (!ok) { console.log("frag: " + gl.getShaderInfoLog(fShader)); };

                const program = gl.createProgram();
                gl.attachShader(program, vShader);
                gl.attachShader(program, fShader);
                gl.bindAttribLocation(program, 0, "aPosition");
                gl.bindAttribLocation(program, 1, "aNormal");
                gl.linkProgram(program);
                ok = gl.getProgramParameter(program, gl.LINK_STATUS);
                if (!ok) { console.log("link: " + gl.getProgramInfoLog(program)); };
                gl.useProgram(program);

                // Create a cube
                //    v6----- v5
                //   /|      /|
                //  v1------v0|
                //  | |     | |
                //  | |v7---|-|v4
                //  |/      |/
                //  v2------v3

                // const vertPositions = [
                //     0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, // v0-v1-v2-v3 front
                //     0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, // v0-v3-v4-v5 right
                //     0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, // v0-v5-v6-v1 up
                //     -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, // v1-v6-v7-v2 left
                //     -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, // v7-v4-v3-v2 down
                //     0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5 // v4-v7-v6-v5 back
                // ];
                const vertPosBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
                // gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPositions), gl.STATIC_DRAW);
                // const vertPosData = new Uint8Array(binData, 0, 288);
                const vertPosData = new Float32Array(binData, 0, 288 / Float32Array.BYTES_PER_ELEMENT);
                gl.bufferData(gl.ARRAY_BUFFER, vertPosData, gl.STATIC_DRAW);
                gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(0);

                // const normals = [
                //     0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
                //     1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
                //     0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
                //     -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
                //     0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, // v7-v4-v3-v2 down
                //     0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0 // v4-v7-v6-v5 back  
                // ];
                const normalBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
                // gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
                // const normalData = new Uint8Array(binData, 288, 288);
                const normalData = new Float32Array(binData, 288, 288 / Float32Array.BYTES_PER_ELEMENT);
                gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW);
                gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(1);

                // const indices = [
                //     0, 1, 2, 0, 2, 3,       // front
                //     4, 5, 6, 4, 6, 7,       // right
                //     8, 9, 10, 8, 10, 11,    // up
                //     12, 13, 14, 12, 14, 15, // left
                //     16, 17, 18, 16, 18, 19, // down
                //     20, 21, 22, 20, 22, 23  // back 
                // ];
                const indexBuffer = gl.createBuffer();
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
                const indexData = new Uint8Array(binData, 576, 72);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);

                const projMatrix = glMatrix.mat4.create();
                glMatrix.mat4.perspective(projMatrix, 55 * Math.PI / 180, 1, 0.1, 500);
                const viewMatrix = glMatrix.mat4.create();
                glMatrix.mat4.lookAt(viewMatrix, [4, 5, 20], [0, 0, 0], [0, 1, 0]);
                const projViewMatrix = glMatrix.mat4.create();
                glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);

                const modelMatrix = glMatrix.mat4.create();
                glMatrix.mat4.fromTranslation(modelMatrix, [0, 0, 0]);
                glMatrix.mat4.rotate(modelMatrix, modelMatrix, 0 * Math.PI / 180, [1, 0, 0]);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, [5, 5, 5]);
                const mvpMatrix = glMatrix.mat4.create();
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                const uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                const uModelMatrixLocation = gl.getUniformLocation(program, "uModelMatrix");
                gl.uniformMatrix4fv(uModelMatrixLocation, false, modelMatrix);

                const normalMatrix = glMatrix.mat4.create();
                glMatrix.mat4.invert(normalMatrix, modelMatrix);
                glMatrix.mat4.transpose(normalMatrix, normalMatrix);
                const uNormalMatrixLocation = gl.getUniformLocation(program, "uNormalMatrix");
                gl.uniformMatrix4fv(uNormalMatrixLocation, false, normalMatrix);

                const lightPosition = glMatrix.vec3.fromValues(4, 7, 5);
                const uLightPositionLocation = gl.getUniformLocation(program, "uLightPosition");
                gl.uniform3fv(uLightPositionLocation, lightPosition);

                gl.clearColor(0.2, 0.2, 0.2, 1);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
            });
        });

        function loadFile(path, callback)
        {
            const xhr = new XMLHttpRequest();
            xhr.onreadystatechange = () =>
            {
                if (xhr.readyState === 4 && xhr.status != 404)
                {
                    callback(xhr.responseText);
                }
            };
            xhr.open("GET", path, true);
            xhr.send();
        }

        function loadBin(path, callback)
        {
            const xhr = new XMLHttpRequest();

            xhr.onreadystatechange = () =>
            {
                if (xhr.readyState === 4 && xhr.status != 404)
                {
                    callback(xhr.response);
                }
            };

            xhr.open("GET", path, true);
            xhr.responseType = "arraybuffer";
            xhr.send();
        }
    </script>
</body>

</html>

BoxBlender3.gltf

{
    "asset" : {
        "generator" : "Khronos glTF Blender I/O v1.7.33",
        "version" : "2.0"
    },
    "scene" : 0,
    "scenes" : [
        {
            "name" : "Scene",
            "nodes" : [
                0
            ]
        }
    ],
    "nodes" : [
        {
            "mesh" : 0,
            "name" : "Cube"
        }
    ],
    "materials" : [
        {
            "doubleSided" : true,
            "name" : "Material",
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.800000011920929,
                    0.800000011920929,
                    0.800000011920929,
                    1
                ],
                "metallicFactor" : 0,
                "roughnessFactor" : 0.4000000059604645
            }
        }
    ],
    "meshes" : [
        {
            "name" : "Cube",
            "primitives" : [
                {
                    "attributes" : {
                        "POSITION" : 0,
                        "NORMAL" : 1
                    },
                    "indices" : 2,
                    "material" : 0
                }
            ]
        }
    ],
    "accessors" : [
        {
            "bufferView" : 0,
            "componentType" : 5126,
            "count" : 24,
            "max" : [
                1,
                1,
                1
            ],
            "min" : [
                -1,
                -1,
                -1
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 1,
            "componentType" : 5126,
            "count" : 24,
            "type" : "VEC3"
        },
        {
            "bufferView" : 2,
            "componentType" : 5123,
            "count" : 36,
            "type" : "SCALAR"
        }
    ],
    "bufferViews" : [
        {
            "buffer" : 0,
            "byteLength" : 288,
            "byteOffset" : 0
        },
        {
            "buffer" : 0,
            "byteLength" : 288,
            "byteOffset" : 288
        },
        {
            "buffer" : 0,
            "byteLength" : 72,
            "byteOffset" : 576
        }
    ],
    "buffers" : [
        {
            "byteLength" : 648,
            "uri" : "BoxBlender3.bin"
        }
    ]
}

索引似乎是 16 位整数而不是 8 位整数:

const indexData = new Uint8Array(binData, 576, 72);

72 是 36 个索引,每个索引 2 个字节,用于立方体的 6 个边中的每一个的 2 个三角形。

使用 16 位数组:

const indexData = new Uint16Array(binData, 576, 36);

gl.UNSIGNED_SHORT 而不是 gl.UNSIGNED_BYTE:

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