three.js 筆記六 InstancedMesh InstancedBufferGeometry

游戲DrawCall參考UnityShader精要筆記四 渲染流水線
游戲合批參考Cocos 3.x 由淺到淺入門批量渲染

THREEJS參考官方文檔
https://threejs.org/docs/index.html#api/zh/objects/InstancedMesh
https://threejs.org/examples/webgl_instancing_performance.html

參考
Threejs性能優(yōu)化:Instance實例化幾何體 和 Merge合并幾何體

當(dāng)我們有大量的相同的幾何體形狀和相同的材質(zhì)時卧须,比如我有一千個立方幾何體要渲染权埠,他們的材質(zhì)時相同的,但是坐標(biāo)佛呻、大小矩陣變換這些不相同。如果按照常規(guī)的一個個Mesh的渲染撞羽,要生成一千個geometry允瞧,一千個material,一千個Mesh泌绣,占用太多內(nèi)存和性能。

我們可以使用合并幾何體的方式预厌,但這樣合并后變?yōu)橐粋€個體 阿迈,失去了對單個小模型的控制。three.js還提供了InstanceMesh實例化模型可以實現(xiàn)轧叽。

image.png
一苗沧、靜態(tài)合批

官方示例相關(guān)代碼如下

const geometries = [];
const matrix = new THREE.Matrix4();

for ( let i = 0; i < api.count; i ++ ) {

    randomizeMatrix( matrix );

    const instanceGeometry = geometry.clone();
    instanceGeometry.applyMatrix4( matrix );

    geometries.push( instanceGeometry );

}

const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );

scene.add( new THREE.Mesh( mergedGeometry, material ) );


從Native切換過來,可以看到DrawCall變成1炭晒,但是占用內(nèi)存暴增待逞。

二、實例化渲染InstancedMesh

官方示例相關(guān)代碼如下

const matrix = new THREE.Matrix4();
const mesh = new THREE.InstancedMesh( geometry, material, api.count );

for ( let i = 0; i < api.count; i ++ ) {

    randomizeMatrix( matrix );
    mesh.setMatrixAt( i, matrix );

}

scene.add( mesh );

const randomizeMatrix = function () {

    const position = new THREE.Vector3();
    const rotation = new THREE.Euler();
    const quaternion = new THREE.Quaternion();
    const scale = new THREE.Vector3();

    return function ( matrix ) {

        position.x = Math.random() * 40 - 20;
        position.y = Math.random() * 40 - 20;
        position.z = Math.random() * 40 - 20;

        rotation.x = Math.random() * 2 * Math.PI;
        rotation.y = Math.random() * 2 * Math.PI;
        rotation.z = Math.random() * 2 * Math.PI;

        quaternion.setFromEuler( rotation );

        scale.x = scale.y = scale.z = Math.random() * 1;

        matrix.compose( position, quaternion, scale );

    };

}();

效果很好网严,DrawCall和內(nèi)存都很低

1.構(gòu)造函數(shù)

InstancedMesh( geometry : BufferGeometry, material : Material, count : Integer )
要創(chuàng)建一個InstancedMesh识樱,需要三個參數(shù),幾何體(BufferGeometry類型),材質(zhì)(Material類型)和要創(chuàng)建的總數(shù)(Integer 類型)

2.屬性
  • count : Integer 實例的數(shù)量怜庸。被傳入到構(gòu)造函數(shù)中的count表示mesh實例數(shù)量的最大值当犯。 可以在運行時改變這個數(shù)值到 [0, count] 區(qū)間的一個整數(shù)
  • instanceColor : InstancedBufferAttribute 代表所有實例的顏色。默認(rèn)情況下null休雌。 如果通過.setColorAt()修改實例化數(shù)據(jù)灶壶,則必須將它的needsUpdate標(biāo)志設(shè)置為 true
  • instanceMatrix : InstancedBufferAttribute 表示所有實例的本地變換肝断。 如果你要通過 .setMatrixAt() 來修改實例數(shù)據(jù)杈曲,你必須將它的 needsUpdate 標(biāo)識為 true
  • isInstancedMesh : Boolean 只讀屬性,判斷一個對象是否是InstancedMesh類型
