首页 > 解决方案 > WebGL:无法显示两个纹理

问题描述

我正在尝试使用纹理天空盒(使用立方体贴图)渲染纹理立方体(使用顶点、索引和 tex 坐标),但不知何故我总是收到以下错误消息:

WebGL:INVALID_OPERATION:bindTexture:纹理不能用于多个目标

我有两个纹理并且可能错误地使用 gl.activeTexture 但我无法弄清楚。

如您所见,纹理立方体在天空盒似乎被绘制在其上之前短暂闪烁。

使用此代码的临时(24 小时)网站:http: //priceless-dijkstra-4bf2a5.netlify.com/

有任何想法吗?

<!-- Licensed under a BSD license. See license.html for license -->
<!-- src: https://webglfundamentals.org/ -->
<!DOCTYPE html>
<html>
    <head>
        <meta charset = "utf-8">
        <meta name = "viewport" content = "width=device-width, initial-scale=1.0, user-scalable=yes">
        <title> WebGL - Textures - Data Texture 3 x2</title>
        <link type = "text/css" href = "./webgl-tutorials.css" rel = "stylesheet" />
    </head>
    <body>
        <div class = "description">
            A 3 x2 texture <br />
        </div>
        <canvas id = "canvas"></canvas>
    </body>
    <!-- vertex shader -->
    <script id = "3d-vertex-shader" type = "x-shader/x-vertex">
        attribute vec4 a_position;
        attribute vec2 a_texcoord;
        uniform mat4 u_matrix;
        varying vec2 v_texcoord;
        void main()
        {
            // Multiply the position by the matrix.
            gl_Position = u_matrix * a_position;
            // Pass the texcoord to the fragment shader.
            v_texcoord = a_texcoord;
        }
    </script>
    <!-- fragment shader -->
    <script id = "3d-fragment-shader" type = "x-shader/x-fragment">
        precision mediump float;
        // Passed in from the vertex shader.
        varying vec2 v_texcoord;
        // The texture.
        uniform sampler2D u_texture;
        void main()
        {
            gl_FragColor = texture2D(u_texture, v_texcoord);
        }
    </script>
    <!--skybox vertex shader-->
    <script id="skybox-vertex-shader" type="x-shader/x-vertex">
        attribute vec4 a_position;
        varying vec4 v_position;
        void main() 
        {
          v_position = a_position;
          gl_Position = a_position;
        }
    </script>
    <!--skybox fragment shader-->
    <script id="skybox-fragment-shader" type="x-shader/x-fragment">
        precision mediump float;
        uniform samplerCube u_skybox;
        uniform mat4 u_viewDirectionProjectionInverse;
        varying vec4 v_position;
        void main() 
        {
          vec4 t = u_viewDirectionProjectionInverse * v_position;
          gl_FragColor = textureCube(u_skybox, normalize(t.xyz / t.w));
        }
    </script>
    <script src = "./webgl-utils.js"></script>
    <script src = "./m4.js"></script>
    <script src = "./primitives.js"></script>
    <script type = "module">
        "use strict";
        function main()
        {
            // Get A WebGL context
            /** @type {HTMLCanvasElement} */
            var canvas = document.getElementById("canvas");
            var gl = canvas.getContext("webgl");
            if (!gl)
            {
                return;
            }
            // setup GLSL program
            var program = webglUtils.createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]);
            // look up where the vertex data needs to go.
            var positionLocation = gl.getAttribLocation(program, "a_position");
            var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
            // lookup uniforms
            var matrixLocation = gl.getUniformLocation(program, "u_matrix");
            var textureLocation = gl.getUniformLocation(program, "u_texture");

            //create program for skybox
            const skyboxProgramInfo = webglUtils.createProgramInfo(gl, ["skybox-vertex-shader", "skybox-fragment-shader"]);
            var sb_textureLocation = gl.getUniformLocation(skyboxProgramInfo.program, "u_skybox");
            // create buffers and fill with vertex data
            const cubeBufferInfo = primitives.createCubeBufferInfo(gl, 1);
            const quadBufferInfo = primitives.createXYQuadBufferInfo(gl);
            // Create a texture.
            const sb_texture = gl.createTexture();
            gl.activeTexture(gl.TEXTURE0 + 1);
            gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
            const faceInfos = 
            [
                { target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, url: './pos-x.jpg', },
                { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, url: './neg-x.jpg', },
                { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, url: './pos-y.jpg', },
                { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, url: './neg-y.jpg', },
                { target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, url: './pos-z.jpg', },
                { target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, url: './neg-z.jpg', },
            ];
            faceInfos.forEach((faceInfo) => 
            {
                const {target, url} = faceInfo;
                // Upload the canvas to the cubemap face.
                const level = 0;
                const internalFormat = gl.RGBA;
                const width = 512;
                const height = 512;
                const format = gl.RGBA;
                const type = gl.UNSIGNED_BYTE;
                // setup each face so it's immediately renderable
                gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, null);
                // Asynchronously load an image
                const image = new Image();
                image.src = url;
                image.addEventListener('load', function() 
                {
                    // Now that the image has loaded make copy it to the skybox texture.
                    gl.activeTexture(gl.TEXTURE0 + 1);
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
                    gl.texImage2D(target, level, internalFormat, format, type, image);
                    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
                });
            });
            gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
            gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);


            // Create a buffer for positions
            var positionBuffer = gl.createBuffer();
            // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            // Put the positions in the buffer
            setGeometry(gl);

            // Create a buffer for positions
            var indexBuffer = gl.createBuffer();
            // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
            // Put the positions in the buffer
            setIndices(gl);

            // provide texture coordinates for the rectangle.
            var texcoordBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
            // Set Texcoords.
            setTexcoords(gl);
            // Create a texture.
            var texture = gl.createTexture();
            //void gl.bindTexture(target, texture);
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, texture);
            // fill texture with 3x2 pixels
            const level = 0;
            const internalFormat = gl.RGB;
            const width = 2;                
            const height = 2;               
            const border = 0;
            const format = gl.RGB;          
            const type = gl.UNSIGNED_BYTE;
            const data = new Uint8Array     
            ([
                255, 0, 0,      0, 255, 0,
                0, 0, 255,      128, 128, 128,
            ]);
            const alignment = 1;
            gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
            gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, format, type, data);
            // set the filtering so we don't need mips and it's not filtered
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

            function degToRad(d)
            {
                return d * Math.PI / 180;
            }
            var fieldOfViewRadians = degToRad(60);
            var modelXRotationRadians = degToRad(0);
            var modelYRotationRadians = degToRad(0);
            // Get the starting time.
            var then = 0;
            requestAnimationFrame(drawScene);
            // Draw the scene.
            function drawScene(time)
            {
                // convert to seconds
                time *= 0.001;
                // Subtract the previous time from the current time
                var deltaTime = time - then;
                // Remember the current time for the next frame.
                then = time;
                webglUtils.resizeCanvasToDisplaySize(gl.canvas);
                // Tell WebGL how to convert from clip space to pixels
                gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
                gl.enable(gl.CULL_FACE);
                gl.enable(gl.DEPTH_TEST);
                // Animate the rotation
                modelYRotationRadians += -0.7 * deltaTime;
                modelXRotationRadians += -0.4 * deltaTime;
                // Clear the canvas AND the depth buffer.
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                // Tell it to use our program (pair of shaders)
                gl.useProgram(program);

                // Turn on the position attribute
                gl.enableVertexAttribArray(positionLocation);
                // Bind the position buffer.
                gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
                // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
                var size = 3; // 3 components per iteration
                var type = gl.FLOAT; // the data is 32bit floats
                var normalize = false; // don't normalize the data
                var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
                var offset = 0; // start at the beginning of the buffer
                gl.vertexAttribPointer( positionLocation, size, type, normalize, stride, offset);

                // Turn on the teccord attribute
                gl.enableVertexAttribArray(texcoordLocation);
                // Bind the position buffer.
                gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
                // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
                var size = 2; // 2 components per iteration
                var type = gl.FLOAT; // the data is 32bit floats
                var normalize = false; // don't normalize the data
                var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
                var offset = 0; // start at the beginning of the buffer
                gl.vertexAttribPointer( texcoordLocation, size, type, normalize, stride, offset);

                // Compute the projection matrix
                var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
                var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
                var cameraPosition = [0, 0, 2];
                var up = [0, 1, 0];
                var target = [0, 0, 0];
                // Compute the camera's matrix using look at.
                var cameraMatrix = m4.lookAt(cameraPosition, target, up);
                // Make a view matrix from the camera matrix.
                var viewMatrix = m4.inverse(cameraMatrix);
                var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
                var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
                matrix = m4.yRotate(matrix, modelYRotationRadians);
                // Set the matrix.
                gl.uniformMatrix4fv(matrixLocation, false, matrix);
                // Tell the shader to use texture unit 0 for u_texture
                gl.uniform1i(textureLocation, 0);
                // Draw the geometry.
                gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);

                // Skybox: we only care about direction so remove the translation
                var viewDirectionMatrix = m4.copy(viewMatrix);
                viewDirectionMatrix[12] = 0;
                viewDirectionMatrix[13] = 0;
                viewDirectionMatrix[14] = 0;
                var viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewDirectionMatrix);
                var viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
                // draw the skybox
                gl.useProgram(skyboxProgramInfo.program);
                webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
                webglUtils.setUniforms(skyboxProgramInfo, {
                  u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
                  u_skybox: texture,
                });
                // Tell the shader to use texture unit 0 for u_texture
                gl.uniform1i(sb_textureLocation, 1);
                webglUtils.drawBufferInfo(gl, quadBufferInfo);

                requestAnimationFrame(drawScene);
            }
        }

        // Fill the buffer with the values that define a cube.
        function setGeometry(gl)
        {
            var positions = new Float32Array
            ([  
                // Front face
                -0.5, -0.5,  0.5,
                 0.5, -0.5,  0.5,
                 0.5,  0.5,  0.5,
                -0.5,  0.5,  0.5,
                // Back face
                -0.5, -0.5, -0.5,
                -0.5,  0.5, -0.5,
                 0.5,  0.5, -0.5,
                 0.5, -0.5, -0.5,
                // Top face
                -0.5,  0.5, -0.5,
                -0.5,  0.5,  0.5,
                 0.5,  0.5,  0.5,
                 0.5,  0.5, -0.5,
                // Bottom face
                -0.5, -0.5, -0.5,
                 0.5, -0.5, -0.5,
                 0.5, -0.5,  0.5,
                -0.5, -0.5,  0.5,
                // Right face
                 0.5, -0.5, -0.5,
                 0.5,  0.5, -0.5,
                 0.5,  0.5,  0.5,
                 0.5, -0.5,  0.5,
                // Left face
                -0.5, -0.5, -0.5,
                -0.5, -0.5,  0.5,
                -0.5,  0.5,  0.5,
                -0.5,  0.5, -0.5,
            ]);
            gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
        }

        // Fill the buffer with texture coordinates the cube.
        function setTexcoords(gl)
        {
            gl.bufferData
            (
                gl.ARRAY_BUFFER,
                new Float32Array
                ([
                    // Front
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                    // Back
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                    // Top
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                    // Bottom
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                    // Right
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                    // Left
                    0.0,  0.0,
                    1.0,  0.0,
                    1.0,  1.0,
                    0.0,  1.0,
                ]),
            gl.STATIC_DRAW);
        }


        // Fill the buffer with vertex indices
        function setIndices(gl)
        {
            var indices = new Uint16Array
            ([  
                0,  1,  2,      0,  2,  3,    // front
                4,  5,  6,      4,  6,  7,    // back
                8,  9,  10,     8,  10, 11,   // top
                12, 13, 14,     12, 14, 15,   // bottom
                16, 17, 18,     16, 18, 19,   // right
                20, 21, 22,     20, 22, 23,   // left
            ]);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
        }

        main();
    </script>
