unity 半透明混合问题_unity的半透明阴影

本文深入探讨Unity中半透明物体接受阴影的问题,分析了由于篡改渲染顺序导致的错误,并提出正确的解决方案。通过理解Unity的渲染机制和阴影计算,实现了在3000渲染队列接收阴影,同时讨论了支持非平行光阴影的扩展方法和性能考量。
摘要由CSDN通过智能技术生成

常见方案问题

半透明的阴影主要涉及2个功能 投递阴影和接收阴影,投递阴影 unity里比较好实现,可自定义shadowcast的pass 或直接fallback 带这个pass的 内置shader即可。甚至可以不改任何shader 直接同位置摆放一个阴影代替投射物也行,unity支持 不显示自己但只投递阴影的shadowonly 渲染模式。所以不会代码的美术自己也能搞定这问题,网上大量可用教程。

这里讨论的是接受阴影,虽然也有大量教程但都是说一半就停了,基本都是利用 "AutoLight.cginc" 内对shadowmap的计算 然后 渲染队列改成 2500以下。这种篡改了正确排序的做法, 至少有2种错误。1与天空盒遮挡关系,2与远处半透明遮挡关系。如图错误方法 与本方案对比因为强改渲染顺序 导致 天空混合 和半透明绿球混合错误正确做法 依然用3000队列渲染 天空和半透明绿球混合正确

原因分析

半透明是个很模糊的概念,至少有2种不同技术角度描述,1根据混合模式,2根据渲染队列。这里讨论隐藏较多的渲染队列问题。

引擎有自己的规则 2500以下按OpaqueGeometry阶段 (不论forward还是deferred)渲染。2501及以上 按TransparentGeometry阶段渲染。这2个阶段有哪些额外区别呢?

1 这2个阶段中间会插入 skybox的渲染。skybox 虽然定义成1000的渲染队列 符合理论的描述 1clear 2天空 3实体从近到远排序渲染 4半透明从远到近排序渲染 。但实际上unity没这样做 而是在2500之后 2501之前渲染,就是所有的实体渲染完渲染。这是为了性能 把天空作为 最远的实体 避免overdraw。

2 这2个阶段引擎渲染的keywords 不同,这里主要是2501之后 不传shadowmap了

代码实现

知道了 这2个原因后 就可以实现 3000队列的阴影接收了。

首先获得shadowmap并传给shader的一个全局贴图变量 这种做法避免了rt拷贝 但不支持多份

然后shader内 有了这张图 就可以 走一般的 阴影流程

Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }

Blend SrcAlpha OneMinusSrcAlpha

Pass

{

Tags{ "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_fwdbase

#include "UnityCG.cginc"

#include "AutoLight.cginc"

struct appdata

{

float4 vertex : POSITION;

};

struct v2f

{

float4 pos : SV_POSITION;

float3 worldPos : texcoord0;

SHADOW_COORDS(2)

};

fixed4 _ShadowColor;

v2f vert(appdata v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.worldPos = mul(UNITY_MATRIX_M, v.vertex);

TRANSFER_SHADOW(o);

return o;

}

fixed4 frag(v2f i) : SV_Target

{

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

return fixed4(_ShadowColor.rgb * atten ,0.5);

}

ENDCG

}

虽然这样已经好了很多 解决了2个顺序问题,但依然不足以一个真实项目的使用(这就是我一直不肯离开一线开发的原因吧),实际上 还需要考虑 非平行光的阴影,如果仅仅考虑平行光,上面改用screenshadowmask 性能更好些。

应用扩展

如果要支持点光或聚光灯的半透明阴影接收,cs代码和上面一样,但需要增加一个forwardadd的pass 特别是 #pragma multi_compile_fwdadd_fullshadows

.但是这也仅仅是实现单个光源阴影接收。如果要实现 同个场景同时多个光源给半透明物体 投递阴影,可以实现但比较复杂,要么挨个复制shadowmap 挨个采样。要么做个挨个计算出screenshadowmask合并到引擎默认的screenshadowmask 图上,对象渲染不需要额外采样。都是性能不高的做法,最后说下 unity默认不让半透明采样shadowmap的原因我做了个小小猜测,粒子等 大量半透明重叠的情况下 如果半透明默认采样shadowmap会非常非常消耗性能。

完整代码如下

Shader "Unlit/alphaShadow"

{Properties

{

_ShadowColor("Shadow Color", Color) = (0.1, 0.1, 0.1, 0.53)

}

SubShader

{

Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }

Blend SrcAlpha OneMinusSrcAlpha

Pass

{

Tags{ "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_fwdbase

#include "UnityCG.cginc"

#include "AutoLight.cginc"

struct appdata

{

float4 vertex : POSITION;

};

struct v2f

{

float4 pos : SV_POSITION;

float3 worldPos : texcoord0;

SHADOW_COORDS(2)

};

fixed4 _ShadowColor;

v2f vert(appdata v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.worldPos = mul(UNITY_MATRIX_M, v.vertex);

TRANSFER_SHADOW(o);

return o;

}

fixed4 frag(v2f i) : SV_Target

{

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

return fixed4(_ShadowColor.rgb * atten ,0.5);

}

ENDCG

}

Pass

{

Blend SrcAlpha OneMinusSrcAlpha

Tags{ "LightMode" = "ForwardAdd" }

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_fwdadd

#pragma multi_compile_fwdadd_fullshadows

#include "UnityCG.cginc"

#include "AutoLight.cginc"

struct appdata

{

float4 vertex : POSITION;

};

struct v2f

{

float4 pos : SV_POSITION;

float3 worldPos : texcoord0;

#ifdef DIRECTIONAL

SHADOW_COORDS(1)

#endif

};

fixed4 _ShadowColor;

v2f vert(appdata v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.worldPos = mul(UNITY_MATRIX_M, v.vertex);

#ifdef DIRECTIONAL

TRANSFER_SHADOW(o);

#endif

return o;

}

fixed4 frag(v2f i) : SV_Target

{

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

return fixed4(_ShadowColor.rgb * atten ,0.5);

}

ENDCG

}

}

FallBack "Diffuse"

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值