来介绍一种适用于移动平台的高性能实时阴影解决方案——平面阴影(Planar Shadow)。

由于Unity内置的实时阴影实现方式是屏幕空间阴影贴图(Screen Space Shadow Map)非常消耗性能,所以移动端的阴影一般需要使用更加性能友好的替代方案。常用的方案有简单贴图法、平面阴影、投影生成法和环境遮挡法,常用的的插件有Dynamic Shadow Projector和Fast Shadow Receiver。有兴趣的同学可以在网上很容易找到相关的详细介绍,或者看一下文末的拓展阅读部分。

这篇主要来分享一下平面阴影Unity Shader的实现并总结优缺点,平面阴影又称作顶点投射阴影,手游《王者荣耀》中就用了类似的技术。

原理及实现

具体原理和实现方式可参考:

喵喵Mya:使用顶点投射的方法制作实时阴影​zhuanlan.zhihu.com

我在这个shader的基础上又做了两个修改:

  • 一个是从场景中读取主光源的方向来调整阴影
  • 一个是支持了透明度剔除Alpha Clipping

(其实还加了个地面高度属性_GroundHeight)

修改一:从场景中读取主光源的方向

//灯光方向
// float3 lightDir = normalize(_LightDir.xyz);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

原来是在Shader属性中手动调整,现在如果场景中有多个物体时改变光源方向就不需要一个一个调Shader了。

修改二:支持了透明度剔除Alpha Clipping

if (_Clipping) {float alpha = tex2D(_MainTex, i.uv.xy).a;i.color.a *= step(_Cutoff, alpha);
}

提取主贴图的A通道做透明度剔除。

优缺点

这种平面阴影的优点是性能消耗小,阴影品质较高,简单好实现,非常适合MOBA类、俯视视角类型的游戏(角色和相机有一定距离)。

但是此方法的局限性也很大,最明显的缺点是阴影只投影在特定平面上,会插到墙体等障碍物中。另外它模拟的并不是真实的物理阴影和实际差别很大,阴影边缘很硬,真实的边缘会有虚化,如果镜头距离较近的话缺陷就会比较明显。

平面阴影的边缘很硬

修改后的Shader(只有平面阴影Pass)

PlanarShadow.shader

