1. 逐顶点光照和逐片元光照

(1)逐顶点光照

  • 在哪计算???
    • 顶点着色器 回调函数中
  • 计算方式:
    • 逐顶点光照会在每个物体的顶点上进行光照计算
    • 这意味着光照计算只在物体的顶点位置上执行
    • 而在顶点之间的内部区域``使用插值来获得颜色信息
  • 优点:
    • 逐顶点光照的计算量较小,通常在移动设备性能较好,适用于移动游戏等要求性能的场景
  • 缺点:
    • 照明效果可能不够精细,特别是在物体表面上的细节区域,因为颜色插值可能不足以捕捉到细微的照明变化
  • 适用场景:
    • 逐顶点光照适用于需要在有限资源下获得较好性能的场景,例如移动游戏

(2)逐片元光照

  • 在哪计算???
    • 片元着色器 回调函数中
  • 计算方式:
    • 逐片元光照会在每个像素(片元)上进行光照计算
    • 这意味着每个像素都会根据其位置、法线、材质等信息独立地进行光照计算
  • 优点:
    • 逐片元光照提供了更高的精细度,可以捕捉到物体表面上的细微照明变化,提供更逼真的效果
  • 缺点:
    • 计算量较大,对于像素密集的场景需要更多的计算资源
  • 适用场景:
    • 逐片元光照通常用于需要高质量照明效果的PC和主机游戏,以及要求视觉逼真度较高的场景。

(3)关于逐顶点光照的插值运算

  • 大概规则:

    • 假设:三角面片的三个顶点A、B、C,该三角面片中的任何像素P,首先会计算出它相对于3个顶点的位置权重

      ​ 然后使用这个权重参与到P点的颜色计算中

    • PixelColorP = WeightA * ColorA + WeightB * ColorB + WeightC * ColorC

(4)为什么逐片元比逐顶点光滑???

  • 为何逐顶点光照比较粗糙??

    顶点以外的像素点颜色,是进行插值运算的结果,效果不真实

  • 为什么逐片元光照的渲染表现更加的平滑??

    每一个像素点都利用其相关数据(法线、位置等)进行颜色计算,效果更真实

    image-20250722140601709 image-20250722140627009

2. 兰伯特光照模型(Lambert)

(1)两个颜色相乘

  • 两个颜色变量相乘,通常用于计算光照材质混合纹理混合等等需求
  • 计算结果可以理解为两种颜色叠加在一起的效果
  • 举例:
    • 颜色A、B都是 fixed4 类型的变量
    • 颜色A * 颜色B = (A.r * B.r, A.g * B.g, A.b * B.b, A.a * B.a);
    • 那么得到的这个结果就是A、B颜色叠加后的结果

(2)漫反射的基本概念

  • 漫反射(Diffuse Reflection)是光线撞击一个物体表面后以各个方向均匀地反射出去的过程
  • 在漫反射下,光线以无规律的方式散射,而不像镜面反射那样按照特定的角度反射。这种散射导致了物体表面看起来均匀而不闪烁的效果。

(3)兰伯特光照模型原理及公式

  • 原理:漫反射光的强度仅与入射光的方向反射点处表面法线的夹角的余弦正比

  • DiffuseLight=L * cosθ(L为入射光的强度)

  • L * N =|P|(投影长度)

  • cosθ =|P|/|L| = |P| (|L|模长为1)

  • 所以|P| = cosθ= L * N

    img

  • 兰伯特光照模型的公式:

    • 漫反射光照颜色 = 光源的颜色 * 材质的漫反射颜色 * max(0, 标准化后物体表面法线向量· 标准化后光源方向向量)
      1. 标准化后物体表面法线向量· 标准化后光源方向向量 得到的结果就是 cosθ
      2. max(0,cosθ)的目的是避免负数,对于模型背面的部分,认为找不到光,直接乘以0,变为黑色

(4)如何获取在Shader中的关键信息

  1. 光源的颜色

    Lighting.cginc 内置文件中的 _LightColor0

  2. 光源的方向

    _WorldSpaceLightPos0 表示光源0在世界坐标系下的位置,方向是从光源到照射的位置

  3. 向量归一化方法

    normalize

  4. 取最大值方法

    max

  5. 点乘方法

    dot

  6. 兰伯特光照模型环境光变量(用于模拟环境光对物体的影响,避免物体阴影部分完全黑暗)

    UNITY_LIGHTMODEL_AMBIENT.rgb

  7. 将法线从模型空间转换到世界空间

    UnityObjectToWorldNormal

(5)实现逐顶点兰伯特光照

  • 效果图:
