Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

双面渲染的透明效果

一、双面渲染的透明效果用来处理哪些需求

对于现实世界的半透明物体,我们不仅可以透过它的样子看到其他物体的样子 也可以看见这个物体自己的内部结构 但是 透明测试和透明度混合 都无法看见模型的内部结构 2024-09-13 155709.png 而双面渲染的透明效果 Shader 就是来处理这种问题的 我们不仅仅可以透过半透明物体看见其他物体,还可以看见自己的内部结构

二、基本原理

默认情况下,Unity 会自动剔除物体的背面,而只渲染物体的正面 所以双面渲染的基本原理就是 使用 Cull 剔除指令来进行指定操作

Cull Back 背面剔除 Cull Front 正面剔除 Cull Off 不剔除

  • 对于透明度测试 Shader 由于无需混合直接关闭剔除即可
  • 对于透明度混合 Shader 由于需要进行混合 需要使用两个 Pass,一个用于渲染背面,一个用于渲染正面 两个 Pass 除了剔除指令不同,其余相同

三、具体实现

1.实现思路

  • 透明度测试
    1. 复制透明度测试的代码
    2. 在 Pass 中关闭剔除 Cull off
  • 透明度混合
    1. 复制透明度混合的代码
    2. 复制实现透明度混合的 Pass
    3. 第一个 Pass 中剔除正面 Cull Front 第二个 Pass 中剔除背面 Cull Back 相当于一个片元先渲染背面再渲染正面

2.实例代码

1).透明度测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
Shader "Unlit/DoubleAlphaTest"
{
Properties
{
//主要就是将单张纹理Shader和BlinnPhong模型逐片元模型的结合
//纹理贴图
_MainTex("MainTex",2D)=""{}
//漫反射颜色
_MainColor("MainColor",Color)=(1,1,1,1)
//高光反射颜色
_SpecularColor("SpecularColor",Color)=(1,1,1,1)
//光泽度
_SpecularNum("SpecularNum",Range(0,20))=15

//透明度测试用的阈值
_Cutoff("Cutoff",Range(0,1)) = 0
}
SubShader
{
//设置渲染队列 决定对象在何时渲染
Tags{"Queue" = "AlphaTest" "IngnoreProject"="Ture" "RenderType" = "TransparentCutout"}
Pass
{
//设置光照模式
Tags{"LightMode"="ForwardBase"}
Cull off


CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//引用内置文件
#include "UnityCG.cginc"
#include "Lighting.cginc"

//纹理贴图的映射成员
sampler2D _MainTex;
float4 _MainTex_ST;
//漫反射颜色、高光反射颜色、光泽度
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
//透明度测试用的阈值
fixed _Cutoff;
struct v2f
{
//裁剪空间下的顶点坐标
float4 pos:POSITION;
//UV坐标
float2 uv:TEXCOORD0;
//世界空间下的法线
float3 wNormal:NORMAL;
//世界空间下的顶点坐标
float3 wPos:TEXCOORD1;

};
v2f vert (appdata_base v)
{
v2f data;
//将模型的顶点坐标转换到裁剪空间下
data.pos = UnityObjectToClipPos(v.vertex);
//uv坐标计算
data.uv = v.texcoord.xy *_MainTex_ST.xy + _MainTex_ST.zw;
//世界空间下的法线
data.wNormal = UnityObjectToWorldNormal(v.normal);
//世界空间下的顶点坐标
data.wPos = mul(UNITY_MATRIX_M,v.vertex);
return data;
}

fixed4 frag (v2f i) : SV_Target
{

//颜色纹理的颜色信息
fixed4 texColor = tex2D(_MainTex,i.uv);
//判断贴图的 颜色信息中的透明通道 有没有小于阈值
//如果小于 就直接丢弃
clip(texColor.a-_Cutoff);
/* if(texColor.a<_Cutoff)
discard; */
//纹理颜色需要和漫反射材质颜色叠加(乘法)
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _MainColor.rgb;
//光的方向(指向光源方向)
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特漫反射颜色 = 光的颜色 * 漫反射颜色 * max(0,dot(光的方向,世界坐标下的法线))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb *max(0,dot(i.wNormal,lightDir));
//高光反射颜色
// 视角方向
/*
//方法一:
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos);
*/
//方法二:
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));
//半角向量 = 标准化后的视角方向 + 光的方向
float3 halfA = normalize(viewDir + lightDir);
//高光反射颜色 = 光的颜色 * 高光反射颜色 * pow(max(0,dot(世界坐标系下的法线,半角方向),光泽度))
fixed3 specularColor = _LightColor0.rgb * _SpecularColor * pow(max(0,dot(i.wNormal,halfA)),_SpecularNum);
//bliinPongColor = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo.rgb + lambertColor + specularColor;
return fixed4(color.rgb,1);


}
ENDCG
}
}
}

