第二章:Shader和渲染管线

【图形学】学长沉浸式啃《实时渲染4th》- 第一章图形渲染流水线 - 深入浅出实时渲染的整体流程_哔哩哔哩_bilibili

2.1 什么是Shader

  • GPU流水线上高度可编程阶段。
  • 有特定类型的着色器,如顶点着色器,片元着色器。
  • 依靠着色器可以控制流水线中的渲染细节。

2.2 什么是顶点、片元

  • 顶点就是点,包含了空间坐标信息。
  • 图元是由顶点组成的。一个顶点,一条线段,一个三角形或者多边形都可以成为图元。
  • 片元是在图元经过光栅化阶段后,被分割成一个个像素大小的基本单位。
  • 像素是最终呈现在屏幕上的包含RGBA值的图像最小单元。

2.3 渲染流水线

  • 计算机从一系列的顶点数据,纹理等信息出发,将这些信息转换为图像,这个工作通常由GPU和CPU共同完成。
  • 渲染流水线是概念流水线。

渲染流水线:应用阶段->几何阶段->光栅化阶段->像素化阶段

2.3.1 应用阶段

这一阶段由三个主要任务:

  • 准备场景数据
  • 粗粒度剔除(可以剔除不可见物体,提升渲染性能)
  • 输出渲染图元。

渲染图元:可以是点,线,三角面等。渲染图元会传递到几何阶段。

步骤 详细
把数据加载到显存中 渲染数据从硬盘加载到系统内存(RAM),网格和纹理等数据被加载到显存(VRAM)。因为显卡对于显存的访问速度更快,且对于RAM无直接访问权限。
加载到显存后就可以移除RAM内部数据,但是一些数据需要保留给CPU访问。从硬盘加载到RAM非常耗时。
设置渲染状态 通过CPU设置渲染状态,指导GPU如何进行渲染工作。
渲染状态定义了场景中的网格是怎么被渲染的,如片元着色器,顶点着色器,光源属性,材质等。
当没有更改渲染状态的时候,所有网格都使用一种渲染状态,看起来像是同一种材质。
调用Draw Call Draw Call是一个命令,发起方为CPU,接收方GPU。一次DC命令指向一个需要被渲染的图元列表。

GPU流水线

GPU渲染过程就是GPU流水线。

对几何阶段和光栅化阶段开发者无法拥有绝对控制权,实现载体为GPU。

几何阶段和光栅化阶段可以分为若干的流水线阶段,由GPU实现,

每个阶段都提供了不同的可配置性或可编程性

2.几何阶段:

通常在GPU上进行,决定绘制的图元,怎么绘制,在哪里绘制。

3.光栅化阶段

通常在GPU上进行,使用上个阶段传递的数据来产生像素,渲染最终图像。

第三章:Unity Shader

3.1 shader lab

3.1.1 什么是shaderlab

Unity Shader是Unity提供的高层级渲染抽象层。在Unity中,所有的Unity Shader都通过ShaderLab来编写。ShaderLab是Unity提供编写Unity Shader的一种说明性语言。

img

3.1.2 Unity Shader结构

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
Shader "MyShader"
{
Properties
{
//properties needs
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0

}

SubShader
{
//true Shader code will be write here
//Surface Shder 表面着色器
//Vertex/Fragment Shader 顶点/片元着色器
//Fixed Function Shader 固定函数着色器
}

SubShader
{
//可选的标签 见下文
[Tags]

//可选的状态 渲染状态设置选项见下文
[RenderSetUp]

//一个Pass定义了一个完整的渲染流程
Pass
{
//true Shader code will be write here
//Surface Shder 表面着色器
//Vertex/Fragment Shader 顶点/片元着色器
//Fixed Function Shader 固定函数着色器

[Name]
[Tags]
[RenderSetUp]
//Other code
}
}
}

标签(Tages)

标签的作用是告诉Unity渲染引擎要怎样以及何时渲染这个对象。

标签仅可在Subshader中声明,不可在Pass中声明。

结构如下:

1
Tags {"TagName1" = "Value1" "TagName2" = "Value2"}

状态设置

ShaderLab常见的渲染状态设置选项:

img

如果在SubShader块中设置了状态,就会应用到全部的Pass。可以在Pass中单独设置状态。

Pass

结构如下

1
2
3
4
5
6
7
Pass
{
[Name]
[Tags]
[RenderSetUp]
//Other code
}

可以在定义该Pass的名称

1
Name "MyPassName"

