Render Flow of Divinity II (part 2 shadow map)


Render Flow of Divinity II (part 2 shadow map)

作者:clayman 僅供個人學習使用,轉載請保留作者以及原文鏈接,勿用於任何商業用途。

       上一篇文章介紹了Divinity2渲染流程的pre pass階段,最后稍稍漏了一部分,這里補上。Pre-pass中ps 76行以后的代碼是alpha test實現,這里asm代碼邏輯看起來稍微有些奇怪,邏輯其實並不復雜,通過改變c0變量(AlphaTestFunction)的值,可以選擇裁剪alpha值大於,小於或者等於AlphaTestRef的像素,比普通alpha test稍微高級一點點的版本:)    好了,接下來讓我們看shadow map生成。

       Shadow map生成在pre-pass之后從EID 14924開始,點擊0x226D8C80處的紋理,可以看到shadow map如下圖所示:

   這是一張包含了4張shadow map的紋理,2048*2048,R16G16F格式,顯然這是cascaded/parallel-split shadow maps。但如果你仔細看貼圖中的內容,這並不是標准的ppsm,而是以視點為中心與觀察方向無關的csm,類似於real-time 3rd中介紹Hellgate:London所用的方法: generate a fixed set of shadow map at differernt resolutions , nested around the viewer.

        這里可以找到該算法的詳細信息以及demo。由於與視線無關,這種方法的好處是不必每幀都重新渲染所有層次的shadow map,可以把計算分散到n幀進行,比如每幀總是渲染最靠近觀察者的左上角L0級shadow map,n%3==0幀刷新L1級,n%3==1幀刷新L2,n%3==2刷新L3級shadow map,犧牲一定的質量換取性能。實際上游戲設置就有每幀刷新所有shadow map還是分步刷新的選項。Pix文件抓數據時用了最高設置,所以后面可以看到4張shadow map都刷新了。注意,因為是渲染到texture atlases,需要為不同的shadow map設置不同的viewport。下面是生成shadow map所用的vertex shader:

 1 // Generated by Microsoft (R) D3DX9 Shader Compiler 
 2 // Parameters:
 3 //   float4x4 kLightViewProj;
 4 //   float4x4 kWorld;
 5 // Registers:
 6 //   Name           Reg   Size
 7 //   -------------- ----- ----
 8 //   kWorld         c0       4
 9 //   kLightViewProj c4       4
10 //
11 // Default values:
12 //   kWorld
13 //     c0   = { 0, 0, 0, 0 };
14 //     c1   = { 0, 0, 0, 0 };
15 //     c2   = { 0, 0, 0, 0 };
16 //     c3   = { 0, 0, 0, 0 };
17 //
18 //   kLightViewProj
19 //     c4   = { 0, 0, 0, 0 };
20 //     c5   = { 0, 0, 0, 0 };
21 //     c6   = { 0, 0, 0, 0 };
22 //     c7   = { 0, 0, 0, 0 };
23 //
24 
25 vs_1_1
26 def c8, 0, 0, 0, 0
27 dcl_position v0
28    
29 dp4 r0.x, v0, c0       //worldPos = mul(inputPos,kWorld)
30 dp4 r0.y, v0, c1
31 dp4 r0.z, v0, c2
32 dp4 r0.w, v0, c3
33 
34 dp4 oPos.x, r0, c4     //lightViewProjPos = mul(worldPos,lightViewProj);
35 dp4 oPos.y, r0, c5     //outPos.xy = lightViewProjPos.xy;
36 dp4 r1.x, r0, c6
37 dp4 r0.y, r0, c7
38 
39 slt r0.z, r1.x, c8.x   //sign = (lightViewProjPos.z < 0)? 1 : 0;
40 mad r0.x, r0.z, -r1.x, r1.x   // temp = sign * (-lightViewProjPos.z) + lightViewProjPos.z;
41 
42 mov oPos.zw, r0.xyxy   //outPos.zw = float2(temp ,lightViewProjPos.w);
43 mov oT0.xy, r0        //outTexcoord.xy = xxxx

     代碼非常簡單,唯一需要注意的是39,40行對z值為負數的情況作了處理(當物體位於camera之后)。另外,slt/mad也是最常見的用branchless代碼實現branch的方法,翻譯為普通代碼相當於:
if(lightViewProjPos.z < 0)
   lightViewProjPos.z = 0;

   接下來是pixel shader代碼:

