WebGL: не может отображать две текстуры - PullRequest
0 голосов
/ 04 ноября 2019

Я пытаюсь визуализировать текстурированный куб (используя вершины, индексы и текс-координаты) с текстурированным скайбоксом (используя кубическую карту) вокруг него, но каким-то образом я всегда получаю следующее сообщение об ошибке:

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>

1 Ответ

0 голосов
/ 04 ноября 2019

Чтобы заставить код работать, мне пришлось сделать 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. Это означает, что во второй раз через drawScene indexBuffer не привязан к вашему кубу.

  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>
...