WebGL:索引的使用导致模型消失

WebGL: usage of indices causes model to dissapear

我目前正在开发一个关于 WebGL 的网站。我和我的搭档想使用一个小而有趣的 WebGL 背景。

我们要做的是让一个盒子在 canvas 上旋转。现在,它的原理已经在起作用了。我们已经有了一个旋转框,但是许多三角形使用错误的顶点来形成它们的三角形,导致下图:

The rotating cube with a bad triangle setup.

所以,我们认为:让我们使用索引来正确设置它。然而,这会导致框从 canvas 中消失。它似乎不再存在于任何地方。

我们的问题是:有人能看出为什么会这样吗?我们感觉我们已经很接近了,但是这个小细节确实给我们带来了一些麻烦。

下面提供了我们使用的代码。也随时欢迎任何其他反馈。

当前计划

// ROTATING EXAMPLE

// The canvas that the GL environment will be using.
var canvas = null;

// The Graphics Library, aka: WebGL.
var openGL = null;

// The shader used by the graphics library.
var shaderProgram = null;

// Our matrices.
var modelViewMatrix;
var modelNormalMatrix;
var perspectiveMatrix;

var VertexBuffer;
var ColorBuffer;
var NormalBuffer;
var IndicesBuffer;

var vertexPositionAttribute;
var vertexColorAttribute;
var vertexNormalAttribute;

var squareRotation = 0.0;
var lastSquareUpdateTime = null;

// Encapsulation of the initialisation of WebGL.
function initWebGL(openGLCanvas) {

    var gl = null;

    // Attempt to build the context.
    try {
        gl = openGLCanvas.getContext("webgl", { premultipliedAlpha: false })
            || openGLCanvas.getContext("experimental-webgl", { premultipliedAlpha: false });
    }

    // Report back to use when some exception is thrown.
        catch (exception) {
            console.log("An error happened when trying to initialise the openGL environment. See the exception below for details.");
            console.log(exception);

            gl = null;
        }

    return gl;

}

function initialise(){      
    // Keep this variable local. We do not need it globally.
    var openGLCanvas = document.getElementById("openGLCanvas");

    openGL = initWebGL(openGLCanvas);

    if(openGL)
        {
            // The color to use to clear the screen.
            openGL.clearColor(0.9, 0.9, 0.9, 1.0);
            openGL.clearDepth(1.0);

            // Enables the depth testing.
            openGL.enable(openGL.DEPTH_TEST);

            // Closer things will overlap with things further away.
            openGL.depthFunc(openGL.LEQUAL);

            // Clear both the color as the depth buffer.
            openGL.clear(openGL.COLOR_BUFFER_BIT |openGL.DEPTH_BUFFER_BIT);

            openGL.viewport(0, 0, openGLCanvas.width, openGLCanvas.height);

            InitialiseShaders();

            InitialiseBuffers();

            setInterval(drawScene, 15);
            //drawScene();
        }
}

function InitialiseShaders()
{
    var fragmentShader = retrieveShader(openGL, "04fragment", "f", "shader");
    var vertexShader = retrieveShader(openGL, "04vertex", "v", "shader");

    //console.log(fragmentShader);
    //console.log(vertexShader);

    shaderProgram = openGL.createProgram();
    openGL.attachShader(shaderProgram, vertexShader);
    openGL.attachShader(shaderProgram, fragmentShader);
    openGL.linkProgram(shaderProgram);

    if(!openGL.getProgramParameter(shaderProgram, openGL.LINK_STATUS))
        {
            console.log("Something went wrong during the initialisation of the shader program. See below for extra information.");
            console.log(openGL.getProgramParameter(shaderProgram, openGL.LINK_STATUS));
        }

    openGL.useProgram(shaderProgram);

    vertexPositionAttribute = openGL.getAttribLocation(shaderProgram, "position");
    openGL.enableVertexAttribArray(vertexPositionAttribute);

    vertexColorAttribute = openGL.getAttribLocation(shaderProgram, "color");
    openGL.enableVertexAttribArray(vertexColorAttribute);

    vertexNormalAttribute = openGL.getAttribLocation(shaderProgram, "normal");
    openGL.enableVertexAttribArray(vertexNormalAttribute);
}

