一、shaderLab
1.pass渲染通道
具体实现着色器代码的地方,每个subshader至少有一个pass。
可以利用UsePass命令在其他Shader当中复用该Pass的代码,只需要在其他Shader当中使用
UsePass "Shader路径/Pass名",Unity内部会把Pass名称转换为大写字母,因此在使用UsePass命令时必须使用大写形式的名字。
cs
UsePass ".../MYPASS"
Pass{
Name "MyPass"
}
Pass中有自己专门的渲染标签,不能使用其他subshader的渲染标签
剔除方式决定了模型正面背面是否能够被渲染;深度缓冲和深度测试决定了景深关系的确定以及透明效果的正确表达;混合方式决定了透明半透明颜色的正确表现,以及一些特殊颜色效果的表现。这些染状态都可以在单个Pass中进行设置
如果在SubShader语句块中使用会影响之后的所有渲染通道Pass,如果在Pass语句块中使用只会影响当前Pass渲染通道,不会影响其他的Pass
Pass中还可以使用固定管线着色器的命令
2.shader备用着色器
Fallback "ShaderName"
Fallback off
3.shader编写形式
表面着色器:
表面着色器(Surface Shader) 是Unity自己创造的一 种着色器代码类型,本质是对顶点/片元着色器的一层封装,需要的代码量很少。但是缺点是渲染的消耗较大,可控性较低
特点:
1.直接在SubShader语句块中书写者色器逻辑
2.我们不需要关心也不需要使用多个Pass, 每个Pass如何染,Unity会在内部帮助我们去处理
3.可以使用CG或HL SL两种Shader语言去编写Shader逻辑
4.代码量较少,可控性较低,性能消耗较高
5.适用于处理需要和各种光源打交道的着色器(主机、PC平台时更适用,移动平台需要考虑性能消耗)
顶点片元着色器:
顶点/片元着色器的着色器代码是编写在Pass语句块中,我们需要自己定义每个Pass需要使用的Shader代码。虽然比起表面着色器来说我们需要编写的代码较多,但是灵活性更高,可控性更强,可以控制更多的渲染细节
特点:
1.需要在Pass江染通道中编写着色器逻辑
- 可以使用CG或HLSL两种Shader语言去编写Shader逻辑
3.代码量较多,灵活性较强,性能消耗更可控,可以实现更多渲染细节
4.适用于光照处理较少,自定义汇染效果较多时(移动平台首选)
二、CG
1.CG语言写在哪里?
对于顶点\片元着色器来说,CG语句需要写在Pass渲染通道语句块中。需要在Pass语句块中
加入指令CGPROGRAM和ENDCG
在这两个指令之间就是我们书写CG代码的地方
在真正书写CG代码之前需要先使用#pragma 声明编译指令
定义实现顶点/片元着色器代码的函数名称
#pragma vertex name(实现顶点着色器的函数名)
#pragma fragment name (实现片元着色器的函数名)
这两个编译指令的作用是将顶点\片元着色器实现定位到两个函数中,之后只需要在这两个函数中书写Shader逻辑即可
2.数据类型
基础数据类型:
uint
32为无符号整形
int
32位整形
float
32位浮点数符号:f
half
16位浮点数符号:h
fixed
12位浮点数
bool
布尔类型
string
字符串
sampler纹理对象句柄
数组(类似于c#)
结构体(类似于c#,没有访问修饰符,结构体生命结束加分号,一般在函数外声明)
特殊数据类型:
向量(CG语言的内置数据类型,基于基础数据类型声明,最大维度不超过四维,数据类型可以是任意数值类型)
cs
float2 res =float2(13.14,14.13);
//二维
float3 ress =float3(13.14,14.13,11.11);
//三维
矩阵(最大行列不大于四,不小于一,数据类型可以是任意数值类型)
cs
int2x3 res2x3 ={1,2,3,
4,5,6};
int3x3 res3x3 ={1,2,3,
4,5,6,
7,8,9};
bool类型的特殊使用(bool类型同样可以用于如同向量一样声明,可以用于存储一些逻辑判断结果)
3.Swizzle操作符
用于获取向量中的元素,通常以(.)的形式进行使用,后面跟着所需的分量顺序,对于四维向量,我们可以通过向量.xyzw或.rgba的写法来表示四维向量的四个元素
使用细节可参考文章:【Unity3d Shader】Swizzle 操作符_swizzle shader-CSDN博客
注意:我们可以利用其来提取分量,重新排列分量,并且创建新的向量,但是swizzle操作符只能对结构体和向量使用,不能对数组使用
4.运算符
比较运算符
大于>
小于<
大于等于>=
小于等于<=
等于==
不等于!=
条件运算符
condition? value1:value2
condition是个条件表达式,如果为真将返回value1,否则返回value2
数学运算符
加法+
减法-
乘法*
除法/
取余%
自增减++ --
注意: CG中取余符号只能向整数取余
逻辑运算符
逻辑或运算符 ||
逻辑与运算符 &&
逻辑非运算符 !
CG中不存在C#中的"短路"操作(短路:如果前面的表达式已经能得出结果,那么后面的表达式不会再计算)
5.流程控制语句
条件分支语句(和C#中相同)
if
switch
循环语句(和C#中相同)
for
while
do while
6.函数(使用和声明和C#中相似)
例如:无返回值类型
cs
void name(in 参数类型 参数名,out参数类型 参数名){
函数体
}
7.语义
CG语言中提供了语义这种特殊关键字用于修饰函数中的传入参数和返回值,主要作用是让Shader知道从哪里读取数据,并把数据输出到哪里,在Shader开发当中可以获取到想要的数据,并且可以把数据传递出去。(Unity中只支持CG当中部分的语义)
应用阶段-》顶点着色器:
应用阶段传递模型数据给顶点着色器时Unity支持的语义
一般在顶点着色器回调函数的传入参数中应用
POSITION:模型空间中的顶点位置,通常是float4类型
NORMAL:顶点法线,通常是float3类型
TANGENT :顶点切线,通常是float4类型
TEXCOORDn:比如TEXCOORDO , TEXCOORD1....该顶点的纹理坐标,通常是float2或者float4类型
TEXCOORD0表示第一 组纹理坐标,依次类推
纹理坐标:也称UV坐标,表示该顶点对应纹理图像上的位置
COLOR:顶点颜色,通常是fixed4或float4类型
顶点着色器->片元着色器:
一般在顶点色器回调函数的返回值中应用
SV_ POSITION:裁剪空间中的顶点坐标(必备)
COLOR0:通常用于输出第一组顶点颜色(不是必须的)
COLOR1:通常用于输出第二组顶点颜色(不是必须的)
TEXCOORDO~TEXCOORD7 :通常用于输出纹理坐标(不是必须的)
片元着色器输出:
一般在片元着色器回调函数的返回值中应用
SV_ Target:|输出值会存储到渲染目标中
语义的主要作用是让Shader知道从哪里读取数据,并把数据输出到哪里。在编写顶点片元着色器回调函数的相关逻辑时,需要使用语义去修饰函数参数和返回值。这样才能获取到我们想要的数据,并且把结果数据明确的返回出去用于下一 流程处理
8.顶点/片元着色器
基本结构
片元着色器的基本结构
cs
#pragma fragment myFrag
fixed4 myFrag():SV_Target{
return fixed4(1,0,0,1);
}
顶点着色器的基本结构
cs
#pragma vertex myVert
float4 myVert(float4 v:POSITION):SV_POSITION
{
return mul(UNITY_MATRIX_MVP,v);
//内置函数乘法运算
}
如何传递多个参数
片元着色器
可以使用结构体对数据进行封装,通过对结构体中成员变量加语义的方式来定义想要获取的信息。片元着色器中获取的数据基本上都是由顶点着色器传递过来的,封装的结构体需要作为顶点着色器的返回值类型
顶点着色器
采用的方式还是封装结构体的方式,通过对结构体中成员变量加语义的方式来定义想要获取的信息。
9.ShaderLab和CG的类型匹配关系
ShaderLab属性类型 CG变量类型
Color,Vector float3,half4, fixed4
Range, Float, Int float ,half,fixed
2D sampler2D
Cube samplerCube
3D Sampler3D
2DArray Sampler2DArray
10.CG内置文件
在Unity的安装目录中找到CG内置文件,在Editor->Data->CGIncludes中
后缀为cginc的文件为CG语言内置文件;后缀为glslinc的文件为GLSL语言内置文件
这些是预定义的Shader文件,里面包含了些已经写好的Shader相关逻辑,可以提升我们的Shader开发效率,可以直接使用其中的方法等内容来进行逻辑开发
Unity中常用的内置文件有
1.UnityCG. cginc:包含最常用的帮助函数、宏和结构体等
2.Lighting . cginc :包含各种内置光照模型。如果编写的是Surface Shader (标准表面着色器) ,会自动包含进来
-
UnityShaderVariables. cginc:编译UnityShader时,会自动包含进来。包含许多内置的全局变量
-
HLSLSupport. cginc :编译UnityShader时,会自动包含进来。声明了很多用于跨平台编译的宏和定义
11.如何使用CG内置文件
在CG语句块中进行引用,通过编译指令#include "内置文件名. cginc"的形式进行引用,我们便可以在CG语言中使用其中的内容。
( " 宏"通常指的是种预处理指令 或代码片段,用于在代码中进行文本替换。宏允许程序员定义一个标识符(通常以大写字母表示)来代表一个代码片段,之后在编译时将这个标识符替换为相应的代码,这种替换过程称为宏展开。)
三、Shader
1.光照模型
光照模型指的是用于模拟光照效果的一组数学公式和算法,用于确定在3D场景中的模型表面应该如何对光进行反射和散射,从而实现视觉上逼真的照明效果(物体表面的光照颜色是由入射光、物体材质、以及材质和光的交互规律共同决定的)
漫反射光照模型
漫反射:
指光线被粗糙表面无规则地向各个方向反射的现象,是一种在各个方向上平均反射光线的模型,比如:墙壁、纸张、布料等等物体的表现效果
高光反射光照模型
高光反射:
考虑了视角和光源的方向,使物体表面出现高光点,比如:金属、陶瓷、塑料等等物体的表现效果
逐顶点光照
顶点着色器回调函数中计算,逐顶点光照会在每个物体的顶点上进行光照计算,这意味着光照计算只在物体的顶点位置上执行,而在顶点之间的内部区域使用插值来获得颜色信息
优点:逐顶点光照的计算量较小,通常在移动设备上性能较好,适用于移动游戏等要求性能的场景
缺点:照明效果可能不够精细,特别是在物体表面上的细节区域,因为颜色插值可能不足以捕捉到细微的照明变化
适用场景:逐顶点光照适用于需要在有限资源下获得较好性能的场景,例如移动游戏
逐片元光照
片元着色器回调函数中计算,逐片元光照会在每个像素(片元) 上进行光照计算,这意味着每个像素都会根据其位置、法线、材质等信息独立地进行光照计算
优点:逐片元光照提供了更高的精细度,可以捕捉到物体表面上的细微照明变化,提供更逼真的效果
缺点:计算量较大,对于像素密集的场景需要更多的计算资源
适用场景:逐片元光照通常用于需要高质量照明效果的PC和主机游戏,以及要求视觉逼真度较的场景
兰伯特光照模型
两个颜色相乘
计算结果可以理解为两种颜色叠加在一 起的效果,在颜色相乘中,通常是使用颜色通道的值来执行逐通道的乘法。举例:颜色A、B都是fixed4类型的变量
颜色A *颜色B=(A.r*B.r,A.g*B.g,A.b*B.b,A.a*B.a);
得到的这个结果就是A、B颜色叠加后的结果
兰伯特光照模型的公式
认为漫反射光的强度仅与入射光的方向和反射点处表面法线的夹角的余弦成正比
公式:漫反射光照颜色=光源的颜色*材质的漫反射颜色*max (0,标准化后物体表面法线向量.标准化后光源方向向量)
其中:1.标准化后物体表面法线向量,标准化后光源方向向量得到的结果就是cos
2.max(0, cos)的目的是避免负数,对于模型背面的部分,认为找不到光,直接乘以0,变为黑色
如何在Shader中获取公式的关键信息
1.光源的颜色:Lighting. cginc内置文件中的LightColore
2.光源的方向:_WorldSpaceLightPos0 表示光源0在世界坐标系下的位置
3.向量归一化方法:normalize
4.取最大值方法:max
5.点乘方法:dot
6.兰伯特光照模型环境光变量(用于模拟环境光对物体的影响,避免物体阴影部分完全黑暗):UNITY_ L IGHTMODEL AMBIENT . rgb
7.将法线从模型空间转换到世界空间:UnityobjectToWorldNormal
利用兰伯特模型实现光照效果
cs
Shader "Unlit/Lambert"
{
Properties
{
_MainColor("MainColor",Color)=(1,1,1,1)
//白色
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
//光照模式------》向前渲染
//一般都要设置
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//材质的漫反射颜色
fixed4 _MainColor;
//顶点着色器传递给片元着色器的内容
struct v2f
{
//裁剪空间下的顶点坐标信息
float4 pos:SV_POSITION;
//对应顶点的漫反射光照颜色
fixed3 color:COLOR;
};
v2f vert(appdata_base v){
v2f v2fData;
//把模型空间下的顶点转换成裁剪空间
v2fData.pos =UnityObjectToClipPos(v.vertex);
//逐顶点关照,计算需要写顶点着色器的回调函数中
//光照颜色
//_LightColer0
//模型空间下的法线
//v.normal
//获取到相对于世界坐标系下的法线信息
float3 normal= UnityObjectToWorldNormal(v.normal);
//
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 color =_LightColor0.rgb *_MainColor.rgb*max(0,dot(normal,lightDir));
v2fData.color=color;
return v2fData;
}
//记录颜色传递给片元着色器
fixed4 frag (v2f i):SV_Target{
//把计算好的光照颜色传递出去
return fixed4(i.color.rgb,1);
}
//没有考虑透明度
ENDCG
}
}
}
半兰伯特光照模型
只是一种视觉加强技术,让背光面也有明暗变化
兰伯特光照公式:漫反射光照颜色=光源的颜色*材质的漫反射颜色*max (0,标准化后物体表面法线向量.标准化后光源方向向量)
半兰伯特光照公式:漫反射光照颜色=光源的颜色*材质的漫反射颜色* ( (标准化后物体表面法线向量.标准化后光源方向向量) * 0.5 + 0.5)
Phong式高光反射模型
原理:
Phong式高光反射光照模型的理论是基于光的反射行为和观察者的位置决定高光反射的表现效果。认为高光反射的颜色和光源的反射光线以及观察者位置方向向量夹角的余弦成正比,并且通过对余弦值取n次幂来表示光泽度(或反光度)
公式:高光反射光照颜色=光源的颜色*材质高光反射颜色* max (0, 标准化后观察方向向量,标准化后的反射方向) 幂
1.标准化后观察方向向量,标准化后的反射方向得到的结果就是cos
2.幂代表的是光泽度 余弦值取n次幂
如何获取关键信息?
1.观察者的位置(摄像机的位置) _Wor ldSpaceC ameraPos
2.相树对于法向量的反射向量方法 reflect(入射向量,顶点法向量)返回反射向量
3.指数幂方法 pow(底数,指数)返回计算结果
如何实现?
1.属性声明(材质高光反射颜色、光泽度 )
2.渲染标签Tags设置将L ightMode光照模式设置为F orwardBase向前渲染(通常用于不透 明物体的基本渲染)
3.引用内置文件UnityCG. cginc和Lighting .cginc
4.结构体声明
5.基本公式实现逻辑
cs
Shader "Unlit/Phong"
{
Properties
{
//高光反射颜色
_SpecularColor("SpecularColor",Color)=(1,1,1,1)
//光泽度
_SpecularNum("SpecularNum",Range(0,20))=0.5
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
//裁剪空间下的 顶点坐标
float4 pos:SV_POSITION;
//颜色信息
float3 color : COLOR;
};
fixed4 _SpecularColor;
float _SpecularNum;
v2f vert (appdata_base v)
{
v2f data;
//1.将顶点坐标转换到裁剪空间中
data.pos= UnityObjectToClipPos(v.vertex);
//2.计算颜色色相关
//标准化后观察向量变量
float3 worldPos =mul(UNITY_MATRIX_M,v.vertex);
float3 viewDir=_WorldSpaceCameraPos.xyz-worldPos;
viewDir = normalize(viewDir);
//标准化后的反射方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 normal= UnityObjectToWorldNormal(v.normal);
float3 reflectDir = reflect(-lightDir,normal);
fixed3 color =_LightColor0.rgb * _SpecularColor.rgb *pow(max(0,dot(viewDir,reflectDir)),_SpecularNum);
data.color =color;
return data;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color.rgb,1);
}
ENDCG
}
}
}
Phong光照模型
该模型物体表面反射光线是由三部分组成的:环境光+漫反射光+镜面反射光(高光反射光)
Phong光照模型公式:
物体表面光照颜色=环境光颜色+漫反射光颜色+高光反射光颜色
其中:
环境光颜色= UNITY_LIGHTMODEL AMBIENT (unity_ AmbientSky、unity_AmbientEqulator、 unity_ AmbientGround )
漫反射光颜色=兰伯特光照模型计算得到的颜色
高光反射光颜色= Phong式高光反射光照模型计算得到的颜色
Blinn Phong式高光反射模型
Blinn - Phong式高光反射光照模型的理论是:是对Phong式高光反射光照模型的改进,不再使用反射向量计算镜面反射,而是使用半角向量来进行计算。
半角向量为视角方向和灯光方向的角平分线方向
公式:
高光反射光照颜色=光源的颜色*材质高光反射颜色* max (0, 标准化后顶点法线方向向量.标准化后半角向量方向向量)幂
1.标准化后顶点法线方向向量,标准化后半角向量方向向删得到的结果就是cosθ
2.半角向量方向向量=视角单位向量+入射光单位向量
3.幂代表的是光泽度 余弦值取n次幂
Phong和Blinn-Phong的区别
表现上的不同
1.高光散射
Blinn- Phong模型的高光通常会产生相对均匀的高光散射,这会使物体起来光滑而均匀。
Phong模型的高光可能会呈现更为锐利的高光散射,因为它基于观察者和光源之间的夹角。
这可能导致一些区域看起来特别亮,而另一些区域则非常暗。
2.高光锐度
Blinn- Phong模型的高光通常具有较广的散射角,因此看起来不那么锐利。
Phong模型的高光可能会更加锐利,特别是在观察者和光源夹角较小时,可能表现为小而的点。
3.光滑度和表面纹理
Blinn- Phong模型通常更适合表现光滑的表面,因为它考虑了表面微观凹凸之间的相互作用,使得光照在表面.上更加均匀分布。
Phong模型有时更适合表现具有粗糙表面纹理的物体,因为它的高光散射可能会使纹理和细节更加突出。
4.镜面高光大小
Blinn- Phong模型通常产生的镜面高光相对较大,但均匀分布。
Phong模型可能会产生较小且锐利的镜面高光。
性能上的不同
Blinn- Phong模型通常比Phong模型计算更快
Blinn Phong光照模型
和Phong一样是一 个经验模型,并不符合真实世界中的光照现象,只是看起来正确
原理:Blinn Phong和Phong光照模型一样,认为物体表面反射光线是由三部分组成的:环境光+漫反射光+镜面反射光(高光反射光)
具体数学原理可参考:Blinn-Phong光照模型详解-CSDN博客