Unity游戏图形渲染效果系列之阴影篇(二)

继上一章从阴影产生的原理去讲解和实现阴影效果,本章将继续优化阴影效果和实现抗锯齿,以及阴影的多种产生方式及其应用场合,下面进入正题:


一.高精度锯齿优化

该方法实现简单,但是效果也是最好的一种,不过消耗非常大,所以适合的场合是精度要求较高的模型 比如主角渲染等,不适合全场景渲染。该方法下Shader代码基本不需要改什么,只需要提高精度就够了,其一是提高存储阴影相机深度图的分辨率,其二是减少阴影相机的渲染范围,也就是花最大的贴图显示出最小的场景,这样精度自然是最高的。

如下图,显示的是阴影相机渲染的场景阴影范围的深度法线图:




图一是用阴影相机渲染了场景20米范围的所有物体 把他渲染到一张512大小的rendertexture上面,图二是用阴影相机渲染了场景5米范围的物体,rendertexture精度达到2048。那么实际的阴影渲染效果要从下面的图可以看出:

如下图一所示,锯齿感非常强烈,是因为使用了低精度的rendertexture并且渲染范围很大,再看如图二精度大幅度提高后,锯齿感已经非常小了,原则上来说,根据显示要求 不断提高精度 可以得到更高的阴影效果。






可以开看到几乎不需要任何算法的情况下,就使阴影效果得到了很大的提升。不过在实际应用中,用这种方式提高精度消耗比较大,特别是实时渲染,一般应用于离线渲染或者实时渲染局部模型,比如对主角细节阴影要求很高的情况下,可以尽可能的让阴影相机渲染范围只包含主角那么大,然后rendertexture精度尽可能提高,不过考虑实时渲染的消耗,还是要选一个折中大小比较合适。然后下图是用高精度渲染模式得到的实时阴影效果。




二.常规抗阴影效果

通常在实时渲染过程中,根本没有那么高的配置去实现高精度渲染,特别是在手机游戏上面,通常情况下的实时阴影渲染都是采用上面图一的方式,阴影相机一次性渲染场景范围几十米的范围,而且为了性能,贴图精度可能尽可能的低,这就导致了阴影锯齿感必然非常严重,那么这种普通的实时阴影就只能通过算法去抗锯齿了。

从上一篇实现阴影的教程中,我们可以看出标准情况下只对阴影进行了一次采样进行深度对比 然后计算产生阴影,再实际应用中,我们可能会对阴影进行分级,比如:

1级:基础阴影,可能只对深度贴图采样一次参与计算,性能高,但是效果很差。

2级:中级阴影,对深度贴图进行2-4次采样参与计算,通过在y方向lerp的方式,会对阴影效果有一点点软化效果。

3级:高级阴影或者叫软阴影,对深度贴图进行9次以上的采样和lerp,效果可以达到很好的效果了。

如下三张图,分别对应了我们实现的不同阴影效果:



从上面的三张对比图就可以看出阴影分级的效果,效果越好消耗自然也会越大,在实际应用中可以根据实际情况去开启对应的等级,下面我们来简单讲下实现过程,其实主要集中讲解的是关于阴影系数的求解方法:

       在第一章的文章中,我们实现了最简单的阴影系数的求解方法,如下:

	float4 col = tex2D(_ShadowDepth, depuv);
		float olddepth = DecodeDepth(col.xy);//0-1
		float newdepth = i.shadowvpos.z  / invShadowViewport.w;//farclip
		float occ=newdepth-olddepth;
		return occ;//其中的ooc就是阴影系数,要么是0,要么是1,就把阴影和非阴影的区域分开了。