image-20250721145433803
  • 关键步骤:
    1. 材质漫反射颜色属性声明
    2. 渲染标签Tags设置 将LightMode光照模式 设置为ForwardBase向前渲染(通常用于不透明物体的基本渲染)
    3. 引用内置文件UnityCG.cginc和Lighting.cginc
    4. 结构体声明
    5. 基于公式实现逻辑
  • 注意:为了阴影出不全黑,需要加上兰伯特环境光颜色公共变量
  • 实现如下:
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
Shader "Unlit/Lambert_vert_Test" {
Properties {
//材质漫反射光照颜色
_MainColor ("MainColor", Color) = (1, 1, 1, 1)
}
SubShader {
//设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
Tags { "LightMode" = "ForwardBase" }
LOD 100

Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work


#include "UnityCG.cginc"
#include "Lighting.cginc"

//顶点着色器接受来自应用阶段的数据
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL0;
};

//片元着色器接收顶点着色器的数据
struct v2f {

float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};

//材质漫反射颜色
fixed3 _MainColor;

//逐顶点光照主要写在顶点着色器
v2f vert(appdata v) {
v2f o;
//将顶点坐标从模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
//标准化光源方向向量
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//标准化物体法线向量 光照都在世界空间处理
float3 normal = normalize(UnityObjectToWorldNormal(v.normal));

//记录颜色 传递给片元着色器
//我们加上兰伯特光照模型环境光变量的目的 是希望阴影处不要全黑 不然看起来有一些不自然
//目的就是为了让表现效果更接近于真实世界 所以需要加上它
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(lightDir, normal)) + UNITY_LIGHTMODEL_AMBIENT.rgb;
o.color = color;
return o;
}

fixed4 frag(v2f i) : SV_Target {
//把计算好的颜色传递出去
return fixed4(i.color.rgb, 1);
}
ENDCG
}
}
}

(6)实现逐片元兰伯特光照

  • 逐片元效果图:

    image-20250721150531247
  • 关键步骤:基本和逐顶点一致,只是计算的位置不一样

  • 实现代码:

    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 "Unlit/Lambert_frag" {
    Properties {
    //材质漫反射光照颜色
    _MainColor ("MainColor", Color) = (1, 1, 1, 1)
    }
    SubShader {
    //设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
    Tags { "LightMode" = "ForwardBase" }
    LOD 100

    Pass {
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // make fog work


    #include "UnityCG.cginc"
    #include "Lighting.cginc"

    //顶点着色器接受来自应用阶段的数据
    struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL0;
    };

    //片元着色器接收顶点着色器的数据
    struct v2f {
    float3 normal : NORMAL;
    float4 vertex : SV_POSITION;
    };

    //材质漫反射颜色
    fixed3 _MainColor;

    //逐片元,在顶点着色器中,只需做空间变换就行
    v2f vert(appdata v) {
    v2f o;
    //将顶点坐标从模型空间转换到裁剪空间
    o.vertex = UnityObjectToClipPos(v.vertex);
    //标准化物体法线向量 光照都在世界空间处理
    o.normal = UnityObjectToWorldNormal(v.normal);


    return o;
    }

    //逐片元光照主要写在片元着色器
    fixed4 frag(v2f i) : SV_Target {
    //标准化光源方向向量 它实际上是指向光源的
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

    //记录颜色 传递给片元着色器
    //我们加上兰伯特光照模型环境光变量的目的 是希望阴影处不要全黑 不然看起来有一些不自然
    //目的就是为了让表现效果更接近于真实世界 所以需要加上它
    fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(lightDir, normalize(i.normal))) + UNITY_LIGHTMODEL_AMBIENT.rgb;
    //把计算好的颜色传递出去
    return fixed4(color.rgb, 1);
    }
    ENDCG
    }
    }
    }

3. 半兰伯特光照模型(Half-Lambert)

(1)单位向量点乘的结果范围

  • 假设单位向量A单位向量B,它们的点乘结果为:A·B = |A| * |B| * cosθ = cosθ
  • 它们的结果是 -1 ~ 1
  • 根据 漫反射光照颜色 = 光源的颜色 * 材质的漫反射颜色 * max(0, 标准化后物体表面法线向量· 标准化后光源方向向量)这个公式,结果为负数的情况会直接变成 0,所以背光面变黑。

(2)半兰伯特光照模型的来历和原理

  • 半兰伯特光照模型是基于 兰伯特光照模型 的基础上进行改进的
  • 它出现的主要原因是因为我们在使用兰伯特光照模型时,在背光面是全黑的,而半兰伯特光照模型可以让背光面也可以有明暗变化。
  • 原理:
    • 和兰伯特光照模型的理论是一样的,只是公式映射的区间不同

