Steam VR - The Lab Renderer學習筆記

燕良發表於2016-10-16

The Lab Renderer

前言

Unity Vision VR/AR Summit來到中國了(http://www.bagevent.com/event/197605?bag_track=http://www.bagevent.com/event/197605   ),最近也關注了一下Unity的VR開發。

UnityVRSummit

大概是6月份看到新聞:Steam釋出了The Lab所使用的渲染器的所有原始碼。我一直挺好奇的,對於Unity3D這樣不開源的引擎,如果搞一個渲染器呢?今天有時間讀一下程式碼,一探究竟。

相關連結
- 官方帖子:http://steamcommunity.com/games/250820/announcements/detail/604985915045842668
- GitHub下載:https://github.com/ValveSoftware/the_lab_renderer
- Unity Asset Store下載:https://www.assetstore.unity3d.com/en/#!/content/63141

特性&實現

從GitHub下載了The Lab Renderer之後,粗略的瀏覽一遍,內容不多,主要是幾個元件的C#程式碼和一些Shader。接下來就看看它的主要特性是怎麼實現的。

Single-Pass Forward Rendering

The Lab Renderer使用Forward Rendering的原因主要是為了MSAA(MultiSampling Anti-Aliasing)和效率。然而Unity預設的Forward Rendering使用了Multi-Pass來渲染所有燈光(每個物體的每個動態燈要多一個Pass來渲染它的光照),The Lab Renderer提供了一個單Pass渲染多個燈光的解決方案。

TheLab-PlayerSettings

為了實現Single-Pass Forward Rendering,首先要在Player Settings做一些設定,如上圖所示。所謂的“Single-Pass”主要靠Shader來實現了。大體思路就是在“vr_lightng.cginc”這個shader檔案中定義了一系列燈光引數的陣列:

#define MAX_LIGHTS 18
...
float4 g_vLightColor[ MAX_LIGHTS ];
float4 g_vLightPosition_flInvRadius[ MAX_LIGHTS ];
float4 g_vLightDirection[ MAX_LIGHTS ];

然後使用一個for迴圈,一次性計算所有燈光的光照:

LightingTerms_t ComputeLighting( float3 vPositionWs ...)
{
    [ loop ] for ( int i = 0; i < g_nNumLights; i++ )
    {}
}

接下來就是在C#層來處理燈光資訊了。

  • 首先,需要為每個Unity中的燈光物件新增“ValveRealtimeLight.cs ”指令碼,class ValveRealtimeLight管理一個靜態變數“List< ValveRealtimeLight > s_allLights”用來簿記所有的燈光資料。
  • 然後,需要在Main Camera物件上新增“ValveCamera.cs”指令碼。在class ValveCamera.UpdateLightConstants()成員函式中,會計算所有的燈光相關的引數,並設定到Shader的常量中。

    以上就是The Lab Renderer的Single-Pass Forward Rendering這個特性的實現思路。

Shadows

The Lab Renderer還接管了陰影的渲染。需要在Unity的Quality->Shadows settings 中選擇“Disable Shadows”來關閉Unity預設的陰影。

TheLab-ShadowMap

如上圖所示,The Lab Renderer使用Shadow Mapping的演算法來生成實時陰影。這個演算法粗略的過程是這樣的:

  1. 從燈光的角度渲染一個深度緩衝。這個深度緩衝的幾何意義,可以粗略的理解為每個畫素點到燈光最近的距離;這個深度緩衝又被成為Shadow Buffer或者Shadow Map。
  2. 在渲染Back Buffer的時候,對於每個需要著色的點,將其“投影”(Projection)到上述的Shadow Map空間,然後進行比較,來判斷這個點是不是離燈光最近—-也就是有沒有被其他物體遮擋,即在陰影之中。
  3. 生成Shadow Buffer的渲染,對於Spot Light非常直觀啦;對於方向光,The Lab採用了近似的方法:將方向光替換成一個“非常遠”的點光源;對於點光源,The Lab使用6個假的Spot Light來替代。0_0|||

上述演算法的流程控制,在ValveCamera.cs指令碼中實現。首先它需要一個從燈光角度渲染的Camera、一個RenderTexture用做Shadow Map,還需要一個Shader來進行Shadow Map渲染(Resources/vr_cast_shadows.shader)。

[ExecuteInEditMode]
[RequireComponent( typeof( Camera ) )]
public class ValveCamera : MonoBehaviour
{
    ...
    [NonSerialized] private Camera m_shadowCamera = null;
    [NonSerialized] public RenderTexture m_shadowDepthTexture = null;
    [NonSerialized] public Shader m_shaderCastShadows = null;
    ...
}

在ValueCamera.OnPreCull()指令碼回撥函式中會呼叫ValueCamera.ValveShadowBufferRender()來渲染Shadow Buffer。如上圖的Shadow所示,The Lab把所有燈光渲染到了一個整體的Shadow Buffer之中,把每個燈光Shadow Buffer對應的區域,儲存到Shader引數“g_vShadowMinMaxUv”之中。這樣在前面講的Single-Pass Forward Rendering過程中,就可以在一個Pass實現所有燈光的光影計算了。

至於vr_cast_shadows.shader的內容,就很簡單了,它核心就是一個Vertex Shader,用來計算Projection之後的Position座標就好,UV啊什麼之類的都可以省略掉了。

在燈光渲染的Shader中(vr_lighting.cginc)通過ComputeShadow_PCF_3x3_Gaussian()函式來計算陰影。所謂的PCF就是Percentage Closer Filter,為的是產生陰影的邊緣柔滑效果。在這個函式中,它才算有了高斯過濾來對目標點周圍的3x3範圍進行計算。

Adaptive Quality

對於VR來說,幀速率是非常重要的,所以Valve的大牛就新增了這個特性:動態調節渲染質量,達到穩定的搞效率,這是他在GDC 2016上的一個演講:https://www.youtube.com/watch?v=eIlb688pUu4
這部分主要涉及到何時去調節質量,調節哪些地方(哪些是不能隨便調的),具體的邏輯都在ValveCamera.UpdateAdaptiveQuality()這個函式裡了。

相關文章