WebGL光照陰影對映

2022-02-09 09:19:14 字數 3884 閱讀 9082

經過前面的學習,webgl的基本功能都已經掌握了,我們不僅掌握了著色器的編寫,圖形的繪製,矩陣的變換,新增光照,還通過對webgl的基礎api封裝,編寫出了便利的工具庫. 是時候進一步深入學習webgl的高階功能了,我認為要做逼真的3d特效,陰影絕對是乙個必不可少的環節.現在我們就在之前光照的基礎上新增陰影效果吧.

首先看一下陰影效果的例項:

陰影綜合(多物體高精度pcf)

點光源聚光燈陰影

我們以陰影綜合(多物體高精度pcf)為例, 開始學習陰影相關知識.

幀緩衝提高陰影精度

抗鋸齒(pcf)

我們實現陰影效果使用的是叫陰影對映的技術, 而實現陰影對映需要用到幀緩衝區。預設情況下,webgl 在顏色緩衝區繪圖,使用隱藏面消除的話,還會用到深度緩衝區。即正常繪製的情況下包含:

幀緩衝區物件 framebuffer object可以用來代替顏色緩衝區或深度緩衝區。繪製在幀緩衝區中的物件並不會直接顯示canvas上,可以先對幀緩衝區中的內容進行一些處理再顯示,或者直接用其中的內容作為紋理影象。在幀緩衝區中進行繪製的過程又稱為離屏繪製 offscreen drawing。

繪製操作並不是直接發生在幀緩衝區中,而是發生在幀緩衝區所關聯的物件 attachment上,乙個幀緩衝區有3個關聯物件:

而我們現在先有這個概念,來看看幀緩衝區的建立和配置:

建立幀緩衝區物件 gl.createframebffer().

建立文理物件並設定其尺寸和引數 gl.createtexture()、gl.bindtexture()、gl.teximage2d()、gl.parameteri().

建立渲染緩衝區物件 gl.createrenderbuffer().

繫結渲染緩衝區物件並設定其尺寸 gl.bindrenderbuffer()、gl.renderbufferstorage().

將幀緩衝區的顏色關聯物件指定為乙個文理物件 gl.frambuffertexture2d().

將幀緩衝區的深度關聯物件指定為乙個渲染緩衝區物件 gl.framebufferrenderbuffer().

檢查幀緩衝區是否正確配置 gl.checkframebufferstatus().

在幀緩衝區中進行繪製 gl.bindframebuffer().

它的建立和配置是乙個非常繁瑣的過程,我們先熟悉了怎麼使用,再慢慢研究它內部的原理,所以先把上面的步驟封裝成乙個黑盒子,我這裡就是createframebuffer這個函式.

陰影對映的原理很簡單,首先從光的角度渲染場景,從光的角度看到的所有東西都被點亮了,而看不見的部分一定是在陰影裡.。想象有乙個盒子和它的光源照射下的地板,由於光源會看到這個盒子而它後面的地板部分是看不到的.那麼當視線角度變化的時候,從光源角度照不到的那部分地板就渲染為陰影,原理如下圖

接著我們使用陰影對映的演算法實現, 它要使用到前面介紹的幀緩衝區. 陰影對映要渲染兩遍:

從光源的角度渲染場景,同時把場景的深度值當成紋理渲染到幀緩衝區,也就是把它當作資料容器.

從眼睛的角度渲染場景,把物體真正渲染到畫布中,同時對比紋理的深度值,將陰影部分也渲染出來.

左邊的影象是第一遍渲染的原理, 乙個方向光源(所有的光線都是平行的)在立方體下面的表面投下陰影.我們通過用光源的檢視投影矩陣渲染場景(從光線的角度)來建立景深圖然後把它儲存到幀緩衝區中.

右邊的圖形是第二遍渲染的原理, 從眼睛的檢視投影矩陣渲染場景(從眼睛的角度), 光源角度下的xy座標相同的c點和p點,p深度值比c要大, 那麼它一定處於陰影當中,那麼p點就渲染為陰影.

來看實現以上功能的著色器**,因為要渲染兩遍,所以也就要建立兩對的著色器(頂點/片段),頂點著色器比較簡單,基本不涉及陰影對映,在此省略:

#ifdef gl_es 

