0%

前置知识

颜色变量相乘

两个颜色变量相乘通常用于计算光照、材质混合、纹理混合等要求 计算结果可以理解为两个颜色叠加在一起的效果 ## 漫反射 漫反射(Diffuse Reflection)是光线撞击一个物体表面后以各个方向均匀地反射出去的过程 与之相反的则是镜面反射以特定角度反射 这种漫反射可以使物体表面看起来均匀而不闪烁,不会有高亮点

兰伯特光照模型

兰伯特光照模型认为漫反射光的强度仅与入射光的方向和反射点处表面法线的夹角的余弦成正比

入射光强度为L,则反射光强度为:L × cos (θ)

公式:漫反射光照颜色 =  ×  × max(0, 线 ⋅ ) 其中的点乘其实就是cos (θ) 而max是避免负数,如模型的背面部分是照不到光的,设为0为黑色

模型公式对应信息

  1. 光源颜色 = Lighting.cginc内置文件中的_LightColor0
  2. 光源方向 = _WorldSpaceLightPos0 表示光源0在世界坐标下的位置
  3. 兰伯特光照模型环境光变量 UNITY_LIGHTMODEL_AMBIENT.rgb(模拟环境光对物体的影响,避免阴影部分全黑)
  4. 法线从模型空间转换到世界空间 UnityObjectToWorldNormal

UnityShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Shader "Unlit/Lambert"
{
Properties
{
//1材质漫反射颜色属性声明
_MainColor("MainColor",Color) = (1,1,1,1)//白色
}
SubShader
{
Tags { "LightMode"="ForwardBase" }//不透明的向前渲染模式


Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"//引用Unity文件
#include "Lighting.cginc"

fixed4 _MainColor;//材质漫反射颜色
struct v2f
{
float4 pos: SV_POSITION;//裁剪坐标系下顶点位置
float3 color : COLOR;//漫反射光照颜色
};
//逐顶点光照
//所以相关的漫反射光照颜色的计算需要写在顶点着色器回调函数中
v2f vert (appdata_base v)//UnityCG自带结构体包含法线顶点相关CG信息
{
v2f v2fData;
//_LightColor0//光照颜色
//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;
v2fData.pos = UnityObjectToClipPos(v.vertex);//模型空间转裁剪坐标系下顶点位置
//为了阴影处不全黑,加个兰伯特光照模型环境变量
v2fData.color = UNITY_LIGHTMODEL_AMBIENT.rgb+color;
return v2fData;
}

fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color.rgb,1);//返回漫反射光照颜色
}
ENDCG
}
}
}

[!TIP] > CGPROGRAM ENDCG # 重要的编译指令 -指定着色器函数 Utility Shader –顶点片元着色器

1
2
3
4
5
使用#pragma声明编译指令
定义实现 顶点/片元着色器 代码的函数名称
#pragma vertex name 顶点
#pragma fragment name 片元

基础数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
基础数据类型
uint
int
float f
half 16位浮点数 h
fixed 12位浮点数
bool
string
sampler 纹理对象句柄
sampler
sampler1D
sampler2D
sampler3D
samplerCUBE
samplerRECT


基础复合数据类型
数组: fload arrayf[4]={2,3,4,5}
CG中无法通过length获取数组长度
arrayff[3][3] = {{1,2,3},{4,5,6},{7,8,9}}
struct

特殊数据类型

1
2
3
4
5
6
7
8
9
10
11
向量
数据类型2 = 数据类型2()
数据类型3 = 数据类型3()
数据类型4 = 数据类型4()
矩阵
数据类型‘n’x'm' ={n1m1,n1m2,n1m3......}
bool类型特殊使用
float3 a
float3 b
bool3 c = a<b;
计算结果也是个三维向量,三个个个比较。

Swizzle操作符

上节课我们学习了向量
但是我们并没有讲解如何获取向量中某元素的相关知识点
而这节课将要学习的Swizzle操作符就可以用于获取向量中元素

Swizzle操作符通常以点号(.)的形式使用,后面跟着所需的分量顺序
对于四维向量来说
我们可以通过
向量.xyzw
或
向量.rgba
两种分量的写法来表示向量中的四个值
其中 xyzw和rgba分别代表四维向量中的四个元素
在此的意义就是向量一般可以用来表示坐标和颜色

