溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

three.js中的needsUpdate是什么

發(fā)布時(shí)間:2021-09-13 10:53:47 來(lái)源:億速云 閱讀:194 作者:柒染 欄目:web開發(fā)

本篇文章為大家展示了three.js中的needsUpdate是什么,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

three.js里的很多對(duì)象都有一個(gè)needsUpdate屬性,文檔中很少有寫(不過(guò)three.js的文檔本來(lái)就沒(méi)多少,很多問(wèn)題還得靠github上的issues),網(wǎng)上各式各樣的教程中也不太會(huì)寫這個(gè),因?yàn)閷?duì)于簡(jiǎn)單的入門程序而言,是用不到這個(gè)屬性的。
那么這個(gè)屬性到底是用來(lái)干嘛的,一言以敝之就是告訴renderer這一幀我該更新緩存了,盡管作為一個(gè)標(biāo)志位用途很簡(jiǎn)單,但是因?yàn)橐罏槭裁匆戮彺妫履男┚彺?,所以還是有必要好好了解下的。
為什么需要needsUpdate
首先還是來(lái)看下為什么需要緩存,緩存的存在一般都是為了減少數(shù)據(jù)傳輸?shù)拇螖?shù),從而減少程序在數(shù)據(jù)傳輸上消耗的時(shí)間,這里也是,一般一個(gè)物體(Mesh)要最后能夠成功顯示到屏幕前是很不容易的,需要轉(zhuǎn)三次戰(zhàn)場(chǎng)
首先是通過(guò)程序?qū)⑺械捻旤c(diǎn)數(shù)據(jù)和紋理數(shù)據(jù)從本地磁盤讀取到內(nèi)存當(dāng)中。
然后程序在內(nèi)存中做了適當(dāng)?shù)奶幚碇缶鸵獙⒛切┬枰L制到屏幕前的物體的頂點(diǎn)數(shù)據(jù)和紋理數(shù)據(jù)傳輸?shù)斤@存當(dāng)中。
最后在每一幀渲染的時(shí)候?qū)@存中的頂點(diǎn)數(shù)據(jù)和紋理數(shù)據(jù)flush到GPU中進(jìn)行裝配,繪制。
根據(jù)那個(gè)金字塔式的數(shù)據(jù)傳輸模型,第一步顯然是最慢的,如果是在WebGL這樣的環(huán)境中通過(guò)網(wǎng)絡(luò)來(lái)傳輸,那就更加慢了,其次是從內(nèi)存?zhèn)鬏數(shù)斤@存的時(shí)間,這個(gè)后面會(huì)做一個(gè)簡(jiǎn)單的數(shù)據(jù)測(cè)試。
然后是這三步操作的使用頻率,對(duì)于小場(chǎng)景來(lái)說(shuō),第一步是一次性的,就是每次初始化程序的時(shí)候就會(huì)將一個(gè)場(chǎng)景的所有數(shù)據(jù)都加載到內(nèi)存中了,對(duì)于大場(chǎng)景來(lái)說(shuō),可能會(huì)做一些異步加載,但是目前暫時(shí)不在我們考慮的問(wèn)題當(dāng)中。 對(duì)于第二步的頻率,應(yīng)該是這次要講的最主要的,首先寫個(gè)簡(jiǎn)單的程序測(cè)試一下做這一步傳輸所帶來(lái)的消耗

代碼如下:


var canvas = document.createElement('canvas');
var _gl = canvas.getContext('experimental-webgl');
var vertices = [];
for(var i = 0; i < 1000*3; i++){
vertices.push(i * Math.random() );
}
var buffer = _gl.createBuffer();
console.profile('buffer_test');
bindBuffer();
console.profileEnd('buffer_test');
function bindBuffer(){
for(var i = 0; i < 1000; i++){
_gl.bindBuffer(_gl.ARRAY_BUFFER, buffer);
_gl.bufferData(_gl.ARRAY_BUFFER, new Float32Array(vertices), _gl.STATIC_DRAW);
}
}


