当前位置:   article > 正文

ttreeview 只展开一层节点_Shader Graph自定义节点获取光照踩坑实录

invalid subscript '_shadowcoord

a6ba0484b4fd87359872b117f8e18f96.png

总之终于有阴影了,也很优雅(大概),测试的模型我是从Asset Store下的。

零、最开始的坑

相信不少人看过这份教程:

Custom Lighting in Shader Graph: Expanding your graphs in 2019​blogs.unity3d.com

用Custom Function自定义扩展节点获取灯光。

本文亦为基于此教程的一个扩展与补充,也默认各位看官已经看过操作过这份教程。

但是我实际上经过操作会发现,在Unity 2019.3.0f3(我使用的)和 URP 7.1.8环境下,只能拿到灯光向量,颜色等参数,但是阴影贴图无法获取。

在原帖的底部也有指出这个问题,是因为丢失了_MAIN_LIGHT_SHADOWS,不过如果直接给加上去,那就会报错了。

如图所示:

98b3ab7848396ff9cf3a2cfd7ea3d7b3.png

ff60a65c56846a428110812333ac0e11.png

在原帖下面的回复里面。也有朋友回复表示,直接用PBR节点整,然后连到Emission上面就完事了。

但是这样的做法微妙过于的不优雅。

所以还是从头理顺一下,并且找到一个尽可能优雅的解决方案。

壹、分析修改一波推

首先从报错信息开始

Shader error in 'Unlit Master': invalid subscript 'shadowCoord' at /TShoot/TShoot_T/Library/PackageCache/com.unity.render-pipelines.universal@7.1.8/Editor/ShaderGraph/Includes/Varyings.hlsl(118) (on d3d11)

先找到URP包的源码,打开Varyings.hlsl代码【请注意,往后的操作我们都在URP的包里面进行,请确定好打开的包的版本没错】

3aebb2ff01ad2e3838a5be9d3165736f.png

可以看到是Varyings.hlsl(118)调用的shadowCoord字段丢失。

此处的output,是一个Varyings的结构体。

521393525da12803d5303a54316469fd.png

也就是说Varyings结构体里面shadowCoord这个字段并没有被定义。

而Shader Graph的代码生成过程,可以理解成是一个个节点,各自带着一段代码,最后组装而成。也就是说,Unlit Master同样不例外。

由此所以可以推测,要不就是Master有节点模板,类似于Amplify Shader Editor插件一样的模式。要不就是Unlit Master的C#代码里面带上了什么操作。

那么先直接简单粗暴的搜索Varyings看看能得到些什么。

一般来说,因为是结构体的定义,所以以关键字struct Varyings直接搜就可以搜到定义名称为Varyings的结构体定义的地方。但是实际上我们这样做并不能得到什么有意义的东西。

ed29c630b074e37f42054430a95d42fc.png

在URP的源码里面,Varyings的出现率就跟Build-in管线里面的appdata和v2f一样普遍。

要是干脆的以Varyings为关键字搜索的话,倒是运气比较好了

66fd2c48e91c62503ef1a0d0ba43de5a.png

我们搜到了看起来就很有意思的东西,而且出现在UniversalPBRSubShader.cs里面

831f48b4758d4752da5ead995b749f95.png

18373740dff0b7ffebd1d6671cb18b23.png

这一段是出现在new Shader Pass里面的操作。给一个名叫requiredVaryings的字段创建了一条List,里面的内容就包含了我们定义所需Shader的Varyings结构体需要的内容。而最后一行也就是我们想要的shaowCoord。

而事实上,回忆一下刚才所说,在原帖的回复里面有人指出在PBR节点下进行灯光获取。

也就是说,这个很可能就是我们想要的东西了。

同样,顺着名字找到UniversalUnlitSubShader.cs文件

5911b6c484dcb207ef482ba88c4e9699.png

然后我们惊恐的发现,他压根就没给new这个List!