提取分量

1
2
3
fixed4 f4 = fixed4(1,2,3,4);
fixed f = f4.x;
f = f4.r;

重新排列分量

1
f4 = f4.yzxw

创建新的向量

1
2
fixed3 f3 = f4.xyz;
flxed4 f4_2 = fixed(f3,2);

向量声明矩阵

1
2
3
4
5
flxed4 f4 = fixed4(1,2,3,4)
fixed4x4 f4x4 = {f4,
fixed4(5,6,7,8),
fixed4(9,10,11,12),
f4};

获取矩阵元素

1
2
3
和二维数组一样
float4x4 f4x4;
f = f4x4[0][0];

利用向量获取矩阵中的某一行

1
f4_2 = f4x4[0];

高维转低维

1
2
fixed3 f3_2 = f4;自动取出xyz赋值
f3x3 = f4x4;

运算符相关

c# 短路操作 f<f2||4>5如果f<f2成立,则4>5的比较不会被执行 CG中不存在短路操作 CG中只能对整数取余

CG函数相关

1
2
3
4
5
6
void name(in 参数类型 参数名,out 参数类型 参数名)
{
}
in:输入参数,外部传入内部,不修改该参数
out 输出参数,内部传向外部,在内部必须初始化或者修改
in out 都可以省略

顶点片元着色器基本结构

顶点着色器

1
2
3
4
5
6
7
8
CGPROGRAM
#pragma vertex myVert顶点着色器返回的肯定是个齐次顶点坐标
float4 myVert(float4 v:POSITION):SV_POSITION语义,让系统默认传入一个顶点坐标,传出的是一个裁剪空间坐标
{
return mul(UNITY_MATRIX_MVP,v);变换出在裁剪空间的顶点坐标
UnityObjectToClipPos(v);新版变换矩阵
}
ENDCG

片元着色器

1
2
3
4
5
#pragma fragment myFrag
fixed4 myFrag():SV_Target 告诉渲染器,把用户输出颜色存储到一个渲染目标中,这里输出到帧缓存中
{
return fixed(1,0,0,1);返回红色
}

每一个Shader至少包含一个SubShader SubShader中包含最终渲染效果的代码,决定最终渲染效果 1. 渲染标签 决定什么时候渲染以及如何对物体进行渲染 2. 渲染状态 决定渲染时候的剔除方式、深度测试方式、混合方式等内容 3. 渲染通道 具体实现着色器代码的地方

1
2
3
4
5
6
7
8
9
SubShader
{
Tags{“标签名1”=“标签值1”,“标签名2” = “标签值2”}
Pass
{
第一个渲染通道
}
每一个Pass都会让物体执行一次渲染
}

渲染标签

语法结构

1
Tags{“标签名1”=“标签值1”,“标签名2” = “标签值2”}
## 渲染队列 > [!INFO] > 确定物体的渲染顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 主要作用:
确定物体的渲染顺序

Tags{ "Queue" = "标签值" }
常用Unity预先定义好的渲染队列标签值:

1.Background(背景)(队列号: 1000)
最早被渲染的物体的队列,一般用来渲染天空盒或者背景
Tags{ "Queue" = "Background" }

2.Geometry(几何)(队列号: 2000)
不透明的几何体通常使用该队列,当没有声明渲染队列时,Unity会默认使用这个队列
Tags{ "Queue" = "Geometry" }

3.AlphaTest(透明测试)(队列号: 2450)
有透明通道的,需要进行Alpha测试的几何体会使用该队列
当所有Geometry队列实体绘制完后再绘制AlphaTest队列,效率更高
Tags{ "Queue" = "AlphaTest" }

4.Transparent(透明的)(队列号: 3000)
该队列中几何体按照由远到近的顺序进行绘制,半透明物体的渲染队列,所有进行透明混合的几何体都应该使用该队列
比如:玻璃材质,粒子特效等
Tags{ "Queue" = "Transparent" }

5.Overlay(覆盖)(队列号: 4000)
用是放在最后渲染的队列,于叠加渲染的效果,比如镜头光晕等
Tags{ "Queue" = "Overlay" }