</html>

标签: webgltexturesskybox

解决方案


为了让代码正常工作,我必须做 3 件事

  1. 在绘制立方体之前绑定 indexBuffer

          gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    
  2. 不要在底部设置纹理

    gl.uniform1i(sb_textureLocation, 1);
    
  3. 对天空盒使用正确的纹理

            webglUtils.setUniforms(skyboxProgramInfo, {
              u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
              u_skybox: texture,      // wrong---------------
              u_skybox: sb_texture,   // right---------------
            });
    

一些东西。

  1. webglUtils.setBuffersAndAttributes设置绘制给定对象所需的所有缓冲区和属性。在这种情况下,这意味着当你打电话时

          webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
    

    天空盒所需的索引绑定到 ELEMENT_ARRAY_BUFFER。这意味着第二次通过drawSceneindexBuffer 不会绑定到您的多维数据集。

  2. webglUtils.setUniforms为您管理活动的纹理单元。这意味着这个电话

            webglUtils.setUniforms(skyboxProgramInfo, {
              u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
              u_skybox: texture,
            });
    

    设置texture为活动单元 0。setUniforms刚从 0 开始,并为使用的每个纹理计数。texture错误的纹理,u_skybox这就是您收到错误的原因。上面的代码转换为

     gl.uniformMatrix4fv(u_viewDirectionProjectionInverseLocation, false, viewDirectionProjectionInverseMatrix);
     gl.activeTexture(gl.TEXTURE0 + 0);
     gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
     gl.uniform1i(u_skyboxLocation, 0);
    

