9. Transparent shaders
48. Alpha transparency in a surface shader
贯穿全章节的是不要忘记首先改变渲染类型和队列
48.1 Using the alpha channel for transparency
Alpha Chanel 的 含义:
- Black is totally transparent 黑的透明,也就是alpha = 0
- White is opaque白的不透明,也就是alpha = 1
- distance ordering is used for transparent surfaces,transparent queue can not write z buffer
这里彩色玻璃窗的alpha通道是根据图像的灰度生成的。因此我们想要不透明的地方反而是黑色的,这就会导致生成的alpha通道是翻转的,所以我们需要再翻转。
我们把这个思想带入到下面的shader中。
Shader "NiksShaders/Shader65Lit"
{
Properties {
_MainTex ("Texture", 2D) = "white" {}
_AlphaTest ("Alpha Test", Float) = 0.7
}
SubShader {
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }//改变标签
ZWrite Off//关闭深度写入
CGPROGRAM
#pragma surface surf Lambert alpha:fade//透明,表面着色器必须要有这一句才起作用。
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 col = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = col.rgb;
o.Alpha = 1 - col.a;//这里就是上面的,因为灰度图生成的,所以需要翻转
}
ENDCG
//这个用来生成阴影
ColorMask 0
CGPROGRAM
#pragma surface surf Lambert alpha:fade alphatest:_AlphaTest addshadow
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 col = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = col.rgb;
o.Alpha = 1 - col.a;
}
ENDCG
}
Fallback "Diffuse"
}
49. Space Pirate turns to glass
把海盗变成透明材质。
这里我们要思考的问题在于,我们说海盗是透明材质了,那我们为什么还能看见透明的海盗呢?
所以这里,我称之为,伪透明。
什么叫伪透明呢?就是我们可以通过其他的方式看见这个物体,比如我们看见玻璃的方式。
我们怎么看见玻璃?
玻璃反射光线(环境照到玻璃上发生反射)+玻璃折射背景和自身光线(透过物体看到的背景的颜色在物体内部发生折射)。
我们来想象一下,这两个哪个可以缺少?
如果不反射环境光,那么也就是不反射环境光,可能就是不那么华丽了。
如果不会折射背景和自身光线,意味着,自身颜色会直接输出,不会经过折射,那就不会有透明效果。
这里有个tips:加法是没有关系的颜色之间的叠加,而乘法是模拟光的照射过程,比如自发光(emissive),环境光(ambient),漫反射(diffuse),高光反射(specular) 这四个之间没有关系,就是加法,而这里自发光的颜色除了纹理,还有背景的折射,所以就是乘以。
We will grab the screen behind the space pirate character
Add GrabPass{"_GrabTexture"}
before the pass line
49.1 ComputeGrabScreenPos(float4)
Input is clip space position. They return float4
where the final coordinate to sample texture with can be computed via perspective division (for example xy/w
).它们返回 float4
,其中用于纹理采样的最终坐标可以通过透视除法(例如 xy/w
)计算得出。
之前,ComputeScreenPos返回的值是齐次坐标系下的屏幕坐标值,其范围为[0, w]。那么为什么Unity要这么做呢?Unity的本意是希望你把该坐标值用作tex2Dproj指令的参数值,tex2Dproj会在对纹理采样前除以w分量。
49.2 reflect 函数
o.reflect = reflect(-viewDir,worldNormal);
reflect函数,这里的第一个参数是入射光线,是从某个地方入射到球上,第二个是从球上出发的法线,返回一个反射光线。
49.3 tex2Dproj
纹理进行采样之前,tex2Dproj
将输入的UV xy
坐标除以其w
坐标。
- 透视除法:这对透视相机有用,而正交相机w本来就是1,除了没除都一样,作用是将坐标从剪裁空间(相机的)转换到屏幕空间。
- 为什么只有ComputeScreenPos和ComputeGrabScreenPos才使用透视除法呢?因为Unity在顶点着色器中的顶点只要转换到剪裁空间,之后就会在片元着色器中自动转换到屏幕空间。
- unity之所以不在顶点着色器中除以w分量,是因为要留到光栅化阶段进行线性插值后再除以w,这样得到的结果是正确的。如果提前除以w再经过光栅化线性插值后,得到的结果就不准确,因为投影空间不是线性空间
tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.screenPosition)).r;
//等同于
tex2D(_GrabTexture, UNITY_PROJ_COORD(i.screenPosition.xy / i.screenPosition.w)).r;
49.4 宏UNITY_PROJ_COORD(a)
UNITY_PROJ_COORD(a) 给定一个 4 分量矢量(比如抓取的屏幕纹理),此宏返回一个适合投影纹理读取的纹理坐标。
Shader "NiksShaders/Shader66Glass"
{
Properties
{
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
_Colour ("Colour", Color) = (1,1,1,1)
_BumpMap("Bump Map",2D) = "bump"{}
_Magnitude("Magnitude",Range(0,1)) = 0.05
_TintStrength("Tint Strength", Range(0,1)) = 0.3
_EnvironmentMap ( "Environment Map", CUBE) ="cube" {}
_ReflectionStrength ("Reflection",Range(0,1)) = 0.3
}
SubShader
{
Tags{"Queue" = "Transparent"}
Tags {"RenderType"="Transparent" }
GrabPass{"_GrabTexture"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _Colour;
sampler2D _BumpMap;//法线
float _Magnitude;//大小
float _TintStrength;//颜色强度
samplerCUBE _EnvironmentMap;//环境贴图
float _ReflectionStrength;//反射光强
sampler2D _GrabTexture;//抓取的材质
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD0;
float4 uvgrab : TEXCOORD1;//uvgrab算出来为什么是float4
float3 reflect : TEXCOORD2;//反射光线是float3
};
// Vertex function
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color;
o.uv = v.texcoord;
o.uvgrab = ComputeGrabScreenPos(o.vertex);//计算抓取的帧的屏幕uv
float3 viewDir = WorldSpaceViewDir(v.vertex);//视线,这里计算出来的是从表面到摄像机
float3 worldNormal = UnityObjectToWorldNormal(v.normal);//法线
o.reflect = reflect(-viewDir,worldNormal);//reflect函数,这里的第一个参数是入射光线,是从某个地方入射到球上
return o;
}
// Fragment function
fixed4 frag (v2f i) : COLOR
{
fixed4 maincol = lerp(tex2D(_MainTex,i.uv),_Colour,_TintStrength);//为什么是越靠近白色越透明呢?
half3 bump = UnpackNormal(tex2D(_BumpMap,i.uv));//这里使用i.uv,实际上法线和maintex常常用一样的uv
half2 distortion = bump.rg;//扭曲,法线然后取出xy值,也就是切线和副切线
i.uvgrab.xy += distortion * _Magnitude;//将截取的纹理进行扭曲放缩,也就是法线折射
//和tex2D功能基本一致,就是增加了一个变换到透视投影的功能。
//裁剪空间的坐标经过缩放和偏移后就变成了(0,w),而当分量除以分量W以后,就变成了(0,1),这样在计算需要返回(0,1)值的时候,就可以直接使用tex2Dproj了
//这里要返回屏幕上的颜色
fixed4 grabcol = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD (i.uvgrab));//透视除法
fixed4 reflcol = texCUBE(_EnvironmentMap,i.reflect);//环境光反射贴图
reflcol *= maincol.a;
reflcol *= _ReflectionStrength;
return grabcol * maincol * _Colour + reflcol;
}
ENDCG
}
}
}