6.自定义队列
基于Unity预先定义好的这些渲染队列标签来进行加减运算来定义自己的渲染队列
比如:
Tags{ "Queue" = "Geometry+1" }
代表的队列号就是 2001
Tags{ "Queue" = "Transparent-1" }
代表的队列号就是 2999
自定义队列在一些特殊情况下,特别有用
比如 一些水的渲染 想要在不透明物体之后,半透明物体之前进行渲染,就可以自定义

注意:自定义队列只能基于预先定义好的各类型进行计算,不能在Shader中直接赋值数字
如果实在想要直接赋值数字,可以在材质面板中进行设置
## 渲染类型

[!INFO] >主要作用 对着色器进行分类,之后可以用于着色器替换功能 摄像机上有对应的API,可以指定这个渲染类型来替换成别的着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Tags{ "RenderType" = "标签值" }
常用Unity预先定义好的渲染类型标签值:

1.Opaque(不透明的)
用于普通Shader,比如:不透明、自发光、反射等
2.Transparent(透明的)
用于半透明Shader,比如:透明、粒子
3.TransparentCutout(透明切割)
用于透明测试Shader,比如:植物叶子
4.Background(背景)
用于天空盒Shader
5.Overlay(覆盖)
用于GUI纹理、Halo(光环)、Flare(光晕)

了解即可
6.TreeOpaque
用于地形系统中的树干
7.TreeTransparentCutout
用于地形系统中的树叶
8.TreeBillboard
用于地形系统中的Billboarded树
9.Grass
用于地形系统中的草
10.GrassBillboard
用于地形系统中的Billboarded草

禁用批处理

    主要作用:
    当使用批处理时,模型会被变换到世界空间中,模型空间会被丢弃
    这可能会导致某些使用模型空间顶点数据的Shader最终无法实现想要的结果
    可以通过开启禁用批处理来解决该问题

    总是禁用批处理
    Tags{ "DisableBatching" = "True" }

    不禁用批处理(默认值)
    Tags{ "DisableBatching" = "False" }

    了解即可
    LOD效果激活时才会禁用批处理,主要用于地形系统上的树
    Tags{ "DisableBatching" = "LODFading" }

禁用阴影投影

    主要作用:
    控制该SubShader的物体是否会投射阴影

    不投射阴影
    Tags{ "ForceNoShadowCasting" = "True" }
    投射阴影(默认值)
    Tags{ "ForceNoShadowCasting" = "False" }

忽略投影机

    主要作用:
    物体是否受到Projector(投影机)的投射
    Projector是Unity中的一个功能(以后讲解)

    忽略Projector(一般半透明Shader需要开启该标签)
    Tags{ "IgnoreProjector" = "True" }

    不忽略Projector(默认值)
    Tags{ "IgnoreProjector" = "False" }

其他标签

    1.是否用于精灵
    想要将该SubShader用于Sprite时,将该标签设置为False
    Tags{ "CanUseSpriteAtlas" = "False" }

    2.预览类型
    材质在预览窗口默认为球形,如果想要改变为平面或天空盒
    只需要改变预览标签即可
    平面
    Tags{ "PreviewType" = "Panel" }
    天空盒
    Tags{ "PreviewType" = "SkyBox" }

[!WARNING] > 以上这些标签只能在SubShader语句块中声明 之后讲解的Pass渲染通道语句块中也可以声明渲染标签 但是今天讲解的内容都不能在Pass中声明 Pass中有自己专门的标签类型,我们会在之后讲解

渲染状态

语法结构

1
2
渲染状态 状态类型
若有多个渲染状态,可以用空行隔开

剔除方式

1
2
3
4
5
6
   Cull Back     背面剔除
Cull Front 正面剔除
Cull Off 不剔除
不设置的话,默认为背面剔除

一般情况下,我们需要两面渲染时,会设置为不剔除

深度缓存

[!TIP] > 保证互相遮挡的物体公共区域优先渲染挡在前面的片元

![[Pasted image 20250209230433.png]] 如果没有开启深度写入,则只要通过深度测试一定会被渲染 每一个屏幕上的像素点都有一个内存区域记录深度信息

1
2
3
4
5
6
7
8
9
10
11
主要作用:
是否写入深度缓冲
深度缓冲(Depth Buffer):
深度缓冲是一个与屏幕像素对应的缓冲区,用于存储每个像素的深度值(距离相机的距离)。
在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
最后留在深度缓冲中的信息会被渲染
ZWrite On 写入深度缓冲
ZWrite Off 不写入深度缓冲
不设置的话,默认为写入