通过这个名称,可以使用ShaderLab的UsePass命令来使用其他Unity Shader中的Pass

1
UsePass "MyShader/MYPASSNAME"

需要注意,Pass的名称会被Unity转换成大写的表示,因此在使用UsePass命令的时候需要将Pass名称全部大写。

Pass也可以使用标签,不过不同于SubShader。

img

一些特殊Pass

  • UsePass
  • GrabPass

Fallback

在SubShader语义块后,可以添加Fallback指令。如果所有的SubShader在这块显卡上都无法运行,就会使用这个Fallback指定的最低级shader。

1
2
3
4
5
6
Fallback "name"
//for example
Fallback "VertexLit"
//or
Fallback Off
//means do noting

在渲染阴影纹理时,Unity会寻找一个阴影投射Pass。通常Fallback使用的内置Shader包含了一个这样的通用Pass。

3.1.3 着色器

表面着色器

Surface Shader是Unity创造的着色器代码类型,定义在Pass中。需要的代码量很少,效果比较差。

本质上是将开发者提供的表面着色器转换为顶点/片元着色器。是对顶点/片元着色器的高层抽象。可以交由Unity处理光照细节。

顶点/片元着色器

顶点/片元着色器需要定义在CGPROGRAM和ENDCG之间,同时要写在Pass语义块,同样使用GL/HLSL编写。

我们需要自定义每个Pass的Shader代码,可以控制渲染实现的细节。

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
Pass
{
CGPROGRAM

#include "UnityCG.cginc"

#pragma vertex vert
#pragma fragment frag

uniform float4 _Color;

struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};

struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};

v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + fixed3(.5, .5, .5);
return o;
}

fixed4 frag(v2f i) : SV_TARGET
{
fixed3 c = i.color;
// c *= _Color.rgb;
return fixed4(c, 1.0);
}

ENDCG
}

固定函数着色器

一些老旧设备不支持可编程管线着色器,需要使用固定函数着色器,这种方法已经逐渐被淘汰。

3.1.4 怎么选择Unity Shader

  • 使用可编程管线着色器,即表面着色器或者顶点/片元着色器。
  • 表面着色器与光源相关,但是影响性能。
  • 如果光照数目少,使用顶点/片元着色器。
  • 有很多自定义渲染效果,顶点/片元着色器。

第五章:Unity Shader的基本语法结构

5.1顶点片元着色器的基本结构:

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
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter5-SimpleShader"
{
SubShader
{
Pass
{
//start cg
CGPROGRAM

//函数声明,告诉Unity哪个函数包含顶点着色器代码,哪个包含片元着色器
//#pragma vertex [name]
#pragma vertex vert
#pragma fragment frag

//顶点着色器
//POSITION 语义,不可省略。把模型顶点坐标填充到参数v中
//SV_POSITION 告诉Unity顶点着色器的输出是裁剪空间中的顶点坐标
float4 vert(float4 v : POSITION) : SV_POSITION
{
//更新后,unity自带MPV函数 模型 观察 投影矩阵
return UnityObjectToClipPos (v);
}

//片元着色器
//SV_TARGET 也是HLSL的系统语义,告诉渲染器把用户输出的颜色存储到一个渲染目标中
//这里输出到默认的帧缓存中
fixed4 frag() : SV_TARGET
{
//返回颜色
//每个分量都是[0,1],(0,0,0,0)为黑色,(1,1,1,1)白色
return fixed4(.5, .5, .5, .5);
}

ENDCG
//end cg
}
}
}

5.1.1 语义

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
Shader "Unity Shaders Book/Chapter 5/Simple Shader" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1) // 颜色属性
}
SubShader {
Pass {
CGPROGRAM

#pragma vertex vert // 指定顶点着色器函数为 vert
#pragma fragment frag // 指定片段着色器函数为 frag

uniform fixed4 _Color; // uniform 颜色变量

struct a2v {
float4 vertex : POSITION; // 顶点位置
float3 normal : NORMAL; // 顶点法线
float4 texcoord : TEXCOORD0; // 顶点纹理坐标
};

struct v2f {
float4 pos : SV_POSITION; // 顶点位置
fixed3 color : COLOR0; // 顶点颜色
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 将顶点从对象空间变换到投影空间
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); // 根据法线计算颜色
return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 c = i.color;
c *= _Color.rgb; // 对颜色进行着色
return fixed4(c, 1.0); // 输出最终颜色
}

ENDCG
}
}
}

