一、計算機圖形學
1. 概述
- Unity的代碼在CPU中運行,圖形學的代碼在GPU中運行
- 圖形學使用CG(C Graph)語言,是英偉達和微軟聯合開發的跨平臺語言
- 操作系統中的圖形處理軟件DX12,使用GLSL(OpenGL Shading Language)、DX語言
- shader 1.0實現的功能很少,更像設置參數;shader 2.0,為開發者留下了開發接口,可以進行可編程式的開發;Unity的shader,表面著色器,介于1.0和2.0之間,翻譯成2.0然后發給GPU
- shader 1.0 :固定管線著色器、shader 2.0:頂點片段著色器、Unity shader:表面著色器
- shader:著色器,使用紋理、數據、顏色等原材料,通過編程的方式進行一些邏輯運算,設置到材質球上
- 寫shader,要使用的原材料盡量可以變化,適應多種平臺多種需求
2. 渲染繪圖管線
- 圖像從GPU里的體現過程:一個個頂點連線,組成三角形作為面,由面拼成一個圖形。
- 繪圖流程:頂點處理、面處理、光柵化、像素處理
2.1 頂點處理
頂點處理就是坐標轉換,將物體的本地坐標最終轉換成屏幕坐標
- 坐標按這四種坐標系的順序進行轉換:本地坐標,世界坐標,觀察坐標系,投影坐標系
- unity中拼合的頂點分開算頂點,例如一個Cube有4 * 6 = 24個頂點
2.2 面的處理
- 面組裝:將頂點處理得到的點集合進行處理,把點連成面
- 面截取:連成面之后,判斷哪些在屏幕范圍,將這些面截取
- 面剔除:面截取后,判斷哪些面會被別的面擋住,顯示不出來,將這些面剔除
2.3 光柵化
- 將面處理得到的面轉換成一個個像素,稱為光柵化
- 邊緣鋸齒等問題出在這步
2.4 像素處理
輸入像素的位置、深度、貼圖坐標、法線、切線、顏色等,給像素著色,使用四通道RGBA
2.5 shader各版本能做的事情
- shader 1.0:像素處理
- shader 2.0:像素處理、頂點處理
3. 固定管線著色器(Fixed Shader、Shader 1.0)
每有一個像素,就執行一次
3.1 結構
// Shader關鍵字 路徑
Shader "AShader/FixedShader/Base001"{
// 屬性模塊
Properties
{
}
// 子著色器,可以有多個,作用是面對不同的設備,從上到下做嘗試
// 質量高,渲染效果好的,性能消耗大的
SubShader
{
// 通道1
Pass
{
}
// 通道2
Pass
{
}
}
// 中等質量
SubShader
{
Pass
{
}
}
// 質量差
SubShader
{
Pass
{
}
}
// 最基礎的,Unity 4.x 預設的漫反射
Fallback "Diffuse"
}
3.2 屬性
Properties
{
// 屬性定義語法:屬性名("面板顯示名稱",屬性類型) = 初值
// 浮點型(使用較少)
_FloatValue("浮點數", float) = 0.5
// 范圍浮點型(常用)
_RangeValue("范圍浮點數", Range(0, 100)) = 30
// 四維數 (x, y, z, w) (使用較少)
_VectorValue("四維數", Vector) = (1, 1, 1, 1)
// 顏色,范圍[0 - 1](常用)
_ColorValue("顏色值", Color) = (1, 0, 0, 1)
// 2階貼圖,材質的長寬是2的多少次冪 2x2 4x4 8x8 16x16(常用)
// Tiling x, y :平鋪
// Offset x, y :起始位置
_MainTexture("主紋理", 2D) = ""{}
// 非2階貼圖(使用較少)
_RectTexture("非2階紋理", Rect) = ""{}
// 立方體貼圖 6個紋理,每個紋理表示一個面(使用很少)
_CubeTexture("3D紋理", Cube) = ""{}
}
3.3 命令
- Tag命令
寫在SubShader中
// 標簽
Tags
{
// 渲染隊列,數字越小越先渲染,數字大的會擋住數字小的
// 隊列在賦值的時候可以使用運算,但是是字符串賦值,所以+號左右不能有空格
"Queue" = "Transparent+1"
}
預設的四種渲染隊列級別
級別 | 翻譯 | 代表數字 |
---|---|---|
Background | 后臺 | 1000 |
Geometry(默認) | 幾何體 | 2000 |
Transparent | 透明 | 3000 |
Overlay | 覆蓋 | 4000 |
- 渲染命令
寫在Pass(通道)中
命令 | 作用 |
---|---|
Color(r, g, b, a) | 用指定顏色渲染 |
Color[Color] | 用屬性顏色渲染 |
Lighting On/Off | 開啟/關閉頂點光照 |
Cull Front/Back/Off | 剔除正面/剔除背面/關閉剔除 |
ZWrite On/Off(默認為On) | 是否要將像素的深度寫入深度緩存中 |
ZTest Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off(默認為LEqual) | 通過比較深度來更改深度緩存的值,Always指的是直接將當前像素顏色(不是深度)寫進顏色緩沖區中,Never指的是永遠不會將當前像素顏色寫進顏色緩沖區中,相當于消失,Off指的是關閉深度測試 |
SeparateSpecular On/Off | 鏡面反射開啟/關閉 |
利用渲染隊列修改顯示順序
Shader "AShader/FixedShader/Fixed003"
{
Properties
{
_MainColor("主顏色", Color) = (1, 1, 1, 1)
}
SubShader
{
// 標簽
Tags
{
// 渲染隊列
"Queue" = "Transparent+1"
}
Pass
{
// 關閉深度測試
ZTest Off
Lighting On
Material
{
Diffuse[_MainColor]
}
}
}
}
- 透明混合命令
命令 | 作用 |
---|---|
Blend SrcAlpha OneMinusSrcAlpha | 透明混合,有時候后面的東西會消失,不穩定,基本不在shader 1.0中使用 |
Blend One One | 相加,主顏色更亮 |
Blend One OneMinusDstColor | 更柔和的相加 |
Blend DstColor Zero | 相乘 |
Blend DstColor SrcColor | 2倍乘法 |
Shader "AShader/FixedShader/Fixed009"{
Properties
{
_MainTexture("主紋理", 2D) = ""{}
}
SubShader
{
// 透明混合
//Blend SrcAlpha OneMinusSrcAlpha
//Blend One One
//Blend One OneMinusDstColor
//Blend DstColor Zero
Blend DstColor SrcColor
Pass
{
SetTexture[_MainTexture]
{
combine texture
}
}
}
}
- Alpha測試命令
命令 | 作用 |
---|---|
AlphaTest Off | 關閉 |
AlphaTest Greater | 大于 |
AlphaTest GEqual | 大于等于 |
AlphaTest Less | 小于 |
AlphaTest LEqual | 小于等于 |
AlphaTest Equal | 等于 |
AlphaTest NotEqual | 不等于 |
AlphaTest Always | 渲染所有像素 |
AlphaTest Never | 不渲染任何像素 |
Shader "AShader/FixedShader/Fixed008"{
Properties
{
_AlphaTest("透明測試", Range(0, 1)) = 1
_MainTexture("主紋理", 2D) = ""{}
}
SubShader
{
Pass
{
// 溶解效果
AlphaTest Greater [_AlphaTest]
SetTexture[_MainTexture]
{
combine texture
}
}
}
}
- Material 材質命令
寫在Pass -> Material中
命令 | 作用 |
---|---|
Diffuse[Color] | 漫反射顏色 |
Ambient[Color] | 環境光反射顏色 |
Shininess[float] | 光澤度 |
Specular[Color] | 高光顏色 |
Emission[Color] | 自發光顏色 |
Shader "AShader/FixedShader/Fixed006"{
Properties
{
_MainColor("主顏色", Color) = (1, 1, 1, 1)
_SpecularColor("高光顏色", Color) = (1, 1, 1 ,1)
_Shininess("光澤度", Range(0.1, 1)) = 0.5
_Ambient("環境光顏色", Color) = (1, 1, 1, 1)
_Emission("自發光顏色", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Color[_MainColor]
Lighting On
SeparateSpecular On
Material
{
Diffuse[_MainColor]
Specular[_SpecularColor]
Shininess[_Shininess]
Ambient[_AmbientColor]
Emission[_EmissionColor]
}
}
}
}
- Texture 紋理命令
紋理命令寫在Pass中,紋理命令不區分大小寫
SetTexture[Texture]
{
// 定義constantColor
constantColor(r, g, b, a)
constantColor[Color]
// 合并命令
// 因為RGBA的值范圍是[0 - 1],所以 + 更趨向于(1, 1, 1, 1)(白色)更亮,* 更趨向于(0, 0, 0, 0)(黑色)更暗
combine src1 + src2
combine src1 * src2
// -src 為補色
combine src1 - src2
// 使用src2的透明通道值在src1和src3之間取插值,插值是反向的,透明度為0的時候取src3,透明度為1的時候取src1
combine src1 lerp(src2) src3
}
源類型(src) | 描述 |
---|---|
primary | 來自光照計算的顏色或是當它綁定時的頂點顏色 |
texture | 在SetTexture中定義的紋理的顏色 |
previous | 上一次SetTexture的結果 |
constant | 被constantColor定義的顏色 |
- 兩張紋理圖漸變切換
Fixed007.shader
Shader "AShader/FixedShader/Fixed007"{
Properties
{
_LerpScale("插值比例", Range(0, 1)) = 1
_MainTexture("主紋理", 2D) = ""{}
_DetailTexture("細節紋理", 2D) = ""{}
}
SubShader
{
Pass
{
SetTexture[_MainTexture]
{
constantColor(1, 0, 0, 0)
}
SetTexture[_DetailTexture]
{
constantColor(1, 1, 1, [_LerpScale])
combine texture lerp(constant) previous
}
}
}
}
SkinLerp.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkinLerp : MonoBehaviour
{
// 拿到meshRenderer
private MeshRenderer _meshRenderer;
// 當前的lerpScale
private float lerpScale;
// 目標lerpScale
private float targetScale;
private void Awake()
{
_meshRenderer = GetComponent<MeshRenderer>();
}
private void Update()
{
// 取插值
lerpScale = Mathf.Lerp(lerpScale, targetScale, Time.deltaTime * 2);
// 0 - 1 之間來回切換
if (Mathf.Abs(lerpScale - targetScale) < 0.05)
targetScale = (targetScale == 0) ? 1 : 0;
// 使用代碼控制
// name : 屬性名
// value : 值
_meshRenderer.material.SetFloat("_LerpScale", lerpScale);
// 修改Offset使圖片動起來
_meshRenderer.material.SetTextureOffset("_MainTexture", new Vector2(Time.time, 0));
_meshRenderer.material.SetTextureOffset("_DetailTexture", new Vector2(-Time.time, 0));
}
}
4. 表面著色器(Surface Shader)
4.1 CG語言
-
基本數據類型(所有的數據類型后面都可以加數字,表示多個該類型的,類似Vector)
- float:32位 浮點數
- float2:2個浮點數
- float3:3個浮點數
- float4:4個浮點數
- half:16位 浮點數
- int:32位 整型
- fixed:12位 定點數,取值范圍 [0 - 1] 的小數 或 整數
- bool
- sampler2D:紋理對象
- string
- float:32位 浮點數
-
預設的Structure
-
輸入
變量名 代表內容 float2 uv_MainTex 紋理貼圖UV,模型上每個像素對應到紋理圖的坐標,_MainTex為紋理圖的名字 float3 viewDir 像素的視圖方向 float3 worldPos 像素的世界坐標位置 float4 screenPos 屏幕空間位置,是float4類型,只需用前兩個值 float4 color:COLOR 每個頂點的內插值顏色 -
輸出
變量名 代表內容 half3 Albedo 反射率,即顏色紋理RGB half3 Normal 法線,即法向量(x, y, z) half3 Emission 自發光顏色RGB half Specular 鏡面反射度 half Gloss 光澤度 half Alpha 透明度
-
語義綁定
語法:變量名:語義定義
,表示這個變量代表著某種指定的值,例:color:COLOR
表示color
這個變量是顏色
4.2 結構
- 表面著色器沒有Pass通道
- 外部是Shader Lab,內部嵌入CG語言
Shader "Ashader/SurfaceShader/Shader001"
{
Properties
{
}
SubShader
{
// 不需要Pass通道
// 預編譯指令 粉色高亮
// --------- CG語言開始 ---------
CGPROGRAM
// 表面著色器 入口函數名稱 蘭伯特光照模型(漫反射)
#pragma surface surf Lambert
// 輸入結構體
struct Input
{
float3 viewDir;
}; // CG語言結構體定義后要加";"
// 聲明外部屬性,要求和屬性名字相同
fixed4 _MainColor;
// 入口函數
// 參數
// Input IN : 輸入結構體
// in/out/inout SurfaceOutput o : 常用于輸出
// in:輸入 out:輸出 inout:既能輸入又能輸出
void surf(Input IN, inout SurfaceOutput o)
{
// 輸出純色
o.Albedo = float3(1, 0, 0);
// 四個數字rgba或xyzw 轉三個數字:變量.rgb .gba .xyz .yzw,用到哪個寫哪個
o.Albedo = _MainColor.rgb;
}
// --------- CG語言結束 ---------
ENDCG
}
}
4.3 方法使用
- 設置紋理圖
Shader "AShader/SurfaceShader/Surface002"
{
Properties
{
_MainTexture("主紋理", 2D) = ""{}
_MainColor("主顏色", Color) = (1, 1, 1, 1)
}
SubShader
{
// --------- CG語言開始 ---------
CGPROGRAM
// 表面著色器 入口函數名稱 蘭伯特光照模型(漫反射)
#pragma surface surf Lambert
// 聲明外部屬性
sampler2D _MainTexture;
fixed4 _MainColor;
// 輸入結構體
struct Input
{
// 主紋理的UV坐標
float2 uv_MainTexture;
};
// 入口函數
void surf(Input IN, inout SurfaceOutput o)
{
// 渲染紋理圖,通過UV坐標在紋理貼圖中獲取當前像素點的顏色值
// 參數
// _MainTexture:紋理圖
// IN.uv_MainTexture:紋理圖UV
o.Albedo = tex2D(_MainTexture, IN.uv_MainTexture).rgb;
// 與顏色進行混合
o.Albedo *= _MainColor.rgb;
}
// --------- CG語言結束 ---------
ENDCG
}
}
- 設置法線貼圖
Shader "AShader/SurfaceShader/Surface003"
{
Properties
{
_MainTexture("主紋理", 2D) = ""{}
_NormalTexture("法線紋理", 2D) = ""{}
}
SubShader
{
// --------- CG語言開始 ---------
CGPROGRAM
// 表面著色器 入口函數名稱 蘭伯特光照模型(漫反射)
#pragma surface surf Lambert
// 聲明外部屬性
sampler2D _MainTexture;
sampler2D _NormalTexture;
// 輸入結構體
struct Input
{
// 主紋理的UV坐標
float2 uv_MainTexture;
float2 uv_NormalTexture;
};
// 入口函數
void surf(Input IN, inout SurfaceOutput o)
{
// 渲染紋理圖,通過UV坐標在紋理貼圖中獲取當前像素點的顏色值
// 參數
// _MainTexture:紋理圖
// IN.uv_MainTexture:紋理圖UV
o.Albedo = tex2D(_MainTexture, IN.uv_MainTexture).rgb;
// 設置法線貼圖
o.Normal = UnpackNormal(tex2D(_NormalTexture, IN.uv_NormalTexture));
}
// --------- CG語言結束 ---------
ENDCG
}
}
- 邊緣發光
Shader "AShader/SurfaceShader/Surface004"
{
Properties
{
_MainTexture("主紋理", 2D) = ""{}
_NormalTexture("法線紋理", 2D) = ""{}
_RimColor("發光顏色", Color) = (1, 1, 1, 1)
_RimPower("發光強度", Range(1, 10)) = 1
}
SubShader
{
// --------- CG語言開始 ---------
CGPROGRAM
// 表面著色器 入口函數名稱 蘭伯特光照模型(漫反射)
#pragma surface surf Lambert
// 聲明外部屬性
sampler2D _MainTexture;
sampler2D _NormalTexture;
fixed4 _RimColor;
half _RimPower;
// 輸入結構體
struct Input
{
// 主紋理的UV坐標
float2 uv_MainTexture;
float2 uv_NormalTexture;
// 視圖方向
float3 viewDir;
};
// 入口函數
void surf(Input IN, inout SurfaceOutput o)
{
// 渲染紋理圖,通過UV坐標在紋理貼圖中獲取當前像素點的顏色值
// 參數
// _MainTexture:紋理圖
// IN.uv_MainTexture:紋理圖UV
o.Albedo = tex2D(_MainTexture, IN.uv_MainTexture).rgb;
// 設置法線貼圖
o.Normal = UnpackNormal(tex2D(_NormalTexture, IN.uv_NormalTexture));
// 計算邊緣發光系數
float rim = 1 - saturate(dot(normalize(IN.viewDir), normalize(o.Normal)));
// 系數直接乘
//o.Emission = _RimColor * rim * _RimPower;
// 系數的光強次冪
o.Emission = _RimColor * pow(rim, 10 - _RimPower);
}
// --------- CG語言結束 ---------
ENDCG
}
}
5. 頂點片段著色器(Shader 2.0)
5.1 頂點語義
* model本地坐標
* view觀察坐標
* projection投影坐標
語義 | 代表內容 |
---|---|
float4 POSITION | 頂點坐標位置 |
float3 NORMAL | 頂點法線向量坐標 |
float4 TEXCOORD0 | 第一個UV坐標(用作臨時變量,存坐標存顏色等) |
float4 TEXCOORD1/2/3... | 第二/三/四...個UV坐標 |
float4 TENGENT | 頂點切線向量坐標 |
float4 COLOR | 頂點顏色值 |
5.2 常用函數庫
庫名 | 作用 |
---|---|
HLSLSupport.cginc | 輔助為跨平臺的著色器編譯宏和定義 |
UnityShaderVariables.cginc | 常用全局變量 |
UnityCG.cginc | 常用輔助函數 |
AutoLight.cginc | 光、影函數 |
Lighting.cginc | 光照模型相關 |
TerrainEngine.cginc | 地形植被輔助 |
5.3 結構
Shader "AShader/VFShader/VF001"
{
Properties
{
_MainColor("主顏色", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
CGPROGRAM
// 著色器類型 入口函數名稱
#pragma vertex vert
#pragma fragment frag
// 聲明外部屬性
fixed4 _MainColor;
// 頂點著色器,進行頂點處理
// 返回值 入口函數名稱(參數類型 參數名:語義綁定):返回值語義綁定
float4 vert(float4 vertexPos:POSITION):SV_POSITION
{
// 完成頂點變換,返回投影坐標
// 5.4之前
//return mul(UNITY_MATRIX_MVP, vertexPos);
// 5.4之后
return UnityObjectToClipPos(vertexPos);
}
// 片段著色器,著色
fixed4 frag():COLOR
{
//return fixed4(1, 0, 0, 1);
return _MainColor;
}
ENDCG
}
}
}
5.4 案例
- 彩虹顏色
將物體的坐標作為顏色值,會呈現彩虹效果
Shader "AShader/VFShader/VF002"
{
Properties
{
_ColorOffset("顏色偏移量", Range(0, 1)) = 0
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 聲明外部屬性變量
fixed _ColorOffset;
// 由于return只能返回一個變量,所以當想返回多個值的時候使用結構體
// 頂點著色器輸出的結構體
struct v2f
{
// 0級紋理坐標(臨時變量,用于存儲頂點著色器算出來的頂點坐標,給片段著色器用)
float4 col:TEXCOORD0;
// 屏幕坐標
float4 pos:SV_POSITION;
};
// 頂點著色器入口函數
v2f vert(float4 vertexPos:POSITION)
{
// 定義結構體對象
v2f content;
// 執行頂點變換
content.pos = UnityObjectToClipPos(vertexPos);
// 獲取頂點坐標
content.col = vertexPos + float4(_ColorOffset, _ColorOffset, _ColorOffset, 0);
return content;
}
// 片段著色器入口函數
float4 frag(v2f content):COLOR
{
// 將頂點坐標當成顏色值輸出
return content.col;
}
ENDCG
}
}
}
- 設定漫反射
Shader "AShader/VFShader/VF003"
{
Properties
{
_MainColor("漫反射顏色", COLOR) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 引入庫
#include "UnityCG.cginc"
// 聲明外部變量
fixed4 _MainColor;
// 聲明內置變量
fixed4 _LightColor0;
// 頂點著色器輸入結構體
struct appdata
{
// 頂點坐標
float4 vertexPos:POSITION;
// 頂點法線
float3 vertexNormal:NORMAL;
};
// 頂點著色器輸出結構體
struct v2f
{
// 屏幕坐標
float4 pos:SV_POSITION;
// 屏幕法線
float3 normal:NORMAL;
};
// 頂點著色器入口函數
v2f vert(appdata data)
{
v2f content;
content.pos = UnityObjectToClipPos(data.vertexPos);
// 計算法線
content.normal = mul(float4(data.vertexNormal, 1), unity_WorldToObject).xyz;
return content;
}
// 片段著色器入口函數
fixed4 frag(v2f content):COLOR
{
// 求法線的標準化向量
float3 lightNormal = normalize(content.normal);
// 求入射光的標準化向量
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
// 計算漫反射顏色
float3 diffuseColor = _MainColor * _LightColor0 * -min(0, dot(lightNormal, lightDir));
return fixed4(diffuseColor, 1) + UNITY_LIGHTMODEL_AMBIENT;
}
ENDCG
}
}
}
- 技能釋放范圍
Shader "SkillShader/Skill1"
{
Properties
{
// 技能釋放原點
_BossPosition("BossPosition", Vector) = (1, 1, 1, 1)
// 透明通道值
_Alpha("BossSkillAlpha", Range(0, 1)) = 0
// 用于代碼控制顯示范圍的值
_Dis("SkillDis", Range(0, 10)) = 0
}
SubShader
{
Pass
{
// 開啟透明混合
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 _BossPosition;
float _Alpha;
float _Dis;
struct v2f
{
float4 vertPos:TEXCOORD0;
float4 pos:SV_POSITION;
};
v2f vert(float4 vertexPos:POSITION)
{
v2f content;
content.pos = UnityObjectToClipPos(vertexPos);
content.vertPos = vertexPos;
return content;
}
float4 frag(v2f content):COLOR
{
// 根據傳入坐標計算需要渲染的地方
if (distance(content.vertPos.z, _BossPosition.z) < _Dis && abs(distance(content.vertPos.x, _BossPosition.x)) < 1)
{
return float4(1, 0, 0, _Alpha);
}
else
{
return float4(1, 0, 0, 0);
}
}
ENDCG
}
}
}
6. 小知識點
- 法線貼圖:法線決定光的反射方向,法線是一個向量,向量是vector3,顏色的rgb也是vector3,所以法線貼圖使用顏色來代表向量(例:紅色(1, 0, 0)代表右)
- 邊緣發光公式:
RimColor = EmissionColor * (1 - clamp01(dot(normalize(normal), normalize(viewDir))))
附:Shader詞典
單詞 | 描述 | 單詞 | 描述 | 單詞 | 描述 |
---|---|---|---|---|---|
Shader | 著色器 | Properties | 屬性 | SubShader | 子著色器 |
Pass | 通道 | Color | 顏色 | Cull | 剔除 |
ZTest | 深度測試 | SeparateSpecular | 鏡面反射 | Diffuse | 漫反射 |
Ambient | 環境光 | Shininess | 光澤度 | Specular | 高光 |
Emission | 自發光 | Combine | 結合 | Primary | 基本的 |
Previous | 之前的 | Constant | 恒定的 | Gloss | 光澤度 |
Vertex | 頂點 | Fragment | 片段 |