webgl筆記-3.紋理、幀、深度檢測和混合


圖形學編程的幾個基本問題包括實現顏色、實現紋理、實現光照、實現混合(即透明效果);這幾個問題實際上是一個問題:確定圖元(像素)的在屏幕上的顏色。
確定圖元顏色的過程在頂點着色器和片元着色器中進行:為每一個頂點(注意這里立方體有24個頂點而不是8個)指定一種顏色(並線形內插到每個像元上)以實現顏色;為每個頂點指定從紋理中取色的坐標(並線形內插到每個像元上)以實現紋理;為每個頂點指定光線強度和方向,以此計算出每個頂點的光照影響權值(並線形內插到每個像元上)以實現光照。

這篇博文補充了如何從圖片文件中加載紋理、總結了WebGL中幀的繪制過程,其中涉及到深度檢測和混合兩種繪制方法(即幀上已有像素時的處理方法)。這篇博文內容比較雜,應該也會比較短,算是對前兩篇的補充吧。

紋理加載

通常情況下,紋理是一張圖片,而紋理對象是一個Javascript對象,類似於緩沖區,由gl對象的方法創建。將紋理加載到紋理對象的代碼如下:

var theTexture = gl.createTexture(); 
theTexture.image = new Image(); 
theTexture.image.onload = function() { handleLoadedTexture(theTexture)}; 
theTexture.image.src = "texture.jpg";

gl.createTexture函數返回一個新創建的空紋理對象,將這個空紋理對象傳遞給handleLoadedTexture函數,這個函數將在Image對象加載完成后執行。注意這個Image對象並不必須是紋理對象的屬性,這里這樣做只是為了方便。你完全可以重寫handleLoadedTexture函數,傳入兩個參數theTexture和theImage,或者干脆不寫這個函數,直接在theImage.onload事件中編寫實際工作的代碼。handledLoadedTexture函數的代碼如下:

function handleLoadedTexture(texture) { 
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 
    gl.bindTexture(gl.TEXTURE_2D, texture); 
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); 
    gl.generateMipmap(gl.TEXTURE_2D); 
    gl.bindTexture(gl.TEXTURE_2D, null); 
}

首先指定紋理坐標垂直翻轉,這是因為圖片坐標以圖片左上角作為原點,Y軸垂直向下,而在WebGL中Y軸是垂直向上的。然后需要將紋理對象綁定到“當前對象”,正和緩沖區一樣。再然后用texImage2D函數將Image對象上傳到顯卡中。最后還需要指定紋理過濾方式,比如這里將圖像放大時的過濾方式指定為雙線性內插(linear);紋理縮小時的紋理過濾方式指定為金字塔圖層方式(Linear MipMaps Nearest)。 

紋理圖片,也是由離散的像素組成,不可能還原表面的每一個細節。假設一張11×11的圖片,紋理坐標在[0,0]之間[1,1]:我可能會希望獲取[0.3, 0.5]的顏色值,那自然是[4][6]像素的值;但我幾乎一定會獲取[0.12, 0.25]這樣的顏色值,這個坐標不完全對應於紋理圖片中的像素,如何決定應該返回怎樣的顏色值的方法,就是紋理過濾方式。

有必要解釋一下幾種常用的過濾方式
最鄰近法:選取離需求紋理坐標最近的像素的顏色值作為返回顏色值。
雙線性內插法:選取需求紋理坐標周圍的4個點,根據這4個點的顏色值線形內插出一個顏色值返回。

BlogTechEssayFig11

金字塔圖層:金字塔圖層適合紋理圖片較大像素較多,但是三維空間中紋理所覆蓋的平面較小像素較少的情況。這種情況下“取色”往往希望得到紋理圖片中多個像元的綜合信息,而不是某一個或某幾個像元。金字塔圖層針對不同的尺度預先計算出不同分辨率的圖層,當紋理覆蓋的表面縮小時,就是用這些計算好的圖層代替原紋理取值。

11

在使用紋理(drawElements函數)之前,還應當激活紋理,並將紋理的編號上傳到着色器中的sample2D對象中。WebGL能夠維護至多32個紋理,其編號為0-31,你應該記得在texImage2D方法中,我們將紋理上傳為0號紋理。在真正的三維編程中,你也許會需要維護一個紋理序列。

gl.activeTexture(gl.TEXTURE0); 
gl.bindTexture(gl.TEXTURE_2D, theTexture); 
gl.uniform1i(shaderProgram.samplerUniform, 0);

