Unity Shader 阴影系列(1)--内置阴影处理

什么是阴影

阴影是由于光线被遮挡,而导致某些平面受光减少,会出现暗色。如图:

int //![在这里插入图片描述](https://img-blog.csdnimg.cn/20200630095853450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA3NzgyMjk=,size_16,color_FFFFFF,t_70)

黑线部分为阴影,本来他应该接受到的光被墙挡住后,就产生了阴影。现实中我们会发现墙根的影子更重一些,靠近外侧的影子淡,其实是因为外侧的部分有接受了更多其他部分的光,

Unity中如何模拟阴影

知道了怎么产生的阴影,我们将阴影的产生模型化。
1.想一个问题:如何确定一个点是否有阴影?可以停下来好好想想。。
解:当点A与光源之间的连线不存在其他物体时那么点A就不会有这个光源产生的阴影,反之,若被其他物体遮挡点A就会有阴影。
2.unity中如何进行1的判断?
解:解决这个问题,想象自己在光源处朝着光发射的方向看去,记录下来看到的物体的距离我们的距离,也就是深度,存储在数据A中,这个时候,如果有某个位置再问我们,我在不在阴影里啊?我们说你告诉我,你相对于灯光的位置是多少?你说C,我们根据C的xy坐标对数据A进行采样获取数据A中的深度,然后如果C和灯光的距离比B和灯光的距离大那C就在阴影中,反之就不再阴影中。

上面的说的就是ShadowMap技术

即:
1)当光源投射阴影时,将阴影映射相机放到光源位置上(理论上,平行光为正交,点光源为透视)
2)调用LightMode标签为ShadowCaster的Pass,这个Pass主要用来更新光源阴影映射纹理
3) 这个通道通过对顶点的变化得到光源空间下的位置,并将深度信息写入光源阴影映射纹理中(ShadowMap )
4)使用顶点的xy分量对光源阴影映射纹理进行采样,得到光源阴影映射纹理中该位置的深度信息
5)如果获得的深度值小于该顶点的深度值,则该点位于阴影中(即该顶点的位置在光源阴影映射纹理中存储的表面后面)
FrameDebug截图:

int//![在这里插入图片描述](https://img-blog.csdnimg.cn/20200630105414460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA3NzgyMjk=,size_16,color_FFFFFF,t_70)

记录一下生成的图的名称:

出处存储名shader变量名使用的shader
光源处相机ShadowMap_ShadowMapTexture所有shader,引入UnityShadowLibrary.cginc

屏幕空间阴影

当然Unity还有另一种阴影计算方式,屏幕空间阴影。Unity会根据平台选择使用哪种方式。
1)当光源投射阴影时,将阴影映射相机放到光源位置上
2)调用LightMode标签为ShadowCaster的Pass,获得光源处深度纹理(ShadowMap)以及场景相机的深度纹理(CameraDepthTexture)
3)采样所有场景相机深度纹理信息(屏幕空间),将其坐标做屏幕空间–世界空间–光源空间的转换,然后和光源处深度纹理比较深度,将其结果保存一张纹理中(ScreenspaceShadowMap).
4 )渲染场景物体时使用ScreenspaceShadowMap计算阴影信息

 int//![在这里插入图片描述](https://img-blog.csdnimg.cn/20200630135218456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA3NzgyMjk=,size_16,color_FFFFFF,t_70)

记录一下生成的图的名称:

出处存储名shader变量名使用的shader
场景相机深度图CameraDepthTexture_CameraDepthTexture所有shader
光源处相机ShadowMap_ShadowMapTexture所有shader
屏幕空间阴影图ScreenspaceShadowMap_ShadowMapTexture所有,执行完后会将光源处的替换,Internal-ScreenSpaceShadows.shader

Unity深度图的获取

上面我们讲述了理论过程,但是实际上unity深度图的获取并不是把在光源处放置一个摄像机进行照射获取深度图的。为什么呢?1.不可能,我们知道平行光是方向光,并没有位置信息,也就是我们把灯光放在-1000,-1000,-1000的位置(场景在0,0,0),只要调整角度依然可以进行光照,但是如果我把相机放到了这个位置根本不会得到深度图。所以深度相机的位置要与场景相机相关(注意这里说的是相关而不是一直)。2.既然如此,那我们深度相机就把场景中的物体都照射进去吧,这样做确实没问题,但是会浪费,试想一个20002000的场景,如果我们用深度图保存了他的信息,但是我们场景相机能看到的可能大部分时候都是100100范围的东西,那就会造成很大的浪费,当然也会造成的Shadow acne,也就是严重的锯齿。
这里有一些概念性的东西,还是需要了解的,资料有很多。
1.Shadow acne、Peter Panning
2.FIt to scene和 FIt to view
3.Slope-Scale Depth Bias
4.Shadow Map Aliasing
看看相关的资料,对于阴影处理还是很有帮助的,接下来做unityshadow源码解析也会用到这些概念。经过测试发现Unity的深度相机的unity_MatrixVP矩阵,是由灯光方向,场景相机的位置、角度、fieldofview、以及屏幕分辨率共同决定的。

对比分析

在unity中如何切换两种方式:Edit–Projectsetting–Graphics–TierSettings–CascadedShadows开启便是开启屏幕阴影,否则默认使用shadowmap。
以下对比为场景中仅有一个cube和一个plane:

项目ShadowMap屏幕空间阴影
drawcall26
效果锯齿、硬软,较平滑
申请纹理数量13
计算复杂度不知道咋描述应该就是1:3的关系

shadowmap的弊端

Shadow acne和Peter Panning

首先,手机上最好还是选择使用shadowmap而不使用屏幕阴影,毕竟翻倍的dc还是很恐怖的。上面提到了shadowmap是通过场景相机,将要 渲染的片元转换到深度相机的空间,然后采样这个位置深度图的深度信息进行比较。如果 深度图分辨率较低就会造成Shadow acne.。
这个问题我当初怎么也想不明白,一直觉得shadowacne应该出现在阴影边界,而不是呈交替性出现,当初钻了牛角尖,也做了很多次测试,去复现shadowacne(我也只是原理上去复现,但是unity的vp矩阵没能复现成功,应该是做了特殊优化)。。。最后发现确实就两点原因,一个是分辨率问题,一个是精度问题。分辨率比较好理解,深度图一个像素存储了真是的4个像素的深度,但是被存储的这4个深度的像素其实是不相等的。精度问题就是比如认为0.01-0.02的深度存储成了0.01,这就会导致当深度为0.015的像素作比较时就会得到错误的结果。
为了解决这种问题,我们可以考虑在进行对比的时候做一个偏差,比如某个点的深度图存储的深度为0.5,而我们计算得到的深度为0.4999,认为应该是在阴影中,但是我们给0.4999+0.0002这样他就可以不再阴影中,但是这样带来的问题就会出现我们影子看起来移位了,也就是Peter Panning。我们通过动态的计算偏差值来减少这种移位的情况,偏差值跟灯光摄像机和物体的法线相关,夹角越大偏差值就越大,至于为什么?想想吧。。

总结

基本上阴影产生的原理,就是这次的内容。下一篇就可以实战了。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘培玉--大王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值