纹理单元通常只在绘制时才关心,而不是在初始化时。它们是一组全局位置,用于为下一次绘制调用附加纹理。在每次抽奖调用之间,您都应该设置它们,但是对于您将要进行的抽奖来说,这是必需的。

For each texture the shaders used by the next draw call need
    gl.activeTexture(gl.TEXTURE0 + n);
    gl.bindTexture(targetTypeForTexture, texture);
    gl.uniform1i(n);

另请参阅https://webglfundamentals.org/webgl/lessons/webgl-texture-units.html

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }
  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]);
  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
  // lookup uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");
  var textureLocation = gl.getUniformLocation(program, "u_texture");

  //create program for skybox
  const skyboxProgramInfo = webglUtils.createProgramInfo(gl, ["skybox-vertex-shader", "skybox-fragment-shader"]);
  var sb_textureLocation = gl.getUniformLocation(skyboxProgramInfo.program, "u_skybox");
  // create buffers and fill with vertex data
  const cubeBufferInfo = primitives.createCubeBufferInfo(gl, 1);
  const quadBufferInfo = primitives.createXYQuadBufferInfo(gl);
  // Create a texture.
  const sb_texture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE0 + 1);
  gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
  const faceInfos = [
    {
      target: gl.TEXTURE_CUBE_MAP_POSITIVE_X,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-x.jpg',
    },
    {
      target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-x.jpg',
    },
    {
      target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-y.jpg',
    },
    {
      target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-y.jpg',
    },
    {
      target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/pos-z.jpg',
    },
    {
      target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
      url: 'https://webglfundamentals.org/webgl/resources/images/computer-history-museum/neg-z.jpg',
    },
  ];
  faceInfos.forEach((faceInfo) => {
    const {
      target,
      url
    } = faceInfo;
    // Upload the canvas to the cubemap face.
    const level = 0;
    const internalFormat = gl.RGBA;
    const width = 512;
    const height = 512;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    // setup each face so it's immediately renderable
    gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, null);
    // Asynchronously load an image
    const image = new Image();
    image.src = url;
    image.crossOrigin = 'anonymous';
    image.addEventListener('load', function() {
      // Now that the image has loaded make copy it to the skybox texture.
      gl.activeTexture(gl.TEXTURE0 + 1);
      gl.bindTexture(gl.TEXTURE_CUBE_MAP, sb_texture);
      gl.texImage2D(target, level, internalFormat, format, type, image);
      gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
    });
  });
  gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);


  // Create a buffer for positions
  var positionBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Put the positions in the buffer
  setGeometry(gl);

  // Create a buffer for positions
  var indexBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  // Put the positions in the buffer
  setIndices(gl);

  // provide texture coordinates for the rectangle.
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  // Set Texcoords.
  setTexcoords(gl);
  // Create a texture.
  var texture = gl.createTexture();
  //void gl.bindTexture(target, texture);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  // fill texture with 3x2 pixels
  const level = 0;
  const internalFormat = gl.RGB;
  const width = 2;
  const height = 2;
  const border = 0;
  const format = gl.RGB;
  const type = gl.UNSIGNED_BYTE;
  const data = new Uint8Array([
    255, 0, 0, 0, 255, 0,
    0, 0, 255, 128, 128, 128,
  ]);
  const alignment = 1;
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, format, type, data);
  // set the filtering so we don't need mips and it's not filtered
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

  function degToRad(d) {
    return d * Math.PI / 180;
  }
  var fieldOfViewRadians = degToRad(60);
  var modelXRotationRadians = degToRad(0);
  var modelYRotationRadians = degToRad(0);
  // Get the starting time.
  var then = 0;
  requestAnimationFrame(drawScene);
  // Draw the scene.
  function drawScene(time) {
    // convert to seconds
    time *= 0.001;
    // Subtract the previous time from the current time
    var deltaTime = time - then;
    // Remember the current time for the next frame.
    then = time;
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);
    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);
    // Animate the rotation
    modelYRotationRadians += -0.7 * deltaTime;
    modelXRotationRadians += -0.4 * deltaTime;
    // Clear the canvas AND the depth buffer.
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the position attribute
    gl.enableVertexAttribArray(positionLocation);
    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 3; // 3 components per iteration
    var type = gl.FLOAT; // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0; // start at the beginning of the buffer
    gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);

    // Turn on the teccord attribute
    gl.enableVertexAttribArray(texcoordLocation);
    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
    // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 2; // 2 components per iteration
    var type = gl.FLOAT; // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0; // start at the beginning of the buffer
    gl.vertexAttribPointer(texcoordLocation, size, type, normalize, stride, offset);
    
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

    // Compute the projection matrix
    var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
    var cameraPosition = [0, 0, 2];
    var up = [0, 1, 0];
    var target = [0, 0, 0];
    // Compute the camera's matrix using look at.
    var cameraMatrix = m4.lookAt(cameraPosition, target, up);
    // Make a view matrix from the camera matrix.
    var viewMatrix = m4.inverse(cameraMatrix);
    var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
    var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
    matrix = m4.yRotate(matrix, modelYRotationRadians);
    // Set the matrix.
    gl.uniformMatrix4fv(matrixLocation, false, matrix);
    // Tell the shader to use texture unit 0 for u_texture
    gl.uniform1i(textureLocation, 0);
    // Draw the geometry.
    gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);

    // Skybox: we only care about direction so remove the translation
    var viewDirectionMatrix = m4.copy(viewMatrix);
    viewDirectionMatrix[12] = 0;
    viewDirectionMatrix[13] = 0;
    viewDirectionMatrix[14] = 0;
    var viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewDirectionMatrix);
    var viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
    // draw the skybox
    gl.useProgram(skyboxProgramInfo.program);
    webglUtils.setBuffersAndAttributes(gl, skyboxProgramInfo, quadBufferInfo);
    webglUtils.setUniforms(skyboxProgramInfo, {
      u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
      u_skybox: sb_texture,
    });
    // Tell the shader to use texture unit 0 for u_texture
    webglUtils.drawBufferInfo(gl, quadBufferInfo);

    requestAnimationFrame(drawScene);
  }
}

