URP 无光模式实现平面阴影

 闲谈:

        最近项目中我发现程序找了个影子插件PlaneShaodw 还挺好用的,效率也高,之后我就将所有项目中新优化制作的shader 全部采用插件的影子方案,结果不出所料,上周测试项目的时候 ,发现影子不能合批(SRP),更邻人费解的是插件自己写了个库文件,里面算法非常深(做了宏处理,分了好了些变体)。所以周末准备自己搞一波搞效果影子。

     大多数网上找的影子大家是不是遇到这样的问题:

     

                                                   几乎无解 

       在研究的过程中(重点研究了王者荣耀,欸,又下载下来玩了一会儿,给自己打酱油找借口,HAHA~~!)之前我一直认为王者荣耀的实现是Projector 方式,今天才发现,大错特错,他的原理还是使用的PlaneShaodw ,重点对影子做了虚化处理(最终实现效果会 完美还原王者荣耀影子效果)。

    一:  先来对比两种方式 优缺点和原理:

  Projector: 

    投影纹理使用的技术叫做Projective Texture Mapping

 对于被投影的物体,将他的顶点坐标变换到投影体的Texture Space中,计算得到UV坐标,使用UV坐标采样投影纹理,将颜色输出。 

      

具体实现

实现投射阴影,需要如下几个关键步骤:

  • 创建新的相机,将投射阴影的物体渲染到一张Render Texture中
  • 利用Projector组件,阴影纹理投射到接受阴影的物体上
  • 相机渲染阴影纹理的Shader
  • Projector组件需要的材质纹理

优缺点:

1:优点

性能较为高效
简单场景下,相比Shadow Map,节省了深度纹理进行深度比较的计算。

2:缺点

复杂场景的性能消耗较大

URP、HDRP不支持  
RT会额外占用现存;

大范围视角时,需要较高阴影精度;

复杂的地形,性能消耗较大,需要遍历所有接收阴影的物体;

需要定义Layer。(管理不当会加大开发难度)

未接收到阴影的物体,也会渲染一遍阴影。

                                 总结 :  淘汰淘汰,淘汰就对了~~~!   

Planar Shadow:

沿光线方向,将模型的顶点投射到某个平面上。

核心实现思路:将顶点有模型坐标转为平面坐标

L:平行光方向,单位向量(顶点指向光源)。

N:平面法线方向。

d1:单位长度的光方向向量在法线方向的投影长度。

A:模型顶点,在世界空间的坐标。

B:模型顶点,垂直于平面的投影坐标。

C:模型顶点,沿平行光投射方向,在平面的投影坐标。

高中数学:   已知A,L,N,求C坐标 ?

    分析:AC与-L平行长度应该为:d2/d1,最终就可以得到C与已知变量的关系:

     d1= dot(L,N)

     d2=A.y -B.y      (注意这是空间,别看我画的图是平面, 所以空间坐标:xyzw 别忘记了)

     c =A-L*(d2/d1)

  知道大家看不懂,所以觉得厉害就行了~HAHA~~!

优缺点:

1:优点

性能高效
计算量较小;
无需额外的显存带宽存储阴影纹理;
支持SRP Batcher,渲染流程跟普通的模型无异,且更轻。

动态性较好
阴影跟随动画实时变动。

视觉效果好
不存在类似Shadow Mapping中出现的锯齿、失真等情况,轮廓清晰。

2:缺点

场景限制较大
仅适用于平面,阴影不会投射到平面以外的物体上。

Artifacts
瑕疵较多,淡出效果、地表穿模等情况下,效果存在问题。

  不好意思,熊猫老师 完美解决了淡出效果。~HAHA~!

                                 总结 : 用就对了,移动端高速起飞~! 

核心实现:

添加模板缓冲区是解决影子重叠问题(降低透明度就能发现)

	  Stencil
    {
        Ref 1  //和模板缓冲区的值进行比较。
        Comp NotEqual   //比较函数, 决定参考值和模板缓冲区的值如何比较 (是否相等) NotEqual
        Pass Replace   //模板测试通过时候的操作 。  Replace 模板缓冲区中的值设为参考值。Replace
        Fail Keep    //模板测试失败时候的操作 。    keep是保持缓冲区当前的值
		//ZFail Keep  
		
    }

模板测试语义:

       1.Ref:参考值,和模板缓冲区的值进行比较。

       2.Comp :比较函数,参考值和缓冲区的值如何比较

       3.Pass : 模板缓冲区测试通过时的操作。

       4.Fail : 模板缓冲区测试失败时的操作。

       5.ZFail : 深度测试失败时的操作。

操作有以下几类类型:

     1.Keep : 保持缓冲区中的当前值。

     2.Zero :  修改缓冲区的值为0 .

     3.Replace : 将模板缓冲区中的值设置为参考值。

     4.IncrSat :  递增模板缓冲区中的值,且不超过最大值255 。

     5.DecrSat :   递增模板缓冲区中的值,且不低于0 。

     6.Invert :  按位取反模板缓冲区中的值。

     7.IncrWarp : 递增模板缓冲区中的值,并在溢出时从0 重新开始 。

     8.DecrWrap : 递增模板缓冲区中的值,并在溢出时从255重新开始 。

