[Shader] 6.透明效果的实现

本文详细介绍了Unity 3D中处理透明效果的两种常见方法:透明测试和透明混合。透明测试通过设置alpha阈值剔除部分像素,实现完全透明或不透明的效果,而透明混合则通过混合新图像和已存在图像来创建半透明效果。文章还探讨了渲染流程、混合模式、渲染队列以及如何处理双面透明和交叉重叠问题。同时,提供了具体的Shader代码示例,帮助读者理解并应用这些技术。
目前处理透明效果的常见方式有透明测试(适用于完全透明/不透明,非0即1)和透明混合(适用于颜色的混合叠加)

透明测试

和深度测试的思路类似,剔除某些小于透明阈值的像素,实现某些位置完全透明,我们设定一个alpha阈值,当偏于不满足条件(通常是小于阈值)就直接舍弃当作全透明,满足条件的片元就直接当作不透明物体进行处理。所以注意AlphaTest并没有实现真正的半透明效果。
注意渲染流程先做深度测试,再做透明度测试,最后才是Blending混合渲染。在渲染队列中,透明度测试在非透明物体渲染之后,半透明物体之前,这样更高效。
一般透明测试的Shader包含以下指令:
SubShader { //设置渲染标签 Tags { "Queue" = "AlphaTest" ////透明测试的渲染队列 "RenderType" = "TransparentCutout" "IgnoreProjector" = "True" //透明物体不接受投影仪的投射 } Pass { CGPROGRAM clip(textureColor.a - alphaCutoffValue); ENDCG } }
实例Shader(就与Lambert光照模型)
Shader "Custom/AlphaTest Transparent" { Properties { _MainTex("MainTex",2D) = "white"{} _AlphaTest("Alpha Test",Range(0, 1)) = 0 } SubShader { //设置渲染标签 Tags { "Queue" = "AlphaTest" "RenderType" = "TrannsparentCutout" "IgnoreProjector" = "True" } //渲染背面 Pass { Tags{"LightMode" = "ForwardBase"} //关闭几何体剔除 Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 pos : SV_POSITION; float4 worldPos : TEXCOORD0; float2 texcoord : TEXCOORD1; float3 worldNormal : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed _AlphaTest; v2f vert (appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float3 worldNormal = UnityObjectToWorldNormal(v.normal); o.worldNormal = normalize(worldNormal); return o; } fixed4 frag (v2f i) : SV_Target { //灯光法线 等同于_WorldSpaceLightPos0 float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz); worldLight = normalize(worldLight); //按照公式计算漫反射 fixed ndotl = dot(i.worldNormal,worldLight); fixed4 color = tex2D(_MainTex, i.texcoord); //开启AlphaTest clip(color.a - _AlphaTest);//丢弃小于阈值的像素 color.rgb *= _LightColor0 * saturate(ndotl); color.rgb += unity_AmbientSky; return color; } ENDCG } } }
效果:
开启透明测试:

混合透明效果

将新渲染出来的图像和已存在的进行合并。
常用的混合模式有:
  • Blend Off 关闭混合处理。
  • Blend SrcFactor DstFactor 开启混合处理,自定义混合模式,将目标图像(新渲染出的图像)和源图像(已存在的图像)按照混合系数进行rgba四通道的加权混合。
  • Blend SrcFactor DstFactor,SrcFactorA DstFactorA 与上条类似,只不过分别计算rgb通道和alpha通道的混合。
  • BlendOp Op:使用逻辑操作指令指定加权后的图像像素值具体如何混合,比如Add,Sub等(默认不写为Add)。
  • BlendOp OpColor,OpAlpha: 使用逻辑操作,分别计算rgb通道和alpha通道。
注意,先用BlendOp声明逻辑操作类型,在用Blend声明混合系数。
常用混合逻辑操作:
混合系数:
常用混合指令(操作为Add)

混合透明的使用办法

一般混合透明Shader包含以下指令:
SubShader { Tags { "Queue" = "Transparent" // 透明物体的单独渲染队列,使得透明物体在不透明物体之后被渲染 "RenderType" = "Transparent" //透明物体的渲染类型,使得透明物体在不透明物体之后被渲染 "IgnoreProjector" = "True" //透明物体不接受投影仪的投射 } Pass { ZwriteOff //关闭透明物体深度写入 Blend SrcAlpha OneMinusSrcAlpha //透明叠加 } }

混合透明实例

对Lambert光照模型的代码进行修改。
Shader "Custom/Blending Transparent" { Properties { _MainTex("MainTex",2D) = "white"{} _MainColor("MainColor",Color) = (1, 1, 1, 1) } SubShader { //设置渲染标签 Tags { "Queue" = "Transparent" // 透明物体的单独渲染队列,使得透明物体在不透明物体之后被渲染 "RenderType" = "Transparent" //透明物体的渲染类型,使得透明物体在不透明物体之后被渲染 "IgnoreProjector" = "True" //透明物体不接受投影仪的投射 } Pass { Tags{"LightMode" = "ForwardBase"} //设置渲染状态 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 pos : SV_POSITION; float4 worldPos : TEXCOORD0; float2 texcoord : TEXCOORD1; float3 worldNormal : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _MainColor; v2f vert (appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float3 worldNormal = UnityObjectToWorldNormal(v.normal); o.worldNormal = normalize(worldNormal); return o; } fixed4 frag (v2f i) : SV_Target { //灯光法线 等同于_WorldSpaceLightPos0 float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz); worldLight = normalize(worldLight); //按照公式计算漫反射 fixed ndotl = dot(i.worldNormal,worldLight); fixed4 color; color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl); color.rgb += unity_AmbientSky; //通过_MainColor属性的a分量控制透明度 color.a = _MainColor.a; return color; } ENDCG } } FallBack "Diffuse" }
效果

半透明物体的双面渲染

上节实现的透明混合效果实际上是没有背面(物体的内表面,背朝摄像机)的,这是因为渲染状态默认是Cull Back,将背面进行了剔除。
可以使用Cull Off关闭背面剔除,但由于关闭了深度写入,深度缓存中没有物体的深度信息,导致背面和正面绘制顺序错误,出现了背面的图像叠加到正面的现象(背面的图片反而更亮,经过了一次透明叠加,而正面反而经过了两次)。
为此,需要分两个Pass,先渲染背面再渲染正面。
Pass { Tags{"LightMode" = "ForwardBase"} //开启正面剔除 Cull front //设置渲染状态 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 pos : SV_POSITION; float4 worldPos : TEXCOORD0; float2 texcoord : TEXCOORD1; float3 worldNormal : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _MainColor; v2f vert (appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float3 worldNormal = UnityObjectToWorldNormal(v.normal); o.worldNormal = normalize(worldNormal); return o; } fixed4 frag (v2f i) : SV_Target { //灯光法线 等同于_WorldSpaceLightPos0 float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz); worldLight = normalize(worldLight); //按照公式计算漫反射 fixed ndotl = dot(i.worldNormal,worldLight); fixed4 color; color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl); color.rgb += unity_AmbientSky; //通过_MainColor属性的a分量控制透明度 color.a = _MainColor.a; return color; } ENDCG } //渲染正面 Pass { Tags{"LightMode" = "ForwardBase"} //开启背面剔除 Cull Back //设置渲染状态 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityLightingCommon.cginc" struct v2f { float4 pos : SV_POSITION; float4 worldPos : TEXCOORD0; float2 texcoord : TEXCOORD1; float3 worldNormal : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _MainColor; v2f vert (appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float3 worldNormal = UnityObjectToWorldNormal(v.normal); o.worldNormal = normalize(worldNormal); return o; } fixed4 frag (v2f i) : SV_Target { //灯光法线 等同于_WorldSpaceLightPos0 float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz); worldLight = normalize(worldLight); //按照公式计算漫反射 fixed ndotl = dot(i.worldNormal,worldLight); fixed4 color; color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl); color.rgb += unity_AmbientSky; //通过_MainColor属性的a分量控制透明度 color.a = _MainColor.a; return color; }
正常的双面渲染:
半透明物体的交叉重叠问题也可以用类似的想法解决,用两个Pass,第一个Pass写入深度信息不做渲染,第二个Pass再做渲染
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值