一 .Unity移动端软阴影技术总结:
https://blog.csdn.net/jxw167/article/details/82422891
二. 平面阴影的原理
https://zhuanlan.zhihu.com/p/42781261
https://zhuanlan.zhihu.com/p/31504088
王者荣耀游戏使用的就是该方法,已经有上线产品验证过的方法,这说明我们的游戏产品也可以使用,该技术叫平面投影阴影(Planar Projected Shadows)技术,由Jim Blinn 1988年提出。http://www.twinklingstar.cn/2015/1717/tech-of-shadows/#21_Blinns
它实现的原理是:通过相似三角形求一个物体每一个顶点在某个平面上的投影位置,说白了就是求直线在平面上的交点。进一步通俗点讲:物体上的某一点,以直线光源的方向作为方向,从该点出发,移动一定距离,使得该点落在要显示阴影的平面上(比如地表),随后将该点显示为阴影即可。
建议大家在平时可以阅读一下几个比较好的会议论文:GDC系列文章和Siggraph系列文章,这个两个会议的论文都是代表当前比较超前的算法实现,这些算法我们可以将其用Unity或者UE4其他引擎实现出来增加渲染效果。
线段与平面的交点推导(很重要):
三. 平面阴影的实现
1.第一个pass正常渲染物体,根据各项目的需求实现(跟阴影实现无关)
2.另一个pass渲染该物体的阴影
阴影pass的实现:
1.从C#脚本中传入Shader需要投影的平面及一些相关信息
public Light mainLight;private Vector4 _ShadowFadeParams = new Vector4(0.0f, 1.5f, 0.7f, 0.0f);private void UpdateShader(){Vector4 worldpos = transform.position;Vector3 shadowPlaneNrm = transform.up;//计算平面法线与平面上某一点的点乘,由于人物行走在平面上,人物脚下的点必定在平面上float nrmDotPos = Vector3.Dot(shadowPlaneNrm, worldpos);// Vector4 projdir = new Vector4(-0.2f,-0.8f,-0.6f,0);Vector4 projdir = mainLight.transform.forward;//Debug.Log("projdir" + projdir);Material mat = Renderer.material;// foreach (var mat in mMatList)// {if (mat == null)return;mat.SetVector("_WorldPos", worldpos);mat.SetVector("_ShadowProjDir", projdir);// mat.SetVector("_ShadowPlane", new Vector4(2.289143f, -11.88877f, 28.79983f, 0.0f));mat.SetVector("_ShadowPlane", new Vector4(shadowPlaneNrm.x, shadowPlaneNrm.y, shadowPlaneNrm.z, nrmDotPos));mat.SetVector("_ShadowFadeParams", _ShadowFadeParams);mat.SetFloat("_ShadowFalloff", 1.35f);// }}
2.顶点Shader计算该点在阴影平面的投影
v2f vert(appdata v){v2f o;float3 lightdir = normalize(_ShadowProjDir);float3 worldpos = mul(unity_ObjectToWorld, v.vertex).xyz;// _ShadowPlane.w = p0 * n // 平面的w分量就是p0 * nfloat distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz, worldpos)) / dot(_ShadowPlane.xyz, lightdir.xyz);worldpos = worldpos + distance * lightdir.xyz;o.vertex = mul(unity_MatrixVP, float4(worldpos, 1.0));o.xlv_TEXCOORD0 = _WorldPos.xyz;o.xlv_TEXCOORD1 = worldpos;return o;}
3.片段Shader进行模糊算法等处理,改善阴影效果
float4 frag(v2f i) : SV_Target{float3 posToPlane_2 = (i.xlv_TEXCOORD0 - i.xlv_TEXCOORD1);float4 color;color.xyz = float3(0.0, 0.0, 0.0);// 下面两种阴影衰减公式都可以使用(当然也可以自己写衰减公式)// 王者荣耀的衰减公式color.w = (pow((1.0 - clamp(((sqrt(dot(posToPlane_2, posToPlane_2)) * _ShadowInvLen) - _ShadowFadeParams.x), 0.0, 1.0)), _ShadowFadeParams.y) * _ShadowFadeParams.z);// 另外的阴影衰减公式//color.w = 1.0 - saturate(distance(i.xlv_TEXCOORD0, i.xlv_TEXCOORD1) * _ShadowFalloff);return color;}
其中计算核心的两行代码就是
float distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz, worldpos)) / dot(_ShadowPlane.xyz, lightdir.xyz);
worldpos = worldpos + distance * lightdir.xyz;
对应的公式就是下式,其中P0和平面法线的点乘是由C#代码计算后传入的,存储在_ShadowPlane的w分量(如果平面是水平的,即法线方向就是y轴的话, _ShadowPlane的w分量可以直接传入的是平面的y值)
四. 平面阴影在URP中的实现
URP中常规渲染是单Pass渲染,平面shadow需要两个pass,为了实现平面shadow,需要先解决URP中的两个Pass渲染。
https://blog.csdn.net/nxl76450106/article/details/101290283
在需要执行的第一个pass添加Tags{ "LightMode" = "LightweightForward(或者UniversalForward,或者Universal2D,根据当前正常渲染时使用的render设置)" },第二个pass添加Tags{ "LightMode" = "SRPDefaultUnlit" }即可让这两个pass同时生效。
比如我们项目目前用的是Universal2D渲染,即2D的光照渲染,就需要把第一个Pass的Tag设置为Universal2D,此时你会发现如果camera使用的Render不是Universal2D render,就无法渲染出物体。所以总结来讲就是camera使用的Render要和Pass中的Tag对应。
解决两个Pass渲染问题后,第一个pass我直接copy URP内置的SimpleLit函数的光照Pass,第二个Pass用HLSL语言重写了上边的CG语言代码,替换了几处API:
Pass{Tags{"LightMode" = "SRPDefaultUnlit"}Blend SrcAlpha OneMinusSrcAlphaZWrite OffCull BackColorMask RGBStencil{Ref 0Comp EqualWriteMask 255ReadMask 255//Pass IncrSatPass InvertFail KeepZFail Keep}//CGPROGRAMHLSLPROGRAM#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"#pragma vertex vert#pragma fragment fragCBUFFER_START(UnityPerFrame)float4x4 unity_MatrixVP;CBUFFER_ENDCBUFFER_START(UnityPerDraw)float4x4 unity_ObjectToWorld;CBUFFER_END#define UNITY_MATRIX_M unity_ObjectToWorldfloat4 _ShadowPlane;float4 _ShadowProjDir;float4 _WorldPos;float _ShadowInvLen;float4 _ShadowFadeParams;float _ShadowFalloff;struct appdata{float4 vertex : POSITION;};struct v2f{float4 vertex : SV_POSITION;float3 xlv_TEXCOORD0 : TEXCOORD0;float3 xlv_TEXCOORD1 : TEXCOORD1;};v2f vert(appdata v){v2f o;float3 lightdir = normalize(_ShadowProjDir);//float3 worldpos = mul(unity_ObjectToWorld, v.vertex).xyz;//float3 worldpos = TransformObjectToWorld(v.vertex.xyz);float4 worldPos = mul(UNITY_MATRIX_M, float4(v.vertex.xyz, 1.0));// _ShadowPlane.w = p0 * n // 平面的w分量就是p0 * nfloat distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz, worldPos.xyz)) / dot(_ShadowPlane.xyz, lightdir.xyz);worldPos = worldPos + distance * float4(lightdir.xyz, 0.0);//o.vertex = mul(unity_MatrixVP, float4(worldpos, 1.0));//o.vertex = TransformWorldToHClip(float4(worldpos, 1.0));o.vertex = mul(unity_MatrixVP, worldPos);o.xlv_TEXCOORD0 = _WorldPos.xyz;o.xlv_TEXCOORD1 = worldPos;return o;}float4 frag(v2f i) : SV_Target{float3 posToPlane_2 = (i.xlv_TEXCOORD0 - i.xlv_TEXCOORD1);float4 color;color.xyz = float3(0.0, 0.0, 0.0);// 下面两种阴影衰减公式都可以使用(当然也可以自己写衰减公式)// 王者荣耀的衰减公式color.w = (pow((1.0 - clamp(((sqrt(dot(posToPlane_2, posToPlane_2)) * _ShadowInvLen) - _ShadowFadeParams.x), 0.0, 1.0)), _ShadowFadeParams.y) * _ShadowFadeParams.z);// 另外的阴影衰减公式//color.w = 1.0 - saturate(distance(i.xlv_TEXCOORD0, i.xlv_TEXCOORD1) * _ShadowFalloff);return color;}//ENDCGENDHLSL}
项目链接:
https://github.com/Dejavu0709/Plane-Shadow-For-URP.git