3.方法
  • dispose () 釋放實例的內(nèi)部資源
  • getColorAt ( index : Integer, color : Color ) 獲取實例的顏色胸懈,它有兩個參數(shù)担扑,
    index:實例索引,取值范圍為0~count
    color:已定義的顏色對象
  • getMatrixAt ( index : Integer, matrix : Matrix4 )
    獲得已定義實例的本地變換矩陣趣钱,它有兩個參數(shù)
    index: 實例的索引涌献。值必須在 [0, count] 區(qū)間
    matrix: 該4x4矩陣將會被設(shè)為已定義實例的本地變換矩陣
  • setColorAt ( index : Integer, color : Color )
    將給定的顏色設(shè)置為定義的實例,它包含兩個參數(shù)
    index:實例索引首有,取值范圍為0~count
    color:單個實例的顏色
    這里需要注意 確保在使用setColorAt 更新所有顏色后將.instanceColor.needsUpdate設(shè)置為true
  • setMatrixAt ( index : Integer, matrix : Matrix4 )
    設(shè)置給定的本地變換矩陣到已定義的實例燕垃,需要兩個參數(shù)
    index: 實例的索引。值必須在 [0, count] 區(qū)間
    matrix: 一個4x4矩陣井联,表示單個實例本地變換
    這里需要注意 確保在使用setMatrixAt 更新所有矩陣后將 .instanceMatrix.needsUpdate 設(shè)置為true
三卜壕、點擊進行單個控制

參考
three.js 性能優(yōu)化 實例化網(wǎng)格模型InstancedMesh

//this.clickObjects是我存放可點擊模型的數(shù)組
var intersects = this.raycaster.intersectObjects(this.clickObjects);
if (intersects.length) {
    var mesh = intersects[0].object;//這里就是需要操作的網(wǎng)格模型了
    var instanceId = intersects[0].instanceId;//這里的instanceId就是該實例的索引,對應(yīng)我們之前初始化時的index
    //判斷點擊得到的是不是isInstancedMesh
    if (mesh.isInstancedMesh && instanceId>= 0) {
        //如果要更改顏色
        mesh.setColorAt(instanceId, 0x424242);
        mesh.instanceColor.needsUpdate = true;
        //如果要更改矩陣烙常,matrix是要改成的矩陣轴捎,可以參考初始化時的那樣得到矩陣
        mesh.setMatrixAt(instanceId, matrix);
        mesh.instanceMatrix.needsUpdate = true;
    }
}
四、案例視頻中的boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage)

搜索一下:

new THREE.Float32BufferAttribute(positions, 3).setUsage(THREE.StreamCopyUsage)

相應(yīng)的d.ts文件蚕脏,setUsage有如下類型:

// usage types
export enum Usage {}
export const StaticDrawUsage: Usage;
export const DynamicDrawUsage: Usage;
export const StreamDrawUsage: Usage;
export const StaticReadUsage: Usage;
export const DynamicReadUsage: Usage;
export const StreamReadUsage: Usage;
export const StaticCopyUsage: Usage;
export const DynamicCopyUsage: Usage;
export const StreamCopyUsage: Usage;

WebGL編程指南筆記二 第三章第四章 繪制和變換中侦副,有如下介紹:

usage:表示程序?qū)⑷绾问褂么鎯υ诰彌_區(qū)對象中的數(shù)據(jù)。該參數(shù)將幫助WebGL優(yōu)化操作驼鞭,但是就算你傳入了錯誤的值秦驯,也不會終止程序(僅僅是降低程序的效率)

  • gl.STATIC_DRAW 只會向緩沖區(qū)對象中寫入一次數(shù)據(jù),但需要繪制很多次(many times)
  • gl.STREAM_DRAW 只會向緩沖區(qū)對象中寫入一次數(shù)據(jù)挣棕,然后繪制若干次(at most a few times)
  • gl.DYNAMIC_DRAW 會向緩沖區(qū)對象中多次寫入數(shù)據(jù)译隘,并繪制很多次(many times)
五、InstancedBufferGeometry

參考
https://threejs.org/examples/?q=instanc#webgl_buffergeometry_instancing
https://www.bilibili.com/video/BV1pW4y177yt P62

1.在官方示例中穴张,使用方式如下:
const geometry = new THREE.InstancedBufferGeometry();

// set so its initalized for dat.GUI, will be set in first draw otherwise
geometry.instanceCount = instances; 

geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );

geometry.setAttribute( 'offset', 
new THREE.InstancedBufferAttribute( new Float32Array( offsets ), 3 ) );

geometry.setAttribute( 'color', 
new THREE.InstancedBufferAttribute( new Float32Array( colors ), 4 ) );

geometry.setAttribute( 'orientationStart', 
new THREE.InstancedBufferAttribute( new Float32Array( orientationsStart ), 4 ) );

geometry.setAttribute( 'orientationEnd', 
new THREE.InstancedBufferAttribute( new Float32Array( orientationsEnd ), 4 ) );