關於紋理,最后還應當注意一點,圖片文件加載到對象的過程是異步進行的。也就是說,很可能你的整個html文檔和腳本全部加載完成了,紋理文件還沒有加載完成。這種情況下,你應當采取tick函數不斷地刷新canvas區域,即使你僅僅想繪制一個靜態的3D場景。如果你僅在body標簽的onload方法里繪制一次canvas區域,很可能這時候紋理還沒有加載完成,所有視圖貼紋理的表面都是黑色。

根據計算機的性能,瀏覽器每隔一段時間發出一個刷新canvas區域的請求。在兩次刷新canvas區域的短暫時間段內,該區域所呈現的圖像稱為“幀”。

function tick() { 
    requestAnimFrame(tick); 
    drawScene(); 
    animate(); 
}

requestAnimFrame將不同瀏覽器刷新canvas區域的請求封裝起來,我只需要知道每隔一個很小的時間間隔,tick函數就會執行一次。animate方法負責改變控制空間中物體和相機運動的變量。drawScene函數繪制完整的一幀。 

空間中存在多個物體時,drawScene函數中需要進行多次gl.drawElements方法、gl.drawArrays方法或其他類似作用的方法,每一次繪制都是針對像素。比如,可能會在場景中先繪制一個金字塔,再繪制一個立方體。

function drawScene(){ 
    …… 
    gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems); 
    …… 
    gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); 
}

這樣,在繪制立方體的時候,很可能會處理已經繪制為金字塔的像素了,這表明兩個物體“重疊”了,一個在前一個在后,如果他們不是透明的,那么前面的物體會擋住視線,從屏幕上看不到后面的物體(的一部分)。事實上,在單一的繪制的過程中,也很有可能會處理已處理過的像素:比如繪制金字塔的函數gl.drawArrays(args),需要繪制4個三角形和1個正方形,4個三角面一定會有面在后方被遮擋。

繪制過程中如何對已繪制的像素進行處理,這里介紹兩種方式,就是深度檢測和混合。

深度檢測

深度檢測是處理已繪制像素的一種方式。齊次坐標的第三個分量,z值在投影變換中保持了單調性:空間中點的z值較大的,映射到CCV中的z值也較大。z值經過圖元光柵化,線形內插到對應於每個像素上。相機向着z負半軸方向觀察,因此z值小的在前方,會遮擋相機的實現。

某個已經處理過的像素(x,y,zd,1),顏色為(Rd,Gd,Bd,1),將要繪制上去的像素(x,y,zs,1)的顏色為(Rs,Gs,Bs,1),最終繪制的顏色為(R,G,B,1),則深度檢測的過程可以這樣表示:

$$if(z_{d}>z_{s})\begin{bmatrix}R\\ G\\ B\\ 1\end{bmatrix}=\begin{bmatrix}R_{s}\\ G_{s}\\ B_{s}\\ 1\end{bmatrix},z_{d}=z_{s}$$

深度監測將z值較小的像素顏色作為最終繪制的顏色,顯然,前方的物體將后方的物體擋住了。除非需要繪制透明物體,一般都會開啟深度檢測。開啟和關閉深度檢測的代碼是:

gl.enable(gl.DEPTH_TEST); 
gl.disable(gl.DEPTH_TEST);

混合

混合是用以模擬透明效果的一種方法。類似於深度檢測,對某個已經處理過的像素(x,y,zd,1),顏色為(Rd,Gd,Bd,ahpad),將要繪制上去的像素(x,y,zs,1)的顏色為(Rs,Gs,Bs,aphas),最終繪制的顏色為(R,G,B,1),混合的過程可以這樣表示。

$$\begin{bmatrix}R\\ G\\ B\\ \alpha\end{bmatrix}=\begin{bmatrix}R_{s}\\ G_{s}\\ B_{s}\\ \alpha_{s}\end{bmatrix} \cdot S_{factor}+\begin{bmatrix}R_{d}\\ G_{d}\\ B_{d}\\ \alpha_{d}\end{bmatrix}\cdot D_{factor}$$

其中Sfactor和Dfactor可以通過gl.blendFunc方法指定。比如,將Sfactor指定為待繪制顏色的alpha值,Dfactor指定為1。

gl.blendFunc(gl.SRC_ALPHA, gl.ONE); 
gl.enable(gl.BLEND); 
gl.disable(gl.DEPTH_TEST);

混合可以實現類似於透明的效果。開啟混合的時候必須關閉深度檢測,因為深度檢測優先於混合,換言之如果同時開啟了混合和深度檢測,待繪制的像素z值大於已繪制的z值時,就會直接不繪制,而待繪制z值小於已繪制z值時會正常的混合。

其他

本篇博文中的代碼全部來自HiWebGL站點翻譯的WebGL教程,對代碼的解釋是我自己的理解。因為我也是初學WebGL,所以我的理解幾乎一定會有錯誤,如果你發現了,懇請你指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM