Unity Shader学习记录(15) —— Unity的光源类型

本文介绍了Unity中的四种光源类型——平行光、点光源、聚光灯,重点讨论了它们的几何属性、衰减特性,并详细阐述了在前向渲染中如何在Shader中处理不同光源的光照计算,包括颜色、强度和衰减。此外,还提到了光源的RenderMode对性能的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

光源类型

Unity 一共支持4种光源类型:平行光、点光源、聚光灯和面光源 (area light)。面光源仅在烘焙时才可发挥作用,因此不在本节讨论范围内。由于每种光源的几何定义不同,因此它们对应的光源属性也就各不相同。这就要求我们要区别对待它们。幸运的是,Unity 提供了很多内置函数来帮我们处理这些光源,在本章的最后我们会介绍这些函数,但首先我们需要了解它们背后的原理。

平行光:
平行光之所以简单,是因为它没有~个唯一的位置,也就是说,它可以放在场景中的任意位置(回忆一下,我们小时候是不是总感觉太阳跟着我们一起移动)。它的几何属性只有方向,我们可以调整平行光的 Transform组件中的 Rotation 属性来改变它的光源方向,而且平行光到场景中所有点的方向都是一样的,这也是平行光名字的由来。除此之外,由于平行光没有一个具体的位置,因此也没有衰减的概念,也就是说,光照强度不会随着距离而发生改变。

点光源
点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光。图9.6给出了Uity 中点光源在Scene 视图中的表示以及 Light组件的面板。

球体的半径可以由面板中的 Range 属性来调整,也可以在 Scene 视图中直接拖拉点光源的线框(如球体上的黄色控制点)来修改它的属性。点光源是有位置属性的,它是由点光源的 Transform组件中的 Position 属性定义的。对于方向属性,我们需要用点光源的位置减去某点的位置来得到它到该点的方向。而点光源的颜色和强度可以在 Light 组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。

聚光灯
这块锥形区域的半径由面板中的 Range 属性决定,而锥体的张开角度由 SpotAngle 属性决定。我们同样也可以在 Scene 视图中直接拖拉聚光灯的线框(如中间的黄色控制点以及四周的黄色控制点)来修改它的属性。聚光灯的位置同样是由 Transform组件中的Position属性定义的。对于方向属性,我们需要用聚光灯的位置减去某点的位置来得到它到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减小,在锥形的顶点处光照强度最强,在锥形的边界处强度为 0。其中间的衰减值可以由一个函数定义,这个函数相对于点光源衰减计算公式要更加复杂,因为我们需要判断一个点是否在锥体的范围内。

在前向渲染中处理不同的光源类型

在了解了3种光源的几何定义后,我们来看一下如何在Unity Shader 中访问它们的5个属性位置、方向、颜色、强度以及衰减。

Shader "MyShader/9-ForwardRendering"
    Properties 
    {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}


	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// 前向渲染标签
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Shader 中使用光照衰减等光照变量可以被正确赋值
			#pragma multi_compile_fwdbase	
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				//环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//反射光
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				//反射光的衰减可以认为是没有的
				fixed atten = 1.0;
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// 额外渲染的标签
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				//当前处理的逐像素光源的类型
				#ifdef USING_DIRECTIONAL_LIGHT
				//平行光
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
				//非平行光计算
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif
				
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				#ifdef USING_DIRECTIONAL_LIGHT
				//平行光衰减
					fixed atten = 1.0;
				#else
					#if defined (POINT)
					//不同光源的衰减情况
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

步骤:
1 BasePass用来计算场景平行光照,阴影,环境光等,所以需要在Pass中计算这部分,选用的是场景中最亮的平行光,默认无衰减。

2 AdditionPass用来计算在基础光照之外叠加的其他光源光照,#pragma multi_compile_fwdadd 定义后,判断光源的类型,平行光依然按照上述方法计算,根据不同的光源类型做不同的计算,光源的方向和衰减。

从图9.12可以看出,Unity 是如何一步步将不同光照染到物体上的:在第一个渲染事件中Unity 首先清除颜色、深度和模板缓冲,为后面的渲染做准备;在第二个染事件中,Unity利Chapter9-ForwardRendering 的第一个Pass,即Base Pass,将平行光的光照染到缓存中:在面的4个染事件中,Unity使用Chapter9-ForwardRendering的第二个Pass,即AdditionalPass依次将4个点光源的光照应用到物体上,得到最后的渲染结果。

我们知道,如果逐像素光源的数目很多的话,该物体的Additional Pass 就会被调用多次,影响性能。我们可以通过把光源的 Render Mode设为 Not Important来告诉 Unity,我们不希望把亥光源当成逐像素处理。在本例中我们可以把4个点光源的Render Mode都设为NotImportant,可以得到图9.14中的结果。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值