1  ps_2_0
2     def c0, 0, 0, 0, 0
3     dcl t0.xy
4 
5     rcp r0.w, t0.y           r0.w = 1 / lightViewProjPos.w
6     mul r0.x, r0.w, t0.x     r0.x = r0.w * t0.x;
7     mul r0.y, r0.x, r0.x     r0.y = r0.x * r0.x;
8     mov r0.zw, c0.x          r0.zw = 00;
9     mov oC0, r0

  PS輸出post-perspective距離,特別注意G通道中輸出了距離的平方,所以這還是一張variance shadow map!EID24883完成了4張shadow map的渲染。 Divinity2只有靜態物體產生陰影:(, 唯一比較特別的是樹葉,樹葉總是在每張shadow map最后渲染,如果采用跨幀渲染渲染,樹葉陰影總是靜態的,如果是全刷選項,樹葉則是動態陰影。感興趣可以查看24883處DP所用的shader,這里不再仔細分析。接下來EID24884 – 24928對shadow map做了blur,vs代碼非常簡單,只看第一個blur pass的ps代碼:

 1 // Generated by Microsoft (R) D3DX9 Shader Compiler 
 2 // Parameters:
 3 //   float g_SunSMTexelSize;
 4 // Registers:
 5 //   Name             Reg   Size
 6 //   ---------------- ----- ----
 7 //   g_SunSMTexelSize c0       1
 8 
 9 // Default values:
10 //g_SunSMTexelSize
11 //c0   = { 0.000488281, 0, 0, 0 };
12 
13 preshader
14 mul c0.x, c0.x, (-2)
15 add c1.x, c0.x, c0.x
16 
17 // approximately 2 instructions used
18 // Generated by Microsoft (R) D3DX9 Shader Compiler 
19 // Parameters:
20 //   float g_SunSMTexelSize;
21 //   sampler2D shadowMapSampler;
22 
23 
24 // Registers:
25 //   Name             Reg   Size
26 //   ---------------- ----- ----
27 //   g_SunSMTexelSize c2       1
28 //   shadowMapSampler s0       1
29 // Default values:
30 //   g_SunSMTexelSize
31      c2   = { 0.000488281, 0, 0, 0 };
32 
33 ps_2_0
34 def c3, 0, 0.0625, 0, 0
35 def c4, 0.0625, 0.25, 0.375, 0.25
36 def c5, 0, 1, 0, 0
37 dcl t0.xy
38 dcl_2d s0
39 
40 mov r0.x, c3.x               r0.x = c3.x
41 mov r0.y, c0.x               r0.y = g_SunSMTexelSize * -2
42 add r0.xy, r0, t0            r0.xy += texcoord
43 
44 mov r1.x, c3.x
45 mov r1.y, -c2.x
46 add r1.xy, r1, t0            r1.xy = texcoord + float2(0,g_SunSMTexelSize *2);
47 
48 mov r2.x, c3.x
49 mov r2.y, c2.x
50 add r2.xy, r2, t0            r2.xy = texcoord + float2(0,g_SunSMTexelSize);
51 
52 mov r3.x, c3.x
53 mov r3.y, c1.x
54 add r3.xy, r3, t0            r3.xy = texcoord + float2(0,g_SunSMTexelSize * 4);
55 
56 texld r0, r0, s0      
57 texld r1, r1, s0
58 texld r2, r2, s0
59 texld r4, t0, s0
60 texld r3, r3, s0
61 
62 mov r0.y, r1.x          
63 mov r0.w, r2.x
64 mov r0.z, r4.x               r0 = float4(sample0.x,sample1.x,sample2.x,sample4.x);
65 
66 
67 mul r1, r0, r0               r1 = mul(r0,r0); //r0^2
68 dp4 r0.w, r0, c4             weightSum = dot(r0,c4);  
69 dp4 r1.w, r1, c4             weightSum2 = dot(r1,c4); 
70 mul r2.w, r3.x, r3.x         r2.w = r3 * r3
71 mad r0.x, r3.x, c3.y, r0.w   r0.x = r3.x * 0.0626 + weightSum 
72 mad r0.y, r2.w, c3.y, r1.w   r0.y = r2.w * 0.0625 + weightSum2 
73 mov r0.z, c5.x
74 mov r0.w, c5.y
75 mov oC0, r0                  return float4(r0.x,r0.y,0,1);
76 
77 // approximately 29 instruction slots used (5 texture, 24 arithmetic)

  標准的水平模糊,做了5個采樣點 -2,0,1,2,4。有意思的是采樣只用了普通的深度,然后做了一次平方,而沒有直接讀取shadow map中之前輸出的深度平方值。這樣的話,其實shadow map生成還有優化余地,直接使用f16的紋理,或者f32的紋理也行。最后一段代碼看起來稍微有些復雜,其實只是加上了第5個參數的權重而已。

         從EID24948開始,又專為主角渲染了一張shadow map,紋理大小1024*1024,R32F格式,代碼和之前的類似,只不過vs多了骨骼動畫的計算.這里不詳細討論。

         下一次,我將介紹最復雜也是最有意思的light pass階段。最后,foward渲染階段時,再着重分析各種不同物體(terrain,model,sky...)的渲染。

--------------------------本人才學疏漏,如有錯誤歡迎指證,未完待續--------------------------------

 

 


免責聲明!

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



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