(3)半兰伯特光照模型的公式

  • 公式:
    • 漫反射光照颜色 = 光源的颜色 * 材质的漫反射颜色 * ((标准化后物体表面法线向量· 标准化后光源方向向量) * 0.5 + 0.5)
  • 对比:
    • 兰伯特光照模型的 后半部分: max(0, 标准化后物体表面法线向量· 标准化后光源方向向量)
      • 点乘小于0的部分都会变成0
    • 半兰伯特光照模型的 后半部分: ((标准化后物体表面法线向量· 标准化后光源方向向量)* 0.5 + 0.5)
      • 点乘小于0的部分都会变成 0 ~ 0.5
    • -1 ~ 1 映射到了 0 ~ 1

(4)半兰伯特光照逐顶点和逐片元的实现

  • 具体内容与兰伯特实现几乎一样
  • 只需要改变一下公式内容即可。
  • 效果展示:

    • 半兰伯特光照模型(逐顶点)
    image-20250721181052647
    • 代码:

      1
      fixed3 color = _LightColor0.rgb * _MainColor.rgb * (dot(lightDir, normal) * 0.5 + 0.5) + UNITY_LIGHTMODEL_AMBIENT.rgb;
    • 半兰伯特光照模型(逐片元)

      image-20250721181255338

4.Phong式高光反射模型

(1) Phong式高光反射光照模型的原理

  • 原理:

    • 基于光的反射行为和观察者的位置决定高光反射的表现效果

    • 认为高光反射的颜色和 光源的反射光线以及观察者位置方向向量夹角的余弦成正比

    • 并且通过对余弦值取n次幂来表示光泽度(或反光度)

      US3S1L4——Phong式高光反射光照模型 | 文KRIFE齐的博客

(2)Phong式高光反射光照模型的公式

  • 公式:
    • 高光反射光照颜色 = 光源的颜色 * 材质高光反射颜色 * max(0, 标准化后观察方向向量· 标准化后的反射方向)幂
      1. 标准化后观察方向向量· 标准化后的反射方向 得到的结果就是 cosθ
      2. 幂 代表的是光泽度 余弦值取n次幂

(3)如何在Shader中获取公式中的关键信息

  1. 观察者的位置(摄像机的位置)

    _WorldSpaceCameraPos

  2. 相对于法向量的反射向量 方法

    reflect(入射向量,顶点法向量) 返回反射向量

  3. 指数幂 方法

    pow(底数,指数) 返回计算结果