计算影子方向位置 算法  (其实就是上面的数学公式演变过来)

   float3 PlanarShadowPos(float3 posWS)
        {
          float3 L = normalize(_LightDir);
          float3 N = float3(0, 1, 0);

          float d1 = dot(L, N);
          float d2 = posWS.y - _PlaneY;

          // 阴影坐标沿法线方向偏移一点,防止与平面重叠时出现z-fighting的问题
          float3 offsetByNormal = N * 0.001;
          return posWS - L * (d2 / d1) + offsetByNormal;

          }

 顶点坐标 处理影子:(建议放弃片元中处理影子)

 重点之重,解决影子虚化效果:

  //得到中心点世界坐标
		    	float3 center = float3(unity_ObjectToWorld[0].w,  _PlaneY, unity_ObjectToWorld[2].w);
		    	//计算阴影衰减
				float falloff = 1 - saturate(distance(posWS, center) * _ShadowFalloff);

  

最后说明下:URP其实不是不支持多Pass 渲染, URP最多支持3个Pass  

但是每个Pass一定要对 Tags 做 处理:


m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));
m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));

上才艺:

Shader "XiongMaoWuDao/Shadow"
{
    Properties
    {
	    
	    [MainColor] _BaseColor("BaseColor", Color) = (1,1,1,1)
        [MainTexture] _BaseMap("BaseMap", 2D) = "white" {}
	    //影子处理
		//影子淡出处理
        _ShadowColor("Color", Color) = (0, 0, 0, 1)
        _LightDir ("Light Direction", Vector) = (0, 1, 0, 0)
        _PlaneY ("Plane Height", Float) = 0	
		_ShadowFalloff ("ShadowFalloff ",Range (0,1)) = 0.5

    }
    SubShader
    {
		Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Opaque"}
	  HLSLINCLUDE
		  #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

	         TEXTURE2D(_BaseMap);
             SAMPLER(sampler_BaseMap);

           CBUFFER_START(UnityPerMaterial)
		
            float4 _BaseMap_ST;
            half4 _BaseColor;
		    float4 _ShadowColor;
            half3 _LightDir;
			half _PlaneY;
			half _ShadowFalloff;

            CBUFFER_END
        ENDHLSL

		pass 
		{
			Name "Forward"
       
            Tags {
		  
			"LightMode"="UniversalForward" 
			}
		  HLSLPROGRAM
		    #pragma vertex vert
            #pragma fragment frag
				  #pragma multi_compile_instancing
			  struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
				 UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;   
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

			   v2f vert (appdata v)
            {
                v2f o;
				  UNITY_SETUP_INSTANCE_ID(v);
				  UNITY_TRANSFER_INSTANCE_ID(v, o);
                o.vertex = TransformObjectToHClip(v.vertex.xyz);;
                o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
           
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                 UNITY_SETUP_INSTANCE_ID(i);
             return SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv) * _BaseColor;
            }
	
			ENDHLSL
		}
        Pass
        {

		Name "XiongMaoWuDao"
		Tags{
	   
	    	"LightMode" = "SRPDefaultUnlit"
		}
		//添加模板缓冲区是解决影子重叠问题(降低透明度就能发现)
		  Stencil
    {
        Ref 1  //和模板缓冲区的值进行比较。
        Comp NotEqual   //比较函数, 决定参考值和模板缓冲区的值如何比较 (是否相等) NotEqual
        Pass Replace   //模板测试通过时候的操作 。  Replace 模板缓冲区中的值设为参考值。Replace
        Fail Keep    //模板测试失败时候的操作 。    keep是保持缓冲区当前的值
		//ZFail Keep  
		
    }
		Blend SrcAlpha OneMinusSrcAlpha
		//	ZWrite off
       //     Offset -1 , 0
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
		
      
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;   
                float4 vertex : SV_POSITION;
		    	float4 color : COLOR;
            };
		

			  //阴影方向计算。
        float3 PlanarShadowPos(float3 posWS)
        {
          float3 L = normalize(_LightDir);
          float3 N = float3(0, 1, 0);

          float d1 = dot(L, N);
          float d2 = posWS.y - _PlaneY;

          // 阴影坐标沿法线方向偏移一点,防止与平面重叠时出现z-fighting的问题
          float3 offsetByNormal = N * 0.001;
          return posWS - L * (d2 / d1) + offsetByNormal;

          }


            v2f vert (appdata v)
            {
                v2f o;
				 // 顶点坐标,转世界坐标
                float3 posWS = TransformObjectToWorld(v.vertex.xyz);
               // 世界顶点坐标转为平面坐标
                posWS = PlanarShadowPos(posWS);
                o.vertex = TransformWorldToHClip(posWS);
                o.uv =v.uv;         
		
			   //得到中心点世界坐标
		    	float3 center = float3(unity_ObjectToWorld[0].w,  _PlaneY, unity_ObjectToWorld[2].w);
		    	//计算阴影衰减
				float falloff = 1 - saturate(distance(posWS, center) * _ShadowFalloff);
 
			   	o.color = _ShadowColor;
				o.color.a *=falloff;
                return o;
            }



            half4 frag (v2f i) : SV_Target
            {
                return i.color;
            }
            ENDHLSL
        }

  }}

效果如下:

下一篇:Unity URP无光照下Shadow,SRP高效合批 制作 <二> ,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值