一般情况下,我们在做透明等特殊效果时,会设置为不写入
## 深度测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
主要作用:
设置深度测试的对比方式

深度测试的主要目的是确保在渲染时,像素按照正确的深度(距离相机的距离)顺序进行绘制,
从而创建正确的遮挡关系和透视效果
在渲染场景之前,深度缓冲被初始化为最大深度值,表示所有像素都在相机之外。
在渲染过程中,对于每个像素,深度测试会将当前像素的深度值与深度缓冲中对应位置的值进行比较。
一般情况下
1.如果当前像素的深度值小于深度缓冲中的值,说明当前像素在其他物体之前,它会被绘制,并更新深度缓冲。
2.如果当前像素的深度值大于等于深度缓冲中的值,说明当前像素在其他物体之后,它会被丢弃,不会被绘制,并保持深度缓冲不变。

ZTest Less
小于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest Greater
大于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest LEqual
小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest GEqual
大于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest Equal
等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest NotEqual
不等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
ZTest Always
始终通过深度测试写入深度缓冲中
不设置的话,默认为LEqual 小于等于

一般情况下,我们只有在实现一些特殊效果时才会区修改深度测试方式,比如透明物体渲染会修改为Less,描边效果会修改为Greater等

混合方式

在深度测试之后进行

![[混合流程图.png]]

1
2
3
4
5
6
7
8
9
10
11
12
主要作用:
设置渲染图像的混合方式(多种颜色叠加混合,比如透明、半透明效果和遮挡的物体进行颜色混合)

Blend One One 线性减淡
Blend SrcAlpha OneMinusSrcAlpha 正常透明混合
Blend OneMinusDstColor One 滤色
Blend DstColor Zero 正片叠底
Blend DstColor SrcColor X光片效果
Blend One OneMinusSrcAlpha 透明度混合
等等
不设置的话,默认不会进行混合
一般情况下,我们需要多种颜色叠加渲染时,就需要设置混合方式,具体情况具体处理

其他渲染状态

1
2
3
4
5
1.LOD         控制LOD级别,在不同距离下使用不同的渲染方式处理
2.ColorMask 设置颜色通道的写入蒙版,默认蒙版为RGBA
等等

我们目前主要掌握剔除方式、深度缓冲、深度测试、混合方式即可

渲染状态的注意事项

[!NOTE] >以上这些状态不仅可以在SubShader语句块中声明之后讲解的Pass渲染通道语句块中也可以声明这些渲染状态,如果在SubShader语句块中使用会影响之后的所有渲染通道Pass,如果在Pass语句块中使用只会影响当前Pass渲染通道,不会影响其他的Pass

总结

1
2
3
4
5
6
7
8
9
渲染状态对于我们来说很重要
它可以影响最终我们看到的渲染效果
其中
剔除方式决定了 模型正面背面是否能够被渲染
深度缓冲和深度测试 决定了景深关系的确定以及透明效果的正确表达等
混合方式 决定了透明半透明颜色的正确表现,以及一些特殊颜色效果的表现

这些内容,大家可以将其记入自己的笔记当中
之后在使用他们时,翻看笔记进行复习

渲染通道

渲染通道的语法结构

1
2
3
4
5
6
Pass{
1.Name 名称
2.渲染标签
3.渲染状态
4.其他着色器代码
}

渲染通道的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
主要作用:
我们对Pass命名的主要目的
可以利用UsePass命令在其他Shader当中复用该Pass的代码,
只需要在其他Shader当中使用
UsePass "Shader路径/Pass名"
注意:
Unity内部会把Pass名称转换为大写字母
因此在使用UsePass命令时必须使用大写形式的名字

Pass{
Name MyPass
}

在其他Shader中复用该Pass代码时
UsePass "TeachShader/Lesson4/MYPASS"

渲染通道中的渲染标签

1
2
3
4
Pass中的渲染标签语法和SubShader中相同
Tags{ "标签名1" = "标签值1" "标签名2" = "标签值2" "标签名2" = "标签值2".......}
但是我们之前讲解过的SubShader语句块中的渲染标签不能够在Pass中使用
Pass当中有自己专门的渲染标签