(4)实现Phong式高光反射模型

  • 逐顶点光照:

    • 效果:

      image-20250721204219816

    • 代码:

      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
      Shader "Unlit/PhongHighLigh_vert" {
      Properties {
      _MainColor ("MainColor", Color) = (1, 1, 1, 1)
      _LightValue ("LightValue", Range(-10, 10)) = 1
      }
      SubShader {
      Tags { "LightMode" = "ForwardBase" }
      LOD 100

      Pass {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      // make fog work
      #pragma multi_compile_fog

      #include "UnityCG.cginc"
      #include "Lighting.cginc"
      struct appdata {
      float4 vertex : POSITION;
      float3 normal : NORMAL;
      };

      struct v2f {
      fixed3 color : COLOR;
      float4 vertex : SV_POSITION;
      };

      //材质高光反射颜色
      fixed3 _MainColor;
      //光泽度
      float _LightValue;

      //在顶点着色器实现逐顶点
      v2f vert(appdata v) {
      v2f o;
      //计算世界坐标下的顶点坐标
      //计算视角方向 指向摄像机
      float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - UnityObjectToWorldDir(v.vertex).xyz);
      //计算入射光方向 实际上,这个是指向光源的,实际适用时要取反
      float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

      //计算反射方向 reflect(入射向量,顶点法向量) 返回反射向量
      float3 reflectDir = normalize(reflect(-lightDir, UnityObjectToWorldNormal(v.normal)));
      //`高光反射光照颜色` = `光源的颜色` * `材质高光反射颜色` * max(0, 标准化后观察方向向量· 标准化后的反射方向)幂
      o.color = _LightColor0.rgb * _MainColor.rgb * pow(max(0, dot(viewDir, reflectDir)), _LightValue);

      o.vertex = UnityObjectToClipPos(v.vertex);
      return o;
      }

      fixed4 frag(v2f i) : SV_Target {

      return fixed4(i.color.rgb, 1);
      }
      ENDCG
      }
      }
      }
  • 逐片元光照:

    • 效果:

      image-20250721204802464
    • 代码:

      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
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      		Shader "Unlit/PhongHighLight_frag" {
      Properties {
      _MainColor ("MainColor", Color) = (1, 1, 1, 1)
      _LightValue ("LightValue", Range(0, 20)) = 1
      }
      SubShader {
      Tags { "LightMode" = "ForwardBase" }
      LOD 100

      Pass {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      // make fog work
      #pragma multi_compile_fog

      #include "UnityCG.cginc"
      #include "Lighting.cginc"
      struct appdata {
      float4 vertex : POSITION;
      float3 normal : NORMAL;
      };

      struct v2f {
      //世界空间下顶点
      float3 wVertex : TEXCOORD0;
      //裁剪空间下的顶点
      float4 vertex : SV_POSITION;
      //世界空间下的法线
      float3 normal : NORMAL;
      };

      //材质高光反射颜色
      fixed3 _MainColor;
      //光泽度
      float _LightValue;
      v2f vert(appdata v) {
      v2f o;

      o.normal = UnityObjectToWorldNormal(v.normal);
      o.vertex = UnityObjectToClipPos(v.vertex);
      o.wVertex = UnityObjectToWorldDir(v.vertex);
      return o;
      }
      //在片元着色器实现逐片元
      fixed4 frag(v2f i) : SV_Target
      {
      //计算世界坐标下的顶点坐标
      //计算视角方向 指向摄像机
      float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wVertex.xyz);
      //计算入射光方向 实际上,这个是指向光源的,实际适用时要取反
      float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

      //计算反射方向 reflect(入射向量,顶点法向量) 返回反射向量
      float3 reflectDir = normalize(reflect(-lightDir, UnityObjectToWorldNormal(i.normal)));
      //`高光反射光照颜色` = `光源的颜色` * `材质高光反射颜色` * max(0, 标准化后观察方向向量· 标准化后的反射方向)幂
      fixed3 color = _LightColor0.rgb * _MainColor.rgb * pow(max(0, dot(viewDir, reflectDir)), _LightValue);
      return fixed4(color.rgb, 1);
      }
      ENDCG
      }
      }
      }


      # 5.`Phong式光照模型`

      ## (1)两个颜色相加

      * 作用:同样是让两个颜色叠加在一起
      * 它和两个颜色相乘的`区别`是:
      * 相乘:
      * 颜色相乘时,最终颜色会`往黑色靠`拢
      * 计算两个`颜色混合`时一般`用颜色相乘`
      * 因为真实世界中多个颜色混在一起最终会变成黑色
      * 相加:
      * 颜色相加时,最终颜色会往白色靠拢
      * `计算光照反射`时一般`用颜色相加`,因为向白色靠拢能带来 更亮的感觉,复合光的表现

      ## (2)Unity Shader中的环境光

      * 在计算完漫反射光照后,加上了一个环境光变量 `UNITY_LIGHTMODEL_AMBIENT`
      * 那么这个环境光变量其实是可以在Unity中进行设置的
      * Window——>Rendering——>Lighting
      * Environment(环境)页签中的Environment Lighting(环境光),这里可以设置环境光来源。
      * 当是Skybox和Color时,我们可以通过 UNITY_LIGHTMODEL_AMBIENT 获取到对应环境光颜色
      * 当是Gradient(渐变)时,通过以下3个成员可以得到对应的环境光
      * unity_AmbientSky(周围的天空环境光)
      * unity_AmbientEquator(周围的赤道环境光)
      * unity_AmbientGround(周围的地面环境光)

      ## (3)Phong光照模型`原理和公式`

      * 裴祥风认为物体`表面反射光线`是由`三部分组成`的
      * 环境光 + 漫反射光 + 镜面反射光(高光反射光)

      * Phong光照模型公式:
      * 物体表面光照颜色 = 环境光颜色 + 漫反射光颜色 + 高光反射光颜色
      * 其中:
      * 环境光颜色 = UNITY_LIGHTMODEL_AMBIENT(unity_AmbientSky、unity_AmbientEquator、unity_AmbientGround)
      * 漫反射光颜色 = 兰伯特光照模型 计算得到的颜色
      * 高光反射光颜色 = Phong式高光反射光照模型 计算得到的颜色

      ## (4)实现Phong光照模型

      * 其实就是用学过的兰伯特光照和phong高光反射模型混合计算。

      * 逐片元效果:

      <img src="https://skymirror-1316984539.cos.ap-beijing.myqcloud.com/Typoraimage-20250721230603240.png" alt="image-20250721230603240" style="zoom: 50%;" />

      * 逐顶点效果:

      <img src="https://skymirror-1316984539.cos.ap-beijing.myqcloud.com/Typoraimage-20250721224957962.png" alt="image-20250721224957962" style="zoom:50%;" />

      * 逐片元代码



      ```C#
      Shader "Unlit/PhongLight_frag" {

      Properties {
      _LambertColor ("LambertColor", Color) = (1, 1, 1, 1)
      _PhongHighLightColor ("PhongHighLightColor", Color) = (1, 1, 1, 1)
      _HighLightValue ("HighLightValue", Range(0, 20)) = 10
      }
      SubShader {

      LOD 100

      Pass {
      Tags { "LightMode" = "ForwardBase" }
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag


      #include "UnityCG.cginc"
      // #include "Lighting.cginc"
      #include "UnityLightingCommon.cginc"
      struct appdata {

      float4 vertex : POSITION;
      float3 normal : NORMAL;
      };

      struct v2f {

      float3 normal : NORMAL;
      float4 vertex : SV_POSITION;
      float3 wpos : TEXCOORD0;
      };


      //材质漫反射颜色
      fixed3 _LambertColor;
      //材质高光反射颜色
      fixed3 _PhongHighLightColor;
      //高光 光泽度
      float _HighLightValue;



      //计算Lambert光照函数
      //参数为:世界空间下的法线
      fixed3 LambertColor(float3 normal) {
      //标准化漫反射入射光方向
      float3 lamLightDir = normalize(_WorldSpaceLightPos0.xyz);
      //标准化法线
      float3 lamNormal = normalize(normal);
      //计算漫反射光照颜色 这里直接用半兰伯特
      return _LightColor0.rgb * _LambertColor.rgb * max(0, dot(lamLightDir, lamNormal)) ;
      }
      //计算Phong高光颜色
      //参数:世界空间顶点,世界空间法线
      fixed3 PhongColor(float3 vertex, float3 normal) {
      //计算观察方向向量
      float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - vertex);
      //计算入射光向量
      float3 phongLightDir = normalize(_WorldSpaceLightPos0);//方向为指向光源
      //计算反射光向量
      float3 reflectDir = normalize(reflect(-phongLightDir, normal));

      //计算高光颜色
      return _LightColor0 * _PhongHighLightColor * pow(max(0, dot(viewDir, reflectDir)), _HighLightValue);
      }


      v2f vert(appdata v) {
      v2f o;
      o.vertex = UnityObjectToClipPos(v.vertex);
      o.wpos = UnityObjectToWorldDir(v.vertex);
      o.normal = UnityObjectToWorldNormal(v.normal);
      return o;
      }

      fixed4 frag(v2f i) : SV_Target {

      //计算漫反射光照
      fixed3 lamColor = LambertColor(i.normal);


      //计算高光反射
      fixed3 phongColor = PhongColor(i.wpos, i.normal);

      //环境光 + 漫反射光 + 高光
      fixed3 color = UNITY_LIGHTMODEL_AMBIENT + lamColor + phongColor;
      return fixed4(color.rgb, 1);
      }

      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
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    Shader "Unlit/PhongLight_vert" {
    Properties {
    _LambertColor ("LambertColor", Color) = (1, 1, 1, 1)
    _PhongHighLightColor ("PhongHighLightColor", Color) = (1, 1, 1, 1)
    _HighLightValue ("HighLightValue", Range(0, 20)) = 10
    }
    SubShader {

    LOD 100

    Pass {
    Tags { "LightMode" = "ForwardBase" }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag


    #include "UnityCG.cginc"
    // #include "Lighting.cginc"
    #include "UnityLightingCommon.cginc"
    struct appdata {

    float4 vertex : POSITION;
    float3 normal : NORMAL;
    };

    struct v2f {

    fixed3 color : COLOR;
    float4 vertex : SV_POSITION;
    };


    //材质漫反射颜色
    fixed3 _LambertColor;
    //材质高光反射颜色
    fixed3 _PhongHighLightColor;
    //高光 光泽度
    float _HighLightValue;



    //计算Lambert光照函数
    //参数为:模型空间下的法线
    fixed3 LambertColor(float3 normal) {
    //标准化漫反射入射光方向
    float3 lamLightDir = normalize(_WorldSpaceLightPos0.xyz);
    //标准化法线
    float3 lamNormal = normalize(UnityObjectToWorldNormal(normal));
    //计算漫反射光照颜色 这里直接用半兰伯特
    return _LightColor0.rgb * _LambertColor.rgb * max(0, dot(lamLightDir, lamNormal)) ;
    }
    //计算Phong高光颜色
    //参数:模型空间顶点,模型空间法线
    fixed3 PhongColor(float4 vertex, float3 normal) {
    //计算观察方向向量
    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - UnityObjectToWorldDir(vertex));
    //计算入射光向量
    float3 phongLightDir = normalize(_WorldSpaceLightPos0);//方向为指向光源
    //计算反射光向量
    float3 reflectDir = normalize(reflect(-phongLightDir, UnityObjectToWorldNormal(normal)));

    //计算高光颜色
    return _LightColor0 * _PhongHighLightColor * pow(max(0, dot(viewDir, reflectDir)), _HighLightValue);
    }


    v2f vert(appdata v) {
    v2f o;
    //计算漫反射光照
    fixed3 lamColor = LambertColor(v.normal);


    //计算高光反射
    fixed3 phongColor = PhongColor(v.vertex, v.normal);
    o.vertex = UnityObjectToClipPos(v.vertex);
    //环境光 + 漫反射光 + 高光
    o.color = UNITY_LIGHTMODEL_AMBIENT + lamColor + phongColor;
    return o;
    }

    fixed4 frag(v2f i) : SV_Target {

    return fixed4(i.color, 1);
    }

    ENDCG
    }
    }
    }

6. Blinn-Phong式高光反射光照模型

(1)Blinn-Phong式高光反射光照模型的来历和原理

  • Blinn-Phong的来历:

    • Phong模型会出现光照截断或者过度不自然的现象。即断层现象:

      img
    • 出现这种的情况的原因是视角和反射矢量的角度不能超过90度。如果角度大于90度,点积的结果为负导致镜面光分量值为0.0。

    • BlinnPhong使用视线与反射光线的中间向量与法线的点乘进行计算,这样做在任何情况下镜面反射都不会小于0。从而解决Phong模型面临的问题。

  • Blinn-Phong的原理

    • 使用半角向量来替代反射向量进行高光计算

    • 半角向量为:视角方向灯光方向的角平分线方向

    • 认为高光反射的颜色和 顶点法线向量以及半角向量夹角的余弦成正比,并且通过对余弦值取n次幂来表示光泽度(或反光度)

      img

(2) Blinn-Phong式高光反射光照模型的公式

  • 公式:
    • 高光反射光照颜色 = 光源的颜色 * 材质高光反射颜色 * max(0, 标准化后顶点法线方向向量 · 标准化后半角向量方向向量)幂
      1. 标准化后顶点法线方向向量 · 标准化后半角向量方向向量 得到的结果就是 cosθ
      2. 半角向量方向向量 = 视角单位向量 + 入射光单位向量
      3. 幂 代表的是光泽度 余弦值取n次幂

(3)Phong和Blinn-Phong的区别

  1. 高光散射
    • Blinn-Phong模型的高光:通常会产生相对均匀的高光散射,这会使物体看起来光滑而均匀。
    • Phong模型的高光:可能会呈现更为锐利的高光散射,因为它基于观察者和光源之间的夹角。这可能导致一些区域看起 来特别亮,而另一些区域则非常暗
  2. 高光锐度
    • Blinn-Phong模型的高光:通常具有较广的散射角,因此看起来不那么锐利。
    • Phong模型的高光:可能会更加锐利,特别是在观察者和光源夹角较小时,可能表现为小而亮的点。
  3. 光滑度和表面纹理
    • Blinn-Phong模型:通常更适合表现光滑的表面,因为它考虑了表面微观凹凸之间的相互作用,使得光照在表面上更加均 匀分布。
    • Phong模型:有时更适合表现具有粗糙表面纹理的物体,因为它的高光散射可能会使纹理和细节更加突出。
  4. 镜面高光大小
    • Blinn-Phong模型:通常产生的镜面高光相对较大,但均匀分布。
    • Phong模型:可能会产生较小且锐利的镜面高光

(4)实现Blinn-Phong高光反射模型

  • 逐顶点实现

    • 效果:

      image-20250722115024309
      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
      65
      Shader "Unlit/BlinnPhongHighLight_vert"
      {
      Properties
      {
      _MainColor ("MainColor", Color) = (1,1,1,1)
      _ColorValue("ColorValue",Range(0,20)) =10
      }
      SubShader
      {

      LOD 100

      Pass
      {
      Tags { "LightMode"="ForwardBase" }
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag



      #include "UnityCG.cginc"
      #include "Lighting.cginc"
      struct appdata
      {
      float4 vertex : POSITION;
      float3 normal :NORMAL;
      };

      struct v2f
      {
      fixed3 color :COLOR;
      float4 vertex : SV_POSITION;
      };
      //材质高光反射颜色
      fixed3 _MainColor;
      //光泽度
      float _ColorValue;
      v2f vert (appdata v)
      {
      v2f o;
      //计算标准化视角方向
      float3 viewDir =normalize(_WorldSpaceCameraPos.xyz - UnityObjectToWorldDir(v.vertex).xyz);
      //计算入射光线方向 指向光源
      float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
      //计算半角向量
      float3 halfDir = normalize(viewDir+lightDir);
      //计算标准化法线
      float3 normal = UnityObjectToWorldNormal(v.normal);
      o.color = _LightColor0 * _MainColor * pow(max(0,dot(normal,halfDir)),_ColorValue)+UNITY_LIGHTMODEL_AMBIENT;
      o.vertex = UnityObjectToClipPos(v.vertex);

      return o;
      }

      fixed4 frag (v2f i) : SV_Target
      {

      return fixed4(i.color.rgb,1);
      }
      ENDCG
      }
      }
      }

  • 逐片元实现

    • 效果:

      image-20250722115158432
    • 代码:

      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
      Shader "Unlit/BlinnPhongHighLight_frag" {
      Properties {
      _MainColor ("MainColor", Color) = (1, 1, 1, 1)
      _ColorValue ("ColorValue", Range(0, 20)) = 10
      }
      SubShader {

      LOD 100

      Pass {
      Tags { "LightMode" = "ForwardBase" }
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag


      #include "UnityCG.cginc"
      #include "Lighting.cginc"
      struct appdata {
      float4 vertex : POSITION;
      float3 normal : NORMAL;
      };

      struct v2f {
      //裁剪空间下顶点
      float4 vertex : SV_POSITION;
      //世界空间下的顶点
      float3 wPos : TEXCOORD0;
      //世界空间下法线
      float3 normal : NORMAL;
      };
      //材质高光反射颜色
      fixed3 _MainColor;
      //光泽度
      float _ColorValue;


      v2f vert(appdata v) {
      v2f o;


      o.normal = UnityObjectToWorldNormal(v.normal);
      o.vertex = UnityObjectToClipPos(v.vertex);
      o.wPos = UnityObjectToWorldDir(v.vertex);
      return o;
      }

      fixed4 frag(v2f i) : SV_Target {
      //计算标准化视角方向
      float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos);
      //计算入射光线方向 指向光源
      float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
      //计算半角向量
      float3 halfDir = normalize(viewDir + lightDir);
      fixed3 color = _LightColor0 * _MainColor * pow(max(0, dot(halfDir, i.normal)), _ColorValue) + UNITY_LIGHTMODEL_AMBIENT;
      return fixed4(color.rgb, 1);
      }
      ENDCG
      }
      }
      }

7.Blinn-Phong光照模型

(1)Blinn Phong光照模型的原理

  • 原理:
    • Blinn Phong和Phong光照模型一样,认为物体表面反射光线是由三部分组成的
    • 环境光 + 漫反射光 + 镜面反射光(高光反射光)

(2)Blinn Phong光照模型的公式

  • 公式:
    • 物体表面光照颜色 = 环境光颜色 + 漫反射光颜色 + 高光反射光颜色
    • 其中:
      • 环境光颜色 = UNITY_LIGHTMODEL_AMBIENT(unity_AmbientSky、unity_AmbientEquator、unity_AmbientGround)
      • 漫反射光颜色 = 兰伯特光照模型 计算得到的颜色
      • 高光反射光颜色 = Blinn Phong式高光反射光照模型 计算得到的颜色

(3)逐顶点实现BlinnPhong

  • 效果

    image-20250722135158695

  • 代码:

    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
    65
    66
    67
    68
    69
    70
    71
    72
    Shader "Unlit/BlinnPhongLight_vert" {
    Properties {
    _LambertColor ("LambertColor", Color) = (1, 1, 1, 1)
    _BlinnPhongColor ("BlinnPhongColor", Color) = (1, 1, 1, 1)
    _HighLightValue ("HighLightValue", Range(0, 20)) = 10
    }
    SubShader {

    LOD 100

    Pass {
    Tags { "LightMode" = "ForwardBase" }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag


    #include "UnityCG.cginc"
    #include "Lighting.cginc"

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

    struct v2f {
    fixed3 color : COLOR;
    float4 vertex : SV_POSITION;
    };
    //材质漫反射颜色
    fixed3 _LambertColor;
    //材质高光颜色
    fixed3 _BlinnPhongColor;
    //光泽度
    float _HighLightValue ;
    //兰伯特漫反射光照颜色
    fixed3 LambertColor(float3 wNormal) {
    //标准化入射光方向
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
    return _LightColor0 * _LambertColor * max(0, dot(lightDir, wNormal));
    }
    //BlinnPhong高光颜色
    fixed3 BlinnPhongColor(float3 wPos, float3 wNormal) {
    //计算标准化视角方向
    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos.xyz);
    //计算入射光线方向
    float3 lightDir = normalize(_WorldSpaceLightPos0);
    //计算半角向量方向
    float3 halfDir = normalize(viewDir + lightDir);
    return _LightColor0 * _BlinnPhongColor * pow(max(0, dot(wNormal, halfDir)), _HighLightValue);
    }

    v2f vert(appdata v) {
    v2f o;
    //世界坐标下法线
    float3 normal = UnityObjectToWorldNormal(v.normal);
    //世界坐标下顶点
    float3 pos = UnityObjectToWorldDir(v.vertex);
    //颜色混合
    o.color = LambertColor(normal) + BlinnPhongColor(pos, normal) + UNITY_LIGHTMODEL_AMBIENT;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
    }

    fixed4 frag(v2f i) : SV_Target {

    return fixed4(i.color.rgb, 1);
    }
    ENDCG
    }
    }
    }

(4)逐片元实现BlinnPhong

  • 效果:

    image-20250722135302817

  • 代码:

    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
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    Shader "Unlit/BlinnPhongLight_frag" {
    Properties {
    _LambertColor ("LambertColor", Color) = (1, 1, 1, 1)
    _BlinnPhongColor ("BlinnPhongColor", Color) = (1, 1, 1, 1)
    _HighLightValue ("HighLightValue", Range(0, 20)) = 10
    }
    SubShader {

    LOD 100

    Pass {
    Tags { "LightMode" = "ForwardBase" }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag


    #include "UnityCG.cginc"
    #include "Lighting.cginc"

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

    struct v2f {

    float4 vertex : SV_POSITION;
    float3 wPos : TEXCOORD0;
    float3 wNormal : NORMAL;
    };
    //材质漫反射颜色
    fixed3 _LambertColor;
    //材质高光颜色
    fixed3 _BlinnPhongColor;
    //光泽度
    float _HighLightValue ;
    //兰伯特漫反射光照颜色
    fixed3 LambertColor(float3 wNormal) {
    //标准化入射光方向
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
    return _LightColor0 * _LambertColor * max(0, dot(lightDir, wNormal));
    }
    //BlinnPhong高光颜色
    fixed3 BlinnPhongColor(float3 wPos, float3 wNormal) {
    //计算标准化视角方向
    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos.xyz);
    //计算入射光线方向
    float3 lightDir = normalize(_WorldSpaceLightPos0);
    //计算半角向量方向
    float3 halfDir = normalize(viewDir + lightDir);
    return _LightColor0 * _BlinnPhongColor * pow(max(0, dot(wNormal, halfDir)), _HighLightValue);
    }

    v2f vert(appdata v) {
    v2f o;
    //世界坐标下法线
    o.wNormal = UnityObjectToWorldNormal(v.normal);
    //世界坐标下顶点
    o.wPos = UnityObjectToWorldDir(v.vertex);


    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
    }

    fixed4 frag(v2f i) : SV_Target {

    fixed3 color = LambertColor(i.wNormal) + BlinnPhongColor(i.wPos, i.wNormal) + UNITY_LIGHTMODEL_AMBIENT;
    return fixed4(color.rgb, 1);
    }
    ENDCG
    }
    }
    }

8. 常用的光照函数

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
//1. float3 WorldSpaceViewDir(float4 v)
// 传入模型空间下的顶点位置,返回世界空间中从该点到摄像机的观察方向

//2. float3 UnityWorldSpaceViewDir(flaot4 v)
// 传入世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向

//3. float3 ObjSpaceViewDir(float4 v)
// 传入模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向

//4. float3 WorldSpaceLightDir(float4 v)
// (仅用于向前渲染中)
// 传入模型空间中的顶点位置,返回世界空间中该点到光源的光照方向(没有归一化)

//5. float3 UnityWorldSpaceLightDir(float4 v)
// (仅用于向前渲染中)
// 传入世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向(没有归一化)

//6. float3 ObjSpaceLightDir(float4 v)
// (仅用于向前渲染中)
// 传入模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向(没有归一化)

//7. float3 UnityObjectToWorldNormal(float3 normal)
// 把法线从模型空间转换到世界空间中

//8. float3 UnityObjectToWorldDir(float3 dir)
// 把方向矢量从模型空间转换到世界空间中

//9. float3 UnityWorldToObjectDir(float3 dir)
// 把方向矢量从世界空间转换到模型空间中

//10.float4 UnityObjectToClipPos(float4 v)
// 输入模型空间中的顶点位置,返回裁剪空间下的顶点位置

9. 关于光照方向的计算方式

  1. 模拟定向光源

    直接得到_WorldSpaceLightPos0光照位置 作为光照方向

    • 表示光线是平行的,而不是从特定点发射
    • 一般模拟太阳光效果 采用这种方式
  1. 模拟点光源

    用光照位置_WorldSpaceLightPos0 减去 顶点坐标

    • 表示光线是从特定点发射的,并朝着顶点方向
    • 一般定点光源 采用这种方式