性能篇30 # 怎么给WebGL绘制加速?
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了性能篇30 # 怎么给WebGL绘制加速?相关的知识,希望对你有一定的参考价值。
说明
【跟月影学可视化】学习笔记。
常规绘图方式的性能瓶颈
例子:在一个画布上渲染 3000 个不同颜色的、位置随机的三角形,并且让每个三角形的旋转角度也随机。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>常规绘图方式的性能瓶颈</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const vertex = `
attribute vec2 a_vertexPosition;
void main()
gl_Position = vec4(a_vertexPosition, 1, 1);
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 u_color;
void main()
gl_FragColor = u_color;
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
// 创建随机三角形的顶点
function randomTriangle(
x = 0,
y = 0,
rotation = 0.0,
radius = 0.1
)
const a = rotation,
b = a + (2 * Math.PI) / 3,
c = a + (4 * Math.PI) / 3;
return [
[x + radius * Math.sin(a), y + radius * Math.cos(a)],
[x + radius * Math.sin(b), y + radius * Math.cos(b)],
[x + radius * Math.sin(c), y + radius * Math.cos(c)],
];
const COUNT = 3000;
// 依次渲染每个三角形
function render()
for (let i = 0; i < COUNT; i++)
const x = 2 * Math.random() - 1;
const y = 2 * Math.random() - 1;
const rotation = 2 * Math.PI * Math.random();
renderer.uniforms.u_color = [
Math.random(),
Math.random(),
Math.random(),
1,
];
const positions = randomTriangle(x, y, rotation);
renderer.setMeshData([
positions,
,
]);
renderer._draw();
requestAnimationFrame(render);
render();
</script>
</body>
</html>
我这台电脑渲染出来只有 4.2 fps
减少 CPU 计算次数
可以创建一个正三角形,然后通过视图矩阵的变化来实现绘制多个三角形,而视图矩阵可以放在顶点着色器中计算。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>减少 CPU 计算次数</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
// 利用顶点着色器内完成位置和角度的计算
const vertex = `
attribute vec2 a_vertexPosition;
uniform mat3 modelMatrix;
void main()
vec3 pos = modelMatrix * vec3(a_vertexPosition, 1);
gl_Position = vec4(pos, 1);
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 u_color;
void main()
gl_FragColor = u_color;
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
// 生成一个正三角形顶点,并设置数据到缓冲区
const alpha = 2 * Math.PI / 3;
const beta = 2 * alpha;
renderer.setMeshData(
positions: [
[0, 0.1],
[0.1 * Math.sin(alpha), 0.1 * Math.cos(alpha)],
[0.1 * Math.sin(beta), 0.1 * Math.cos(beta)],
],
);
const COUNT = 3000;
// 依次渲染每个三角形
function render()
for (let i = 0; i < COUNT; i++)
const x = 2 * Math.random() - 1;
const y = 2 * Math.random() - 1;
const rotation = 2 * Math.PI * Math.random();
// 用随机坐标和角度更新每个三角形的 modelMatrix 数据
renderer.uniforms.modelMatrix = [
Math.cos(rotation), -Math.sin(rotation), 0,
Math.sin(rotation), Math.cos(rotation), 0,
x, y, 1
];
renderer.uniforms.u_color = [
Math.random(),
Math.random(),
Math.random(),
1,
];
renderer._draw();
requestAnimationFrame(render);
render();
</script>
</body>
</html>
也是 4.2 fps
,由于浏览器的 javascript 引擎的运算速度很快,感觉将顶点计算放到顶点着色器中进行了,性能差别也很微小。
静态批量绘制(多实例绘制)
重复图形的批量绘制,在 WebGL 中也叫做多实例绘制(Instanced Drawing)
,它是一种减少绘制次数的技术。多实例渲染的局限性:只能在绘制相同的图形时使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>静态批量绘制(多实例绘制)</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas);
const vertex = `
attribute vec2 a_vertexPosition;
attribute float id;
uniform float uTime;
highp float random(vec2 co)
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
varying vec3 vColor;
void main()
float t = id / 10000.0;
float alpha = 6.28 * random(vec2(uTime, 2.0 + t));
float c = cos(alpha);
float s = sin(alpha);
mat3 modelMatrix = mat3(
c, -s, 0,
s, c, 0,
2.0 * random(vec2(uTime, t)) - 1.0, 2.0 * random(vec2(uTime, 1.0 + t)) - 1.0, 1
);
vec3 pos = modelMatrix * vec3(a_vertexPosition, 1);
vColor = vec3(
random(vec2(uTime, 4.0 + t)),
random(vec2(uTime, 5.0 + t)),
random(vec2(uTime, 6.0 + t))
);
gl_Position = vec4(pos, 1);
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
varying vec3 vColor;
void main()
gl_FragColor.rgb = vColor;
gl_FragColor.a = 1.0;
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
const alpha = (2 * Math.PI) / 3;
const beta = 2 * alpha;
const COUNT = 3000;
renderer.setMeshData(
positions: [
[0, 0.1],
[0.1 * Math.sin(alpha), 0.1 * Math.cos(alpha)],
[0.1 * Math.sin(beta), 0.1 * Math.cos(beta)],
],
instanceCount: COUNT,
attributes:
id: data: [...new Array(COUNT).keys()], divisor: 1 ,
,
);
function render(t)
renderer.uniforms.uTime = t / 1e6;
renderer.render();
requestAnimationFrame(render);
render(0);
</script>
</body>
</html>
效果如下:每一帧的实际渲染次数(即 WebGL 执行 drawElements 的次数)从原来的 3000 减少到了只有 1 次,而且计算都放到着色器里,利用 GPU 并行处理了,因此性能提升了 3000 倍。
动态批量绘制
如果是绘制不同的几何图形,只要它们使用同样的着色器程序,而且没有改变 uniform 变量,可以将顶点数据先合并再渲染,以减少渲染次数。
例子:将上面常规的代码改成随机的正三角形、正方形和正五边形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>动态批量绘制</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script src="./common/lib/gl-renderer.js"></script>
<script>
const canvas = document.querySelector("canvas");
const renderer = new GlRenderer(canvas);
const vertex = `
attribute vec2 a_vertexPosition;
void main()
gl_Position = vec4(a_vertexPosition, 1, 1);
`;
const fragment = `
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 u_color;
void main()
gl_FragColor = u_color;
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
// 创建随机的正三角形、正方形和正五边形
function randomShape(x = 0, y = 0, edges = 3, rotation = 0.0, radius = 0.1)
const a0 = rotation;
const delta = 2 * Math.PI / edges;
const positions = [];
const cells = [];
for(let i = 0; i < edges; i++)
const angle = a0 + i * delta;
positions.push([x + radius * Math.sin(angle), y + radius * Math.cos(angle)]);
if(i > 0 && i < edges - 1)
cells.push([0, i, i + 1]);
return 以上是关于性能篇30 # 怎么给WebGL绘制加速?的主要内容,如果未能解决你的问题,请参考以下文章
性能篇28 # CanvasSVG与WebGL在性能上的优势与劣势