先簡(jiǎn)單解釋下這個(gè)程序,vertices是一個(gè)保存頂點(diǎn)的數(shù)組,這里是隨機(jī)生成了1000個(gè)頂點(diǎn),因?yàn)槊總€(gè)頂點(diǎn)都有x,y,z三個(gè)坐標(biāo),所以需要一個(gè)3000大小的數(shù)組, _gl.createBuffer命令在顯存中開辟了一塊用來(lái)存放頂點(diǎn)數(shù)據(jù)的緩存,然后使用_gl.bufferData將生成的頂點(diǎn)數(shù)據(jù)從內(nèi)存?zhèn)鬏斠环輈opy到顯存中。 這里假設(shè)了一個(gè)場(chǎng)景中有1000個(gè)1000個(gè)頂點(diǎn)的物體,每個(gè)頂點(diǎn)是3個(gè)32位4個(gè)字節(jié)的float數(shù)據(jù),計(jì)算一下就是差不多1000 x 1000 x 12 = 11M的數(shù)據(jù),profile一下差不多消耗了15ms的時(shí)間,這里可能看看15ms才這么點(diǎn)時(shí)間,但是對(duì)于一個(gè)實(shí)時(shí)的程序來(lái)說(shuō),如果要保證30fps的幀率,每一幀所需要的時(shí)間要控制在30ms左右,僅僅是做一次數(shù)據(jù)的傳輸就花去了一半的時(shí)間怎么成,要知道大頭應(yīng)該是GPU中的繪制操作和在CPU中的各種各樣的處理啊,應(yīng)該吝嗇整個(gè)渲染過(guò)程中的每一步操作。
所以應(yīng)該盡量減少這一步的傳輸次數(shù),其實(shí)可以做到剛加載的時(shí)候就把所有的頂點(diǎn)數(shù)據(jù)和紋理數(shù)據(jù)從內(nèi)存一并傳輸?shù)斤@存當(dāng)中,這就是現(xiàn)在three.js做的,第一次就把需要繪制的物體(Geometry)的頂點(diǎn)數(shù)據(jù)傳輸?shù)斤@存中,并且緩存這個(gè)buffer到geometry.__webglVertexBuffer,之后每次繪制的時(shí)候都會(huì)判斷Geometry的verticesNeedUpdate屬性,如果不需要更新就直接使用現(xiàn)在的緩存,如果看到verticesNeedUpate為true, 就會(huì)重新將Geometry中的頂點(diǎn)數(shù)據(jù)傳輸?shù)絞eometry.__webglVertexBuffer中,一般對(duì)于靜態(tài)物體我們是不需要這一步操作的,但是如果遇到頂點(diǎn)會(huì)頻繁改變的物體,例如用頂點(diǎn)來(lái)做粒子的粒子系統(tǒng),還有使用了骨骼動(dòng)畫的Mesh, 這些物體每一幀都會(huì)改變自己的頂點(diǎn),所以需要每一幀都需要將其verticesNeedUpdate屬性設(shè)為true來(lái)告訴renderer我需要重新傳輸數(shù)據(jù)了!
其實(shí)在WebGL程序中,更多的會(huì)在vertex shader中去改變頂點(diǎn)的位置來(lái)完成粒子效果和骨骼動(dòng)畫,盡管如果放在cpu端計(jì)算更容易擴(kuò)展,但是因?yàn)閖avascript的計(jì)算能力的限制,更多的還是會(huì)把這些計(jì)算量大的操作放到gpu端操作。 這種情況下并不需要重新傳輸一次頂點(diǎn)數(shù)據(jù),所以上面那種case在實(shí)際程序中其實(shí)用到的不多,更多的還是會(huì)去更新紋理和材質(zhì)的緩存。
上面那個(gè)case主要描述的是一個(gè)傳輸頂點(diǎn)數(shù)據(jù)的場(chǎng)景,除了頂點(diǎn)數(shù)據(jù),還有一個(gè)大頭就是紋理,一張1024*1024大小的R8G8B8A8格式的紋理所要占用的內(nèi)存大小也要高達(dá)4M,于是看下面這個(gè)例子

代碼如下:


var canvas = document.createElement('canvas');
var _gl = canvas.getContext('experimental-webgl');
var texture = _gl.createTexture();
var img = new Image;
img.onload = function(){
console.profile('texture test');
bindTexture();
console.profileEnd('texture test');
}
img.src = 'test_tex.jpg';
function bindTexture(){
_gl.bindTexture(_gl.TEXTURE_2D, texture);
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA, _gl.UNSIGNED_BYTE, img);
}


