Shader Graph踩坑實錄

歡迎參與討論,轉載請註明出處。

前言

Demo也到了做渲染的時候了,經過一番鏖戰後,算是大體完成了。但隨着後續需求的到來,發現這套純代碼的Shader方案對於擴展、複用等方面有着諸多不便。於是便打起了Shader Graph的主意……經過一番糾纏,於是有了本篇踩坑實錄。另附源碼地址,但本篇並不會對其做講解。基於Unity2020.1.0a20,渲染管線爲URP7.18。

優劣

首先要明確的是:Shader Graph不支持Builtin渲染管線,且尚未處於徹底成熟的階段,哪怕是最新版本尚有不少缺陷。但瑕不掩瑜,且說其優劣:

  • 優點
    • 由於節點化與Sub Graph的存在,Shader的組裝將變得相當容易,極大提升了模塊化水平。
    • 調整Property與Keyword變得相當方便,純代碼下將需要多些工序。
    • 內置各種節點,降低了美術參與創作的門檻。
    • 能夠實時預覽每個節點造成的變化預覽,雖然對我而言沒啥用。
  • 缺點
    • 編輯器尚不夠穩定,經常會出現整個程序崩潰的情況。
    • 生成的代碼不夠優化,比較暴力,存在各種分支判斷、重複函數(也許最終會優化?)
    • 由於Slot機制的原因,可能會出現很多運算集中在片元着色器中。
    • 對於創建Pass並不友好,需要修改源碼。

來龍

其實一般的淺度Shader Graph使用者並不會如我這般踩這麼多坑:直接使用內置的Graph模板進行創作即可。不幸的是,如前文所言:對於創建Pass並不友好,需要修改源碼。於是便開始了踩坑之旅……
  在默認情況下,Unity的Package將保存在工程的Library/PackageCache目錄下,這樣子是不能直接修改源碼的(Library目錄下的東西屬於可再生物,隨時會被覆蓋),需將之搬遷至工程的Packages目錄下。
  對於考慮到日後Shader Graph的版本升級情況,所以儘可能的不要修改原工程的內容,而是儘量新建文件。但考慮到Shader Graph源碼下存在不少inner元素,直接在外面寫自己的內容也並非徹底可行。只能直接在Shader Graph包下進行添加文件的方式了,這也是要將之移至Packages目錄的原因。
  我要做的事情相當明確:新建一個自定義的Graph類型。在URP下已經自帶Unlit與PBR兩種類型了,於是本人便基於Unlit Graph並結合先前實現的Shader的特性進行新類型的創作。

去脈

我們能接觸到Unlit Graph創建的起點便是Project區下右鍵菜單的Create->Shader->Unlit Graph了,直接在Shader Graph源碼包下全局搜索Create/Shader/Unlit Graph即可找到:
在這裏插入圖片描述
  照葫蘆畫瓢在同目錄下弄個新文件實現相同功能即可,這下我們便知道Unlit Graph的正主了:UnlitMasterNode。經過研究發現,它決定了在編輯器下Unlit Master Node的樣式:
在這裏插入圖片描述
在這裏插入圖片描述
  但這只是個殼子罷了,根據代碼中的IUnlitSubShader爲引,找到了其核心:
在這裏插入圖片描述
  這個UniversalUnlitSubShader的作用相當簡單:根據編輯器的設置生成Shader代碼。如上圖便可看出定義Pass數據結構的行爲,這也是誘使我來改源碼的直接原因。在裏面你將見到形如這般的代碼:

var unlitMasterNode = masterNode as UnlitMasterNode;
var subShader = new ShaderGenerator();

subShader.AddShaderChunk("SubShader", true);
subShader.AddShaderChunk("{", true);
subShader.Indent();
{
    var surfaceTags = ShaderGenerator.BuildMaterialTags(unlitMasterNode.surfaceType);
    var tagsBuilder = new ShaderStringBuilder(0);
    surfaceTags.GetTags(tagsBuilder, "UniversalPipeline");
    subShader.AddShaderChunk(tagsBuilder.ToString());
    
    GenerateShaderPass(unlitMasterNode, m_UnlitPass, mode, subShader, sourceAssetDependencyPaths);
    GenerateShaderPass(unlitMasterNode, m_ShadowCasterPass, mode, subShader, sourceAssetDependencyPaths);
    GenerateShaderPass(unlitMasterNode, m_DepthOnlyPass, mode, subShader, sourceAssetDependencyPaths);   
}
subShader.Deindent();
subShader.AddShaderChunk("}", true);

如此情況便變得相當清晰了,只要清楚你想生成怎樣的Shader代碼,在摸熟了生成代碼的API,便可自由地進行創作了。通過右鍵節點可以隨時查看生成的代碼情況:
在這裏插入圖片描述

注意事項

也許看似還算簡單,但其中坑點還是有不少的:

  • 不要嘗試採用繼承的形式去新建新類型,其本身代碼就沒打算讓你這麼做,必然會碰壁,除非改源碼(如此便違反原則了)
  • 做出了修改後,要到對應的Graph文件進行Save操作觸發檢測。
  • 也許是檢測的原因,有時候HLSL代碼做出了修改後不會被識別到,需要換下行。
  • Shader Graph生成的着色器參數有着自己的一套處理方式,務必參考自帶的代碼。
  • Unlit Graph的主Pass並沒有LightMode,想做背面Pass的時候要注意下。
  • Unlit Graph將渲染模式、混合模式、剔除做成節點設置並不是一個好選擇(無法讓材質修改),推薦按照URP的方式做成材質屬性。在這裏插入圖片描述
  • Shader Graph對於生成的Shader代碼存在分支數限制,需要到Preference->Shader Graph進行修改上限。值得一提的是,全局Keyword與局部Keyword似乎是分別對待的。
  • Shader Graph並不存在完整的環境,它是無法識別到一些渲染管線裏的函數的。所以在編寫Custom Shader的時候需要加上#if SHADERGRAPH_PREVIEW分支判定以處理在編輯模式下的情況:
void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten)
{
#if SHADERGRAPH_PREVIEW
    Direction = float3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
#if SHADOWS_SCREEN
    float4 clipPos = TransformWorldToHClip(WorldPos);
    float4 shadowCoord = ComputeScreenPos(clipPos);
#else
    float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;
    ShadowAtten = mainLight.shadowAttenuation;
#endif
}

後記

現在感覺遊戲開發的未來方向就是連連看了,從這點來說UE4的確算是時代前沿。在編輯器里加入邏輯控制元素,讓更多人能加入創作,儘可能地解放生產力,的確是遊戲開發所需要的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章