总之暴力的从UniversalPBRSubShader把这段复制一下,丢到UniversalUnlitSubShader里面

fa4e5f0ae0d0ef1ec886712615e47d0b.png

7aae73ff878ab66f1c5293ac7cd1e2f6.png

修改完,保存一下回到Unity

8345c5db9a8532883f5f3262b1e1ec71.png

重新点一下Save,我们可以看到,错没报了,然后影子也出来了。

恭喜恭喜!虽然小小的改了一下URP源码,但是结果达成了嘛对不对,虽然还是欠缺一点优雅,但是

又不是不能用.jpg

然后我们愉快的关掉Unity,直到下次再打开...

2a823d69a7a7f5e0c3080a0fdf801997.png

他又红了!

4fbf7c34b175c9d4366feea931697780.png

回去再看看,发现我们改过的地方没了!

这里是我踩到的第一个坑,因为一个意外的操作,我打开的并不是

C:Users[UserName]AppDataLocalUnitycachepackageshttp://packages.unity.comcom.unity.render-pipelines.universal@7.1.8

的目录进行修改,而是工程下的Packages目录下的文件进行修改。所以重启之后就被真正的包覆盖掉了。

要想再次启动不出问题,就必须去到这个真正的包目录下修改代码。

但是问题又来了,一次可以这样改,但是要是多人合作呢?要是官方更新了一下URP包,那么岂不是每次/每个人的包都要修改,随着越来越多需求引起的修改,我们甚至会得到一个和官方版本完全不同的分支。

这样做实在缺乏优雅。

贰、没事还是不动URP管线的源码了

前情提要:

所以,要是基于URP操作,还是尽量不去修改URP管线的源码就得到想要的效果就是最好的。

既然这样,我们还是回头继续去分析好了。

因为keyword _MAIN_LIGHT_SHADOWS 的出现,使得产生了对应的编译分支出现。因此Varying.hlsl第118行:

output.shadowCoord = GetShadowCoord(vertexInput);

出现在激活了_MAIN_LIGHT_SHADOWS的分支里面。

然而Varyings里面并没定义shadowCoord字段

而不能动源码的情况下,也就是我们Varying.hlsl也不能动。

那么我们就得避开_MAIN_LIGHT_SHADOWS的出现。那么要是这个keyword不出现,自然这行代码就不会被编译出来。但是同样的,也不会出现阴影。

那么要做的事情也理清了,那就是我们自己越过这个keyword和对应的代码,把产生阴影的真正有用的部分抽出来。

源码分析:

首先我们可以看到

4296f93502133bb04fa03bfac33e9499.png

GetMainLight是我们获取的主灯光信息的函数,返回的是一个Light结构体,那么事情就很好办了,直接在URP源码里面以关键字Light GetMainLight搜,于是我们非常完美的只得到了两个结果。

cb34f9ae62c683a0706c71ecfb64aa2f.png

总之先点进来看看:

5d65cf48242d8b8982b532a8d6e4e707.png

可以看到,实际上我们已经得到了我们想要的灯光参数,但是丢的是阴影。

而MainLight的阴影可以看到是由MainLightRealtimeShadow获得的。

首先确定Light.shadowAttenuation是half类型(具体可以见源码Lighting.hlsl第45行)

e12e2818b3f9ccf71346c25dbee545bc.png

那么我们再以half MainLightRealtimeShadow为关键字搜这个函数,再次完美的只有一个结果:

3e592d983f4dfac1c74d5d24c73a23bc.png

于是再打开Shadows.hlsl看看

0c5e8a66032fb44810c6afbb9db88581.png

我们在找的keyword出现了,这里是一个宏定义的判断,要是没定义 _MAIN_LIGHT_SHADOWS 或者定义了_RECEIVE_SHADOWS_OFF就会直接返回1.0,不会再编译后面的代码。也就是我们没有阴影的原因。所以我们需要的就是后面的部分。