這里就不需要變態(tài)的重復(fù)1000次了,一次傳輸10241024的紋理就已經(jīng)花了30ms,一張256256的差不多是2ms,所以three.js中對(duì)于紋理也是盡量只在最開始的時(shí)候傳輸一次,之后如果texture.needsUpdate屬性不手動(dòng)設(shè)為true的話就會(huì)一直直接使用已經(jīng)傳輸?shù)斤@存中的紋理。
需要更新哪些緩存
上面通過(guò)兩個(gè)case描述了為什么three.js要加這么一個(gè)needsUpdate屬性,接下來(lái)列舉一下幾個(gè)場(chǎng)景來(lái)知道在什么情況下需要手動(dòng)的更新這些緩存。
紋理的異步加載
這算是一個(gè)小坑吧,因?yàn)榍岸说膱D片是異步加載的,如果在創(chuàng)建好img后直接寫texture.needsUpdate=true的話,three.js的renderer中會(huì)這一幀中就使用_gl.texImage2D將空的紋理數(shù)據(jù)傳輸?shù)斤@存中,然后就將這個(gè)標(biāo)志位設(shè)成false, 之后真正等到圖片加載完成的時(shí)候確不再更新顯存數(shù)據(jù)了,所以必須要在onload事件中等整張圖片加載完成后再寫texture.needsUpdate = true
視頻紋理
大部分紋理都是像上面那個(gè)case直接加載和傳輸一次圖片就行了,但是對(duì)于視頻紋理來(lái)說(shuō)并不是,因?yàn)橐曨l是一個(gè)圖片流,每一幀要顯示的畫面都不一樣,所以每一幀都需要將needsUpdate設(shè)為true來(lái)更新顯卡中的紋理數(shù)據(jù)。
使用render buffer
render buffer是比較特殊的對(duì)象,一般的程序在整個(gè)場(chǎng)景繪制出來(lái)后都是直接flush到屏幕了,但是如果多了post processing或這screen based xxx(例如screen based ambient occlusion)的話,就需要將場(chǎng)景先繪制到一個(gè)render buffer上,這個(gè)buffer其實(shí)就是一張紋理,只不過(guò)是上一步繪制生成的,而不是從磁盤加載的。three.js中有一個(gè)專門的texture對(duì)象WebGLRenderTarget來(lái)初始化和保存renderbuffer, 這種紋理也需要在每一幀設(shè)置一下needsUpdate為true
Material的needsUpdate
材質(zhì)在three.js中是通過(guò)THREE.Material來(lái)描述的,其實(shí)材質(zhì)并沒(méi)有什么數(shù)據(jù)要傳輸,但是為什么還要搞一個(gè)needsUpdate呢,這里還要說(shuō)一下shader這個(gè)東西,shader直譯過(guò)來(lái)是著色器,提供了在gpu中編程處理頂點(diǎn)和像素的可能性,在繪畫中有個(gè)shading的術(shù)語(yǔ)來(lái)表示繪畫的明暗法,GPU中的shading也類似,通過(guò)程序計(jì)算光照的明暗來(lái)表現(xiàn)物體的材質(zhì),ok, 既然shader是一段跑在GPU上的程序,那么像所有程序一樣都需要進(jìn)行一次編譯鏈接的操作, WebGL中是在運(yùn)行時(shí)對(duì)shader程序進(jìn)行編譯的,這當(dāng)然需要消耗時(shí)間,因此也是最好能夠一次編譯就運(yùn)行到程序結(jié)束。所以three.js中就在material初始化的時(shí)候就編譯鏈接了shader程序并且緩存了編譯鏈接后得到的program對(duì)象。一般一個(gè)material是不需要再去重新編譯整個(gè)shader了,材質(zhì)的調(diào)整只需要修改shader的uniform參數(shù)就行了。但是如果是替換了整個(gè)材質(zhì),比如將原來(lái)phong的shader替換成了一個(gè)lambert的shader,就需要將material.needsUpdate設(shè)置成true去重新做一次編譯。不過(guò)這種情況不多見,更常見的是下面提到的一種情況。
添加和刪除燈光
這個(gè)應(yīng)該還是在場(chǎng)景中比較常見了的吧,可能很多剛開始用three.js的人都會(huì)掉進(jìn)這個(gè)坑里,在給場(chǎng)景動(dòng)態(tài)添加了一個(gè)燈光后發(fā)現(xiàn)這個(gè)燈光怎么不起作用,不過(guò)這是在用three.js內(nèi)置的shader的情況下,例如phong, lambert,看renderer里的源代碼就會(huì)發(fā)現(xiàn)three.js在內(nèi)置的shader代碼中使用#define來(lái)設(shè)置場(chǎng)景中燈光的個(gè)數(shù),而這個(gè)#define的值是在每次更新材質(zhì)的時(shí)候通過(guò)字符串拼接shader得到,代碼如下