// Fill the buffer with the values that define a cube.
function setGeometry(gl) {
  var positions = new Float32Array([
    // Front face
    -0.5, -0.5, 0.5,
    0.5, -0.5, 0.5,
    0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
    // Back face
    -0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
    0.5, 0.5, -0.5,
    0.5, -0.5, -0.5,
    // Top face
    -0.5, 0.5, -0.5, -0.5, 0.5, 0.5,
    0.5, 0.5, 0.5,
    0.5, 0.5, -0.5,
    // Bottom face
    -0.5, -0.5, -0.5,
    0.5, -0.5, -0.5,
    0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
    // Right face
    0.5, -0.5, -0.5,
    0.5, 0.5, -0.5,
    0.5, 0.5, 0.5,
    0.5, -0.5, 0.5,
    // Left face
    -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
  ]);
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}

// Fill the buffer with texture coordinates the cube.
function setTexcoords(gl) {
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
      // Front
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      // Back
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      // Top
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      // Bottom
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      // Right
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      // Left
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
    ]),
    gl.STATIC_DRAW);
}


// Fill the buffer with vertex indices
function setIndices(gl) {
  var indices = new Uint16Array([
    0, 1, 2, 0, 2, 3, // front
    4, 5, 6, 4, 6, 7, // back
    8, 9, 10, 8, 10, 11, // top
    12, 13, 14, 12, 14, 15, // bottom
    16, 17, 18, 16, 18, 19, // right
    20, 21, 22, 20, 22, 23, // left
  ]);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
}

