一、计算机图形学
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 | 片段 |