【Unity Shader入门精要 第9章】更复杂的光照(二)

1. 光源类型

Unity中支持四种类型的光源,分别是:平行光、点光源、聚光灯和面光源。

平行光——Directional Light

常被用做太阳的功能,各向光照均匀,不存在衰减,光照角度由光源自身旋转决定(而非光源与物体的相对位置),因此只关心光源方向(旋转),不关心光源位置

在平行光的Inpector面板中

  • Type为Directional
  • Color控制光源颜色
  • Instensity控制光源强度
    在这里插入图片描述

点光源——Point Light

以光源为中心,照亮一定半径的球形范围,各向光照均匀,且光照强度存在距离衰减

在点光源的Inpector面板中

  • Type为Point
  • Range控制范围球半径
  • Color控制光源颜色
  • Instensity控制光源强度

在这里插入图片描述

聚光灯——Spot Light

光照范围为一个以光源为顶点,以光源朝向为中心轴,按照光源指定角度形成的圆锥体,光照强度存在距离衰减,也会随光照方向与中心轴的角度变大而衰减

在聚光灯的Inpector面板中

  • Type为Spot
  • Range控制圆锥半径(远近)
  • Spot Angle控制圆锥张角
  • Color控制光源颜色
  • Instensity控制光源强度

在这里插入图片描述

面光源——Area Light

只用于烘焙,这里不作讨论

对比

几种光源的对比可以参照下表:

光源类型范围位置方向颜色强度衰减
平行光整个场景不关心位置Rotation决定光照方向Color控制颜色Instensity 控制强度无衰减
点光源以光源为中心,Range为半径的球体Position决定位置光源与物体的连线决定光照方向Color控制颜色Instensity 控制强度距离相关
聚光灯以光源为顶点,光源朝向为中心轴,SpotAngle为张角、Range为边长的圆锥Position决定位置光源与物体的连线决定光照方向Color控制颜色Instensity 控制强度距离相关

2. 在Unity中获取光源属性

判断光源类型

Unity中提供了各种内置的宏,可以用来判断当前所处理的光源类型,具体使用如下:

#ifdef USING_DIRECTIONAL_LIGHT
	//当前处理的光源类型为平行光
#else
	#if defined(POINT)
		//当前处理的光源类型为点光源
	#elif defined(SPOT)
		//当前处理的光源类型为聚光灯
	#else
		//其他
	#endif
#endif

获得光源方向/位置

  • 对于平行光,不关心光源位置信息,此时 _WorldSpaceLightPos0.xyz 存储的就是光源的方向
  • 对于非平行光,_WorldSpaceLightPos0.xyz 中存储的是光源在世界空间中的位置,可以通过光源位置减去照射目标位置计算获得光源位置:_WorldSpaceLightPos0.xyz - i.worldPos.xyz
  • 均需要手动归一化

获得光源颜色/强度

  • _LightColor0 可以获取当前处理光源的颜色和强度
  • 其值已经是颜色和强度相乘后的值,包含了颜色和强度两方面的综合信息,不需要再手动处理
  • Clight = _LightColor0.rgb

获得光源衰减

光照衰减对漫反射和高光反射的效果都会产生影响,因此在计算时,除了我们使用的光照模型的相关计算(就像之前的Shader里),还需要乘以衰减系数 atten

平行光由于不存在衰减,atten 始终为1

非平行光的衰减与光源到物体的距离有关,为了避免在计算时进行复杂的开平方等操作,Unity内置了光照衰减纹理(行,就叫这么个名吧)提供光照衰减信息。通过对光照衰减纹理进行采样即可获得所需的衰减系数。

在不同的光源类型下,衰减存储的贴图、如何进行采样均有不同,具体参照以下示例:

#ifdef USING_DIRECTIONAL_LIGHT
	//平行光没有衰减,固定为1
	atten = 1;