const material = new THREE.RawShaderMaterial( {

    uniforms: {
        'time': { value: 1.0 },
        'sineTime': { value: 1.0 }
    },
    vertexShader: document.getElementById( 'vertexShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
    side: THREE.DoubleSide,
    transparent: true

} );

//

const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
2.回顧一下普通的BufferGeometry:
function drawPlaneByBufferGeometryUV() {
  //創(chuàng)建BufferGeometry實例
  const bufferGeom = new THREE.BufferGeometry();

  //初始化存放頂點信息的序列化數(shù)組
  const positions = new Float32Array([
    -5.0, 3.0, 0.0, //point0
    5.0, 3.0, 0.0, //point1
    6.0, -3.0, 0.0, //point2
    -6.0, -3.0, 0.0, //point3

  ]);

  //設(shè)置頂點信息
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

  //初始化存放顏色信息的序列化數(shù)組
  const colors = new Float32Array([
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,

  ]);
  //設(shè)置顏色信息
  bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));


  const indexs = new Uint16Array([
    0, 1, 2,
    0, 2, 3,
    4, 5, 6,
    4, 6, 7
  ]);


  //設(shè)置畫面的索引
  bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

  const uvs = new Uint16Array([
    0, 1,
    1, 1,
    1, 0,
    0, 0,

  ]);
  //設(shè)置UV
  bufferGeom.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

  const planetTexture = new THREE.TextureLoader().load("../assets/textures/test.png");

  //創(chuàng)建材質(zhì)
  const material = new THREE.MeshBasicMaterial({
    map: planetTexture,
    vertexColors: THREE.VertexColors, //使用緩存中的顏色
    side: THREE.DoubleSide
  });

  const mesh = new THREE.Mesh(bufferGeom, material);
  scene.add(mesh);
}
3.對比

對比繼承關(guān)系细燎,可以看出使用API區(qū)別不大:

InstancedBufferGeometry extends BufferGeometry
InstancedBufferAttribute extends BufferAttribute

最大的區(qū)別在于,RawShaderMaterial對傳入的attribute進行控制

    <script id="vertexShader" type="x-shader/x-vertex">
        precision highp float;

        uniform float sineTime;

        uniform mat4 modelViewMatrix;
        uniform mat4 projectionMatrix;

        attribute vec3 position;
        attribute vec3 offset;
        attribute vec4 color;
        attribute vec4 orientationStart;
        attribute vec4 orientationEnd;
                ...
4. What is the difference between InstancedBufferGeometry and InstancedMesh in threeJS?

機翻版本在這里:
https://www.coder.work/article/7602085

節(jié)選部分內(nèi)容如下:

If some or all of your instances will change often, and if you have fewer than ~50K instances, and the differences between the instances can be described by a TRS transform (or color), then InstancedMesh should be a good fit for you.

如果您的部分或全部實例會經(jīng)常更改皂甘,并且您的實例少于 ~50K玻驻,并且實例之間的差異可以通過 TRS 變換(或顏色)來描述,那么 InstancedMesh 應(yīng)該非常適合您。

注:TRS 變換即transform,rotate,sacle變換璧瞬,對應(yīng)的API是setMatrixAt;顏色變換對應(yīng)setColorAt

If most of your instances are going to be static and you'll only update a few at a time, then your number of instances could be potentially unlimited (until you reach GPU rendering bottlenecks or VRAM size).

如果您的大多數(shù)實例都是靜態(tài)的户辫,并且您一次只能更新幾個,那么您的實例數(shù)量可能是無限的(直到您達到 GPU 渲染瓶頸或 VRAM 大小)嗤锉。

If you really need to transform each instance each frame, you may want to consider if you can offload your transformation computations to the GPU itself, the standard way to do that is with vertex shaders. The amount of additional computation that can be done with vertex shaders compared to one cpu thread is staggering.

如果您確實需要在每一幀中轉(zhuǎn)換每個實例渔欢,您可能需要考慮是否可以將轉(zhuǎn)換計算卸載到 GPU 本身,執(zhí)行此操作的標(biāo)準(zhǔn)方法是使用頂點著色器瘟忱。與一個 cpu 線程相比奥额,使用頂點著色器可以完成的額外計算量是 驚人的 .

So when either the volume of data involved with the transform for instancing is too much or when the computational overhead of manipulating that data is too much, you'll have to fall back to the more low-level InstancedBufferGeometry approach and get down and dirty with the shaders.

This is the other thing about InstancedMesh. It allows you to avoid touching shaders.

因此,當(dāng)與用于實例化的轉(zhuǎn)換相關(guān)的數(shù)據(jù)量過多或操作該數(shù)據(jù)的計算開銷過多時访诱,您將不得不回退到更底層的 InstancedBufferGeometry 方法并開始使用著色器垫挨。
這是關(guān)于 InstancedMesh 的另一件事。它允許您避免接觸著色器触菜。

六九榔、InterleavedBuffer

參考
Difference and uses of InstancedMesh and InterleavedBuffer in ThreeJS

InterleavedBuffer provides the possibility to manage your vertex data in an interleaved fashion. The motivation of doing this is to improve the amount of cache hits on the GPU. If you are more interested in the theory behind this approach, I suggest you google "structure of arrays vs. array of structures". The latter one applies to InterleavedBuffer.