同理,既然手动获取阴影了,那么灯光数据我们也手动写一起就完事了,打开Lighting.hlsl,找到98行Light GetMainLight()

然后对着需求一路照抄

最后我们得到这个修改版的函数

  1. void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
  2. {
  3. #if SHADERGRAPH_PREVIEW
  4. Direction = half3(0.5, 0.5, 0);
  5. Color = 1;
  6. DistanceAtten = 1;
  7. ShadowAtten = 1;
  8. #else
  9. #if SHADOWS_SCREEN
  10. half4 clipPos = TransformWorldToHClip(WorldPos);
  11. half4 shadowCoord = ComputeScreenPos(clipPos);
  12. ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
  13. #else
  14. half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
  15. ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
  16. half4 shadowParams = GetMainLightShadowParams();
  17. ShadowAtten = SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, false);
  18. #endif
  19. Direction = _MainLightPosition.xyz;
  20. Color = _MainLightColor.rgb;
  21. DistanceAtten = _MainLightPosition.z;
  22. #endif
  23. }

保存一下回去Unity看看

记得把刚定义的_MAIN_LIGHT_SHADOWS 的keyword去掉

这次不报错了,阴影也来了。

225afd60f1e0c26b1c31dd3c2dd84c29.png

剩下的该干嘛干嘛。

弎、总归还是不够优雅,但是还是做个简单的整理

比起修改URP的源码,现在的做法总体来说优雅多了。

为了让他用起来更舒服,我们先去看看Lit.shader的源码,看看官方的PBR材质到底对阴影定义了哪些keyword。

直接去找现成的实现,是比我们自己一层层方法展开检查宏定义舒服得多的方法。

打开位于Shader/Lit.shader文件

然后往下看,看到第一个Pass,其Name "ForwardLit"

由此可知,这个就是URP管线的标准PBR的PASS

b0a91c1625bfb7782f0488ac81be2ecd.png

再101行开始就是阴影相关的定义。

_MAIN_LIGHT_SHADOWS因为shadowcoord的报错原因,所以我们不能要。而现在我们处理的是MainLight的数据,所以可以无视ADDITIONAL_LIGHT相关的keyword

那么需要的就是

_MAIN_LIGHT_SHADOWS_CASCADE

_SHADOWS_SOFT

然后为了方便使用,所以我们直接把这个Custom Function节点打包成一个Sub Graph方便以后使用。

1c1c4a61fe762c5650e66f4995b98743.png

最后我们得到了这样的一个节点。

肆、终于真的可以用了,可喜可贺。稍微总结一下吧。

到这一步为止,我们终于得到了一个可以有效利用的MainLight节点,也无需单独定义一个cs脚本进行节点扩展。

最大的好处莫过于轻便快捷。不过因为没了_MAIN_LIGHT_SHADOWS 这个Keyword的存在,所以实际最后编译的时候是没了这部分的编译分支。仅限于当前的灯光阴影的获取需求下看起来并非什么大碍。

但是要是其他更多的复杂需求下,这种越过某些keyword进行代码编写,可能会引起失去编译变体,导致这部分带来的优化不存在的情况。比如在本案例的环境下,如果本身并不需要获取阴影_MAIN_LIGHT_SHADOWS的keyword被关闭的情况下,获取阴影是直接返回了一个1.0的确定值,而修改后这部分的优化便失去了,不管实际上有没使用阴影,都会执行全部的采样阴影纹理的操作。

其次是Unlit的Varying.hlsl的问题,既然这里面调用了shadowcoord这个字段,却没有在Unlit Master节点里面对requiredVaryings进行操作,添加Varyings的shadowcroord字段。这个应该可以认为是管线的BUG吧?

还是因为默认Unlit并不会接受投影,所以实际上custom function获取投影这个操作本身就是破坏了设计的行为?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/98231
推荐阅读
相关标签
  

闽ICP备14008679号