WebGL:使用不同的程序渲染大量对象
WebGL: Rendering a lot of objects using different programs
我正在学习 WebGL。
我用 10 个三角形制作了一个简单的场景,当我将三角形的数量增加到 1000 个时,场景开始冻结。我使用 3 个着色器和 2 个程序(用于模拟真实环境)。我知道我应该从渲染循环体中取出一些东西,但我不知道是什么。
我的代码如下:
function render() {
requestAnimationFrame(render);
context.clear(context.COLOR_BUFFER_BIT);
for (let i = 0; i < 10; i++) {
const currentProgram = i % 2 === 0 ? blueProgram : redProgram;
context.useProgram(currentProgram);
const a_Position = context.getAttribLocation(currentProgram, "a_Position");
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
const buffer = context.createBuffer();
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, 3);
}
}
requestAnimationFrame(render);
有什么想法可以优化性能吗?
有很多方法可以优化绘制很多东西,但由于你刚刚开始,最重要的是通常设置缓冲区应该在初始化时发生,而不是渲染时。
见
问题中的代码是查找每个三角形的位置。它应该查找位置作为初始时间。
该代码还为每个三角形创建了一个新缓冲区。创建一个缓冲区并用新三角形更新它会更快,当然最终它会 运行 创建新缓冲区时内存不足。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
const redFS = `
precision highp float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
const blueFS = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 0, 1, 1);
}
`;
const blueProgram = twgl.createProgram(context, [vs, blueFS]);
const blueProgramInfo = {
program: blueProgram,
a_PositionLocation: context.getAttribLocation(blueProgram, "a_Position"),
};
const redProgram = twgl.createProgram(context, [vs, redFS]);
const redProgramInfo = {
program: redProgram,
a_PositionLocation: context.getAttribLocation(redProgram, "a_Position"),
};
const buffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// pre allocate
const triangleData = new Float32Array(6); // 3 vertices, 2 values per
function getTriangleGeometry() {
const x = rand(-1, 1);
const y = rand(-1, 1);
triangleData[0] = x;
triangleData[1] = y;
triangleData[2] = x + rand(-0.1, 0.1);
triangleData[3] = y + rand(-0.1, 0.1);
triangleData[4] = x + rand(-0.1, 0.1);
triangleData[5] = y + rand(-0.1, 0.1);
return triangleData;
}
function render() {
context.clear(context.COLOR_BUFFER_BIT);
for (let i = 0; i < 100; i++) {
const currentProgramInfo = i % 2 === 0 ? blueProgramInfo : redProgramInfo;
context.useProgram(currentProgramInfo.program);
const a_Position = currentProgramInfo.a_PositionLocation;
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, 3);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
问题中的代码似乎使用了 2 个程序,一个绘制蓝色,一个绘制红色。有一个带有统一选择颜色的程序可能会更快。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
const fs = `
precision highp float;
uniform vec4 u_Color;
void main() {
gl_FragColor = u_Color;
}
`;
const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
program: program,
a_PositionLocation: context.getAttribLocation(program, "a_Position"),
u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};
const buffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// pre allocate
const triangleData = new Float32Array(6); // 3 vertices, 2 values per
function getTriangleGeometry() {
const x = rand(-1, 1);
const y = rand(-1, 1);
triangleData[0] = x;
triangleData[1] = y;
triangleData[2] = x + rand(-0.1, 0.1);
triangleData[3] = y + rand(-0.1, 0.1);
triangleData[4] = x + rand(-0.1, 0.1);
triangleData[5] = y + rand(-0.1, 0.1);
return triangleData;
}
const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];
function render() {
context.clear(context.COLOR_BUFFER_BIT);
context.useProgram(programInfo.program);
const a_Position = programInfo.a_PositionLocation;
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
for (let i = 0; i < 100; i++) {
const color = i % 2 === 0 ? blue : red;
context.uniform4fv(programInfo.u_ColorLocation, color);
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.drawArrays(context.TRIANGLES, 0, 3);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
如果您在初始时将所有三角形放在一个缓冲区中,并且在初始时将每个三角形的顶点颜色放在一个缓冲区中,并在渲染时仅通过一次绘制调用绘制它们,速度会明显加快。如果你希望每一帧都有随机三角形,那么在初始时创建一个缓冲区仍然会更快,在一个缓冲区中填充 N 个随机三角形,然后在一次绘制调用中绘制它们。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fs = `
precision highp float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
program: program,
a_PositionLocation: context.getAttribLocation(program, "a_Position"),
a_ColorLocation: context.getAttribLocation(program, "a_Color"),
u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};
const positionBuffer = context.createBuffer();
const colorBuffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
const numTriangles = 1000;
const positionData = new Float32Array(numTriangles * 3 * 2);
const colorData = new Float32Array(numTriangles * 3 * 4);
const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];
// the color data does not change so fill it out at init time
for (let i = 0; i < numTriangles; ++i) {
const offset = i * 4;
colorData.set(i % 2 === 0 ? blue : red, offset);
}
context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
context.bufferData(context.ARRAY_BUFFER, colorData, context.STATIC_DRAW);
function getTriangleGeometry() {
for (let i = 0; i < numTriangles; ++i) {
const offset = i * 3 * 2; // 3 verts per tri, 2 values per ver
const x = rand(-1, 1);
const y = rand(-1, 1);
positionData[offset ] = x;
positionData[offset + 1] = y;
positionData[offset + 2] = x + rand(-0.1, 0.1);
positionData[offset + 3] = y + rand(-0.1, 0.1);
positionData[offset + 4] = x + rand(-0.1, 0.1);
positionData[offset + 5] = y + rand(-0.1, 0.1);
}
return positionData;
}
function render() {
context.clear(context.COLOR_BUFFER_BIT);
context.useProgram(programInfo.program);
const a_Position = programInfo.a_PositionLocation;
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.DYNAMIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
const a_Color = programInfo.a_ColorLocation;
context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
context.enableVertexAttribArray(a_Color);
context.vertexAttribPointer(
a_Color,
4,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, numTriangles * 3);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
像上面那样每帧绘制一堆随机三角形可以说是一个例外。大多数 WebGL 应用程序绘制在建模包中创建的 3D 模型,因此更常见的是在初始化时将数据放入缓冲区一次(例如立方体、球体、汽车、人、树的数据)然后在渲染时绘制它。
另请注意,GPU 只能绘制这么多像素,因此如果您的三角形很大(例如整个屏幕的大小),您将只能绘制 10 到几百个)。 1920x1080 的屏幕大约有 2 百万像素。所以每个全屏三角形也大约有 200 万像素。画1000张就是2000*200万也就是40亿像素。每秒 60 帧 2400 亿像素。中高端 GPU 每秒只能绘制 100 亿,这是理论上的最大值,因此最多只能以每秒约 2 帧的速度完成。
大多数 3D 应用程序绘制的场景中,大多数三角形都很远而且很小。他们还使用深度缓冲区并从前到后绘制不透明对象,这样后面的像素就不会被绘制。
我正在学习 WebGL。
我用 10 个三角形制作了一个简单的场景,当我将三角形的数量增加到 1000 个时,场景开始冻结。我使用 3 个着色器和 2 个程序(用于模拟真实环境)。我知道我应该从渲染循环体中取出一些东西,但我不知道是什么。
我的代码如下:
function render() {
requestAnimationFrame(render);
context.clear(context.COLOR_BUFFER_BIT);
for (let i = 0; i < 10; i++) {
const currentProgram = i % 2 === 0 ? blueProgram : redProgram;
context.useProgram(currentProgram);
const a_Position = context.getAttribLocation(currentProgram, "a_Position");
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
const buffer = context.createBuffer();
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, 3);
}
}
requestAnimationFrame(render);
有什么想法可以优化性能吗?
有很多方法可以优化绘制很多东西,但由于你刚刚开始,最重要的是通常设置缓冲区应该在初始化时发生,而不是渲染时。
见
问题中的代码是查找每个三角形的位置。它应该查找位置作为初始时间。
该代码还为每个三角形创建了一个新缓冲区。创建一个缓冲区并用新三角形更新它会更快,当然最终它会 运行 创建新缓冲区时内存不足。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
const redFS = `
precision highp float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
const blueFS = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 0, 1, 1);
}
`;
const blueProgram = twgl.createProgram(context, [vs, blueFS]);
const blueProgramInfo = {
program: blueProgram,
a_PositionLocation: context.getAttribLocation(blueProgram, "a_Position"),
};
const redProgram = twgl.createProgram(context, [vs, redFS]);
const redProgramInfo = {
program: redProgram,
a_PositionLocation: context.getAttribLocation(redProgram, "a_Position"),
};
const buffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// pre allocate
const triangleData = new Float32Array(6); // 3 vertices, 2 values per
function getTriangleGeometry() {
const x = rand(-1, 1);
const y = rand(-1, 1);
triangleData[0] = x;
triangleData[1] = y;
triangleData[2] = x + rand(-0.1, 0.1);
triangleData[3] = y + rand(-0.1, 0.1);
triangleData[4] = x + rand(-0.1, 0.1);
triangleData[5] = y + rand(-0.1, 0.1);
return triangleData;
}
function render() {
context.clear(context.COLOR_BUFFER_BIT);
for (let i = 0; i < 100; i++) {
const currentProgramInfo = i % 2 === 0 ? blueProgramInfo : redProgramInfo;
context.useProgram(currentProgramInfo.program);
const a_Position = currentProgramInfo.a_PositionLocation;
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, 3);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
问题中的代码似乎使用了 2 个程序,一个绘制蓝色,一个绘制红色。有一个带有统一选择颜色的程序可能会更快。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
const fs = `
precision highp float;
uniform vec4 u_Color;
void main() {
gl_FragColor = u_Color;
}
`;
const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
program: program,
a_PositionLocation: context.getAttribLocation(program, "a_Position"),
u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};
const buffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// pre allocate
const triangleData = new Float32Array(6); // 3 vertices, 2 values per
function getTriangleGeometry() {
const x = rand(-1, 1);
const y = rand(-1, 1);
triangleData[0] = x;
triangleData[1] = y;
triangleData[2] = x + rand(-0.1, 0.1);
triangleData[3] = y + rand(-0.1, 0.1);
triangleData[4] = x + rand(-0.1, 0.1);
triangleData[5] = y + rand(-0.1, 0.1);
return triangleData;
}
const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];
function render() {
context.clear(context.COLOR_BUFFER_BIT);
context.useProgram(programInfo.program);
const a_Position = programInfo.a_PositionLocation;
context.bindBuffer(context.ARRAY_BUFFER, buffer);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
for (let i = 0; i < 100; i++) {
const color = i % 2 === 0 ? blue : red;
context.uniform4fv(programInfo.u_ColorLocation, color);
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
context.drawArrays(context.TRIANGLES, 0, 3);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
如果您在初始时将所有三角形放在一个缓冲区中,并且在初始时将每个三角形的顶点颜色放在一个缓冲区中,并在渲染时仅通过一次绘制调用绘制它们,速度会明显加快。如果你希望每一帧都有随机三角形,那么在初始时创建一个缓冲区仍然会更快,在一个缓冲区中填充 N 个随机三角形,然后在一次绘制调用中绘制它们。
const context = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const fs = `
precision highp float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`;
const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
program: program,
a_PositionLocation: context.getAttribLocation(program, "a_Position"),
a_ColorLocation: context.getAttribLocation(program, "a_Color"),
u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};
const positionBuffer = context.createBuffer();
const colorBuffer = context.createBuffer();
function rand(min, max) {
return Math.random() * (max - min) + min;
}
const numTriangles = 1000;
const positionData = new Float32Array(numTriangles * 3 * 2);
const colorData = new Float32Array(numTriangles * 3 * 4);
const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];
// the color data does not change so fill it out at init time
for (let i = 0; i < numTriangles; ++i) {
const offset = i * 4;
colorData.set(i % 2 === 0 ? blue : red, offset);
}
context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
context.bufferData(context.ARRAY_BUFFER, colorData, context.STATIC_DRAW);
function getTriangleGeometry() {
for (let i = 0; i < numTriangles; ++i) {
const offset = i * 3 * 2; // 3 verts per tri, 2 values per ver
const x = rand(-1, 1);
const y = rand(-1, 1);
positionData[offset ] = x;
positionData[offset + 1] = y;
positionData[offset + 2] = x + rand(-0.1, 0.1);
positionData[offset + 3] = y + rand(-0.1, 0.1);
positionData[offset + 4] = x + rand(-0.1, 0.1);
positionData[offset + 5] = y + rand(-0.1, 0.1);
}
return positionData;
}
function render() {
context.clear(context.COLOR_BUFFER_BIT);
context.useProgram(programInfo.program);
const a_Position = programInfo.a_PositionLocation;
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.DYNAMIC_DRAW);
context.enableVertexAttribArray(a_Position);
context.vertexAttribPointer(
a_Position,
2,
context.FLOAT,
false,
0,
0,
);
const a_Color = programInfo.a_ColorLocation;
context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
context.enableVertexAttribArray(a_Color);
context.vertexAttribPointer(
a_Color,
4,
context.FLOAT,
false,
0,
0,
);
context.drawArrays(context.TRIANGLES, 0, numTriangles * 3);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
像上面那样每帧绘制一堆随机三角形可以说是一个例外。大多数 WebGL 应用程序绘制在建模包中创建的 3D 模型,因此更常见的是在初始化时将数据放入缓冲区一次(例如立方体、球体、汽车、人、树的数据)然后在渲染时绘制它。
另请注意,GPU 只能绘制这么多像素,因此如果您的三角形很大(例如整个屏幕的大小),您将只能绘制 10 到几百个)。 1920x1080 的屏幕大约有 2 百万像素。所以每个全屏三角形也大约有 200 万像素。画1000张就是2000*200万也就是40亿像素。每秒 60 帧 2400 亿像素。中高端 GPU 每秒只能绘制 100 亿,这是理论上的最大值,因此最多只能以每秒约 2 帧的速度完成。
大多数 3D 应用程序绘制的场景中,大多数三角形都很远而且很小。他们还使用深度缓冲区并从前到后绘制不透明对象,这样后面的像素就不会被绘制。