在实际应用中,可能会对阴影进行多次采样,如下:采样的方式可以多种多样,对周围按一定规律去多次采样就好了,主要是插值问题,插值我们采用对uv坐标对应的世界坐标的小数部分进行一个x方向插值后,再进行另一个y方向二次插值,下面的代码仅供一种参考,具体还得自己实现


	float occ=0;  
        float Num=9;  
        for (int i = 0; i < Num; i++)//这里的Num可以根据自己调整可以是4 也可以是9,更好的效果 16或者20都行。  
        {  
            float2 temp = uv+offsetuv[i]*0.01f;  
            float2 temp2 = uv+offsetuv[i+1]*0.02f;  
            float4 col = tex2D(_ShadowDepth, temp);  
            float4 col2 = tex2D(_ShadowDepth, temp2);  
            float olddepth = DecodeDepth(col.xy);  
            float olddepth2 = DecodeDepth(col2.xy);  
            float dot_val.x=olddepth-olddepth2>0;  
            .  
            .  
            .  
            float2 lerp_val = frac((uv+invVP.xy)*1024.0f);  
            float2 temp = lerp(dot_val.xy, dot_val.zw, lerp_val.y);  
            occ+= lerp(temp.x, temp.y, lerp_val.x);  
        }  
  
        return occ/Num;
软阴影算法的计算方式有很多种,大部分都是采用多次采用插值比较,其中还有一种是比较阴影点和周围采样点的深度对比,比如当前阴影点周围的深度和它差不多,说明处于阴影中心,如果周围的采样点的深度和当前阴影点相差过大,说明可能处于阴影边缘,最简单的九宫格采样,比如9次采样种有几个点处于和改阴影点同一深度,其余点和该点深度差值大,越多点不在统一阴影深度,说明阴影系数越小。如下简单的实现作为参考:


float2 offsetuv[12] = {float2(1,0),float2(1,-1),float2(0,1),float2(1,1),
										float2(-1,0),float2(-1,1),float2(0,-1),float2(-1,-1),
										float2(-1,0),float2(-1,1),float2(0,-1),float2(-1,-1)};
				float newdepth = z/ invShadowViewport.w;
				float old_depth = DecodeDepth(tex2D(_ShadowDepth, uv).xy); 
				float dis = newdepth-old_depth;
				if(dis<0) 
				{
					return 1;//非阴影点 就不需要采样了  
				}

				float occ =0;
				float sss=0.001;
				float pix=0.002;
				for (int i = 0; i < 12; i++)//这里的Num可以根据自己调整可以是4 也可以是9,更好的效果 16或者20都行。
				{
					float depth0 = DecodeDepth(tex2D(_ShadowDepth, uv+offsetuv[i]*pix).xy);
					if(abs(old_depth-depth0)>sss)
						occ+=0.1;


				}
				return 0.5+occ;
该代码仅供参考,可以实现一般的过渡阴影,通常采样点数越多效果可能越好,所以可以试着增加或减少采样点数来给阴影分级,另外效果的好坏完全取决于采样点是否有代表性,该数组的采样点用的是九宫格,但在阴影中 采样这种方式会有点块状阴影,所以建议替换一下采样点模型,比如 多圈圆或者螺旋结构点模型等 。


三.阴影的多种生成方式

阴影的生成方式有很多种,在常规的前向渲染下,阴影生成和光照计算放在一块,也就是对光照计算的同时乘以计算出来的阴影系数来得到结果,一般没事建议就用这种好了。另外还有两种阴影的生成方式大家可以了解下,通常用在屏幕后处理阶段去处理,其一是采用DrawMesh的方式实时去绘制阴影,对于屏幕后处理效果较多的情况下,可以采用这种实时绘制模式。另外一种方式是通过BlendOneOne的方式去绘制阴影,通常情况下阴影系数乘在光照里边然后把暗处进行缩放,得到阴影效果,不过我们也可以用Blend方式去加强亮出的效果,也就是亮处部分进行叠加变亮,但是阴影部分只计算一次 显示就会暗一点,这种能得到更好的场景阴影叠加效果。更好的效果也必然会导致性能稍微第一点,然后光照参数稍微难控制一点,大家根据各自场景的特征去选取效果就好了。

 

好了,今天就到此为止了,下章考虑讲解SSAO或者SSS的相关实现,- -也有可能讲其他的...反正你又管不了我!





没有更多推荐了,返回首页