语义(semantics)就是一个赋给Shader输入输出的字符串,这个字符串表达了这个参数的含义。语义可以让Shader知道从哪里读取数据,并把数据输出到哪里。

  • 语义分为有意义语义和无意义语义。
  • 接受系统数据的语义有特殊含义,而用户输入无特殊含义。

SV系统数值(ststem-value)语义

  • SV_POSITION表示光栅化的变换后的顶点坐标(即齐次裁剪空间中的坐标)。
  • SV_TARGET表示告诉渲染器把结果存储到渲染目标。

5.1.2 模型数据从哪里来 a2v

  • 每帧调用Draw Call时,Mesh Render组件会把它负责渲染的模型数据发送给Unity Shader。
  • 定义结构体逐顶点获取模型数据。
1
2
3
4
5
6
7
8
9
10
//application to vertex shader 应用到顶点着色器
struct a2v
{
//用模型空间的顶点坐标填充
float4 vertex : POSITION;
//用模型空间的法线方向填充
float3 normal : NORMAL;
//用模型的第一套纹理坐标填充
float4 texcoord : TEXCOORD0;
}

5.1.3 顶点着色器和片元着色器之间如何通信 v2f

  • 定义结构体接受返回信息
  • 顶点着色器计算后返回结构体
  • 片元着色器接收顶点着色器的输出做插值后得到的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//vertex shader to fragment shader 顶点到片
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
}

v2f vert(v2f v) : SV_POSITION
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = ...
return o
}

fixed4 frag(v2f i) : SV_TARGET
{
return fixed4(i.color, 1.0);
}

5.1.4 顶点着色器和片元着色器的差别

  • 顶点着色器逐顶点,片元着色器逐片元
  • 顶点着色器获取顶点信息
  • 片元着色器获取顶点插值结果信息。

5.1.5 使用属性

通过参数,可以随时调整材质的效果。这些参数需要写在Properties中。

为Shader添加Properties

1
2
3
4
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)// 颜色属性
}

会在材质面板显示一个颜色拾取器,从而直接控制模型在屏幕上显示的颜色。

img

为了在CG代码中可以访问它,还需要在代码片段中定义一个变量。这个变量的名称和类型必须与Properties语义块中的属性定义相匹配。

1
2
3
4
5
6
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
//
float4 _Color;

5.1.6 内置包含文件

包含文件(include file),类似c++的头文件。在Unity中,它们的文件后缀是.cginc。

在编写的时候可以使用#include指令把这些文件包含进来。

1
2
3
4
5
CGPROGRAM
//...
#include "UnityCG.cginc"
//...
ENDCG
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
52
53
54
55
56
57
58
59
60
61
62
63
64
Shader "Unity Shaders Book/Chapter 5/False Color" {
SubShader {
Pass {
CGPROGRAM

#pragma vertex vert // 指定顶点着色器函数为 vert
#pragma fragment frag // 指定片段着色器函数为 frag

#include "UnityCG.cginc" // 包含 Unity 的内置函数库

struct v2f {
float4 pos : SV_POSITION; // 顶点位置
fixed4 color : COLOR0; // 颜色
};

v2f vert(appdata_full v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 将顶点从对象空间变换到投影空间

// 可视化法线
o.color = fixed4(v.normal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);

// 可视化切线
o.color = fixed4(v.tangent.xyz * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);

// 可视化副法线
fixed3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
o.color = fixed4(binormal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);

// 可视化第一组纹理坐标
o.color = fixed4(v.texcoord.xy, 0.0, 1.0);

// 可视化第二组纹理坐标
o.color = fixed4(v.texcoord1.xy, 0.0, 1.0);

// 可视化第一组纹理坐标的小数部分
o.color = frac(v.texcoord);
if (any(saturate(v.texcoord) - v.texcoord)) {
o.color.b = 0.5;
}
o.color.a = 1.0;

// 可视化第二组纹理坐标的小数部分
o.color = frac(v.texcoord1);
if (any(saturate(v.texcoord1) - v.texcoord1)) {
o.color.b = 0.5;
}
o.color.a = 1.0;

// 可视化顶点颜色
// o.color = v.color;

return o;
}

fixed4 frag(v2f i) : SV_Target {
return i.color; // 输出颜色
}

ENDCG
}
}
}

https://zhuanlan.zhihu.com/p/569862136?wd=&eqid=916838b80000e08b00000006647df4f4

Home (shaderslab.com)

GAMES101 笔记 (notion.so)