function retrieveShader(openGL, filename, type, filetype)
{       // Ensure the file type is always given.
    filetype = filetype || "txt";

        // Read out the source file.
    var source = "";    
    $.ajax({
        url: "Shaders/" + filename + "." + filetype,
        async: false,
        success: function(data) { source = data; }
    });

    console.info("Found source for the following filename: " + filename + ", of type: " + type + ".");
    console.info(source);

        // Check what type the shader should be.
        // If the type is unknown we can see the error report within the console.
    var shader;
    if(type == "f")
    { shader = openGL.createShader(openGL.FRAGMENT_SHADER);}
    else if (type == "v")
        { shader = openGL.createShader(openGL.VERTEX_SHADER);}
    else 
        {
            console.log("Unknown shader type. See below for extra information.");
            console.log(identification);
            console.log(shaderScript.type);
            return null;
        }

        // Attempt to compile the shader.
    openGL.shaderSource(shader, source);
    openGL.compileShader(shader);

        // Check whether or not there are any compilation errors.
        // If there are any, we'll be able to see the error report within the console.
    if(!openGL.getShaderParameter(shader, openGL.COMPILE_STATUS))
        {
            console.log("An error occured during the compilation of the shader. See below for extra information.");
            console.log(openGL.getShaderInfoLog(shader));
            return null;
        }

        // All is green. 
        // Lets start this baby up.
    return shader;
}

function InitialiseBuffers()
{
    bufferPosition();
    bufferColor();
    bufferNormal();
    bufferIndices();
}

function bufferIndices(){

    var indices = [
      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
    ];

    IndicesBuffer = openGL.createBuffer();
    openGL.bindBuffer(openGL.ELEMENT_ARRAY_BUFFER, IndicesBuffer);
    openGL.bufferData(openGL.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), openGL.STATIC_DRAW);
}

function bufferPosition()
{
    // Vertex data for a 3D box.
    var vertices = [
      // Front face
      -1.0, -1.0,  1.0,
       1.0, -1.0,  1.0,
       1.0,  1.0,  1.0,
      -1.0,  1.0,  1.0,

      // Back face
      -1.0, -1.0, -1.0,
      -1.0,  1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0, -1.0, -1.0,

      // Top face
      -1.0,  1.0, -1.0,
      -1.0,  1.0,  1.0,
       1.0,  1.0,  1.0,
       1.0,  1.0, -1.0,

      // Bottom face
      -1.0, -1.0, -1.0,
       1.0, -1.0, -1.0,
       1.0, -1.0,  1.0,
      -1.0, -1.0,  1.0,

      // Right face
       1.0, -1.0, -1.0,
       1.0,  1.0, -1.0,
       1.0,  1.0,  1.0,
       1.0, -1.0,  1.0,

      // Left face
      -1.0, -1.0, -1.0,
      -1.0, -1.0,  1.0,
      -1.0,  1.0,  1.0,
      -1.0,  1.0, -1.0
    ];

    // Create a buffer, prep it for filling and then fill it.
    VertexBuffer = openGL.createBuffer();
    openGL.bindBuffer(openGL.ARRAY_BUFFER, VertexBuffer);
    openGL.bufferData(openGL.ARRAY_BUFFER, new Float32Array(vertices), openGL.STATIC_DRAW);
}

function bufferColor()
{
    // Color data for a 3D Box.
    var colorPerFace = [
      [0.8,  0.9,  0.75,  1.0],    // Front face
      [0.8,  0.9,  0.75,  1.0],   // Back face
      [0.8,  0.9,  0.75,  1.0],    // Top face
      [0.8,  0.9,  0.75,  1.0],    // Bottom face
      [0.8,  0.9,  0.75,  1.0],    // Right face
      [0.8,  0.9,  0.75,  1.0],     // Left face
    ];

    var colors = [];

    // For each face.
    for (j=0; j<6; j++) {
      var c = colorPerFace[j];
        // generate a color for every vertex on that face.
      for (var i=0; i<4; i++) {
        colors = colors.concat(c);
      }
    }

    // Create a buffer, prep it and then fill it.
    ColorBuffer = openGL.createBuffer();
    openGL.bindBuffer(openGL.ARRAY_BUFFER, ColorBuffer);
    openGL.bufferData(openGL.ARRAY_BUFFER, new Float32Array(colors), openGL.STATIC_DRAW);
}

function bufferNormal()
{

    var vertexNormals = [
      // Front
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,

      // Back
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,

      // Top
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,

      // Bottom
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,

      // Right
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,

      // Left
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0
    ];

    NormalBuffer = openGL.createBuffer();
    openGL.bindBuffer(openGL.ARRAY_BUFFER, NormalBuffer);
    openGL.bufferData(openGL.ARRAY_BUFFER, new Float32Array(vertexNormals), openGL.STATIC_DRAW);
}

function drawScene()
{           
    openGL.clear(openGL.GL_COLOR_BUFFER_BIT | openGL.DEPTH_BUFFER_BIT);

    updateScene();

    // Clear both the color as the depth buffer.


    openGLBindings();

    openGL.drawElements(openGL.TRIANGLE, 24, openGL.UNSIGNED_SHORT, 0);
    openGL.drawArrays(openGL.TRIANGLE_STRIP, 0, 24);
    console.log("Scene made!");
}

