实习的悲惨事故,主要在两个细小得错误上花费了特别多的时间,两个都是出在宏定义的地方,先把这个东西高高挂起

#pragma multi_compile_fwdadd

写成了multi_comple_fwdadd,导致在写多光源部分找了很久没找到问题,一直以为是代码逻辑出错

光照

光照计算的思考方式

  1. 光源
  2. 模型表面材质结构
  3. 观察方向

光源

  1. 光源类型
    1. 聚光灯和点光有衰减
    2. 光源方向
    3. 光源颜色
  2. 灯光数据传递方式—RenderPath
    1. Forward 前向渲染
      1. Unity内置渲染管线Built-in
      2. Unity URP渲染管线
        1. 光照方面,能够在单个pass处理多个灯光
    2. Deferred 延迟渲染:以灯光为单位进行渲染,因为使用了MRT
      1. UE4默认路径
      2. Unity UDRP渲染管线

RenderPath

决定了shader是以怎样的一个规则去计算灯光的

  • 前向渲染:unity内置的渲染管线, unity URP渲染管线

  • 延迟渲染:UE4默认渲染路径, Unity HDRP渲染管线

前向渲染:物体被重复叠加绘制多次(实时灯光的数量)

forwardadd函数

前向渲染的消耗问题,引擎edit -> project setting -> quality -> pixel light count,不调整会出现粗糙灯光

超过了light count的灯光,就会被当作顶点光源,只能在顶点shader里进行计算

需要shader里面写两个pass:

  1. forward base:主方向光,也处理超出数量的顶点灯光
  2. forward pass:结果通过 blend one one的方式进行叠加

延迟渲染:

主要两个阶段

  1. RenderDeferred gbuffer
    这里使用了MRT — Multi Render Target
    RT0 diffuse
    RT1 金属度
    RT2 法线数据
    Depth 深度
  2. RenderDeferred lighting
    以灯光为单位对场景进行渲染,因为把需要的信息已经提前渲染好了

延迟渲染对多光源友好,有一定使用瓶颈,占用带宽高,普通机型可能受不了,前向渲染就很轻量化

怎么判断是否是主方向光 — 光的亮度最大那个

模型表面材质结构

  1. 顶点法线
  2. 法线贴图
  3. 光滑度
  4. PBR理论框架

多光源的实现

通过forwardadd pass实现

第二个pass修改地方

1
2
3
4
Tags{"LightMode"="ForwardAdd"}
Blend One One
#pragma multi_compile_fwdadd
#include "AutoLight.cginc"

衰减的计算

1
2
3
4
5
6
7
8
9
10
11
12
#if defined(DIRECTIONAL)
// 平行光
half3 light_dir = _WorldSpaceLightPos0.xyz;
half attuenation = 1.0
#elif defined(POINT)
// 点光
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
// 衰减范围计算
half distance = length(_WorldSpaceLightPos0.xyz - i.pos_world);
half range = 1.0 / unity_WorldToLight[0][0];
half attuenation = saturate((range - distance)/range);
#endif

法线贴图

思路:

  1. 切线是在模型导入的时候确定的,根据UV中U方向走向定义的

  2. 再通过法线和切线cross得到副切线,就构成了切线空间

在shader中关键点在tangent.w分量的处理,主要是用来处理不同平台法线反转的问题

压缩格式问题:PC上DXT/BC对法线贴图压缩,这样通道信息就会变更,只剩红色通道和alpha通道(所以红色不一定是错的)

在shader还需要解码操作,因为法线贴图范围是[0,1],但是法线数据需要[-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
vert(appdata v){
...
...
o.normal_dir = normalize(mul(float4(v.normal, 0.0),unity_WorldToObject).xyz);
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz,0.0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w;
...
}

frag(v2f i):SV_TARGET{
...
...
half4 normalmap = tex2D(_NormalMap, i.uv);
half3 normal_data = UnpackNormal(normalmap);

// normalmap
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);

normal_dir = normalize(tangent_dir * normal_data.x + binormal_dir * normal_data.y + normal_dir * normal_data.z);
...
}

blinn-phong

在phong上进行了优化,使用了半程向量

半程向量是光照和视线方向的中间

1
2
half3 half_dir = normalize(light_dir + view_dir);
half NdotH = dot(normalize_dir, half_dir);

显示器的输出结果是在0-1的,但是shader的返回值可以很大,因此会有过曝