摘要:實(shí)例化數(shù)組實(shí)例化是一種只調(diào)用一次渲染函數(shù)卻能繪制出很多物體的技術(shù),它節(jié)省渲染一個(gè)物體時(shí)從到的通信時(shí)間。實(shí)例化如果能夠講數(shù)據(jù)一次性發(fā)送給,然后告訴使用一個(gè)繪制函數(shù),繪制多個(gè)物體,就會(huì)更方便。因此可以看出減少了繪制的遍歷。
實(shí)例化數(shù)組
實(shí)例化是一種只調(diào)用一次渲染函數(shù)卻能繪制出很多物體的技術(shù),它節(jié)省渲染一個(gè)物體時(shí)從CPU到GPU的通信時(shí)間。
實(shí)例數(shù)組是這樣的一個(gè)對(duì)象,使用它,可以把原來(lái)的的uniform變量轉(zhuǎn)換成attribute變量,而且這個(gè)attribute變量對(duì)應(yīng)的緩沖區(qū)可以被多個(gè)對(duì)象使用;這樣在繪制的時(shí)候,可以減少webgl的調(diào)用次數(shù)。
假設(shè)這樣的一個(gè)場(chǎng)景:你需要繪制很多個(gè)形狀相同的物體,但是每個(gè)物體的顏色、位置卻不一樣,通常的做法是這樣的:
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); }
但是這種做法的一個(gè)缺點(diǎn)是:當(dāng)繪制的對(duì)象的數(shù)量巨大之后,執(zhí)行的效率就會(huì)變的很慢了;這是因?yàn)槊恳淮卫L制的時(shí)候,都需要調(diào)用很多webgl 的很多方法,比如綁定VAO對(duì)象,綁定貼圖,設(shè)置uniform變量,告訴GPU從哪個(gè)緩沖區(qū)區(qū)讀取頂點(diǎn)數(shù)據(jù),以及從哪里找到頂點(diǎn)屬性,所有這些都會(huì)是CPU和GPU的資源消耗過(guò)多。
實(shí)例化如果能夠講數(shù)據(jù)一次性發(fā)送給GPU,然后告訴WebGL使用一個(gè)繪制函數(shù),繪制多個(gè)物體,就會(huì)更方便。這種技術(shù),便是實(shí)例化技術(shù)。這種技術(shù)的實(shí)現(xiàn)思路,就是把原本的uniform變量,比如變換矩陣,變成attribute變量,然后把多個(gè)對(duì)象的矩陣數(shù)據(jù),寫(xiě)在一起,然后創(chuàng)建所有矩陣的VBO對(duì)象(頂點(diǎn)緩存區(qū)); 創(chuàng)建好緩沖區(qū)后,把所有對(duì)象的矩陣數(shù)據(jù)通過(guò)bufferData 上傳到緩沖區(qū)中,這和普通的attribute變量的緩沖區(qū)沒(méi)什么差別。
接下來(lái),就是和普通的VBO差異的部分:該緩沖區(qū)可以在多個(gè)對(duì)象之間共享。每個(gè)對(duì)象 取該緩沖區(qū)的一部分?jǐn)?shù)據(jù),作為attribute變量的值,方法如下:
gl.vertexAttribDivisor(index, divisor)
通過(guò)gl.vertexAttribDivisor方法指定緩沖區(qū)中的每一個(gè)值,用于多少個(gè)對(duì)象,比如divisor = 1,表示每一個(gè)值用于一個(gè)對(duì)象;如果divisor=2,表示一個(gè)值用于兩個(gè)對(duì)象。 index表示的attribute變量的地址。
然后,通過(guò)調(diào)用如下方法進(jìn)行繪制:
gl.drawArraysInstanced(mode, first, count, instanceCount); gl.drawElementsInstanced(mode, count, type, offset, instanceCount);
這兩個(gè)方法和 gl.drawArrays與gl.drawElements類(lèi)似,不同的是多了第四個(gè)參數(shù) instanceCount,表示一次繪制多少個(gè)對(duì)象。
通過(guò)這個(gè)方法,便能實(shí)現(xiàn)一次調(diào)用繪制多個(gè)對(duì)象的目標(biāo)。
本案例 將一次繪制多個(gè)四邊形,代碼如下:
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); //給緩沖區(qū)填充數(shù)據(jù) 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);// 清空顏色緩沖區(qū) // // 繪制第一個(gè)三角形 gl.bindVertexArray(triangleArray); gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);定義四邊形VBO、IBO數(shù)據(jù)
首先定義一個(gè)變量count,繪制四邊形的個(gè)數(shù)為 count * count,也就是count 列 count行個(gè)四邊形。 然后一下代碼定義四邊形的頂點(diǎn)坐標(biāo)、顏色和索引相關(guān)數(shù)據(jù),這在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); //給緩沖區(qū)填充數(shù)據(jù)uniform變量改成attribute變量
接下來(lái),為了把每個(gè)四邊形分開(kāi),我們給每個(gè)四邊形定義一個(gè)偏移量(此處的偏移量可以相當(dāng)于變換矩陣),在WebGL1中,這個(gè)偏移量會(huì)以u(píng)niform變量的方式定義,但是在實(shí)例化的技術(shù)下,該偏移量定義為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; } `;定義偏移量的數(shù)據(jù)及VBO
然后定義每個(gè)對(duì)象的偏移量數(shù)據(jù)的數(shù)組:
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); } }
這個(gè)偏移量,將會(huì)使所有的四邊形,按照count 行 count 列排列。
定義了偏移量數(shù)組之后,創(chuàng)建相應(yīng)的緩沖區(qū)和開(kāi)啟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變量從緩沖區(qū)取數(shù)據(jù) gl.vertexAttribPointer(aOffsetLocation, 3, gl.FLOAT, false, 12, 0); // 定義每個(gè)數(shù)據(jù)的長(zhǎng)度為3個(gè)分量,長(zhǎng)度為12 = 3 * 4(浮點(diǎn)數(shù)長(zhǎng)度)。 gl.vertexAttribDivisor(aOffsetLocation, 1);gl.vertexAttribDivisor
注意 gl.vertexAttribDivisor(aOffsetLocation, 1); 這一行,1表示指定每個(gè)數(shù)據(jù)(定義每個(gè)數(shù)據(jù)的長(zhǎng)度為3個(gè)分量,長(zhǎng)度為12 = 3 * 4(浮點(diǎn)數(shù)長(zhǎng)度)) 被一個(gè)四邊形所用,而每一個(gè)四邊形的繪制期間,attribute變量offset保持不變,這個(gè)uniform變量類(lèi)似。
gl.drawElementsInstanced 繪制多個(gè)實(shí)例接下來(lái),調(diào)用方法繪制多個(gè)實(shí)例,
// //////////////// // // DRAW // //////////////// gl.clear(gl.COLOR_BUFFER_BIT);// 清空顏色緩沖區(qū) // // 繪制第一個(gè)三角形 gl.bindVertexArray(triangleArray); gl.drawElementsInstanced(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0,count * count);
gl.drawElementsInstanced 將會(huì)繪制count count個(gè)四邊形的實(shí)例,需要注意的是,繪制實(shí)例的個(gè)數(shù),不能多于attribute變量offset變量的對(duì)應(yīng)的緩沖區(qū)的數(shù)據(jù)個(gè)數(shù),前面代碼offsetArray定義了countcount個(gè)數(shù)據(jù)(注意每個(gè)數(shù)據(jù)有3個(gè)分量,所以數(shù)據(jù)個(gè)數(shù)不等于offsetArray數(shù)組長(zhǎng)度),因此繪制的示例個(gè)數(shù)不能超過(guò)count * count 個(gè),但是可以少于。
案例效果說(shuō)明如果把count 指定為10,最終繪制的效果如下:
可以看出,一次繪制調(diào)用,繪制出了100個(gè)對(duì)象;
如果通過(guò)WebGL1的方式需要遍歷100次繪制。因此可以看出減少了繪制的遍歷。
當(dāng)然如果只是繪制100個(gè)四邊形,遍歷方法也沒(méi)什么不好,實(shí)例化的威力主要體現(xiàn)在,當(dāng)數(shù)據(jù)量變到很大的時(shí)候,比如在筆者電腦上,把count值改為4000,那么會(huì)繪制4000 * 4000 = 一千六百萬(wàn)個(gè)四邊形,如下:
可以看出,還是可以很好的繪制出來(lái)(雖然由于對(duì)象太多,已經(jīng)看不清楚界限)
而采用WebGL1 循環(huán)遍歷的方式,估計(jì)最多也就能夠達(dá)到萬(wàn)級(jí)別的繪制循環(huán)數(shù)量,千萬(wàn)級(jí)別的數(shù)量簡(jiǎn)直不可想象。
當(dāng)然這個(gè)數(shù)量 也是有限制的,比如在筆者的機(jī)器上,把count改成5000,也就是5000 * 5000 = 兩千五百萬(wàn)的時(shí)候,機(jī)器就奔潰了。
在WebGL1中,可以通過(guò)擴(kuò)展來(lái)ANGLE_instanced_arrays來(lái)實(shí)現(xiàn),相關(guān)函數(shù)如下:
var ext = gl.getExtension("ANGLE_instanced_arrays"); ext.vertexAttribDivisorANGLE(index, divisor); ext.drawArraysInstancedANGLE(mode, first, count, primcount); ext.drawElementsInstancedANGLE(mode, count, type, offset, primcount);
更多精彩內(nèi)容,請(qǐng)關(guān)注公眾號(hào):ITman彪叔
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/108656.html
摘要:在很久很久以前,使用的時(shí)候,只能在默認(rèn)的繪制的緩沖區(qū)上面使用,而不能在幀緩沖區(qū)上面實(shí)現(xiàn),更加形象的說(shuō)就是不能用于離屏渲染。下面是該函數(shù)的簽名該函數(shù)的作用就是,把一個(gè)幀緩沖區(qū)上的指定區(qū)域像素轉(zhuǎn)移給另外一個(gè)幀緩沖區(qū)上的指定區(qū)域。 在很久很久以前,盤(pán)古開(kāi)辟了天地,他的頭頂著天,腳踩著地,最后他掛了。他的毛發(fā)變成了森林,他的血液變成了河流,他的肌肉變成了大地。。。。。。卡! 哦,不對(duì),在很久很...
閱讀 3060·2021-11-18 10:02
閱讀 3327·2021-11-02 14:48
閱讀 3391·2019-08-30 13:52
閱讀 555·2019-08-29 17:10
閱讀 2083·2019-08-29 12:53
閱讀 1403·2019-08-29 12:53
閱讀 1027·2019-08-29 12:25
閱讀 2164·2019-08-29 12:17