function updateScene()
{
    var currentTime = Date.now();
    if (lastSquareUpdateTime) {
      var delta = currentTime - lastSquareUpdateTime;

      squareRotation += (30 * delta) / 2000.0;
    }

    lastSquareUpdateTime = currentTime;


    // Calculate the matrices

    modelViewMatrix = Matrix.I(4);

    var inRadians = squareRotation * Math.PI / 180;
    modelViewMatrix = modelViewMatrix.x(Matrix.Translation($V([0.0, 0.0, -12.0])).ensure4x4());
    modelViewMatrix = modelViewMatrix.x(Matrix.Rotation(inRadians, $V([1, 1, 0])).ensure4x4());

    modelNormalMatrix = modelViewMatrix.inverse();
    modelNormalMatrix = modelNormalMatrix.transpose();

    perspectiveMatrix = makePerspective(45, 520 / 520, 0.1, 100.0);


    // Send the matrices to the GPU.

    var mvUniform = openGL.getUniformLocation(shaderProgram, "matrixModelView");
    openGL.uniformMatrix4fv(mvUniform, 
                            false, 
                            new Float32Array(modelViewMatrix.flatten()));

    var pUniform = openGL.getUniformLocation(shaderProgram, "matrixPerspective");
    openGL.uniformMatrix4fv(pUniform, 
                            false, 
                            new Float32Array(perspectiveMatrix.flatten()));

    var nUniform = openGL.getUniformLocation(shaderProgram, "matrixNormal");
    openGL.uniformMatrix4fv(nUniform, 
                            false, 
                            new Float32Array(modelNormalMatrix.flatten()));
}

function openGLBindings()
{   
    // Send the data to the GPU.

    openGL.bindBuffer(openGL.ARRAY_BUFFER, VertexBuffer);
    openGL.vertexAttribPointer(vertexPositionAttribute,     // Where to bind it to
                               3,                           // The number of data per set.
                               openGL.FLOAT,                // The type of data
                               false,                       // Normalisation (or not).
                               0,                           // Stride
                               0);                          // Offset

    openGL.bindBuffer(openGL.ARRAY_BUFFER, ColorBuffer);
    openGL.vertexAttribPointer(vertexColorAttribute,        // Where to bind it to
                               4,                           // The number of data per set.
                               openGL.FLOAT,                    // The type of data.
                               false,                       // Normalisation (or not).
                               0,                           // Stride
                               0);                          // Offset

    openGL.bindBuffer(openGL.ARRAY_BUFFER, NormalBuffer);
    openGL.vertexAttribPointer(vertexNormalAttribute,       // Where to bind it to
                               3,                           // The number of data per set.
                               openGL.FLOAT,                // The type of data.
                               false,                       // Normalisation (or not).
                               0,                           // Stride
                               0);                          // Offset

    openGL.bindBuffer(openGL.ELEMENT_ARRAY_BUFFER, IndicesBuffer);
}

片段着色器

varying lowp vec4 fColor;

void main(void) {
    gl_FragColor = fColor;
}

顶点着色器

attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;

uniform mat4 matrixModelView;
uniform mat4 matrixPerspective;
uniform mat4 matrixNormal;

varying lowp vec4 fColor;

void main(void) {
    gl_Position = matrixPerspective * matrixModelView * vec4(position, 1.0);

    highp vec3 directionalVector = vec3(1.0, 0.85, 0.6);

    highp vec4 transformedNormal = matrixNormal * vec4(normal, 1.0);
    highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);

    fColor = color * directional;
}

快速浏览您的代码后,我发现了以下行

openGL.drawElements(openGL.TRIANGLE, 24, openGL.UNSIGNED_SHORT, 0);

很可疑。您总共有 6(quad) * 2(triangles/quad) * 3(vertices/triangles) = 36 个索引,而不是 24 个。此外,您的 bufferindices 包含 36 个整数。

然而,紧接的下一行

openGL.drawArrays(openGL.TRIANGLE_STRIP, 0, 24);

表明您正在将四边形绘制为三角形条带。绘制三角形时需要保持一致,即将它们全部渲染为三角形或 TRIANGLE_STRIP。如果要画成TRIANGLE_STRIP,那么定义

 var vertices = [
  // Front face
  -1.0, -1.0,  1.0,
   1.0, -1.0,  1.0,
   1.0,  1.0,  1.0,
  -1.0,  1.0,  1.0,
........
];

不是定义四边形三角形行程的正确顺序。正确的做法应该是

 var vertices = [
  // Front face
  -1.0, -1.0,  1.0,
   1.0, -1.0,  1.0,
  -1.0,  1.0,  1.0,
   1.0,  1.0,  1.0,
...
]

同样的事情也适用于其他面孔的定义。