代碼如下:


"#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
"#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
"#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
"#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,


確實(shí)這種寫法能夠有效的減少了gpu寄存器的使用,如果只有一盞燈光就可以只聲明一個(gè)一盞燈光所需要的uniform變量,但是在每次燈光數(shù)量改變,特別是添加的時(shí)候就需要重新拼接編譯鏈接一次shader,這時(shí)候也需要將所有材質(zhì)的material.needsUpdate設(shè)為true;
改變紋理
這里的改變紋理指的并不是更新紋理數(shù)據(jù),而是原來(lái)材質(zhì)使用了紋理,后來(lái)不使用了,或者原來(lái)材質(zhì)不使用紋理后來(lái)又加上去了,如果不手動(dòng)強(qiáng)制更新材質(zhì)都會(huì)導(dǎo)致最后出來(lái)的效果跟自己想的不一樣,產(chǎn)生這種問(wèn)題的原因跟上面添加燈光差不多,也是因?yàn)閟hader中加了一個(gè)宏來(lái)判斷是否使用了紋理,

代碼如下:


parameters.map ? "#define USE_MAP" : "",
parameters.envMap ? "#define USE_ENVMAP" : "",
parameters.lightMap ? "#define USE_LIGHTMAP" : "",
parameters.bumpMap ? "#define USE_BUMPMAP" : "",
parameters.normalMap ? "#define USE_NORMALMAP" : "",
parameters.specularMap ? "#define USE_SPECULARMAP" : "",


所以每次map, 或者envMap或者lightMap等改變真值的時(shí)候都需要更新材質(zhì)
其它頂點(diǎn)數(shù)據(jù)的改變
其實(shí)上面紋理的改變還會(huì)產(chǎn)生一個(gè)問(wèn)題,主要是在初始化的時(shí)候沒(méi)有紋理,但是后來(lái)動(dòng)態(tài)添加上去這種環(huán)境下,光是將material.needsUpdate設(shè)為true還不夠,還需要將geometry.uvsNeedsUpdate設(shè)成true, 為什么會(huì)有這種問(wèn)題呢,還是因?yàn)閠hree.js對(duì)程序的優(yōu)化,在renderer中第一次初始化geometry, material的時(shí)候,如果判斷為沒(méi)有紋理,盡管內(nèi)存中的數(shù)據(jù)中有每個(gè)頂點(diǎn)uv數(shù)據(jù),但 three.js 還是不會(huì)將這些數(shù)據(jù)copy到顯存中,初衷應(yīng)該還是為了節(jié)省點(diǎn)寶貴的顯存空間,但是在添加紋理后geometry并不會(huì)很智能的重新去傳輸這些uv數(shù)據(jù)以供紋理使用,必須要我們手動(dòng)的將設(shè)置uvsNeedsUpdate來(lái)告知它該更新uv了, 這個(gè)問(wèn)題真是開始的時(shí)候坑了我很長(zhǎng)時(shí)間。
關(guān)于幾種頂點(diǎn)數(shù)據(jù)的needUpdate屬性可以看這條issue
https://github.com/mrdoob/three.js/wiki/Updates
最后
three.js的優(yōu)化做的是不錯(cuò),但是在各種優(yōu)化下帶來(lái)的是各種可能踩到的坑,這種情況最好的辦法也只能是看源代碼了,或者去github上提issues

上述內(nèi)容就是three.js中的needsUpdate是什么,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI