Shader是什么
a:Shader是一种编程语言,如:GLSL、HLSL、CG等,Unity中主要使用的是CG;
b:Unity会将CG根据平台进行编译;
c:Shader是一种运行在显卡上的程序,影响的是显卡渲染的效果和流程;
d:显卡架构中存在多种的Shader对应渲染流水线中不同的流程;
e:Shader的种类:
(1) 3D Shader
a:Vertex Shader 顶点;
用来处理3D模型中的每一个顶点
b:Geometry shader 生成新的几何图形;
处理三角面片
c:Tesstllation shaders 细分模型
用来将模型根据不同原件进行细分
(2) 2D Shader:
a:Pixel shader 屏幕像素
当模型要渲染到屏幕上到时候决定每个像素怎么渲染
渲染管线
a:种类:
(1) 可编程渲染管线;
(2) 不可变成渲染管线;
b:渲染流程:
taps1:将渲染所需几何数据(顶点、顶点属性、引索……)传入显卡
taps2:将顶点从本地坐标转换到世界坐标 坐标转换
taps3:将顶点变换到摄像机空间 几何裁剪
taps4:光照计算、纹理、混合、深度、像素裁剪
taps5:fog 雾
taps6:投影到屏幕空间
GPU的架构
GPU(显卡)是什么
a:PC显卡:nvidia、AMD、Intel
种类:
a:独立显卡;
b:集成显卡;
将CPU和GPU做到一起
b:游戏机显卡:任天堂、PS、Xbox
类似于PC显卡
c:VR设备上的显卡;
高端的VR设备显卡与PC端类似
d:手机上的显卡;
CPU和GPU是做在一起的,两者共享内存,手机GPU会将屏幕切割成很多的小方块,再去绘制每个小方块中的内容而PC的显卡会直接绘制整块屏幕;
GPU渲染模式
a:immediate mode rendering 立即模式
CPU会将要渲染的三角面逐个传给GPU,CPU需要等待前一个三角面渲染结束才能传第二个三角面
b:Tilebased deferred mode 块基于的延迟渲染
CPU会将所有的三角面放到内存,GPU会自动读取
手机GPU架构
a:种类;
(1) Adreno(高通);
有比较好的性能分析工具(Adreno Profiler)
(2) Mali;
(3) PowerVR
(4) Tegra(英伟达)
b:GPU架构组成
a:Shader core 通用的用于执行shader的核心
b:Tilebased 将三角面分到不同的tile中
c:共享缓存 用于向shader提供数据
d:计算管线 存储访问管线 纹理计算管线
Unity Shader的书写方法
a:ShaderLab的结构;
Shader "fuyumi/MyShader01"//这里指定Shader的名字,不要求等于文件名字
{
//外界调试改变的属性
Properties
{
}
//子着色器,可以有多个;
//显卡运行效果的时候从第一个subShader开始;
//如果第一个里面的效果都可以实现那么就使用第一个SubShader;
//如果某些效果显卡实现不了就会自动运行下一个SubShader
SubShader
{
}
//如果所有的SubShader显卡都不支持则使用FallBack
FallBack "VertexLit"
}
b:ShaderLab的数据类型
Properties
{
//属性名("材质面板中显示的属性名", 类型) = 默认值
_Color("Color", Color) = (1, 1, 1, 1)//颜色
_Vector("Vector", vector) = (1, 2, 3, 4)//向量
_Int("Int", int) = 21//整数
_Float("Float", float) = 1.3//浮点数
_Range("Range", Range(0, 1)) = 0//范围内浮点数
_2D("Texture", 2D) = "white"{}//图片类型 "没有图片时使用的默认颜色"{}
_Cube("Cube", Cube) = "white"//立方体纹理(天空盒)
_3D("Textre", 3D) = "black"{}//3D纹理
}
c:使用Properties中的属性;
在pass代码块中,不能直接使用Properties中的属性,需要重新定义,数据类型可能会有不同;
//外界调试改变的属性
Properties
{
//属性名("材质面板中显示的属性名", 类型) = 默认值
_Color("Color", Color) = (1, 1, 1, 1)//颜色
_Vector("Vector", vector) = (1, 2, 3, 4)//向量
_Int("Int", int) = 21//整数
_Float("Float", float) = 1.3//浮点数
_Range("Range", Range(0, 1)) = 0//范围内浮点数
_2D("Texture", 2D) = "white"{}//图片类型 "没有图片时使用的默认颜色"{}
_Cube("Cube", Cube) = "white"//立方体纹理(天空盒)
_3D("Textre", 3D) = "black"{}//3D纹理
}
//子着色器,可以有多个;
//显卡运行效果的时候从第一个subShader开始;
//如果第一个里面的效果都可以实现那么就使用第一个SubShader;
//如果某些效果显卡实现不了就会自动运行下一个SubShader
SubShader
{
//至少有一个pass块 在这里编写Shader代码
pass
{
//使用CG语言编写Shader代码
CGPROGRAM
float4 _Color;//Color
float4 _Vector;//vector
float _Int;//int
float _Float;//float
float _Range;//Range
sampler2D _2D;//2D
samplerCube _Cube;//Cube
sampler3D _3D;//3D
//float可以使用half和fixed代替
//float 32位存储
//half 使用16位存储,范围在 -6w — 6w
//fixed 11位存储,范围在-2w—2w
ENDCG
}
}
d:subshader基本写法
subshader中至少包含一个pass代码块;
(1) 声明CG代码块:
CGPROGRAM ... ENDCG 这两个关键字之间的代码均为CG代码;
(2) 声明所需变量、数据结构、方法
声明变量:数据类型 变量名;
声明数据结构:如
struct v2c {
数据类型 变量名 : 语义;
....
}
struct中声明的变量必须要有语义;
语义也可以自己声明
(3) 声明方法
subshader中需要有一个顶点函数和片元函数
a:顶点函数
#pragma vertex 函数名;
顶点函数的目的是将顶点从模型空间转换到剪裁空间,也就是从游戏环境到视野相机;
如何改变坐标系:
a:让顶点坐标与变换矩阵相乘
float4 裁剪空间坐标 = mul(UNITY_MATRIX_MVP, 顶点坐标);
UNITY_MATRIX_MVP:四行四列的矩阵(模型空间转裁剪空间)
b:使用UnityCG_cginc中的函数
float4 裁剪空间坐标 = UnityObjectToClipPos(顶点坐标);
b:片元函数
#pragma fragment 函数名;
基本最作用是计算模型对应屏幕上的每一个元素的颜色值;
SubShader
{
pass
{
//表示下的代码到 ENDCG 为止都是CG代码块
CGPROGRAM
//定义函数:
//声明顶点函数的函数名
//顶点函数的目的是将顶点从模型空间转换到剪裁空间,也就是从游戏环境到视野相机
#pragma vertex vert;
//声明片元函数的函数名
//基本最作用是计算模型对应屏幕上的每一个元素的颜色值
#pragma fragment frag;
//变量语义:数据类型 变量名 : POSITION 意思是将顶点坐标传递给变量
//SV_POSITION语义用来解释说明返回值是剪裁空间下的顶点坐标
float4 vert(float4 v : POSITION) : SV_POSITION
{
/*
如何变换坐标系:
a:让顶点坐标与变换矩阵相乘
float4 裁剪空间坐标 = mul(UNITY_MATRIX_MVP, 顶点坐标);
UNITY_MATRIX_MVP:四行四列的矩阵(模型空间转裁剪空间)
b:使用UnityCG_cginc中的函数
float4 裁剪空间坐标 = UnityObjectToClipPos(顶点坐标);
常用函数:
顶点空间转换:
float4 UnityObjectToClipPos(float4 pos) //将顶点从模型空间转到裁剪空间
摄像机方向:
float3 WorldSpaceViewDir(float4 v) //根据模型空间中的顶点坐标,得到世界空间中这个点到相机的方向;
float3 UnityWorldSpaceViewDir(float4 v) //世界空间的顶点坐标到世界空间中这个点到相机的观察方向;
float3 ObjectSpaceViewDir(float4 v) //模型空间中的顶点坐标到模型空间中从这个点到相机的观察方向;
光源方向:
float3 WorldSpaceLightDir(float4 v) //根据模型空间中的顶点坐标,得到在世界坐标中这个点到光源的方向;
float3 UnityWorldSpaceLightDir(float4 v) //根据世界空间中的顶点坐标,得到世界空间中这个点到光源的方向;
float3 ObjectSpaceLightDir(float4 v) //根据模型空间中的顶点坐标,得到模型空间中从这个点到光源的方向;
方向转换:
float3 UnityObjectToWorldNormal(float3 norm) //把法线方向从模型空间转到世界空间
float3 UnityObjectToWorldDir(float3 dir) //把方向从模型空间转到世界空间
float3 UnityWorldToObjectDir(float3 dir) //把方向从世界空间转到坐标空间
*/
float4 pos = mul(UNITY_MATRIX_MVP, v);
return pos;
}
//SV_Target语义用来解释说明返回值是像素的颜色值
fixed4 frag() : SV_Target
{
return fixed4(0.5, 0.5, 1, 1);
}
ENDCG
}
}
UnityCG_cginc中常用函数:
a:顶点空间转换:
float4 UnityObjectToClipPos(float4 pos)
//将顶点从模型空间转到裁剪空间
b:摄像机方向:
float3 WorldSpaceViewDir(float4 v)
//根据模型空间中的顶点坐标,得到世界空间中这个点到相机的方向;
float3 UnityWorldSpaceViewDir(float4 v)
//世界空间的顶点坐标到世界空间中这个点到相机的观察方向;
float3 ObjectSpaceViewDir(float4 v)
//模型空间中的顶点坐标到模型空间中从这个点到相机的观察方向;
c:光源方向:
float3 WorldSpaceLightDir(float4 v)
//根据模型空间中的顶点坐标,得到在世界坐标中这个点到光源的方向;
float3 UnityWorldSpaceLightDir(float4 v)
//根据世界空间中的顶点坐标,得到世界空间中这个点到光源的方向;
float3 ObjectSpaceLightDir(float4 v)
//根据模型空间中的顶点坐标,得到模型空间中从这个点到光源的方向;
d:方向转换:
float3 UnityObjectToWorldNormal(float3 norm)
//把法线方向从模型空间转到世界空间
float3 UnityObjectToWorldDir(float3 dir)
//把方向从模型空间转到世界空间
float3 UnityWorldToObjectDir(float3 dir)
//把方向从世界空间转到坐标空间
常用的语义
a: 从应用程序传递给顶点函数的语义:
POSITION 顶点坐标(模型空间下);
NORMAL 法线(模型空间下);
TANGENT 切线(模型空间下);
TEXCOORD0~n 纹理坐标;
COLOR 顶点颜色;
b:从顶点函数传递给片元函数的时候可以使用的语义:
SV_POSITION 剪裁空间中的顶点坐标,一般是系统直接使用;
COLOR0 fixed4的值,可以传递一组值;
COLOR1 fixed4的值,可以传递一组值;
TEXCOORD0~7 传递纹理坐标;
c:片元函数传递给系统:
SV_Target 颜色值,显示到屏幕上的颜色;
常用工具
a:方法
normalize(float3 v) 向量归一化
max(float min, float max) 取最大值
dot(float3 v1, float3 v2) 向量点乘
pow(float x, float y) x的y次幂
tex2D(samlp2D texture, float4 uv) 获取贴图中对应uv的颜色值
UnpackNormal(fixed4 color) 将颜色值转成法线(切线空间)
clip(float x)、clip(float1 x)、clip(float2 x)、clip(float3 x)、clip(float4 x) 如果给定的参数任何一个值是复数则舍弃当前像素的输出颜色
b:工具集
UnityCG.cginc
Lighting.cginc:
_LightColor0:场景中环境光的颜色
常用宏
TANGENT_SPACE_ROTATION
得到一个矩阵,用来把切模型间下的方向转换成切线空间下,他会自动调用你a2v中的 normal 和 tangent 变量,所以这两个变量一定要有且结构体的变量名必须是 v;
UNITY_LIGHTMOODL_AMBIENT
环境光;
光照模型
a:什么是光照模型
光照模型就是一个公式,使用这个公式来计算某个点的光照效果
b:标准光照模型
在标准光照模型里面,我们把进入摄像机的光分成四个部分
a:自发光
b:高光反射 Specular
Blinn光照模型:
Specular = 直射光 * pow( max(0, cos夹角), 高光的参数 )
夹角:反射光反向和法线的夹角
///高光函数
fixed3 GetApecular(float3 position, float3 normal)
{
//光的方向(入射光反方向)
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz) * -1;
//世界坐标下的法线反方向
fixed3 normalDir = normalize(mul(normal, unity_ObjectToWorld));
//反射光方向和视角方向夹角的cos值
fixed cos = dot(reflect(lightDir, normalDir), normalize(_WorldSpaceCameraPos.xyz - position.xyz));
fixed3 specular = _LightColor0.rgb * pow(max(0, cos), _specularNum);
return specular;
}
题外话:这里效果上的高光会随着相机发移动而移动,所以我觉得夹角用反射方向和法线方向的夹角效果顺眼一些
Blinn-Phong 光照模型:
Specular = 直射光颜色 * pow( max(0, cos夹角), 高光的参数 )
夹角:法线方向与 光的方向和视野方向的平分线 的夹角
///高光函数——Blinn_Phong
fixed3 GetApecular(float3 position, float3 normal)
{
//获取光线方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//获取法线方向
fixed3 normalDir = normalize(mul(normal, unity_ObjectToWorld));
//获取视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - position.xyz);
//获取法线方向和 光线方向与视角方向平分线(计算方法为两向量相加) 之间夹角的余弦值
fixed cos = dot(normalDir, normalize(lightDir + viewDir));
fixed3 specular = _LightColor0.rgb * pow(max(0, cos), _specularNum) * _SpecularColor;
return specular;
}
c:漫反射 Diffuse
逐顶点光照
写在顶点着色器中
Diffuse = 直射光颜色 * max(0, cos夹角(光和法线的夹角))
Properties
{
_diffuse("Diffuse", Range(1, 2)) = 1
_diffuseMin("Diffuse Min", Range(0, 0.5)) = 0
}
SubShader
{
pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
float _diffuse;
float _diffuseMin;
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
fixed4 position:SV_POSITION;
float3 temp:COLOR;
};
///计算漫反射颜色
fixed3 GetDiffuse(float3 normal)
{
fixed3 diffuse;
//光照方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//法线方向(世界坐标)
fixed3 normalWorld = normalize(mul(normal, unity_WorldToObject));
//光纤方向和法线的夹角cos值
fixed3 cos = dot(lightDir, normalWorld);
diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
//计算漫反射
fixed3 diffuse = GetDiffuse(v.normal);
f.temp = diffuse;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
return fixed4(f.temp, 1);
}
ENDCG
}
}
逐像素光照:(效果更好但开销更大)
只需要把上述的算法在frag中调用
///计算漫反射颜色
fixed3 GetDiffuse(float3 normal)
{
fixed3 diffuse;
//光照方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//法线方向(世界坐标)
fixed3 normalWorld = normalize(mul(normal, unity_WorldToObject));
//光纤方向和法线的夹角cos值
fixed3 cos = dot(lightDir, normalWorld);
diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normal = v.normal;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 diffuse = GetDiffuse(f.normal);
return fixed4(diffuse, 1);
}
半兰伯特光照模型:
Diffuse = 直射光颜色 * ( cos夹角 * 0.5 + 0.5)
d:环境光
环境光不需要自己计算,Unity中提供了现成的宏:
UNITY_LIGHTMOODL_AMBIENT
颜色的运算
a:叠加( + )
颜色的叠加只需将两个颜色的值相加
b:融合( * )
融合颜色时只需要将两个颜色值相乘;
纹理映射
a:将纹理映射到模型
纹理映射的本质是通过uv坐标取得纹理中相应位置的颜色在片元着色器中与各种光线混合
获取颜色的方法:
tex2D("纹理贴图", uv);
使用贴图偏移和缩放的方法:
获取偏移和缩放数值:
float4 纹理贴图的属性名(必须一样)_ST;
使用数值:
f.uv = v.uv * _Texture_ST.zw + _Texture_ST.xy;
Properties
{
...
_Texture("Texture", 2D) = "bleak"{}
}
SubShader
{
pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
...
sampler2D _Texture;
float4 _Texture_ST;
struct a2v
{
...
float4 uv:TEXCOORD0;
};
struct v2f
{
...
float4 uv:TEXCOORD0;
};
//漫反射函数
fixed3 GetDiffuse(v2f f)
{
...
return diffuse;
}
///高光函数——Blinn_Phong
fixed3 GetApecular(v2f f)
{
...
return Specular;
}
v2f vert(a2v v)
{
...
f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 color_Specular = GetApecular(f);
fixed3 color_Diffuse = GetDiffuse(f);
fixed3 colorUV = tex2D(_Texture, f.uv.xy);
fixed3 color_Cont = (color_Specular + color_Diffuse + UNITY_LIGHTMODEL_AMBIENT) * colorUV * _Color;
return fixed4(color_Cont, 1);
}
ENDCG
}
}
FallBack "VertexLit"
b:纹理属性
(1) Texture Type :纹理类型
a:Texture :模型贴图
b:Normal map :法线贴图
c:Editor GUI Legacy GUI :编辑器图标、图片
d:Sprite(2D and UI):UI或者2D对象使用
e:Cursor :鼠标
f:Cubemap :立方体纹理(天空盒)
g:Cookie :影子类型
h:Lightmap :光照贴图
(2) Wrap Mode :当纹理坐标超过1时怎么处理
a:Reqeat :重复
b:alicp :剪切,坐标大于一时取等于1时的颜色
c:Mirror :镜像
(3) Fiter Mode :滤波的模式
a:Point
b:Bilinear
c :Trilinear :效果最好但最耗性能
c:凹凸映射
(1) 切线空间:
因为不同的模型模型空间不一样,所以法线贴图的值泵使用模型空间来表示,为了泛用性,法线贴图使用切线空间来表示;
(2) 法线映射:
法线贴图时使用颜色来表示法线坐标
由于法线贴图的每一个轴的值时-1——1,所以颜色值无法存储,所以需要进行一定的处理:
pixel = (normal + 1) / 2;
因此,我们要拿到法线的值需要进行反运算:
normal = pixel * 2 - 1;
但实际编写中这样计算会使运算结果不正确,要使用Unity给出的 UnpackNormal() 方法;
Properties
{
_Color("Color", color) = (1, 1, 1, 1)
_Texture("Texture", 2D) = "bleak"{}
_NormalMap("Normal Map", 2D) = "bleak"{}
}
SubShader
{
pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
sampler2D _Texture;
sampler2D _NormalMap;
float4 _Texture_ST;
float4 _NormalMap_ST;
struct a2v
{
float4 position:POSITION;
//切线空间的确定是通过(存储到模型中的)法线和切线确定的
float3 normal:NORMAL;
//tangent.w 是用来确定切线空间中坐标轴方向的
float4 tangent:TANGENT;
float4 uv:TEXCOORD0;
};
struct v2f
{
fixed4 position:SV_POSITION;
float3 lightDir:COLOR0;
float3 normal:COLOR1;
float4 uv:TEXCOORD0;
};
///获取法线贴图中对应uv的法线值
fixed3 GetNormalMapColor(v2f f)
{
//因为从法线贴图中得到的法线都是切线空间下的,所以法线相关的运算都使用切线空间中运算
return normalize(UnpackNormal(tex2D(_NormalMap, f.uv.wz)));
}
///获取漫反射光照
fixed3 GetDiffuse(v2f f)
{
fixed3 diffuse;
//光照方向
fixed3 lightDir = normalize(f.lightDir);
//法线方向(世界坐标)
fixed3 normal = GetNormalMapColor(f);
//光纤方向和法线的夹角cos值
fixed3 cos = dot(lightDir, normal);
diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normal = v.normal;
f.uv.xy = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
f.uv.zw = v.uv.xy * _NormalMap_ST.zw + _NormalMap_ST.xy;
TANGENT_SPACE_ROTATION;
f.lightDir = mul(rotation, ObjSpaceLightDir(v.position));
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 color_Diffuse = GetDiffuse(f);
fixed3 colorUV = tex2D(_Texture, f.uv.xy);
fixed3 color_Cont = (color_Diffuse + UNITY_LIGHTMODEL_AMBIENT) * colorUV * _Color;
return fixed4(color_Cont, 1);
}
ENDCG
}
控制映射程度
由于切线空间的z轴固定为原法线方向、所以我们只要控制x、y值的缩放就可以让切线方向趋近或者远离法线方向,因此,我们将上边的代码稍作修改:
Properties
{
...
//添加控制变量
_nomorlMapScale("NormalMapScale", Range(0, 2)) = 1
}
SubShader
{
pass
{
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
...
//接取控制变量
float _nomorlMapScale;
...
fixed3 GetNormalMapColor(v2f f)
{
//因为从法线贴图中得到的法线都是切线空间下的,所以法线相关的运算都使用切线空间中运算
float3 normal = normalize(UnpackNormal(tex2D(_NormalMap, f.uv.wz)));
//改变法线向量x、y的值
normal.xy = normal.xy * _nomorlMapScale;
return normal;
}
...
v2f vert(a2v v)
{
...
}
fixed4 frag(v2f f) : SV_Target
{
...
}
ENDCG
}
}
透明模型
a:深度缓冲
决定哪个物体的哪部分会被显示在前面以及哪部分会被遮挡,可以让我们不必考虑渲染顺序也能得到正确的效果;
基本思想:
step1:根据深度缓冲区的值判断该片元距离摄像机的距离;
step2:(如果开启了深度测试)当渲染一个片元时,把他的深度值与已经在缓冲区里的值进行比较;
step3:如果它的值距离相机更远,那么说明有物体挡住了他,他就不应该显示在屏幕上,否则,用这个片元覆盖掉此时颜色缓冲区中的值并把它更新到深度缓冲区去中(如果开启了深度写入);
注:当实现透明效果时要关闭深度写入;
b:实现透明效果的两种方法
(1) 透明度测试:
它产生的效果极为极端,要么完全不可见,要么完全不透明;
只要一个片元不满足条件就会完全的舍弃它,因此深度测试不需要关闭深度写入;
(2) 透明度混合:
它可以实现真正的半透明物体
基本思想:
使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲区中的颜色值进行混合,得到新的颜色;
因为要关闭深度缓冲区所以要十分注意渲染顺序;
由于深度测试的存在,因此他还是会比较深度值,如果片元距离摄像机更远那么便不会进行混合操作;
a:为什么要关闭深度写入
一个半透明表面背后不透明物体的表面应该是可以被看到的,如果开启了深度写入,由于深度测试的判断结果是透明表面距离摄像机更近,那么后面的表面就会被剔除掉,导致我们看不到后边的物体;
c:Unity Shader的渲染顺序
渲染队列:
使用Queue标签来决定我们的模型将归于那个渲染队列。
Unity内部使用了一系列的数值引索来表示每个队列,且引索好越小表示越早被渲染
名称 | 队列引索 | 描述 |
Background | 1000 | 会在任何其他队列之前被渲染,通常用来表示背景 |
Geometry | 2000 | 默认队列,大多数物体都是用这个队列,不同命的物体使用这个队列 |
Alpha Test | 2450 | 需要透明度测试的物体使用这个队列 |
Transparent | 3000 | 会在所有的Gemetry和Alphe Test物体之后从后往前渲染,所有使用透明度混合的物体都应该使用这个队列 |
Overlay | 4000 | 用于实现一些叠加效果任何需要在最后渲染的物体都应该使用这个队列 |
因此,使用透明度测试需要包含:
SubSgader
{
Tags{ "Queue" = "Alpha Test" }
pass
{
...
}
}
因此,使用透明度混合需要包含:
SubShader
{
tags{ "Queue" = "Transparent" }
pass
{
//关闭深度写入,也可以写在SubShader下,表示所有的pass都关闭深度写入
ZWrite Off
...
}
}
d:透明度测试
在片元着色器中使用clip函数来进行透明度测试
Shader "ShaderStudy/AlphaTest"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
_Texture("Texture", 2D) = "white"{}
_Alpha("Alpha", Range(0, 1)) = 1
}
SubShader
{
//Queue:修改渲染队列
//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
pass
{
Tags {"LightMode" = "ForwardBase"}
//关闭剔除,实现双面效果
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _Texture;
fixed4 _Texture_ST;
fixed _Alpha;
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normalWorld:TEXCOORD0;
float4 positionWorld:TEXCOORD1;
float2 uv:TEXCOORD2;
};
fixed3 Diffuse(v2f f)
{
fixed3 diffuse;
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float cos = dot(f.normalWorld, lightDir);
diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
f.positionWorld = mul(unity_ObjectToWorld, v.position);
f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 colorCount;
fixed4 uvColor = tex2D(_Texture, f.uv);
clip(uvColor.a - _Alpha);
uvColor = uvColor * _Color;
colorCount = Diffuse(f);
return fixed4(colorCount.rgb * uvColor.rgb, 1);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertwxLit"
}
e:透明度混合
Blend是Unity提供的设置混合模式的命令,用来决定混合时使用的函数,Blend命令的语义如下:
语义 | 描述 |
Blend Off | 关闭混合 |
Blend SrcFactor DstFactor | 开启混合,并设置混合因子。原颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓冲区中的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲区中 |
Blend SrcFactor DstFactor,SrcFactorA DstFactorA | 和上面几乎一样,只是使用不同的因子来混合透明通道 |
BlendOp BlendOperation | 并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOpration对他们进行其他操作。 |
我们把源颜色的混合因子SrcFactor设置为SrcAlpha,将目标颜色的混合因子设置为OneMinusSrcAlpha
混合公式:
NewSrcColor = SrcAlhpa * SrcColor + (1 - SrcAlpha) * OldDstColor
Shader "ShaderStudy/AlphaTest"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
_Texture("Texture", 2D) = "white"{}
_Alpha("Alpha", Range(0, 1)) = 1
}
SubShader
{
//Queue:修改渲染队列
//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
pass
{
Tags {"LightMode" = "ForwardBase"}
//关闭剔除,实现双面效果
//Cull Off
//关闭深度写入
ZWrite Off
//设置混合模式
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _Texture;
fixed4 _Texture_ST;
fixed _Alpha;
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normalWorld:TEXCOORD0;
float4 positionWorld:TEXCOORD1;
float2 uv:TEXCOORD2;
};
fixed3 Diffuse(v2f f)
{
fixed3 diffuse;
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float cos = dot(f.normalWorld, lightDir);
diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
fixed3 Ambient(v2f f)
{
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
f.positionWorld = mul(unity_ObjectToWorld, v.position);
f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 colorCount;
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
colorCount = Diffuse(f);
return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
}
ENDCG
}
}
//FallBack "Transparent/Cutout/VertwxLit"
}
使用上述代码如果模型网格之间有复杂的遮挡关系就会出现错误的效果,比如原本在后边的面会再前面被显示等等;这时我们可以重新利用深度写入解决:
我们使用两个pass块来渲染模型,第一个pass开启深度写入但不输出颜色值;
使用这种方法,模型仍然可以与他背后的物体混合出半透明效果,但是自身依旧存在遮挡关系;
这种方法的缺点是使用多个pass会对性能造成一些影响;
Shader "ShaderStudy/AlphaTest"
{
Properties
{
...
}
SubShader
{
...
pass
{
ZWrite On
//用于设置颜色通道的写掩码;
//语义: ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
//当为0时意味着该Pass不写入任何颜色通道
ColorMask 0
}
pass
{
...
}
}
//FallBack "Transparent/Cutout/VertwxLit"
}
f:ShaderLab的混合命令
(1)操作数:
源颜色:使用S表示,指代由片元着色器产生的值;
目标颜色:使用D表示,指代从颜色缓冲区取得的值;
当我们使用blend命令设置混合状态时自动开启;
(2)混合等式:
混合时需要有两个不等式,一个用来混合RGB通道,一个用来混合Alpha通道;
在混合过程中,我们实际上设置的是混合等式中的操作和因子;
默认使用加操作;
使用Blend SrcFactor DstFactor 命令时,RGB通道和A通道的混合因子相同,此时的混合等式为:
O_RGB = SrcFactor * S_RGB + DstFactor * D_RGB
O_A = SrcFactor * S_A + DstFactor * D_A
(3)ShaderLab中的混合因子
参数 | 描述 |
---|---|
One | 因子为1 |
Zero | 因子为0 |
SrcColor | 因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量部分作为混合因子;当用于A通道的混合等式时,使用A分量作为混合因子。 |
SrcAlpha | 因子为源颜色的透明通度值 |
DstColor | 因子为目标颜色的值。用于不同通道时同SrcColor |
DstAlpha | 因子为目标颜色的透明度值 |
OneMinusSrcColor | 因子为(1 - 源颜色值) |
OneMinusSrcAlpha | 因子为(1 - 源颜色的透明度值) |
OneMinusDstColor | 因子为(1 - 目标颜色) |
OneMinusDstAlpha | 因子为(1 - 目标颜色的透明度值) |
如果希望以不同的因子来用于不同通道的计算,可以使用Blend SrcFactor DstFactor SrcFactorA DstFactorA,因此比如说我们要在混合后获得的透明度就是源颜色的透明度,可以使用下面的命令:Blend SrcAlhpa OneMinusSrcAlpha One Zero
(4)混合操作
我们可以使用BlendOp BlendOperation
操作 | 描述 |
---|---|
Add | 将混合后的源颜色和混合后的目标颜色相加,默认的混合操作,使用的不等式为: O_RGB = SrcFactor * S_RGB + DstFactor * D_RGB O_A = SrcFactor * S_A + DstFactor * D_A |
Sub | 用混合后的源颜色减去混合后的目标颜色,使用的不等式是: O_RGB = SrcFactor * S_RGB - DstFactor * D_RGB O_A = SrcFactor * S_A - DstFactor * D_A |
RevSub | 用混合后的目标颜色减去混合后的源颜色,使用的不等式是: O_RGB = DstFactor * D_RGB - SrcFactor * S_RGB O_A = SrcFactor * D_A - SrcFactor * S_A |
Min | 使用源颜色和目标颜色的最小值,是逐分量比较的,使用的不等式是: O_RGBA = (min(S_R, D_R), min(S_G, D_G), min(S_B, D_B), min(S_A, D_A)) |
Max | 使用源颜色和目标颜色的最小值,逐分量比较,使用的不等式是: O_RGBA = (max(S_R, D_R), max(S_G, D_G), max(S_B, D_B), max(S_A, D_A)) |
其他逻辑操作 | 仅在DirectX 11.1中支持 |
(5)常见的混合类型
类型 | 代码 |
---|---|
正常(Normal),即透明度混合 | Blend SrcAlpha OneMinusSrcAlpha |
柔和相加 (Soft Additive) | Blend OneMinusSrcAlpha One |
正片叠底 (Multiply) | Blend DstColor Zreo |
两倍相乘 (2x Multiply) | Blend DstColor SrcColor |
变暗 (Darken) | BlendOp Min Blend One One |
变亮 (Lighten) | BlendOp Max Blend One One |
滤色 (Screen) | Blend OneMinusDstColor One 或者 Blend One OneMinusSrcColor |
线性减淡 (Linear Doge) | Blend One One |
双面渲染的透明效果
我们可以通过设置他的剔除面来进行双面效果的渲染
指令语法:
Cull Off | Front | Back
Off:关闭剔除功能
Front:剔除前面
Back:剔除背面
Shader "ShaderStudy/AlphaBlend"
{
Properties
{
_Color("Color", Color) = (1, 1, 1, 1)
_Texture("Texture", 2D) = "white"{}
_Alpha("Alpha", Range(0, 1)) = 1
}
SubShader
{
//Queue:修改渲染队列
//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
pass
{
Tags {"LightMode" = "ForwardBase"}
//关闭剔除,实现双面效果
Cull Front
//关闭深度写入
ZWrite Off
//设置混合模式
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _Texture;
fixed4 _Texture_ST;
fixed _Alpha;
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normalWorld:TEXCOORD0;
float4 positionWorld:TEXCOORD1;
float2 uv:TEXCOORD2;
};
fixed3 Diffuse(v2f f)
{
fixed3 diffuse;
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float cos = dot(f.normalWorld, lightDir);
diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
fixed3 Ambient(v2f f)
{
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
f.positionWorld = mul(unity_ObjectToWorld, v.position);
f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 colorCount;
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
colorCount = Diffuse(f);
return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
}
ENDCG
}
pass
{
Tags {"LightMode" = "ForwardBase"}
//关闭剔除,实现双面效果
Cull Back
//关闭深度写入
ZWrite Off
//设置混合模式
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _Texture;
fixed4 _Texture_ST;
fixed _Alpha;
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
float4 position:SV_POSITION;
fixed3 normalWorld:TEXCOORD0;
float4 positionWorld:TEXCOORD1;
float2 uv:TEXCOORD2;
};
fixed3 Diffuse(v2f f)
{
fixed3 diffuse;
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float cos = dot(f.normalWorld, lightDir);
diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
return diffuse;
}
fixed3 Ambient(v2f f)
{
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
}
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.position);
f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
f.positionWorld = mul(unity_ObjectToWorld, v.position);
f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
return f;
}
fixed4 frag(v2f f) : SV_Target
{
fixed3 colorCount;
fixed4 uvColor = tex2D(_Texture, f.uv);
uvColor = uvColor * _Color;
colorCount = Diffuse(f);
return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
}
ENDCG
}
}
//FallBack "Transparent/Cutout/VertwxLit"
}