2).透明度混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
Shader "Unlit/DoubleTransparent"
{
Properties
{
//光照相关属性
_MainColor("MainColor",Color) = (1,1,1,1)
_specularColor("specularColor",Color) = (1,1,1,1)
_specularNum("specularNum",Range(0,20)) = 15
//单张纹理
_MainTex("MainTex",2D) = ""{}
//法线纹理
_BumpMap("BumpMap",2D) = ""{}
_BumpMapScale("BumpMapScale",Float) = 1
//透明度
_AlphaScale("AlphaScale",Range(0,1)) = 1

}
SubShader
{
//设置透明度混合的 渲染队列 为 透明的Transparent
Tags{"Queue" = "Transparent" "IngnoreProjector" = "true" "RenderType" = "Transparent"}
Pass
{
Tags { "LightMode"="ForwardBase" }
//关闭深度写入
ZWrite off
//将混合因子 设置为半透明效果 的搭配
Blend SrcAlpha OneMinusSrcAlpha
//剔除正面
Cull Front

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 pos:POSITION;
//我们可以单独申明两个float2成员 用于记录 颜色和法线的uv坐标
/* float2 uvTex:TEXCOORD0;
float2 uvBump:TEXCOORD1; */
//我们也可以直接申明一个float4成员
//xy表示颜色纹理的uv zw表示法线的纹理uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下
float3 viewDir:TEXCOORD2;
};
//---光照---//
float4 _MainColor;//漫反射颜色
float4 _specularColor;//高光反色颜色
float _specularNum;//光泽度
//---纹理---//
sampler2D _MainTex;//颜色纹理
float4 _MainTex_ST;//颜色纹理缩放和平移
sampler2D _BumpMap;//法线纹理缩放和平移
float4 _BumpMap_ST;//法线纹理缩放和平移
float _BumpMapScale;//凹凸程度
//---透明度---//
fixed _AlphaScale;
v2f vert (appdata_full v)
{
v2f data;
//将模型空间下的顶点转移到裁剪空间下
data.pos = UnityObjectToClipPos(v.vertex);
//计算纹理的缩放偏移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

//在顶点着色器当中 得到 模型空间到切线空间的转换矩阵
//切线、副切线、法线
//计算副切线————注意乘以切线中的w确定方向
float3 binormal = cross(normalize(v.tangent),normalize(v.normal))*v.tangent.w;
//转换矩阵
float3x3 rotation = float3x3(v.tangent.xyz,
binormal,
v.normal);
//模型空间下的光的方向
data.lightDir = ObjSpaceLightDir(v.vertex);
//转换到切线空间下的光的方向
data.lightDir = mul(rotation,data.lightDir);
//模型空间下的视角方向
data.viewDir = ObjSpaceViewDir(v.vertex);
//转换到切线空间下的视角方向
data.viewDir = mul(rotation,data.viewDir);
return data;
}

fixed4 frag (v2f i) : SV_Target
{


//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩运算 得到切线空间下的法线信息
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal *= _BumpMapScale;
//---处理带颜色纹理的 BlinnPhong光照模型---//

//取出纹理当中的RGBA
fixed4 texColor = tex2D(_MainTex,i.uv.xy);

//颜色纹理和漫反射颜色的叠加
fixed3 albedo = tex2D(_MainTex,i.uv.xy).rgb * _MainColor.rgb;
//计算兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0,dot(tangentNormal,normalize(i.lightDir)));
//计算半角向量
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir));
//计算高光反射
float3 specularColor = _LightColor0.rgb * _specularColor * pow(max(0,dot(tangentNormal,halfA)) , _specularNum);
//计算BlinnPhong
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

//修改我们的Alpha返回值 决定透明度
return fixed4(color.rgb , texColor.a *_AlphaScale );
}
ENDCG
}
Pass
{
Tags { "LightMode"="ForwardBase" }
//关闭深度写入
ZWrite off
//将混合因子 设置为半透明效果 的搭配
Blend SrcAlpha OneMinusSrcAlpha
//剔除背面
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 pos:POSITION;
//我们可以单独申明两个float2成员 用于记录 颜色和法线的uv坐标
/* float2 uvTex:TEXCOORD0;
float2 uvBump:TEXCOORD1; */
//我们也可以直接申明一个float4成员
//xy表示颜色纹理的uv zw表示法线的纹理uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下
float3 viewDir:TEXCOORD2;
};
//---光照---//
float4 _MainColor;//漫反射颜色
float4 _specularColor;//高光反色颜色
float _specularNum;//光泽度
//---纹理---//
sampler2D _MainTex;//颜色纹理
float4 _MainTex_ST;//颜色纹理缩放和平移
sampler2D _BumpMap;//法线纹理缩放和平移
float4 _BumpMap_ST;//法线纹理缩放和平移
float _BumpMapScale;//凹凸程度
//---透明度---//
fixed _AlphaScale;
v2f vert (appdata_full v)
{
v2f data;
//将模型空间下的顶点转移到裁剪空间下
data.pos = UnityObjectToClipPos(v.vertex);
//计算纹理的缩放偏移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

//在顶点着色器当中 得到 模型空间到切线空间的转换矩阵
//切线、副切线、法线
//计算副切线————注意乘以切线中的w确定方向
float3 binormal = cross(normalize(v.tangent),normalize(v.normal))*v.tangent.w;
//转换矩阵
float3x3 rotation = float3x3(v.tangent.xyz,
binormal,
v.normal);
//模型空间下的光的方向
data.lightDir = ObjSpaceLightDir(v.vertex);
//转换到切线空间下的光的方向
data.lightDir = mul(rotation,data.lightDir);
//模型空间下的视角方向
data.viewDir = ObjSpaceViewDir(v.vertex);
//转换到切线空间下的视角方向
data.viewDir = mul(rotation,data.viewDir);
return data;
}

fixed4 frag (v2f i) : SV_Target
{


//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩运算 得到切线空间下的法线信息
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal *= _BumpMapScale;
//---处理带颜色纹理的 BlinnPhong光照模型---//

//取出纹理当中的RGBA
fixed4 texColor = tex2D(_MainTex,i.uv.xy);

//颜色纹理和漫反射颜色的叠加
fixed3 albedo = tex2D(_MainTex,i.uv.xy).rgb * _MainColor.rgb;
//计算兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0,dot(tangentNormal,normalize(i.lightDir)));
//计算半角向量
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir));
//计算高光反射
float3 specularColor = _LightColor0.rgb * _specularColor * pow(max(0,dot(tangentNormal,halfA)) , _specularNum);
//计算BlinnPhong
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

//修改我们的Alpha返回值 决定透明度
return fixed4(color.rgb , texColor.a *_AlphaScale );
}
ENDCG
}
}
}

3.实现效果

2024-09-13 161343.png

评论