InterleavedBuffer提供了以交錯方式管理頂點數(shù)據(jù)的可能性。這樣做的動機是提高GPU上的緩存命中量涡相。如果你對這種方法背后的理論更感興趣哲泊,我建議你谷歌一下"structure of array vs. of structures“诅妹。

In general, the performance benefits of both techniques depends on the specific use case. According to my personal experiences, the benefits of interleaved buffers is hard to measure since the performance improvements depend on the respective GPU. In many cases, I've seen no difference in FPS when using interleaved buffers. However, it's much more easier to see a performance improvement if the amount of draw calls is high and you lower it by using instanced rendering.

three.js provides examples for both techniques. webgl_buffergeometry_instancing_interleaved demonstrates a combination.

通常旅挤,這兩種技術(shù)的性能優(yōu)勢取決于特定的用例。根據(jù)我的個人經(jīng)驗冕房,交叉緩沖的好處很難衡量生逸,因為性能的提高取決于各自的GPU牢屋。在許多情況下,在使用交錯緩沖區(qū)時槽袄,我沒有看到FPS有什么不同烙无。但是,如果繪制調(diào)用量很高遍尺,而您使用實例化渲染來降低調(diào)用量截酷,則更容易看到性能的提高。

1.采用 array of structure (AOS) 還是 structure of arrays (SOA)

參考
優(yōu)化數(shù)據(jù)排布乾戏,讓你的程序加速 4 倍迂苛!

我的程序中需要用到結(jié)構(gòu)體的一維數(shù)組。 比如說鼓择,我有一個粒子系統(tǒng)三幻,每個粒子有 x, y, z, w 四個屬性。我應(yīng)該采用 array of structure (AOS) 還是 structure of arrays (SOA) 的方式性能會更高呐能?

具體來看念搬,在 C++ 中抑堡,我應(yīng)該用

// Array of structures (AOS)
struct Particle {float x, y, z, w};
Particle particles[1000];

還是

// Structure of arrays (SOA)
struct Particles {
    float x[1000];
    float y[1000];
    float z[1000];
    float w[1000];
};
2.webgl_buffergeometry_points_interleaved.html

https://threejs.org/examples/?q=interleaved#webgl_buffergeometry_points_interleaved

將position 以及color 存放在一個ArrayBuffer 中,通過 THREE.InterleavedBufferAttribute 設(shè)置偏移等屬性朗徊,讀取對應(yīng)的字段首妖。

export class InterleavedBufferAttribute {

    constructor(
        interleavedBuffer: InterleavedBuffer,
        itemSize: number,
        offset: number,
        normalized?: boolean
    );

示例使用方式:

const interleavedBuffer32 = new THREE.InterleavedBuffer( interleavedFloat32Buffer, 4 );
const interleavedBuffer8 = new THREE.InterleavedBuffer( interleavedUint8Buffer, 16 );

geometry.setAttribute( 'position', new THREE.InterleavedBufferAttribute( interleavedBuffer32, 3, 0, false ) );
geometry.setAttribute( 'color', new THREE.InterleavedBufferAttribute( interleavedBuffer8, 3, 12, true ) );
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市爷恳,隨后出現(xiàn)的幾起案子有缆,更是在濱河造成了極大的恐慌,老刑警劉巖温亲,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棚壁,死亡現(xiàn)場離奇詭異,居然都是意外死亡铸豁,警方通過查閱死者的電腦和手機灌曙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节芥,“玉大人,你說我怎么就攤上這事逆害⊥纺鳎” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵魄幕,是天一觀的道長相艇。 經(jīng)常有香客問我,道長纯陨,這世上最難降的妖魔是什么坛芽? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮翼抠,結(jié)果婚禮上咙轩,老公的妹妹穿的比我還像新娘。我一直安慰自己阴颖,他們只是感情好活喊,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著量愧,像睡著了一般钾菊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上偎肃,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天煞烫,我揣著相機與錄音,去河邊找鬼累颂。 笑死滞详,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茵宪,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼最冰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稀火?” 一聲冷哼從身側(cè)響起暖哨,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凰狞,沒想到半個月后篇裁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡赡若,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年达布,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逾冬。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡黍聂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出身腻,到底是詐尸還是另有隱情产还,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布嘀趟,位于F島的核電站脐区,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏她按。R本人自食惡果不足惜牛隅,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酌泰。 院中可真熱鬧媒佣,春花似錦、人聲如沸宫莱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽授霸。三九已至巡验,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碘耳,已是汗流浹背显设。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辛辨,地道東北人捕捂。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓瑟枫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親指攒。 傳聞我的和親對象是個殘疾皇子慷妙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容