这篇文章是继这篇文章的序章,更进一步学习及了解投影。
写作思路:
1、简述投影原理(包括剖析Unity的阴影投射原理)4张阴影贴图、阴影贴图采集
2、介绍什么是bias、normalBias,以及为什么会产生 投影瑕疵
3、引入两个Unity的两个对应bias和normalBias的shader内置函数
4、bias、normalBias在Shader中的简单应用
1、简述Unity阴影投射原理
正如上篇所说:
点A在相机的深度值设为 ZA ,点A在阴影纹理的深度值为ZB ,如果ZA >ZB ,则该点处于相机的可见范围并且处于阴影之中,即该点看得到阴影
那Unity是如何获得ZA 和ZB的呢?
在Unity中,ZB 其实是记录在光照深度图(阴影贴图)中的; ZA 就是我们相机中看到的对象的深度! 先记住上面说的这两个东西!
1、首先我们随机创建一个场景,里面放置一些简单的模型对象,并且放置一束平行光,如下图
2、接下来打开Unity的Frame Debug并启用,可以看到渲染的顺序列表中先渲染了模型相关的深度图,点击如下列表的位置,可以看到Game窗口出现第二幅图,这就是模型相关的深度图(也就是上图 3球1方块的深度图)
3、选中下面第一张图,可以看到Game窗口渲染了4张深度图,这四张深度图就是光照深度图(阴影贴图)
因为我们在Project Settings中设置了如下设置,最关键的就是 “Shadow Casades”我们设置了四个变量(具体内部怎么实现不晓得,但是会根据这个数量增加光照深度图的渲染数量!不重要!不影响我们理解),然后大多数情况下是看不到四张图的,需要调整一下第二个红框中的比例
4、Unity渲染原理描述: 在上述几个步骤中,我们获得了模型深度图和光照深度图,其中模型深度图存储的是 相机范围内模型各个点的深度数值,光照深度图存储的是从场景平行光角度获取的 所能照射到的各点的深度值(其实就是相当于从平行光的方向放置一个相机,相机屏幕所能照到的点各点的深度值),然后现在就回到了我们最上面的A,B两点的问题, 如果模型深度图里的点的深度值 >光照深度图的值,则该点就处于阴影之中,则该点颜色为黑
Unity增加阴影精准度的主要方式有以下几种:
①增加光照深度图数量:对应上面的shadow Cascades,即从不同的位置渲染多张光照深度图,然后每次都与模型深度图进行比对,以此增加精准度。但是每增加一张深度图,就要把整个场景多渲染一遍,因此性能消耗也会逐渐增加。我们把shading Mode改成 Shadow Cascades 就能看到四张光照贴图渲染的不同区域
②增加光照深度图的分辨率:对应上面的Shadow Resolution,我们渲染光照深度图的时候,实际上是给屏幕一个分辨率比如(1920*1080),然后Unity按照上面每个像素点一一获取每个点的深度,因此增加分辨率就能增加精准度,同样的性能消耗会增加
2、关于光照设置里面的Bias、NormalBias
1、选中平行光,找到如下截屏的阴影设置,可以看到有Bias、NormalBias这两个选项,我们通过调整这两个属性可以改变投影的“偏移”,这两个属性分别就是对应着UnityShader内置库中的 “UnityApplyLinearShadowBias” 和 “UnityClipSpaceShadowCasterPos”这两个函数
2、我们将设置里面的投影数据为以下图片,并且把Bias和NormalBias都调整为“0”
我们会看到场景内的球体表面产生了一些投影,如下图
我们再次调整投影设置,把Shadow Resolution调整为 High,可以看到,球体表面的投影并没有消失,他只是变得更密集了
上面这种情况我们称之为“投影粉刺”(shadow acne)”,此时,我们只要调整Bias和Normal的值为恰当值,这些表面投影就会消失
3、关于“投影粉刺”(shadow acne)的产生原因
我们在计算光照深度图的时候,计算的是各个像素点所处屏幕位置的可照射深度值,这就可能会出现一种情况,我们计算得到的该点的像素点内包含多个三角片元,由于是处在同个像素点的,他们的光照深度图中的深度是一致的,但实际上片元的深度值是不同的,举个栗子
PS这篇简易草在投影中也有提到阴影粉刺的事
下面借用一下这个博客老哥的图
我们假设存在两个片元A和B都处在像素点P上,像素P的深度值为0.5,A、B分别为0.49和0.51,那么此时B就处在阴影当中,而A就是处在阴影外,但实际上B点应该也是处在阴影外的,因此我们利用Bias将A、B两点的深度值都下移,这样两点都是处于阴影之外
4、下面是Bias和NormalBias在Shader中的简单应用,
#if !defined(MY_SHADOWS_INCLUDED)
#define MY_SHADOWS_INCLUDED
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
//float3 normal : NORMAL;
};
float4 MyShadowVertexProgram(appdata v):SV_POSITION
{
float4 pos = UnityObjectToClipPos(v.vertex);
// 下面是normalBias
// float4 pos = UnityClipSpaceShadowCasterPos(v.vertex,v.normal);
return UnityApplyLinearShadowBias(pos);
}
half4 MyShadowFragmentProgram():SV_TARGET
{
return 0;
}
#endif
Shader "Unlit/ShadowLearn"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
Tags { "RenderType"="Opaque" }
LOD 100
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#include "MyShadows.cginc"
#pragma vertex MyShadowVertexProgram
#pragma fragment MyShadowFragmentProgram
ENDCG
}
}
}
下一篇会利用上述阴影投射的原理,不使用Unity自带的阴影计算库,实现阴影投射
人有多少个一年,每个礼拜一晃眼就过去了,一年也就52个礼拜,别让自己太放松了