WebGL中批量调用的最快方法

9
我正在尝试为我的2D游戏引擎重新编写基于画布的渲染。我已经取得了良好的进展,并且可以完美地将纹理渲染到WebGL上下文中,包括缩放、旋转和混合。但是我的性能很差。在我的测试笔记本电脑上,我可以在屏幕上同时显示1,000个实体的普通2D画布中获得30帧每秒;在WebGL中,我在屏幕上显示500个实体时也只能获得30帧每秒。我本来期望情况会相反!我有一个隐隐的感觉,罪魁祸首可能是我处理的所有Float32Array缓冲垃圾。以下是我的渲染代码:
// boilerplate code and obj coordinates

// grab gl context
var canvas = sys.canvas;
var gl = sys.webgl;
var program = sys.glProgram;

// width and height
var scale = sys.scale;
var tileWidthScaled = Math.floor(tileWidth * scale);
var tileHeightScaled = Math.floor(tileHeight * scale);
var normalizedWidth = tileWidthScaled / this.width;
var normalizedHeight = tileHeightScaled / this.height;

var worldX = targetX * scale;
var worldY = targetY * scale;

this.bindGLBuffer(gl, this.vertexBuffer, sys.glWorldLocation);
this.bufferGLRectangle(gl, worldX, worldY, tileWidthScaled, tileHeightScaled);

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);

var frameX = (Math.floor(tile * tileWidth) % this.width) * scale;
var frameY = (Math.floor(tile * tileWidth / this.width) * tileHeight) * scale;

// fragment (texture) shader
this.bindGLBuffer(gl, this.textureBuffer, sys.glTextureLocation);
this.bufferGLRectangle(gl, frameX, frameY, normalizedWidth, normalizedHeight);

gl.drawArrays(gl.TRIANGLES, 0, 6);

bufferGLRectangle: function (gl, x, y, width, height) {
    var left = x;
    var right = left + width;
    var top = y;
    var bottom = top + height;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        left, top,
        right, top,
        left, bottom,
        left, bottom,
        right, top,
        right, bottom
    ]), gl.STATIC_DRAW);
},

bindGLBuffer: function (gl, buffer, location) {
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
},

这是我的简单测试着色器(它们缺少混合、缩放和旋转):

// fragment (texture) shader
precision mediump float;
uniform sampler2D image;
varying vec2 texturePosition;

void main() {
    gl_FragColor = texture2D(image, texturePosition);
}

// vertex shader
attribute vec2 worldPosition;
attribute vec2 vertexPosition;

uniform vec2 canvasResolution;
varying vec2 texturePosition;

void main() {
    vec2 zeroToOne = worldPosition / canvasResolution;
    vec2 zeroToTwo = zeroToOne * 2.0;
    vec2 clipSpace = zeroToTwo - 1.0;

    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
    texturePosition = vertexPosition;
}

有没有关于如何提高性能的想法?是否有一种批处理drawArrays的方法?是否有一种减少缓冲垃圾的方法?
谢谢!

1
为了吸引更多的兴趣,我建议您将问题命名为“在WebGL上批量绘制数组的最快方法”。 - Mikko Ohtamaa
@AbrahamWalters:你每帧对每个实体都运行了完全相同的渲染代码吗?(即每秒500 * 30 = 1500次)如果是这样,我认为如果你让它静置在那里,那么浏览器或标签页将在一小时内(如果不是十分钟)内耗尽内存。 - Andrew Rasmussen
1
http://www.youtube.com/watch?v=rfQ8rKGTVlg - gman
2个回答

8

我看到两个大问题会影响你的性能。

你创建了许多临时的Float32Arrays,它们目前构造起来很昂贵(这在以后可能会得到改善)。在这种情况下,最好创建一个单一的数组,并每次设置顶点,如下所示:

verts[0] = left; verts[1] = top;
verts[2] = right; verts[3] = top;
// etc... 
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);

然而,更大的问题是您一次只绘制一个四边形。3D API并不是为高效执行此操作而设计的。您需要尝试将尽可能多的三角形挤入每个drawArrays/drawElements调用中。

有几种方法可以做到这一点,最简单的方法是使用相同纹理填充缓冲区中尽可能多的四边形,然后一次性绘制它们。伪代码如下:

var MAX_QUADS_PER_BATCH = 100;
var VERTS_PER_QUAD = 6;
var FLOATS_PER_VERT = 2;
var verts = new Float32Array(MAX_QUADS_PER_BATCH * VERTS_PER_QUAD * FLOATS_PER_VERT);

var quadCount = 0;
function addQuad(left, top, bottom, right) {
    var offset = quadCount * VERTS_PER_QUAD * FLOATS_PER_VERT;

    verts[offset] = left; verts[offset+1] = top;
    verts[offset+2] = right; verts[offset+3] = top;
    // etc...

    quadCount++;

    if(quadCount == MAX_QUADS_PER_BATCH) {
        flushQuads();
    }
}

function flushQuads() {
    gl.bindBuffer(gl.ARRAY_BUFFER, vertsBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW); // Copy the buffer we've been building to the GPU.

    // Make sure vertexAttribPointers are set, etc...

    gl.drawArrays(gl.TRIANGLES, 0, quadCount + VERTS_PER_QUAD);
}

// In your render loop

for(sprite in spriteTypes) {
    gl.bindTexture(gl.TEXTURE_2D, sprite.texture);

    for(instance in sprite.instances) {
        addQuad(instance.left, instance.top, instance.right, instance.bottom);  
    }

    flushQuads();
}

这只是一种过度简化的方式,还有更多批处理的方法,但希望这能让你了解如何开始批量处理调用以获得更好的性能。


这很有道理。我肯定可以批处理像地图瓦片和粒子这样的东西,但归根结底,每帧我都要绑定纹理。如果没有纹理集,有没有其他方法可以解决这个问题? - Abraham Walters

2
如果您使用WebGL检查器,您将在跟踪中看到是否执行任何不必要的GL指令(它们用明亮的黄色背景标记)。这可能会给您优化渲染的想法。
一般来说,对绘制调用进行排序,使所有使用相同程序、然后是属性、纹理和最后是统一变量的调用按顺序完成。这样,您将尽可能少地使用GL指令(和JS指令)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接