WebGL2系列之實例數組(Instanced Arrays)

實例化數組

實例化是一種只調用一次渲染函數卻能繪製出不少物體的技術,它節省渲染一個物體時從CPU到GPU的通訊時間。
實例數組是這樣的一個對象,使用它,能夠把原來的的uniform變量轉換成attribute變量,並且這個attribute變量對應的緩衝區能夠被多個對象使用;這樣在繪製的時候,能夠減小webgl的調用次數。web

背景

假設這樣的一個場景:你須要繪製不少個形狀相同的物體,可是每一個物體的顏色、位置卻不同,一般的作法是這樣的:數組

for(var  i = 0; i < amount_of_models_to_draw; i++)
{
    doSomePreparations(); // bind VAO, bind Textures, set uniforms etc.
    gl.drawArrays(gl.TRIANGLES, 0, amount_of_vertices);
}

可是這種作法的一個缺點是:當繪製的對象的數量巨大以後,執行的效率就會變的很慢了;這是由於每一次繪製的時候,都須要調用不少webgl 的不少方法,好比綁定VAO對象,綁定貼圖,設置uniform變量,告訴GPU從哪一個緩衝區區讀取頂點數據,以及從哪裏找到頂點屬性,全部這些都會是CPU和GPU的資源消耗過多。緩存

實例化

若是可以講數據一次性發送給GPU,而後告訴WebGL使用一個繪製函數,繪製多個物體,就會更方便。這種技術,即是實例化技術。這種技術的實現思路,就是把本來的uniform變量,好比變換矩陣,變成attribute變量,而後把多個對象的矩陣數據,寫在一塊兒,而後建立全部矩陣的VBO對象(頂點緩存區); 建立好緩衝區後,把全部對象的矩陣數據經過bufferData 上傳到緩衝區中,這和普通的attribute變量的緩衝區沒什麼差異。
接下來,就是和普通的VBO差別的部分:該緩衝區能夠在多個對象之間共享。每一個對象 取該緩衝區的一部分數據,做爲attribute變量的值,方法以下:函數

gl.vertexAttribDivisor(index, divisor)

經過gl.vertexAttribDivisor方法指定緩衝區中的每個值,用於多少個對象,好比divisor = 1,表示每個值用於一個對象;若是divisor=2,表示一個值用於兩個對象。 index表示的attribute變量的地址。webgl

而後,經過調用以下方法進行繪製:spa

gl.drawArraysInstanced(mode, first, count, instanceCount);
gl.drawElementsInstanced(mode, count, type, offset, instanceCount);

這兩個方法和 gl.drawArrays與gl.drawElements相似,不一樣的是多了第四個參數 instanceCount,表示一次繪製多少個對象。
經過這個方法,便能實現一次調用繪製多個對象的目標。code

案例說明

代碼展現

本案例 將一次繪製多個四邊形,代碼以下:orm

var count = 3000;
        var positions = new Float32Array([
            -1/count, 1/count, 0.0,
            -1/count, -1/count, 0.0,
            1/count, 1/count, 0.0,
            1/count, -1/count, 0.0,
        ]);
        var positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(0);
        var colors = new Float32Array([
            1.0, 0.0, 0.0,
            0.0, 1.0, 0.0,
            0.0, 0.0, 1.0,
            1.0, 1.0, 1.0,
        ]);
        var colorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
        gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(1);

        var indices = new Uint8Array([
            0,1,2,
            2,1,3
        ]);

        var indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW); //給緩衝區填充數據

        var offsetArray = [];
        for(var i = 0;i < count;i ++){
            for(var j = 0; j < count; j ++){
                var x = ((i + 1) - count/2) / count * 4;
                var y = ((j + 1) - count/2) / count * 4;
                var z = 0;
                offsetArray.push(x,y,z);
            }
        }

        var offsets = new Float32Array(offsetArray)

        var offsetBuffer = gl.createBuffer();
        var aOffsetLocation = 2;
        gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
        gl.enableVertexAttribArray(aOffsetLocation);
        gl.vertexAttribPointer(aOffsetLocation, 3, gl.FLOAT, false, 12, 0);
        gl.vertexAttribDivisor(aOffsetLocation, 1);

        // ////////////////
        // // DRAW
        // ////////////////
        gl.clear(gl.COLOR_BUFFER_BIT);// 清空顏色緩衝區
        // // 繪製第一個三角形
        gl.bindVertexArray(triangleArray);
        gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);

定義四邊形VBO、IBO數據

首先定義一個變量count,繪製四邊形的個數爲 count * count,也就是count 列 count行個四邊形。 而後一下代碼定義四邊形的頂點座標、顏色和索引相關數據,這在WebGL1中屢次使用,不在贅述:對象