Shader "Custom/PlanarShadow"
{Properties{_Tint("_Tint", Color) = (1,1,1,1)_MainTex("_MainTex (albedo)", 2D) = "white" {}[Header(Alpha)][Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1_Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5 // alpha clip threshold[Header(Shadow)]// _GroundHeight("_GroundHeight", Range(-100, 100)) = 0_GroundHeight("_GroundHeight", Float) = 0_ShadowColor("_ShadowColor", Color) = (0,0,0,1)_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05// Blending state[HideInInspector] _SrcBlend("__src", Float) = 1.0[HideInInspector] _DstBlend("__dst", Float) = 0.0[HideInInspector] _ZWrite("__zw", Float) = 1.0[HideInInspector] _Cull("__cull", Float) = 2.0}SubShader{Pass {// 其他Pass请自行实现}// Planar Shadows平面阴影Pass{Name "PlanarShadow"//用使用模板测试以保证alpha显示正确Stencil{Ref 0Comp equalPass incrWrapFail keepZFail keep}Cull Off//透明混合模式Blend SrcAlpha OneMinusSrcAlpha//关闭深度写入ZWrite off//深度稍微偏移防止阴影与地面穿插Offset -1 , 0CGPROGRAM#pragma shader_feature _CLIPPING#pragma shader_feature _ALPHATEST_ON#pragma shader_feature _ALPHAPREMULTIPLY_ON#include "UnityCG.cginc"#pragma vertex vert#pragma fragment fragfloat _GroundHeight;float4 _ShadowColor;float _ShadowFalloff;half4 _Tint;sampler2D _MainTex;float4 _MainTex_ST;float _Clipping;half _Cutoff;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float4 color : COLOR;float2 uv : TEXCOORD0;};float3 ShadowProjectPos(float4 vertPos){float3 shadowPos;//得到顶点的世界空间坐标float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;//灯光方向// float3 lightDir = normalize(_LightDir.xyz);float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//阴影的世界空间坐标(低于地面的部分不做改变)shadowPos.y = min(worldPos .y , _GroundHeight);shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y; return shadowPos;}float GetAlpha (v2f i) {float alpha = _Tint.a * tex2D(_MainTex, i.uv.xy).a;return alpha;}v2f vert (appdata v){v2f o;//得到阴影的世界空间坐标float3 shadowPos = ShadowProjectPos(v.vertex);//转换到裁切空间o.vertex = UnityWorldToClipPos(shadowPos);//得到中心点世界坐标float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);//计算阴影衰减float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);//阴影颜色o.color = _ShadowColor;o.color.a *= falloff;o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag (v2f i) : SV_Target{if (_Clipping){float alpha = GetAlpha(i);i.color.a *= step(_Cutoff, alpha);}return i.color;}ENDCG}}// FallBack "Diffuse"
}

URP 版本

替换了一下引用和API,物体自身着色使用URP自带的ForwardLit,可以直接使用看效果。

PlanarShadow_URP.shader

Shader "Custom/PlanarShadow_URP"
{Properties{[Header(Base Color)][MainColor] _BaseColor("_BaseColor", Color) = (1,1,1,1)[MainTexture] _BaseMap("_BaseMap (albedo)", 2D) = "white" {}[Header(Alpha)][Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1_Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5 // alpha clip threshold[Header(Shadow)]// _GroundHeight("_GroundHeight", Range(-100, 100)) = 0_GroundHeight("_GroundHeight", Float) = 0_ShadowColor("_ShadowColor", Color) = (0,0,0,1)_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05// Blending state[HideInInspector] _SrcBlend("__src", Float) = 1.0[HideInInspector] _DstBlend("__dst", Float) = 0.0[HideInInspector] _ZWrite("__zw", Float) = 1.0[HideInInspector] _Cull("__cull", Float) = 2.0}SubShader{       Tags {"RenderPipeline" = "UniversalPipeline" "RenderType" = "Transparent" "Queue" = "Transparent"}LOD 300// 物体自身着色使用URP自带的ForwardLit passUSEPASS "Universal Render Pipeline/Lit/ForwardLit"// Planar Shadows平面阴影Pass{Name "PlanarShadow"//用使用模板测试以保证alpha显示正确Stencil{Ref 0Comp equalPass incrWrapFail keepZFail keep}Cull Off//透明混合模式Blend SrcAlpha OneMinusSrcAlpha//关闭深度写入ZWrite off//深度稍微偏移防止阴影与地面穿插Offset -1 , 0HLSLPROGRAM#pragma shader_feature _CLIPPING#pragma shader_feature _ALPHATEST_ON#pragma shader_feature _ALPHAPREMULTIPLY_ON#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"#pragma vertex vert#pragma fragment fragfloat _GroundHeight;float4 _ShadowColor;float _ShadowFalloff;half4 _BaseColor;sampler2D _BaseMap;float4 _BaseMap_ST;float _Clipping;half _Cutoff;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float4 color : COLOR;float2 uv : TEXCOORD0;};float3 ShadowProjectPos(float4 vertPos){float3 shadowPos;//得到顶点的世界空间坐标float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;//灯光方向Light mainLight = GetMainLight();float3 lightDir = normalize(mainLight.direction);//阴影的世界空间坐标(低于地面的部分不做改变)shadowPos.y = min(worldPos .y , _GroundHeight);shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y; return shadowPos;}float GetAlpha (v2f i) {float alpha = _BaseColor.a;alpha *= tex2D(_BaseMap, i.uv.xy).a;return alpha;}v2f vert (appdata v){v2f o;//得到阴影的世界空间坐标float3 shadowPos = ShadowProjectPos(v.vertex);//转换到裁切空间// o.vertex = UnityWorldToClipPos(shadowPos);o.vertex = TransformWorldToHClip(shadowPos);//得到中心点世界坐标float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);//计算阴影衰减float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);//阴影颜色o.color = _ShadowColor;o.color.a *= falloff;o.uv = TRANSFORM_TEX(v.uv, _BaseMap);return o;}half4 frag (v2f i) : SV_Target{if (_Clipping){float alpha = GetAlpha(i);// clip(alpha - _Cutoff);i.color.a *= step(_Cutoff, alpha);// i.color.a = alpha;}return i.color;}ENDHLSL}}
}

拓展阅读

关于Unity屏幕空间阴影贴图(Screen Space Shadow Map)的实现原理,推荐阅读《Shader入门精要》9.4 Unity的阴影,网上也有很多文章有介绍。

Unity手游开发札记——使用Fast Shadow Receiver优化渲染效率 84

Funny David:Unity手游开发札记——使用Fast Shadow Receiver优化渲染效率​zhuanlan.zhihu.com

Unity3D游戏制作(三)——移动平台上的角色阴影制作

Unity3D游戏制作(三)--移动平台上的角色阴影制作_游戏点滴-CSDN博客​blog.csdn.net

Unity实现简易体积光/体积阴影 155

zzeero:Unity实现简易体积光/体积阴影​zhuanlan.zhihu.com

屏幕空间深度阴影贴图(Screen Space Deep Shadow Maps) 185

凯奥斯:屏幕空间深度阴影贴图(Screen Space Deep Shadow Maps)​zhuanlan.zhihu.com

Unity3D 基于ShadowMap的平滑硬阴影

Unity3D 基于ShadowMap的平滑硬阴影​www.cnblogs.com

浅谈Unity的实时阴影 7

will Yan:浅谈Unity的实时阴影​zhuanlan.zhihu.com

Unity实时阴影实现——Screen Space Shadow Mapping 54

YOung:Unity实时阴影实现——Screen Space Shadow Mapping​zhuanlan.zhihu.com

有收获的话不妨点个赞支持一下啊~

2d shader unity 阴影_【Unity Shader】平面阴影(Planar Shadow)相关推荐

  1. Shader山下(二十七)平面阴影

    以下摘录自<Unity 3D ShaderLab开发实战详解>,第10章. 转注:这是一个非常简单且廉价的实时假阴影,比较常用到,据我所知,王者荣耀中就使用了类似的手法.缺点就是比较假,只 ...

  2. 边坡稳定性分析软件slope/w用户指南_岩石边坡平面滑动(Planar Sliding)稳定性分析...

    1 引言 在<边坡工程---岩体边坡的破坏模式>一文中,简要描述了岩石边坡的破坏模式,主要有平面破坏,楔形破坏,倾倒破坏和圆形破坏四种破坏模式.作为<边坡工程>课程的一部分,本 ...

  3. 8.3实例程序:平面阴影

    在场景中被灯光照射的地方会产生阴影,这将使场景变的更真实.在这一部分我们将演示怎样实现平面阴影,即在平面上的阴影(如图8.5). 使用这种阴影只是一种权宜之计,虽然它增强了场景的真实效果,但是这并不是 ...

  4. Unity Shader - Planar Shadow - 平面阴影

    文章目录 整体运行效果 思路 Shader 问题 Z-Fighting,解决:按法线方向偏移一丢丢 绘制 Alpha 混合重叠,解决:使用 stencil buffer 来规避 为何出现这个问题 解决 ...

  5. Unity Shader - URP ShadowCast ShadowRecieve - 投影 和 接受阴影

    文章目录 Shadow Caster Using URP Shadow Caster Pass Using Custom Shadow Caster Pass 先来看看 [没有] apply shad ...

  6. unity 半透明混合问题_Unity 实时 半透明 阴影 shader

    简单阴影制作思路: 1:在角色脚底 放置一块平板 2:shader中 根据平板传入的矩阵 以及 光照 对角色进行变换 3: 得到投影在地面上的阴影 4:阴影直接渲染到 屏幕上 缺点: 上面的阴影无法 ...

  7. unity 2020 怎么写shader使其接受光照?_如何在Unity中造一个PBR Shader轮子

    之前有业界大佬建议我去了解下Unity的PBR.说来惭愧,我查找了下资料才发现自己在这方面的知识居然是一片空白.经过几周的学习与尝试我对这一块算是有了初步的了解,于是写了这篇文章,一方面对自己学到的东 ...

  8. shader 反射 水面_【Unity Shader】模拟水面包含折射与反射与波浪动画

    最近研究了一下Unity官方的BoatAttack案例,他们模拟的海浪效果很厉害,用的是Gerstner波来模拟水面起伏和波峰的白浪还有浮力系统,还做加入了焦散效果(Caustics)和平面反射(Pl ...

  9. Unity 2D图片的3D效果(1)——阴影

    项目为方便编辑地图,使用的是Unity自带的Tilemap功能.因为使用的是俯视视角,拼好第一个地图后发现2D图片展现的效果太平了,地面和障碍物很难分辨.所以才有了"把2d图片展现出3d效果 ...

最新文章

  1. FORTRAN学习记录(持续更新)
  2. 【易创课堂】第3期,最后2天报名啦!
  3. innodb_flush_log_at_trx_commit配置
  4. ldap数据库--ODSEE--复制协议
  5. 为什么MCU也要支持AI功能?
  6. Android之提示Cannot call this method while RecyclerView is computing a layout or scrolling
  7. python编写命令行框架_Python2和3的面向命令的命令行框架是什么?
  8. 网上图片的几种保存方法
  9. iis7.5 php isapi映射,IIS7、iis7.5让ISAPI扩展DLL执行的方法
  10. MDM数据分析设计方案
  11. 色织物数据集YDFID-1
  12. java 实现限流器,可用于Rest接口请求处理 | Java工具类
  13. 微信公众号开发—入门系列(一)
  14. Nuxt.js框架启动报错✖ 224 problems (146 errors, 78 warnings) 146 errors and 74 warnings potentially fixab
  15. 保研面试中常见的英语问题有哪些?
  16. TC-Traffic Control in Linux
  17. apache虚拟主机配置(壹)
  18. 青云志 java_青云志手游纯手工架设含本地注册教程
  19. 诺奖罗杰.彭罗斯的量子意识及其他(含朱清时-科学与佛学 77分钟视频)
  20. 微信小程序带清除按钮和搜索记录的实时搜索页面

热门文章

  1. 大一新生开学考计算机知识点,2018年大一新生入学考试科目及考试资料和内容解读...
  2. Android进阶:框架打造之IOC框架
  3. 【译】如何写出一份优秀的软件设计文档
  4. 【李宏毅2020 ML/DL】P83 Generative Adversarial Network | Evaluation
  5. 【Computer Organization笔记17】大实验讨论:各组数据通路展示
  6. 【JAVA基础知识总结】JAVA对象转型之上转型对象与下转型对象
  7. 深入PHP内核之ZVAL
  8. [转载]Jquery mobile 新手问题总汇
  9. android socket编程实例
  10. 正经人一辈子都用不到的 JavaScript 方法总结 (二)