WebGL instancing with ANGLE_instanced_arrays

Ever find yourself in a position where you absolutely, positively have to get a ton of monkeys on screen with a single draw call?

Yeah, okay, me neither. (At least, not until I put together thedemo for this post.)

Butthere's certainly occasions when it makes a lot of sense to redraw the same mesh many times with some minor tweak like position, orientation, color, etc. Simple examples of this are trees and other vegetation, streetlights,  boxes in a warehouse, soldiers in an army, and so on.

Traditionally with WebGL the optimal way to draw those repeated meshes, commonly referred to as instances, would be something like the following pseudocode:

bindMeshArrays(gl);

for (i = 0; i < meshInstances.length; i++) {

var instance = meshInstances[i];

gl.bindUniform3fv(meshPosition, instance.position);

gl.bindUniform4fv(meshColor, instance.color);

gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0);

}

This is good, because we only set up the vertex attributes (implied to be in bindMeshArrays) once,  then make many draw calls in quick succession, only changing the properties that are different from instance to instance. In this case just position and color. This makes for a near minimal amount of overhead for each draw call, and in an environment like Javascript where each call is expensive that's important!

(This example is pretty simple, and in fact you can actually optimize it further by getting creative with how you provide the GPU with the data. Check outGregg Tavares' wonderful Google I/O talkfor more ideas about how to render lots of geometry very quickly.)

Even in this scenario, however, you have to make at least three WebGL calls for every instance of the mesh. That doesn't sound terrible but in the real world that would probably be quite a bit more, and the fact is that in Javascript every call into native code (Like the WebGL API) carries a certain expense. Thus while drawing repeated meshes in this way works, and generally works well, it could still be better.

And that's exactly why we haveANGLE_instanced_arrays.

[Update:Peter Jacobs on Google+ brought up a potential point of confusion. Despite the name, this extension works on any device with the appropriate hardware support (ARB_instanced_arrays and ARB_draw_instanced), not just on Windows when using ANGLE. The ANGLE in the name simply indicates that the extension spec was written by the ANGLE authors.]

This extension, which is currently available in the Chrome Dev channel behind thedraft extensions flag, allows you to pack information about each mesh instance into an attribute array, just like you do vertex position, normal, etc. But instead of listing these values once per vertex, you only have to specify them once per instance. Lets take a look at how it works:

// A nice little line of monkeys down the X axis

var offsets = new Float32Array([

0.0, 0.0, 0.0,

1.0, 0.0, 0.0,

2.0, 0.0, 0.0,

3.0, 0.0, 0.0

]);

var colors = new Float32Array([

1.0, 0.0, 0.0, 1.0, // Red monkey

0.0, 1.0, 0.0, 1.0, // Green monkey

0.0, 0.0, 1.0, 1.0, // Blue monkey

1.0, 1.0, 1.0, 1.0, // White monkey

]);

var offsetBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);

gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);

var colorBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);

gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

This should look pretty familiar! In fact, there's no difference between this code and your standard Vertex buffer setup except that the information is intended to be used once per instance. The real difference come in when you go to draw with that data.

var instanceCount = 4;

var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply!

// Bind the rest of the vertex attributes normally

bindMeshArrays(gl);

// Bind the instance position data

gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);

gl.enableVertexAttribArray(offsetLocation);

gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0);

ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced!

// Bind the instance color data

gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);

gl.enableVertexAttribArray(colorLocation);

gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0);

ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced!

// Draw the instanced meshes

ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount);

This is actually doing the same thing as our first code snippet with the for loop, but as you can see there's only one draw call here! The shader would have to be updated, but essentially you would only change the position and color inputs from a uniform to an attribute. The important bits in this code are the calls to vertexAttribDivisorANGLE and drawElementsInstancedANGLE.

vertexAttribDivisorANGLEspecifies for the given attribute location how often a value should be repeated. A divisor of 0 means the attribute isn't instanced. A divisor of one means that each value in the attribute stream is used for a single instance. A divisor of two would mean that each value is used for two consecutive instances. For example:

ext.vertexAttribDivisorANGLE(offsetLocation, 1);

ext.vertexAttribDivisorANGLE(colorLocation, 2);

Using the data above, this would render two red monkeys at X=0 and X=1, and two green monkeys at X=2 and X=3.

ext.vertexAttribDivisorANGLE(offsetLocation, 2);

ext.vertexAttribDivisorANGLE(colorLocation, 1);

This, on the other hand, would render a green and red monkey at X=0 and a blue and white monkey at X=1.

(To be honest, I don't see much real-world use for divisors other than 0 and 1, but it's nice to know what they do.)

drawElementsInstancedANGLE (anddrawArraysInstancedANGLE) are straightforward replacements for their non-instanced counterparts drawElements and drawArrays, the only difference being that each takes the number of instances to render as an additional argument.

Pretty simple, right? When used correctly this can lead to better performance and less code, both noble goals.

And now for a quick confession: The demo I created is actually a pretty terrible example of this API. On most systems I've tested it on using hardware instancing (that is, using the extension) either yields the same performance as the software instancing (using a for loop) or only provides 2-3 extra FPS. That's because this scene isn't really draw-call bound, and so instancing doesn't have much opportunity to speed it up.

As with anything in graphics, you shouldn't just blindly throw this API at everything you can but instead measure what the bottlenecks actually are. If you are fill-rate bound or vertex-bound then instancing won't solve your problems. But if your seeing your app get hung up on making lots of draw calls while your GPU spins it's wheels waiting for Javascript to catch up, this is the API for you!

From Brandon Jonesat

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容