为什么透明效果的渲染顺序很重要
书上已经解释的很清楚了,这边说一下,为什么对于循环重叠的半透明物体需要在意渲染顺序,而循环重叠的不透明物体不需要在意渲染顺序。
有两个 buffer,一个是颜色缓冲,一个是深度缓冲。现在假设这三个物体都是不透明的,我们先渲染红色的物体,model-view-projection 变换之后,对于组成红色物体的每一个三角形的每一个顶点,都是有 x,y,z 三个值的,然后对于每一个三角形内部的每一个片元(这个片元的采样点),都是可以通过差值,也拥有这三个值,假设我们现在需要渲染 A 点这个片元,因为现在两个缓冲区里面都没有东西,我们就把这个片元的颜色写进颜色缓冲,z 值写进深度缓冲。我们再渲染黄色物体 A 点这个片元,发现这个像素的深度缓冲里面已经有东西了,并且里面的深度要离摄像机更近一些,所以直接舍弃掉黄色的这个片元。对于渲染蓝色物体的 B 点时,也是一样的,不同的是会将这个片元的 z 写进深度缓冲并且把颜色写进颜色缓冲,因为 B 的这个点要离摄像机更近一些。
但假如这三个物体都是半透明的,那就会出现问题了。我们还是按照之前的顺序来理一遍,先渲染红色物体,在 A 点,只执行颜色写入,深度不写。再渲染黄色的 A 点,虽然会进行深度测试,但现在深度缓冲里面没有东西,所以依然会把这个颜色和现在颜色缓冲里面的颜色进行混合。那么就会出现黄色在红色前面的效果了。那如果我们是先渲染黄色,再渲染红色,再渲染蓝色呢?那么对于 A、B 点都没问题的,因为是从远到近得渲染顺序,但是对于 C 点、蓝色就会在黄色上面了。
所以说对于透明的物体来说渲染顺序是很重要的,但这种情况我们也不知道要按什么顺序来进行渲染,因为不能将他们按照离摄像机从远到近的顺序进行排列。不透明的物体之所以表现良好,就是因为他的颜色缓冲和深度缓冲是保持一致的,也就是深度缓冲里面的记载的深度所对应的物体与颜色缓冲里面记载的颜色对应的物体是同一个。但透明物体的出现破坏了这种机制。
透明度测试
Shader "MyShader/Texture/Alpha Test"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
_Gloss ("Gloss",Range(8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
float _Gloss;
fixed4 _Spacular;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
// Alpha test
clip (texColor.a - _Cutoff);
// Equal to
// if ((texColor.a - _Cutoff) < 0.0) {
// discard;
// }
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
float3 half_normal = normalize(worldViewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * _LightColor0.rgb * pow(max(0,dot(worldNormal,half_normal)),_Gloss);
return fixed4(ambient + diffuse + spacularColor, 1.0);
}
ENDCG
}
}
}
透明度混合
Shader "MyShader/Transparent/AlphaBlend"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 0.5 // 用来控制整体的透明度
_Gloss ("Gloss",Range(8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass
{
Tags { "LightMode"="ForwardBase" }
ZWrite Off // 关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
float _Gloss;
fixed4 _Spacular;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
float3 half_normal = normalize(worldViewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * _LightColor0.rgb * pow(max(0,dot(worldNormal,half_normal)),_Gloss);
return fixed4(ambient + diffuse, texColor.a*_AlphaScale);
}
ENDCG
}
}
}
上面的效果是最传统的混合方式,还有很多其他的混合方式。效果如下:
Blend One OneMinusSrcAlpha // 预乘透明度
Blend One One // 线性加法
Blend OneMinusDstColor One // 柔和加法/滤色
// 或者
Blend One OneMinusSrcColor
滤色的特点是,黑色会完全不显示,白色会完全显示,灰色会变亮
Blend DstColor Zero // 乘法/正片叠底
Blend DstColor SrcColor // 两倍乘法
BlendOp Max
Blend One One // 变亮
// 变暗(Darken)
BlendOp Min
Blend One One
// 线性减淡(Linear Dodge)
Blend One One
开启了深度写入的透明混合
Shader "MyShader/Transparent/AlphaBlend"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 0.5 // 用来控制整体的透明度
_Gloss ("Gloss",Range(8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass
{
ZWrite On // 开始深度写入
ColorMask 0 // 不向任何通道写入东西
}
Pass
{
Tags { "LightMode"="ForwardBase" }
ZWrite Off // 关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
float _Gloss;
fixed4 _Spacular;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
float3 half_normal = normalize(worldViewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * _LightColor0.rgb * pow(max(0,dot(worldNormal,half_normal)),_Gloss);
return fixed4(ambient + diffuse + spacularColor , texColor.a*_AlphaScale);
}
ENDCG
}
}
}
对比效果如下:
为什么第一个 pass 里面开启深度写入就能保持透明模型的前后关系呢?因为在第一遍 pass 的时候,深度缓冲里面已经放了最近的深度了,那么在第二个 pass 里面,遇到比这个最近的要远的就会被舍弃,所以其实有个美中不足的遗憾。就是最前面的一个管子是透明的,那应该是能模糊的看到下面红色的线的,但由于他是和当前颜色缓冲里面的颜色做混合,所以是达不到能若隐若现的看到次近的管子的边界的效果的。
双面渲染的透明效果
双面渲染的透明度测试
Shader "MyShader/Transparent/Alpha Test"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
_Gloss ("Gloss",Range(8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass
{
Tags { "LightMode"="ForwardBase" }
Cull Off // 关闭剔除效果,想要只渲染正面,就写 Back(剔除摄像机背面),反之就写 Front。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
float _Gloss;
fixed4 _Spacular;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
// Alpha test
clip (texColor.a - _Cutoff);
// Equal to
// if ((texColor.a - _Cutoff) < 0.0) {
// discard;
// }
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
float3 half_normal = normalize(worldViewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * _LightColor0.rgb * pow(max(0,dot(worldNormal,half_normal)),_Gloss);
return fixed4(ambient + diffuse + spacularColor, 1.0);
}
ENDCG
}
}
}
在我们之前写的透明度测试的代码中,加上一行 Cull Off
就能获得如下效果:
(悄咪咪说一句,这也太好看了)
双面渲染的透明度混合
Shader "MyShader/Transparent/DoubleAlphaBlend"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 10)) = 0.5 // 用来控制整体的透明度
_Gloss ("Gloss",Range(8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
_CutOff("AlphaCutOff",float) = 0.5
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass
{
Cull Front
ZWrite On // 开始深度写入
ColorMask 0 // 不向任何通道写入东西
}
Pass
{
Tags { "LightMode"="ForwardBase" }
Cull Front
ZWrite Off // 关闭深度写入
Blend DstColor Zero
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
float _Gloss;
fixed4 _Spacular;
float _CutOff;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
if(texColor.a - _CutOff < 0.0)
{
discard;
}
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * albedo - 0.1;
return fixed4(diffuse , texColor.a*_AlphaScale);
}
ENDCG
}
//Pass
//{
// Cull Off
// ZWrite On // 开始深度写入
// ColorMask 0 // 不向任何通道写入东西
//}
Pass
{
Tags { "LightMode"="ForwardBase" }
Cull Back
ZWrite Off // 关闭深度写入
Blend SrcColor SrcColor
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
float _Gloss;
fixed4 _Spacular;
float _CutOff;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = float2(v.texcoord.x*_MainTex_ST.x + _MainTex_ST.z,v.texcoord.y*_MainTex_ST.y+_MainTex_ST.w);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
if(texColor.a - _CutOff < 0.0)
{
discard;
}
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * (0.5*dot(worldNormal, worldLightDir)+0.5);
float3 half_normal = normalize(worldViewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * _LightColor0.rgb * pow(max(0,dot(worldNormal,half_normal)),_Gloss);
return fixed4(ambient + diffuse + spacularColor, texColor.a*_AlphaScale);
}
ENDCG
}
}
}
运行效果如下:
下面是重复重叠的模型,也可以表现良好,并且可以看到在三个拐弯处处理的更加真实了。
双面其实就是在两个 Pass 分别剔除正面和背面来渲染两次(先渲染反面再渲染正面这个顺序很重要),然后稍微调整一下正面希望渲染的效果,比如我在反面就去掉了高光和环境光,只留下了漫反射光,甚至也没有用发现来调整光的强弱,只是整体 -1 ,让他稍微变淡了一点。值得注意的是如果要对重复重叠的模型也适用,需要在第一遍 Pass ,也就是深度写入的时候剔除掉正面,也就是按照反面的深入进行写入。