Directional Lights
对 Catlike Coding 教程的解析与补充
点击传送——>Directional Lights
1.设置多个Pass
1 2
| private static ShaderTagId litShaderTagId = new ShaderTagId("CustomLit"); drawingSettings.SetShaderPassName(1, litShaderTagId);
|
DrawRendererSettings.SetShaderPassName(index,shaderPassName), index是Pass索引,用于控制绘制传递的顺序,shaderPassName为Pass名称
2.计算入射光照
1 2 3 4
| float3 IncomingLight(Surface surface, Light light) { return saturate(dot(surface.normal, light.direction)) * light.color; }
|
- 表面法线与光照方向点积后乘灯光颜色得到入射光照
- saturate(v) 将v夹取到[0,1]区间(表面朝向光源时才是对的)
3.向GPU发送灯光数据
1 2 3 4 5 6
| private void SetupDirectionLight() { Light light = RenderSettings.sun; buffer.SetGlobalVector(dirLightColorId, light.color.linear * light.intensity); buffer.SetGlobalVector(dirLightDirectionId, -light.transform.forward); }
|
- RenderSettings.sun 访问场景主光源(可以在 Window -> Rendering -> LightingSetting 设置)
- color.linear 关于为什么用线性值:
物理世界中光的强度和亮度成正比,也就是线性(Linear)关系,但光电转换(显示器)却不是线性的,亮度增加量等于电压增加量的2.2次幂,这个值称为显示器的Gamma值(当然不一定都是这个值),也就是说当电压线性变化时,对比线性空间,Gamma空间的亮度在暗处变化慢占据数据范围更广,颜色会整体偏暗。当然计算机在处理的时候肯定是直接使用线性空间(对计算机而言都是数据)
- Gamma校正:因为显示器显示的颜色空间为非线性,当我们输入一张线性空间的图片,输出时颜色就会失真,这时我们需要校正其色彩。实际上只需要进行一个Gamma值的逆运算$c_0=c_{i}^{\frac{1}{Gamma}}$,即可让图片回归线性空间,输出的图片就和物理世界一样。
- sRGB:上边说了Gamma校正的原理,既然大家都需要做Gamma校正,不妨直接开发一个标准,大家都遵循这个标准,对物理空间的颜色预先做一次处理,然后再进过显示器输出得到物理世界的真实色彩。这个标准就是sRGB,在输出图片时直接对图片进行一次$\frac{1}{Gamma}$的处理(这个幂次一般是1÷2.2≈0.45)
- -light.transform.forward 这里取负是因为需要光的来源方向而不是照射方向
4.发送多个光源数据
1 2 3 4
| private void SetupLights() { NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights; }
|
- 不用List用NativeArray的原因是:不需要GC,生成销毁代价低,适合作为临时数据结构
- cullingResults.visibleLights 获取可见光(Unity在剔除阶段会找到哪些光源会影响相机的可见空间)
5.支持多个方向光
1 2 3 4 5
| private void SetupDirectionLight(int index, ref VisibleLight visibleLight) { dirLightColors[index] = visibleLight.finalColor; dirLightDirections[index] = -visibleLight.localToWorldMatrix.GetColumn(2); }
|
- visibleLight.finalColor 灯光的颜色乘以光强得到最终的颜色值
- visibleLight.localToWorldMatrix 获取光源矩阵
GetColumn(2) 至于为什么矩阵第三列为光源方向:光源空间下光源正方形为z轴
$$
\begin{bmatrix}
m_{00} & m_{01} & m_{02} & m_{03} \
m_{10} & m_{11} & m_{12} & m_{13} \
m_{20} & m_{21} & m_{22} & m_{23} \
m_{30} & m_{31} & m_{32} & m_{33} \
\end{bmatrix}
\begin{bmatrix}
0 \
0 \
1 \
0 \
\end{bmatrix}
\begin{bmatrix}
m_{02} \
m_{12} \
m_{22} \
m_{32} \
\end{bmatrix}
$$
6. ShaderGUI
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class CustomShaderGUI : ShaderGUI { MaterialEditor editor; Object[] materials; MaterialProperty[] properties;
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { base.OnGUI(materialEditor, properties); this.editor = materialEditor; materials = editor.targets; this.properties = properties; } }
|
继承ShaderGUI,在OnGUI方法中实现
- editor.targets获取所有正在检视的material对象
- properties获取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
| bool SetProperty(string name, float value) { MaterialProperty property = FindProperty(name, properties, false); if(property != null) { property.floatValue = value; return true; } return false; } void SetProperty(string name, string keyword, bool enable) { if(SetProperty(name, enable ? 1f : 0f)) { SetKeyword(keyword, enable); } } void SetKeyword(string keyword, bool enable) { if(enable) { foreach(Material m in materials) { m.EnableKeyword(keyword); } } else { foreach(Material m in materials) { m.DisableKeyword(keyword); } } }
|
- FindProperty(string propertyName, MaterialProperty[] properties, bool propertyIsMandatory) 查找材质属性
- propertyName属性名
- properties属性数组
- propertyIsMandatory如果传入true,没有找到属性时会抛出异常
(下接 自底向上的SRP教程IV)