前言

  • 对应 《Games101》

    • 07 着色(光照与基本着色模型)

    • 13 光线追踪(基本原理)

  • 《 Unity Shader 精要》

    • 06 Unity中的基础光照

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 材质
}

Blinn-Phong光照模型

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
Shader "Unity Shaders Book/Chapter 6/BlinnPhong" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

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

struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
fixed3 worldPos : TEXCOORD1;
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

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 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir+viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

return fixed4(ambient + diffuse + specular, 1.0);
}

ENDCG

}
}
Fallback "Specular"
}