不同光源
最常使用的光源属性:
- 光源的位置
- 光源方向(更具体说就是,到某点的方向)
- 颜色
- 强度
- 衰减(更具体说就是,到某点的衰减,与该点到光源的距离有关)
这些属性和光源的几何定义息息相关。
光源类型 | 位置 | 方向 | 颜色和强度 | 衰减 |
---|---|---|---|---|
Unity代码 | _WorldSpaceLightPos0 |
_LightColor0 |
unity_WorldToLight 、_LightTexture0 、UNITY_ATTEN_CHANNEL |
|
平行光 | × | Transform的Rotation属性 | 面板控制 | 1.0(无衰减) |
点光源 | Transform的Position属性 | 点光源的位置减去某点的位置 | 面板控制 | 点光源球心处的光照强度最强,球体边界处的最弱,值为0 |
聚光灯 | Transform的Position属性 | 聚光灯的位置减去某点的位置 | 面板控制 | 锥形的顶点处光照强度最强,在锥形的边界处强度为0 |
阴影
让场景中可以产生阴影:
我们首先需要让平行光可以收集阴影信息。这需要在光源的Light组件中开启阴影。
让物体投射或接收阴影:
在Unity中,我们可以选择是否让一个物体投射或接收阴影。这是通过设置Mesh Renderer组件中的Cast Shadows和Receive Shadows属性来实现的。
一个物体接收来自其他物体的阴影,以及它向其他物体投射阴影是两个过程。
- 如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在Unity中,这个过程是通过为该物体
执行LightMode为ShadowCaster的Pass
来实现的。如果使用了屏幕空间的投影映射技术,Unity还会使用这个Pass产生一张摄像机的深度纹理。 - 如果我们想要一个物体接收来自其他物体的阴影,就必须在Shader中
对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果
。
我们的shader中没有定义这样一个Pass,但我们为它的Fallback指定了一个用于回调Unity Shader,即内置的Specular。虽然Specular本身也没有包含这样一个Pass,但是由于它的Fallback调用了VertexLit,它会继续回调,并最终回调到内置的VertexLit。
我们可以不依赖Fallback,而自行在SubShader中定义自己的LightMode为ShadowCaster的Pass。这种自定义的Pass可以让我们更加灵活地控制阴影的产生。但由于这个Pass的功能通常是可以在多个Unity Shader间通用的,因此直接Fallback是一个更加方便的用法。
在默认情况下,我们在计算光源的阴影映射纹理时会剔除掉物体的背面。但对于内置的平面来说,它只有一个面,因此在本例中当计算阴影映射纹理时,由于右侧的平面在光源空间下没有任何正面(frontface),因此就不会添加到阴影映射纹理中。我们可以将Cast Shadows设置为Two Sided来允许对物体的所有面都计算阴影信息。
实战:
以下shader代码是渲染一个不透明物体在平行光、点光源下的阴影:
Shader "ShaderBook/Chapter9/ForwardRender" {
Properties {
......
}
SubShader {
Tags {
"RenderType"="Opaque" }
Pass {
// 环境光 & 第一个逐像素光照 (平行光)
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//计算阴影时所用的宏都是在这个文件中声明
#include "AutoLight.cginc"
......
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
//【阴影一】声明一个用于对阴影纹理采样的坐标:_ShadowCoord
SHADOW_COORDS(2)
//需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//【阴影二】在顶点着色器中计算上一步中声明的阴影纹理坐标
//根据平台不同而有所差异:把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中
//TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标
//a2v结构体中的顶点坐标变量名必须是vertex.
//顶点着色器的输出结构体v2f必须命名为o,且v2f中的顶点位置变量必须命名为pos
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz