目錄
- Homework1
- shadow Map
- PCF(Percentage Closer Filter)
- PCSS(Percentage Closer Soft Shadow)
GitHub主頁:https://github.com/sdpyy1
作業實現:https://github.com/sdpyy1/CppLearn/tree/main/games202
Homework1
shadow Map
首先需要完成MVP矩陣的構造,在這里的mvp用來表示如果一個模型在shadowmap視角下的位置
CalcLightMVP(translate, scale) {let lightMVP = mat4.create();let modelMatrix = mat4.create();let viewMatrix = mat4.create();let projectionMatrix = mat4.create();// Model transformmat4.translate(modelMatrix,modelMatrix,translate);mat4.scale(modelMatrix,modelMatrix,scale);// View transformmat4.lookAt(viewMatrix, this.lightPos, this.focalPoint, this.lightUp);// Projection transformmat4.ortho(projectionMatrix, -100,100,-100,100,0.1,400); // 實測far要得400可以覆蓋到整個平面mat4.multiply(lightMVP, projectionMatrix, viewMatrix);mat4.multiply(lightMVP, lightMVP, modelMatrix);return lightMVP;}
他在頂點著色器中使用,目的是傳遞每個頂點位置在光源視角下坐標 vPositionFromLight = uLightMVP * vec4(aVertexPosition, 1.0);
在片段著色器中,先實現比較的函數
float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){// shadowmap中存儲的深度float lightDepth = unpack(texture2D(shadowMap, shadowCoord.xy));// 著色點深度float shadowDepth = shadowCoord.z;float visibility = 1.0;// 被擋住了if (shadowDepth > lightDepth) {visibility = 0.0;}return visibility;
}
在主函數中,需要先對光源坐標處理一下,因為它是經過透視投影處理后的NDC坐標,而shadowMap上取值其實需要的是UV坐標(0,1)之間,所以需要先轉到[0,1]之間
??:透視除法在透視投影時才需要,我看別的博客都寫了,其實是不需要的
float visibility = 1.0;// 透視投影時才需要// vec3 shadowCoord = vPositionFromLight.xyz / vPositionFromLight.w;vec3 shadowCoord = (vPositionFromLight.xyz+1.0)/2.0;visibility = useShadowMap(uShadowMap, vec4(shadowCoord, 1.0));
居然沒出現自陰影問題,我們手動創建一個,首先光源位置修改在engine.js中lightPos
// Add lights// light - is open shadow map == truelet lightPos = [0, 90, 80];let focalPoint = [0, 0, 0];let lightUp = [0, 1, 0]const directionLight = new DirectionalLight(5000, [1, 1, 1], lightPos, focalPoint, lightUp, true, renderer.gl);renderer.addLight(directionLight);
把光源變斜一點,就會發現場景從遠到近逐漸出現自陰影現象
y = 40
y=30
y=20
y=10這時候整個地板都出錯了
加一個自偏移,就解決了
if (shadowDepth > lightDepth + 0.01) {visibility = 0.0;}
下面是偏移量改為0.05的效果,效果就太差了,偏移量應該根據光照的角度動態調整
走樣的情況,下面就用PCF來解決~
PCF(Percentage Closer Filter)
簡單理解就是不只判斷shadowmap的一個位置,而是一圈位置的平均。
作業中提供了兩種采樣,它的作用就是減少計算量,沒必要真的一個一個便利來取均值,偏移記錄在了vec2 poissonDisk[NUM_SAMPLES];
下面是我的實現
float PCF(sampler2D shadowMap, vec4 coords) {poissonDiskSamples(coords.xy);// 采樣數float numSamples = 0.0;// 沒有遮擋的采樣數float numUnBlock = 0.0;// 過濾核大小float filterSize = 5.0;float mapSize = 2048.0;// 過濾核范圍float filterRange = filterSize / mapSize;for(int i = 0;i<NUM_SAMPLES;i++){vec2 samplexCoor = coords.xy + poissonDisk[i] * filterRange;// 采樣時可能會越界if(samplexCoor.x > 0.0 && samplexCoor.x < 1.0 && samplexCoor.y > 0.0 && samplexCoor.y < 1.0) {numSamples++;if(useShadowMap(shadowMap,vec4(samplexCoor,coords.z,1.0)) == 1.0) {numUnBlock++;}}}return numUnBlock/numSamples;
}
鋸齒位置的變化(過濾核大小為5)
當改為20時
改為100時,出現了大量噪點
PCSS(Percentage Closer Soft Shadow)
用PCF來做軟陰影,一句話來說就是動態修改過濾核的尺寸,達到不同的因子區域不同的軟硬程度
第一步需要在一定范圍內搜索深度比著色點進的點,從而得到一個平均深度
float findBlocker( sampler2D shadowMap, vec2 uv, float zReceiver ) {int numSamples = 0;float sumDepth = 0.0;float searchSize = 15.0;float mapSize = 2048.0;float searchRange = searchSize / mapSize;for( int i = 0; i < BLOCKER_SEARCH_NUM_SAMPLES; i ++ ) {vec2 sampleCoor = uv + poissonDisk[i] * searchRange;// 采樣時可能會越界if(sampleCoor.x > 0.0 && sampleCoor.x < 1.0 && sampleCoor.y > 0.0 && sampleCoor.y < 1.0) {float depth = unpack(texture2D(shadowMap, sampleCoor));if(depth < zReceiver) {sumDepth += depth;numSamples++;}}}if(numSamples > 0) {return sumDepth / float(numSamples);} else {return zReceiver;}
}
下來就計算半影尺寸并把它作為過濾核尺寸來進行PCF
float PCSS(sampler2D shadowMap, vec4 coords){uniformDiskSamples(coords.xy);// STEP 1: avgblocker depthfloat avgBlockerDepth = findBlocker(shadowMap, coords.xy, coords.z);// STEP 2: penumbra size// 假設光源尺寸為50float Wlight = 50.0;float penumbraSize = (coords.z - avgBlockerDepth) * Wlight / avgBlockerDepth;// STEP 3: filtering// 把半影尺寸當做過濾核大小return PCF(shadowMap, coords,penumbraSize);
}
光源尺寸越大,陰影軟硬區分程度越大,因為計算出的過濾核尺寸區分度越大
Wlight = 50
Wlight = 100
設置過小時,反而看不出什么效果