WebGL 三角形未正确呈现

WebGL triangles not rendered correctly

我正在尝试制作一个 webGL 应用程序来渲染随机生成的地形。地形的渲染工作正常(几乎),但是当我尝试渲染一个简单的四边形来模拟水时,水的三角形不在正确的位置。

图中红色部分是乱七八糟的三角形,应该只是两个三角形组成一个和地形一样大的正方形。我发现如果地形大小为 33x33 点(如图中所示),则水缓冲区大小由 1089 个三角形组成,而不是两个,这有点奇怪。同样的原则适用于其他地形尺寸,即 65x65、129x129 等。

我的水代码是这样的,大小设置为 50:

height: 0,
rotation: [0, 0, 0],
scale: [1, 1, 1],
ver: [
    -size,  0,  size,
    -size,  0, -size,
     size,  0, -size,
    -size,  0,  size,
     size,  0, -size,
     size,  0,  size
],
vao: undefined,

setup_buffer: function(){

    this.vao = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vao);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.ver), gl.STATIC_DRAW);

    gl.vertexAttribPointer(
        water_shader.position_attrib_location, // Attribute location
        3, // Number of elements per attribute
        gl.FLOAT, // Type of elements
        gl.FALSE,
        3 * Float32Array.BYTES_PER_ELEMENT, // Size of an individual vertex
        0 // Offset from the beginning of a single vertex to this attribute
    );

    gl.bindBuffer(gl.ARRAY_BUFFER, null);
}

所以我所做的就是创建和绑定一个缓冲区,在其中存储 6 个顶点并在取消绑定缓冲区之前通过 vertexAttribPointer 指定它们。

terrain.setup_buffer() 函数几乎相同,只是它使用了一个索引缓冲区,并且一个顶点包含 9 个坐标(位置、颜色、法线)而不是 3 个。请注意地形生成和地形的变量不在此代码中,但我可以保证所有功能都在工作并且所有变量都存在并已初始化。

this.vao = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vao);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.ver), gl.STATIC_DRAW);

this.ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.ind), gl.STATIC_DRAW);

gl.vertexAttribPointer(
    terrain_shader.position_attrib_location, // Attribute location
    3, // Number of elements per attribute
    gl.FLOAT, // Type of elements
    gl.FALSE,
    9 * Float32Array.BYTES_PER_ELEMENT, // Size of an individual vertex
    0 // Offset from the beginning of a single vertex to this attribute
);
gl.vertexAttribPointer(
    terrain_shader.color_attrib_location, // Attribute location
    3, // Number of elements per attribute
    gl.FLOAT, // Type of elements
    gl.FALSE,
    9 * Float32Array.BYTES_PER_ELEMENT, // Size of an individual vertex
    3 * Float32Array.BYTES_PER_ELEMENT // Offset from the beginning of a single vertex to this attribute
);
gl.vertexAttribPointer(
    terrain_shader.normal_attrib_location, // Attribute location
    3, // Number of elements per attribute
    gl.FLOAT, // Type of elements
    gl.FALSE,
    9 * Float32Array.BYTES_PER_ELEMENT, // Size of an individual vertex
    6 * Float32Array.BYTES_PER_ELEMENT // Offset from the beginning of a single vertex to this attribute
);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

所以这是我所有初始化的主循环。

var canvas = document.getElementById('opengl-surface');
var gl = canvas.getContext('webgl');

if (!gl) {
    console.log('WebGL not supported, falling back on experimental-webgl');
    gl = canvas.getContext('experimental-webgl');
}

if (!gl) {
    alert('Your browser does not support WebGL');
}

gl.clearColor(0.75, 0.85, 0.8, 1.0);
gl.enable(gl.DEPTH_TEST);

//create shader
water_shader.setup_shader();
terrain_shader.setup_shader();

// Create buffers
terrain.generate(5, 0.9, true);

water.setup_buffer();
terrain.setup_buffer();

var projectionMatrix = new Float32Array(16);
mat4.perspective(projectionMatrix, glMatrix.toRadian(45), canvas.width/canvas.height, 0.1, 1000.0);

gl.useProgram(water_shader.program);
gl.uniformMatrix4fv(water_shader.location_projection_matrix, gl.FALSE, projectionMatrix);
gl.uniform4fv(water_shader.location_color, [1, 0, 0, 1]);
gl.useProgram(null);

gl.useProgram(terrain_shader.program);
gl.uniformMatrix4fv(terrain_shader.location_projection_matrix, gl.FALSE, projectionMatrix);
gl.uniform3fv(terrain_shader.location_light_direction, light.direction);
gl.uniform3fv(terrain_shader.location_light_color, light.color);
gl.useProgram(null);

//
// Main render loop
//
var identity = new Float32Array(16);
mat4.identity(identity);

var loop = function(){

    camera.rotate();
    camera.translate();

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    //render terrain
    {
        gl.useProgram(terrain_shader.program);
        gl.uniformMatrix4fv(terrain_shader.location_view_matrix, gl.FALSE, camera.view_matrix());
        gl.uniformMatrix4fv(terrain_shader.location_model_matrix, gl.FALSE, terrain.model_matrix());

        gl.bindBuffer(gl.ARRAY_BUFFER, terrain.vao);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, terrain.ibo);
        gl.enableVertexAttribArray(terrain_shader.position_attrib_location);
        gl.enableVertexAttribArray(terrain_shader.color_attrib_location);
        gl.enableVertexAttribArray(terrain_shader.normal_attrib_location);
        gl.drawElements(gl.TRIANGLES, terrain.ind.length, gl.UNSIGNED_SHORT, 0);
        gl.disableVertexAttribArray(terrain_shader.position_attrib_location);
        gl.disableVertexAttribArray(terrain_shader.color_attrib_location);
        gl.disableVertexAttribArray(terrain_shader.normal_attrib_location);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.useProgram(null);
    }


    //render water_shader
    {
        gl.useProgram(water_shader.program);
        gl.uniformMatrix4fv(water_shader.location_view_matrix, gl.FALSE, camera.view_matrix());
        gl.uniformMatrix4fv(water_shader.location_model_matrix, gl.FALSE, water.model_matrix());

        gl.bindBuffer(gl.ARRAY_BUFFER, water.vao);
        gl.enableVertexAttribArray(water_shader.position_attrib_location);
        gl.drawArrays(gl.TRIANGLES, 0, 1089); //here should be 2 istead of 1089
        gl.disableVertexAttribArray(water_shader.position_attrib_location);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.useProgram(null);
    }
    requestAnimationFrame(loop);
};

requestAnimationFrame(loop);

着色器非常简单,不需要太多解释。为了完整起见,这是我的水着色器代码

对比:

precision mediump float;

attribute vec3 vertPosition;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

void main()
{
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertPosition, 1.0),
}

FS:

precision mediump float;

uniform vec4 color;

void main()
{
  gl_FragColor = color;
}

还有其他问题,例如如果将地形大小缩小到 (2^3+1)x(2^3+1) 个顶点,我会收到 "GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 0" 错误。这不应该发生,因为我记录了数组并得到了一个大小为 729 (9x9x9) 的顶点数组和一个大小为 384 (8x8x2x3) 的索引数组。

另一个问题是,如果我在 terrain.setup_buffer() 之后调用 water.setup_buffer(),两个渲染调用(地形和水)都会抛出与上述相同的错误 ("GL_INVALID_OPERATION ") .

如果有帮助,我正在研究 google chrome 和 windows 10,但在 ms edge 上会出现相同的错误。

除非您使用顶点数组对象(它是 WebGL2 的一部分,但在 WebGL1 中仅作为扩展是可选的),否则顶点属性状态 IS GLOBAL STATE。即由 gl.vertexAttribPointergl.enableVertexAttribArraygl.vertexAttribXXX 设置的状态都是全局状态,除非你使用的是顶点数组对象(你不是)

这意味着当你打电话时

water.setup_buffer();

全局 属性状态已设置。然后你打电话

terrain.setup_buffer();

这会覆盖之前的 global 属性状态。

这里有一些描述属性状态的答案

你应该

(a) 使用顶点数组对象 (VAO),以便属性状态符合每个 VAO

(b) 将设置缓冲区(初始化时间的东西)与设置属性(渲染时间的东西)分开。

没有 VAO 的正常渲染方式是

for each thing you want to draw
   gl.useProgram
   setup attributes for that thing
   bind textures and set uniforms for that thing
   call gl.drawElements or gl.drawArrays