var positions = new Float32Array([
            -1/count, 1/count, 0.0,
            -1/count, -1/count, 0.0,
            1/count, 1/count, 0.0,
            1/count, -1/count, 0.0,
        ]);
        var positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(0);
        var colors = new Float32Array([
            1.0, 0.0, 0.0,
            0.0, 1.0, 0.0,
            0.0, 0.0, 1.0,
            1.0, 1.0, 1.0,
        ]);
        var colorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
        gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(1);

        var indices = new Uint8Array([
            0,1,2,
            2,1,3
        ]);

        var indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW); //給緩衝區填充數據

uniform變量改爲attribute變量

接下來,爲了把每一個四邊形分開,咱們給每一個四邊形定義一個偏移量(此處的偏移量能夠至關於變換矩陣),在WebGL1中,這個偏移量會以uniform變量的方式定義,可是在實例化的技術下,該偏移量定義爲attribute變量, layout(location=2) in vec4 offset:索引

var vsSource = `#version 300 es
       ......
        layout(location=2) in vec4 offset;
        ......
        void main() {
            vColor = color;
            gl_Position = position  + offset;
        }
`;

定義偏移量的數據及VBO

而後定義每一個對象的偏移量數據的數組:

for(var i = 0;i < count;i ++){
            for(var j = 0; j < count; j ++){
                var x = ((i + 1) - count/2) / count * 4 - 2/count;
                var y = ((j + 1) - count/2) / count * 4 - 2/count;
                var z = 0;
                offsetArray.push(x,y,z);
            }
        }

這個偏移量,將會使全部的四邊形,按照count 行 count 列排列。
定義了偏移量數組以後,建立相應的緩衝區和開啓attribute變量:

var offsetBuffer = gl.createBuffer();
        var aOffsetLocation = 2; // 偏移量attribute變量地址
        gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
        gl.enableVertexAttribArray(aOffsetLocation); // 啓用偏移量attribute變量從緩衝區取數據
        gl.vertexAttribPointer(aOffsetLocation, 3, gl.FLOAT, false, 12, 0); // 定義每一個數據的長度爲3個份量,長度爲12 = 3 * 4(浮點數長度)。
        gl.vertexAttribDivisor(aOffsetLocation, 1);

gl.vertexAttribDivisor

注意 gl.vertexAttribDivisor(aOffsetLocation, 1); 這一行,1表示指定每一個數據(定義每一個數據的長度爲3個份量,長度爲12 = 3 * 4(浮點數長度)) 被一個四邊形所用,而每個四邊形的繪製期間,attribute變量offset保持不變,這個uniform變量相似。

gl.drawElementsInstanced 繪製多個實例

接下來,調用方法繪製多個實例,

// ////////////////
        // // DRAW
        // ////////////////
        gl.clear(gl.COLOR_BUFFER_BIT);// 清空顏色緩衝區
        // // 繪製第一個三角形
        gl.bindVertexArray(triangleArray);
        gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);

gl.drawElementsInstanced 將會繪製count count個四邊形的實例,須要注意的是,繪製實例的個數,不能多於attribute變量offset變量的對應的緩衝區的數據個數,前面代碼offsetArray定義了countcount個數據(注意每一個數據有3個份量,因此數據個數不等於offsetArray數組長度),所以繪製的示例個數不能超過count * count 個,可是能夠少於。

案例效果說明

若是把count 指定爲10,最終繪製的效果以下:

繪製10*10個示例

能夠看出,一次繪製調用,繪製出了100個對象;
若是經過WebGL1的方式須要遍歷100次繪製。所以能夠看出減小了繪製的遍歷。
固然若是隻是繪製100個四邊形,遍歷方法也沒什麼很差,實例化的威力主要體如今,當數據量變到很大的時候,好比在筆者電腦上,把count值改成4000,那麼會繪製4000 * 4000 = 一千六百萬個四邊形,以下:

九百萬個四邊形
能夠看出,仍是能夠很好的繪製出來(雖然因爲對象太多,已經看不清楚界限)
而採用WebGL1 循環遍歷的方式,估計最多也就可以達到萬級別的繪製循環數量,千萬級別的數量簡直不可想象。
固然這個數量 也是有限制的,好比在筆者的機器上,把count改爲5000,也就是5000 * 5000 = 兩千五百萬的時候,機器就奔潰了。

奔潰了

WebGL1 擴展

在WebGL1中,能夠經過擴展來ANGLE_instanced_arrays來實現,相關函數以下:

var ext = gl.getExtension('ANGLE_instanced_arrays');

ext.vertexAttribDivisorANGLE(index, divisor);

ext.drawArraysInstancedANGLE(mode, first, count, primcount);

ext.drawElementsInstancedANGLE(mode, count, type, offset, primcount);

更多精彩內容,請關注公衆號:ITman彪叔
ITman彪叔公衆號

相關文章
相關標籤/搜索