我有一个目前用画布编写的渲染系统。它遇到了性能问题,我希望WebGL可以帮助解决其中的一些问题。基本上,它可以归结为许多简单的形状,例如圆形,三角形,矩形和直线。问题在于,很大一部分形状可能会在框架之间转换(请参见this d3 example for an example use case, though with relatively few shapes)。
我觉得我一定缺少关于WebGL的知识,因为这似乎不是一个不常见的目标,但是据我了解,这基本上是最坏的情况,因为顶点缓冲区必须基本上在每一帧都被替换。有更好的方法吗?
答案 0 :(得分:1)
如果是我,我将计算起点和终点,将其放在顶点缓冲区中,并在它们之间进行约束。与morph targets相同。
示例:
const numAreas = 30;
const numPointsPerArea = 100;
const maxDistFromArea = 10;
const areaWidth = 300;
const areaHeight = 150;
const endPositions = [];
for (let a = 0; a < numAreas; ++a) {
const areaX = rand(maxDistFromArea, areaWidth - maxDistFromArea);
const areaY = rand(maxDistFromArea, areaHeight - maxDistFromArea);;
for (let p = 0; p < numPointsPerArea; ++p) {
const x = areaX + rand(-maxDistFromArea, maxDistFromArea);
const y = areaY + rand(-maxDistFromArea, maxDistFromArea);
endPositions.push(x, y);
}
}
const startPositions = [];
for (let a = 0; a < numAreas * numPointsPerArea; ++a) {
startPositions.push(rand(areaWidth), rand(areaHeight));
}
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + Math.random() * (max - min);
}
const vs = `
attribute vec4 startPosition;
attribute vec4 endPosition;
uniform float u_lerp;
uniform mat4 u_matrix;
void main() {
vec4 position = mix(startPosition, endPosition, u_lerp);
gl_Position = u_matrix * position;
gl_PointSize = 2.0;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// put data in vertex buffers
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
startPosition: { data: startPositions, numComponents: 2 },
endPosition: { data: endPositions, numComponents: 2, },
});
const easingFunc = easingSineOut;
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// set uniforms
twgl.setUniforms(programInfo, {
u_matrix: m4.ortho(0, 300, 150, 0, -1, 1),
u_lerp: easingFunc(Math.min(1, time % 2)),
});
// gl.drawXXX
twgl.drawBufferInfo(gl, bufferInfo, gl.POINTS);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function easingSineOut(t) {
return Math.sin(t * Math.PI * .5);
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
希望您明白这一点。由您决定是否要添加其他任何数据(每种颜色的点数,每个点的大小,如果您要使用正方形以外的其他形状,请使用纹理作为点,或者使用三角形。为线添加类似的数据等)。重要的是将开始数据和结束数据放在一起,并在它们之间拖拉
attribute vec4 startPosition;
attribute vec4 endPosition;
uniform float u_lerp;
void main() {
vec4 position = mix(startPosition, endPosition, u_lerp);
否则,通过bufferData
或bufferSubData
上传每一帧都没有错。 Here's an example中的this talk更新了10000个对象,其中JavaScript正在计算对象的位置,并更新所有210596个顶点位置,并每帧通过bufferData
上传值。
通过bufferData和canvas / svg上传之间的最大区别是,使用WebGL,您可以从循环中删除大量内容。考虑
因此,对于可能是4000个canvas api调用的1000个对象,每个对象可能在内部执行gl.bufferData
,多次gl.drawXXX
调用等操作
在WebGL的情况下,您仍然可以计算1000个位置,但避免了可能的3999个api调用和999个对bufferData的调用,并全部替换为一个绘制调用和一个对bufferData的调用。
Canvas和SVG API并不是魔术。他们做的事情与您在WebGL中手动做的事情相同。不同之处在于它们是通用的,因此它们无法优化到同一水平,并且它们需要大量函数才能产生输出。当然ctx.fillStyle, ctx.beginPath, ctx.arc, ctx.fill
只有4行代码,因此与在WebGL中绘制圆相比,要编写的代码要少得多,但是在WebGL中,一旦定义了圆,则可以在2次调用中绘制更多的圆(gl.uniform, gl.draw),这两个调用非常浅(它们并没有做很多工作),其中ctx.arc
和ctx.fill
正在做大量工作。在此之上,您可以设计在单个绘制调用中绘制100或1000个圆的解决方案。