precision mediump float;

#endif

void main()

深度值後面加了0.005,稍微大於1/256,即8位的表示範圍(因為乙個分量就是8位),這個是消除馬赫帶用的,不加這個值,畫面會產生難看的條紋,具體的原理可查詢馬赫帶,在此不細講.

precision mediump float;

uniform sampler2d u_shadowmap;

varying vec4 v_positionfromlight;

varying vec4 v_color;

void main()

完成了最簡單的陰影效果,但是當你把光源與物體的距離拉遠,問題出來了,怎麼看不到陰影了?這是距離超過了8位的儲存範圍,溢位的緣故.之前我們只使用了乙個分量來儲存,現在我們把其他的分量也利用起來吧,rgba一共32位.

這中間進行複雜的分解運算,並同時去除異常值,請看如下**

/**

* 分解儲存深度值

*/vec4 pack (float depth)

void main()

這裡對應就要解碼出深度值

/**

* 發布深度值z

*/float unpack(const in vec4 rgbadepth)

解決了精度的問題,接著繼續優化. 執行起來吧,陰影很粗糙有木有? 你看看下面左圖,很嚴重的鋸齒, 抗鋸齒有很多種解決方案,我這裡使用pcf, 也就是百分比漸近式過濾演算法,因為它基於**實現的,所以也叫軟陰影.

pcf的原理也很簡單, 採集當前點周圍畫素的陰影值,並將其深度與所有採集的樣本進行比較,最後對結果進行平均,這樣就得到光線和陰影之間更平滑的過渡效果.下面右圖是經過pcf處理之後的陰影,效果要自然得多了.

我們看正常著色器的實現**

vec3 shadowcoord = (v_positionfromlight.xyz/v_positionfromlight.w)/2.0 + 0.5;

float shadows =0.0;

float opacity=0.6;// 陰影alpha值, 值越小暗度越深

float texelsize=1.0/1024.0;// 陰影畫素尺寸,值越小陰影越逼真

vec4 rgbadepth;

// 消除陰影邊緣的鋸齒

for(float y=-1.5; y <= 1.5; y += 1.0)

}shadows/=16.0;// 4*4的樣本

float visibility=min(opacity+(1.0-shadows),1.0);

specular=visibility < 1.0 ? vec3(0.0,0.0,0.0): specular;// 陰影處沒有高光

gl_fragcolor = vec4((diffuse + ambient + specular) * visibility, v_color.a);

webgl的陰影部分,涉及到了很多openggl的底層,計算機圖形學演算法. 為了深入理解它,可真是花費了很多腦力,是到目前為止學習webgl的第一道坎,它裡面的水很深.比如光是反鋸齒部分就涉及到很多低層細節,演算法的實現,顯示卡的效能問題等都是需要考慮的, 陰影部分後續還要慢慢查資料繼續優化.

越是深入學習webgl,就越覺得它相關的資料真是少,必須看opengl es相關的東西才能解決,傷不起啊.

WebGL光照陰影對映

經過前面的學習,webgl的基本功能都已經掌握了,我們不僅掌握了著色器的編寫,圖形的繪製,矩陣的變換,新增光照,還通過對webgl的基礎api封裝,編寫出了便利的工具庫.是時候進一步深入學習webgl的高階功能了,我認為要做逼真的3d特效,陰影絕對是乙個必不可少的環節.現在我們就在之前光照的基礎上新...

webgl 平面陰影效果

在特定的3d場景中,陰影效果有時還是顯得十分重要的,在一般的3d引擎當中設定陰影可以直接通過對物體設定屬性來實現,十分的方便,這裡我們就用webgl來實現一下平面效果。平面陰影是通過燈光將物體的陰影投射在乙個平面內,但是物體之間沒有陰影的疊加,也就是說a物體的陰影不會投射到b物體上,在本案例中我們主...

WebGL 陰影的實現

原理 根據光源與物體之間的距離 也就是物體在光源座標系下的深度z值 來決定物體是否可見。如下圖,同一條光線上有兩個點p1 p2,由於p2的z值大於p1,所以p2在陰影中。需要使用兩對著色器 1 一對著色器用來計算光源到物體的距離 2 另一對根據 1 中計算出的距離繪製場景。2 如何使用 1 中的距離...