第二章: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
}
}
}

6.1模拟光照

模拟真实光照环境来生成一张图像,需要考虑3种物理现象

  • 首先,光线从光源射出
  • 然后,光线和场景中的物体相交:一些被吸收,一些被散射到其他地方。
  • 最后,摄像机吸收了一些光,产生一张图像。

在本章中,假设漫反射部分没有方向性,光线在所有方向上是平均分布的。同时,只考虑一个特定方向上的高光反射。

6.1.1 辐照度

辐照度(irradiance):垂直于I的单位面积上单位时间穿过的能量

一般来说,光照都不会和平面垂直,因此需要使用光源方向和表面法线之间的夹角cos余弦值得到照射面积。

img

6.1.2 吸收和散射

散射(scattering)和吸收(absorption)

  • 散射只会改变光线的方向,而不改变光线的密度和颜色。
  • 吸收只会改变光线的密度和颜色,而不会改变光线的方向。

折射(refraction)或 透射(transmission)

  • 光线在物体表面经过散射后,会散射到内部,称之为折射或透射。

反射(reflection)

  • 散射到外部,称之为反射。

在折射进入物体内部的光线还会与内部颗粒进行相交,一些光线最后会重新发射出物体表面,而一些则被吸收。

那些重新发射出去的光线将具有和入射光线不同的方向分布和颜色。

img

为了区别这两种不同的散射方向,在光照模型中使用不同的部分计算它们:

  • 高光反射(specular)部分表示物体表面是如何反射光线的。
  • 漫反射(diffuse)部分表示有多少光线会被折射、吸收和散射出表面。

根据入射光线的数量和方向,可以计算出射光线的数量和方向,通常用出射度(exitance)描述。

辐照度和出射度满足线性关系,它们的比值就是材质的漫反射和高光反射属性。

6.1.3 着色

着色(shading)。根据材质属性、光源信息,使用一个等式去计算沿某个观察方向的出射度的过程。这个等式也被称为光照模型(Lighting Model)

6.1.4 BRDF光照模型

当已知光源位置和方向、视角方向时,我们就需要知道一个表面是如何和光照进行交互的。

BRDF(Bidirectional reflectance distribution function)包含了对给定模型表面上的一个点的外观的完整描述。

6.2 标准光照模型

将进入摄像机的光分成了四部分:

光类型 简介 例子
环境光 在真实的光照情况下,一个物体也可能会被间接光照所照亮。 红地毯放置应该浅灰色的沙发,沙发底部也会有红色
自发光 光线也可以直接由光源进入摄像机。标准光照模型使用自发光来计算这个部分的贡献度。直接使用改材质的自发光颜色 这个物体不会照亮周围的表面,不会当做一个光源
漫反射 漫反射光照用于对那些被物体表面随机散射到各个方向的辐射度进行建模。在漫反射中,视角的位置并不重要,可以认为在任何反射方向上的分布都是一样的。
高光反射 这里的高光反射是一种经验模型,它可以用于计算完全镜片反射方向被反射的光线 可以让物体看起来有光泽,如金属材质。

6.2.1兰伯特光照模型

漫反射光照符合兰伯特定律(Lambert’s law):

img

Cdiffuse=(Clight·Mdiffuse)max(0,n·l)

  • n是表面法线
  • I是指向光源的单位矢量
  • **m **diffuse是材质的漫反射颜色
  • c light是光源颜色。
  • Lambert 光照模型公式: 最终颜色 = 直射光颜色 * 漫反射颜色 * max(0, dot(光源方向, 法线方向))

这个公式很好理解,就是混合了材质的反射颜色和光源颜色。再将其乘以法向量和入射方向的余弦值,就可以得到最终的漫反射强度。

随着n和L之间的夹角θ逐渐增大,区域dA受到的光线照射量会越来越少 (因为很多光线都无法照射到dA表面上了)。当角度超出[-90, 90]时,点积光源就到了物体的背面。

2.半兰伯特光照模型

半兰伯特模型就是在原模型上进行了一个简单的修改(由Valve公司在开发《半条命》时提出)——不限制区间,而是对光照方向和法线进行缩放以及偏移:

Half Lambert 光照模型公式:

最终颜色 = 直射光颜色 * 漫反射颜色 * (dot(光源方向, 法线方向) * 0.5 + 0.5)

6.2.4 高光反射

这里的高光反射是一种经验模型,它可以用于计算完全镜片反射方向被反射的光线,可以让物体看起来有光泽,如金属材质。