main();
<div class = "description">
    A 3 x2 texture <br />
</div>
<canvas id = "canvas"></canvas>

    <!-- vertex shader -->
    <script id = "3d-vertex-shader" type = "x-shader/x-vertex">
        attribute vec4 a_position;
        attribute vec2 a_texcoord;
        uniform mat4 u_matrix;
        varying vec2 v_texcoord;
        void main()
        {
            // Multiply the position by the matrix.
            gl_Position = u_matrix * a_position;
            // Pass the texcoord to the fragment shader.
            v_texcoord = a_texcoord;
        }
    </script>
    <!-- fragment shader -->
    <script id = "3d-fragment-shader" type = "x-shader/x-fragment">
        precision mediump float;
        // Passed in from the vertex shader.
        varying vec2 v_texcoord;
        // The texture.
        uniform sampler2D u_texture;
        void main()
        {
            gl_FragColor = texture2D(u_texture, v_texcoord);
        }
    </script>
    <!--skybox vertex shader-->
    <script id="skybox-vertex-shader" type="x-shader/x-vertex">
        attribute vec4 a_position;
        varying vec4 v_position;
        void main() 
        {
          v_position = a_position;
          gl_Position = a_position;
        }
    </script>
    <!--skybox fragment shader-->
    <script id="skybox-fragment-shader" type="x-shader/x-fragment">
        precision mediump float;
        uniform samplerCube u_skybox;
        uniform mat4 u_viewDirectionProjectionInverse;
        varying vec4 v_position;
        void main() 
        {
          vec4 t = u_viewDirectionProjectionInverse * v_position;
          gl_FragColor = textureCube(u_skybox, normalize(t.xyz / t.w));
        }
    </script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/primitives.js"></script>


推荐阅读