#else
	#if defined(POINT)
		float3 _pos = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
		//点光源的衰减存储在_LightTexture0纹理中,通过光源空间下点到光源距离的平方构建一个二维采样坐标,对纹理进行采样
		//不同平台下,光照衰减可能会被存储到纹理的不同颜色通道中,一般PC和主机平台为R通道,移动平台为A通道
		//UNITY_ATTEN_CHANNEL宏可以根据当前平台自动返回对应的颜色通道
		atten = tex2D(_LightTexture0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
	#elif defined(spot)
		float4 _pos = mul(_LightMatrix0, float4(i.worldPos, 1));
		//聚光灯的距离衰减存储在_lightTextureB0纹理中,可以通过和点光源一样的方式采样距离衰减
		fixed _atten_1 = tex2D(_lightTextureB0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
		//聚光灯的角度衰减存储在_lightTexture0纹理中,通过NDC坐标的XY值进行采样(上移0.5)
		fixed _atten_2 = tex2D(_lightTexture0, _pos.xy/_pos.w + 0.5).w;
		atten = (_pos.z > 0) * _atten_1 * _atten_2;
	#else
		atten = 1;
	#endif
#endif

3. 多光源前向渲染示例

  • 创建 Chapter_9_Forward_Mat 作为测试材质
  • 创建 Chapter_9_Forward_Shader 作为测试shader,并赋给 Chapter_9_Forward_Mat 材质
  • 场景中创建一个用于测试的胶囊体,将 Chapter_9_Forward_Mat 材质赋给胶囊体
  • 场景中添加一盏平行光,设置平行光的颜色为黄色,并调整平行光角度
  • 场景中添加一盏聚光灯,设置聚光灯的颜色为蓝色,并调整聚光灯的角度和半径,使聚光灯能够照射到胶囊体
  • 场景中添加一盏点光源,设置点光源的颜色为红色,并调整点光源的半径,使聚光灯能够照射到胶囊体

Shader中包含两个Pass:
第一个Pass设置标签为前向渲染的 BasePass

Tags{ "LightMode" = "ForwardBase"}

并添加编译指令:

#pragma multi_compile_fwdbase

第二个Pass设置标签为前向渲染的 Additional Pass

Tags { "LightMode" = "ForwardAdd" }

并添加编译指令:

#pragma multi_compile_fwdadd

为了能够正常使用 _LightMatrix0 、USING_DIRECTIONAL_LIGHT 等内置变量和内置宏,需要增加引用文件:

#include "AutoLight.cginc"

为了使 Additioanl Pass 的光照效果叠加到 Base Pass,而非覆盖,需要设置混合模式:

Blend One One

在片元着色其中,除了上文所说的取关照衰减之外,在计算光源方向时,也需要对平行光和非平行光进行判断(虽然本次的例子中 Additional Pass 不会出现平行光)

#ifdef USING_DIRECTIONAL_LIGHT
    float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
#else
    float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#endif

最终Shader如下:

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'

Shader "MyShader/Chapter_9/Chapter_9_Forward_Shader"
{
    Properties
    {
        _Color("MainColor", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1, 50)) = 16
    }
    
    Subshader
    {
        Pass
        {
            Tags{ "LightMode" = "ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            
            #pragma multi_compile_fwdbase
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
            };
            
            fixed4 _Color;
            fixed4 _Specular;
            half _Gloss;
            
            v2f vert(a2v v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                
                float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                float3 _worldNormal = normalize(i.worldNormal);
                fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldLight, _worldNormal));
                
                float3 _worldView = normalize(UnityWorldSpaceViewDir(i.worldPos));
                float3 _h = normalize(_worldLight + _worldView);
                fixed3 _specular = _LightColor0.rgb * _Specular.xyz * pow(saturate(dot(_worldNormal, _h)), _Gloss);
                
                return fixed4(_ambient + _diffuse + _specular, 1);
            }
            ENDCG
        }
        
        Pass
        {
            Tags { "LightMode" = "ForwardAdd" }
            
            Blend One One
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #pragma multi_compile_fwdadd
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };
            
            fixed4 _Color;
            fixed4 _Specular;
            half _Gloss;
            
            v2f vert(a2v v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
            
                fixed atten;
                #ifdef USING_DIRECTIONAL_LIGHT
                    atten = 1;
                #else
                    #if defined(POINT)
                        float3 _pos = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                        atten = tex2D(_LightTexture0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
                    #elif defined(SPOT)
                        float4 _pos = mul(unity_WorldToLight, float4(i.worldPos, 1));
                        fixed _atten_1 = tex2D(_LightTextureB0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
                        fixed _atten_2 = tex2D(_LightTexture0, _pos.xy/_pos.w + 0.5).w;
                        atten = (_pos.z > 0) * _atten_1 * _atten_2;
                    #else
                        atten = 1;
                    #endif
                #endif
            
                float3 _worldNormal = normalize(i.worldNormal);
                #ifdef USING_DIRECTIONAL_LIGHT
                    float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                #else
                    float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
                #endif
                fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldNormal, _worldLight));
                
                float3 _worldView = UnityWorldSpaceViewDir(i.worldPos);
                float3 _h = normalize(_worldView + _worldLight);
                fixed3 _specular = _LightColor0.rgb * _Color.xyz * pow(saturate(dot(_worldNormal, _h)), _Gloss);
                
                return fixed4((_diffuse + _specular) * atten, 1);
            }
            
            ENDCG
        }
        
    
    }

}

效果如下:
在这里插入图片描述

在 Frame Debug 面板中,可以看到此时通过3个 Draw Call 对胶囊体进行了绘制:

  • 首先执行第0号Pass,也就是我们定义的Base Pass,处理了平行光的光照
    在这里插入图片描述
  • 其次执行第1号Pass,也就是我们定义的Addition Pass,处理了聚光灯的光照(测试时手动调高了聚光灯的强度)
    在这里插入图片描述
  • 最后又再次执行第1号Pass,处理了点光源的光照
    在这里插入图片描述

还可以调整光源的Render Mode设置或者Project Settings里逐像素光源的最大数量,根据 Frame Debug 面板信息观察渲染过程的变化,就不一一截图了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值