img

计算高光反射需要知道:

  • 表面法线n
  • 视角方向v
  • 光源方向I
  • 反射方向r

反射方向可以通过表面法线和光源方向求得。

shader基础学习摘要(三)高光反射-CSDN博客

在这里插入图片描述

1.Phong光照模型

  • mgloss,是材质的光泽度(gloss),也被称为**反光度(shininess)**。用于控制高光区域的“亮点”有多宽。它的值越大,亮点就越小。
  • mspecular 是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。
  • clight 则是光源的颜色和强度。同样,这里也需要防止**(v·r)**的结果为负数。

2.Blinn-Phong模型

这种模型的基本思想就是避免计算反射方向r。为此,引入了一个新的单位矢量h,通过对单位矢量v和I取平均后再归一化得到,即:

然后使用n与h之间的夹角进行计算:

3.两种模型对比

  • 一般Blinn更快(在摄像机和光源足够远)。
  • 两者都是经验模型。

6.3 逐像素还是逐顶点

  • 在顶点着色器中计算,称为逐顶点光照(per-vertex lighting)
  • 在片元着色器中计算,称为逐像素光照(per-pixel lighting)

1.逐顶点光照

也被称为高洛德着色(Gouraud shading)。在逐顶点光照中,对每个顶点计算光照,然后在渲染图元内部进行线性插值,最后输出像素颜色。由于顶点数量小于像素,因此逐顶点光照的计算量往往小于逐像素光照。但是逐顶点光照依赖于线性插值,因此当光照模型中有非线性计算时,逐顶点光照就会出现问题。

而且由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗与顶点处的最高颜色值,某些情况下会产生明显的棱角现象。

2.逐像素光照

在逐像素光照中,以每个像素为基础,得到他的法线(通过顶点法线插值或者法线纹理采样得到),然后计算光照模型。这种在面片之间对顶点法线进行插值的技术称为Phong着色(Phong shading),也称为Phong插值或法线插值着色技术。

3.总结

比如画一个三角形,顶点为A/B/C,由于光源的影响使得A亮BC暗。 在顶点着色器中计算光效再传给片元着色器,则顶点之间的片元的颜色是OpenGL插值的结果。 在片元着色器中计算光效,则能保证所有片元颜色均是由光源计算得到的。

6.3实现漫反射光照模型

逐顶点光照漫反射模型

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
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1) // 漫反射颜色属性
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" } // 指定此 Pass 用于 ForwardBase 光照模式

CGPROGRAM

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

#include "Lighting.cginc" // 包含 Unity 的光照相关的库

fixed4 _Diffuse; // 漫反射颜色

struct a2v {
float4 vertex : POSITION; // 顶点位置
float3 normal : NORMAL; // 顶点法线
};

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

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

// 获取环境光照分量
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

// 将法线从对象空间变换到世界空间
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 获取光照方向在世界空间中的方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// 计算漫反射分量
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight)));

o.color = ambient + diffuse; // 计算最终颜色

return o;
}

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

ENDCG
}
}
FallBack "Diffuse" // 回退到 Diffuse 材质
}

逐片元光照漫反射模型

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
// 请注意,此代码中包含 Pixel-Level 的漫反射着色器,顶点着色器与像素着色器被分开

Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1) // 漫反射颜色属性
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" } // 指定此 Pass 用于 ForwardBase 光照模式

CGPROGRAM

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

#include "Lighting.cginc" // 包含 Unity 的光照相关的库

fixed4 _Diffuse; // 漫反射颜色

struct a2v {
float4 vertex : POSITION; // 顶点位置
float3 normal : NORMAL; // 顶点法线
};

struct v2f {
float4 pos : SV_POSITION; // 顶点位置
float3 worldNormal : TEXCOORD0; // 世界空间法线
};

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

// 将法线从对象空间变换到世界空间
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

return o;
}

fixed4 frag(v2f i) : SV_Target {
// 获取环境光照分量
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

// 获取世界空间中的法线
fixed3 worldNormal = normalize(i.worldNormal);
// 获取世界空间中的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

// 计算漫反射分量
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

fixed3 color = ambient + diffuse; // 计算最终颜色

return fixed4(color, 1.0); // 输出最终颜色
}

ENDCG
}
}
FallBack "Diffuse" // 回退到 Diffuse 材质
}

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

Home (shaderslab.com)

GAMES101 笔记 (notion.so)