1.Tags{ “LightMode” = “标签值” }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
主要作用:
指定了该Pass应该在哪个阶段执行
可以将着色器代码分配给适当的渲染阶段,以实现所需的效果

1 - 1.Always
始终渲染;不应用光照
1 - 2.ForwardBase
在前向渲染中使用;应用环境光、主方向光、顶点 / SH 光源和光照贴图
1 - 3.ForwardAdd
在前向渲染中使用;应用附加的每像素光源(每个光源有一个通道)
1 - 4.Deferred
在延迟渲染中使用;渲染 G 缓冲区
1 - 5.ShadowCaster
将对象深度渲染到阴影贴图或深度纹理中
1 - 6.MotionVectors
用于计算每对象运动矢量
1 - 7.PrepassBase
在旧版延迟光照中使用;渲染法线和镜面反射指数
1 - 8.PrepassFinal
在旧版延迟光照中使用;通过组合纹理、光照和反光来渲染最终颜色
1 - 9.Vertex
当对象不进行光照贴图时在旧版顶点光照渲染中使用;应用所有顶点光源
1 - 10.VertexLMRGBM
当对象不进行光照贴图时在旧版顶点光照渲染中使用;在光照贴图为 RGBM 编码的平台上(PC 和游戏主机)
1 - 11.VertexLM
当对象不进行光照贴图时在旧版顶点光照渲染中使用;在光照贴图为双 LDR 编码的平台上(移动平台)

关于向前渲染、延迟渲染、旧版光照等概念了解
https://docs.unity.cn/cn/2019.4/Manual/RenderingPaths.html

2.Tags{ “RequireOptions” = “标签值” }

1
2
3
4
5
6
主要作用:
用于指定当满足某些条件时才渲染该Pass

目前Unity仅支持
Tags{ "RequireOptions" = "SoftVegetation" }
仅当Quality窗口中开启了SoftVegetation时才渲染此通道

3.Tags{ “PassFlags” = “标签值” }

1
2
3
4
5
6
7
主要作用:
一个渲染通道Pass可指示一些标志来更改渲染管线向Pass传递数据的方式

目前Unity仅支持
Tags{ "PassFlags" = "OnlyDirectional" }
在 ForwardBase 向前渲染的通道类型中使用时,此标志的作用是仅允许主方向光和环境光 / 光照探针数据传递到着色器
这意味着非重要光源的数据将不会传递到顶点光源或球谐函数着色器变量

Pass中的渲染状态

    我们上节课在SubShader语句块中学习的渲染状态同样适用于Pass
    比如
    剔除方式决定了 模型正面背面是否能够被渲染
    深度缓冲和深度测试 决定了景深关系的确定以及透明效果的正确表达等
    混合方式 决定了透明半透明颜色的正确表现,以及一些特殊颜色效果的表现
    这些渲染状态都可以在单个Pass中进行设置
    需要注意的是
    如果在SubShader语句块中使用会影响之后的所有渲染通道Pass
    如果在Pass语句块中使用只会影响当前Pass渲染通道,不会影响其他的Pass

    不仅如此,Pass中还可以使用固定管线着色器的命令

其他着色器代码

1
2
3
其他代码部分就是实现着色器的核心代码部分
我们可能会用到CG或HLSL等着色器语言来进行逻辑书写
我们之后会详细讲解

GrabPass命令

1
2
3
4
5
6
7
8
9
10
11
我们可以利用GrabPass命令把即将绘制对象时的屏幕内容抓取到纹理中
在后续通道中即可使用此纹理,从而执行基于图像的高级效果。

举例:
将绘制该对象之前的屏幕抓取到 _BackgroundTexture 中
GrabPass
{
"_BackgroundTexture"
}
注意:
该命令一般写在某个Pass前,在之后的Pass代码中可以利用_BackgroundTexture变量进行处理

总结

1
2
3
4
5
6
7
Pass渲染通道语句块中
主要包含了
1.名字:可以帮助我们复用Pass代码
2.渲染标签:不能使用SubShader中的渲染标签,有自己独有的渲染标签
3.渲染状态:SubShader当中的渲染状态同样可以在Pass中使用,影响的区域不同
4.着色器语言逻辑:我们之后会详细学习的部分
四个部分

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment