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.vertexAttribPointer
、gl.enableVertexAttribArray
、gl.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
我正在尝试制作一个 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.vertexAttribPointer
、gl.enableVertexAttribArray
、gl.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