初级--多光源光照

目录

[TOC]

写在前面

本文将在上一篇单一光源光照中的PBS光照模型的基础上增加对多光源的支持(使用前向渲染)。全篇基于Unity2018.3.2。

在PC前的各位读者,建议下载MD文件阅读,观感更好。若有图片无法显示,请开启网络。欢迎转载,只需注明转载来源地址即可。MD文件及工程Package下载地址(提取码:4c1s)

创建自己的Shader包含文件

为了给我们的着色器增加多光源支持,我们需要增加多个Pass通道。新增的Pass通道包含了几乎相同的代码。为了减少重复代码,我们将上一篇的代码做成自己的Shader包含文件(Shader Include File)。

首先,复制上一篇的基于PBS的光照Shader;然后变更文件后缀名为”.cginc“;然后打开变更好后缀的cginc文件,将#include "UnityPBSLighting.cginc"之前的代码全部删除,将从”ENDCG”开始的代码全部删除;最后在开头和结尾加上:

#if !defined(MY_LIGHTING_INCLUDED)
#define MY_LIGHTING_INCLUDED
......
#endif

开头加上的宏可以保证我们的自定义包含文件不会被其他Shader重复Include。避免重复引入造成的变量重复命名的编译错误。

多光源

我们将会使用前向渲染来实现多光源支持。

1.创建文件夹结构:

使用HTUtility工具(Shift+Alt+G)创建合适的文件夹结构,重命名Root文件夹为”2_多光源”。在场景中创建”Plane”,Reset到原点位置并旋转使其正对摄像机。创建Material,命名为”MultipleLightingMat”;创建Unlit Shader,命名为”MultipleLighting”。选择”MultipleLightingMat”的Shader为”MultipleLighting”,并将材质赋值给场景中的”Plane”。

picture0

2.光源类型:

平行光、点光、聚光灯,面光源等可见Unity手册介绍。

3.前向渲染路径:

(1)概述

前向渲染一般包含有两个Pass通道,一个是”ForwardBase”Pass通道,一个是”ForwardAdd”Pass通道。

(2)相关设置

在Unity中对于光照的计算方式有三种,分别为逐像素[^逐像素是?]、逐顶点[^逐顶点是?]、球谐函数(SH)。

逐像素:

逐像素计算光照是效果最好的,但性能消耗也最高,所以Unity限制了可使用逐像素计算光照的光源最大数量。

逐像素计算光照效果:

picture1

具体设置在”Edit–Project Settings–Quality–Rendering“中的”Pixel Light Count“。

picture2

可设置范围:[0,4]。

即使设置为0,也不会影响逐像素计算主光(最重要的平行光[^最重要的平行光是?]),主光的计算默认为逐像素,不会占用PiexlLightCount个数。

逐顶点:

逐顶点计算出来的效果一般,不够平滑,因为其计算是在各个顶点之间插值计算得到的结果,而顶点数较少,所以插值结果不够平滑,但也因此拥有较好的性能。

逐顶点计算光照效果:

picture3

当场景中的光源数量大于PiexlLightCount个数时,会采用逐顶点方式计算光照。同样的逐顶点计算也有限制个数。Unity默认的限制个数为4个,而且无法设置变更。

逐顶点相对于逐像素的缺点在于:不支持光照剪影和法线贴图。

球面谐波函数(SH):

简称球谐函数(SH),球谐函数光源的渲染速度很快。当场景中的光源数量超过逐像素个逐顶点的最大个数之和时,超出的光源部分会使用球谐函数计算。

这些光源的CPU成本很低,并且使用GPU成本几乎为零。球谐函数会在”ForwardBase”中常驻计算,且成本不变。

球谐函数也是逐顶点计算的,所以同样拥有逐顶点的缺点:不支持光照剪影和法线贴图。除此之外,球谐函数光照只影响漫反射光照(diffuse),而且球谐函数光照频率很低,无法实现快速光照过渡。最后,球谐函数光照不属于局部光照,所以当点光源和聚光灯以SH方式计算的时候,如果靠近物体表面,是不会有正确的光照显示的,看起来就像没有光照一样,因为SH光照被计算到了间接光照中。

(3)前向渲染的Pass通道

“ForwardBase”基础通道:

在”ForwardBase”中会以逐像素的方式计算主光,除此之外还会以逐顶点的方式计算最多4个次级光,以及会以逐顶点方式使用球谐函数(SH)计算额外的光照。

“ForwardAdd”额外通道:

在”ForwardAdd”中只会计算逐像素光照,而且每个逐像素光照的计算都会使用一个”ForwardAdd”通道。

也就是说,当场景中存在n个物体,以及m个逐像素光源时(m<=4,且不包含主光),此时光源计算的Batches=n+n*m;其中n为”ForwardBase”通道,n * m为”ForwardAdd”通道。

增加逐像素光数量会成倍增加Batches,而Batches过多会在CPU处产生性能瓶颈。所以在移动端逐像素光要慎用。

更多关于前向渲染的信息可以查看Unity官方手册

4.Shader编写:

(1)准备工作

由于我们自定义了自己的光照包含文件,所以大部分光照代码在光照包含文件中写即可,在Shader中只需要对Pass通道进行相关设置即可。

打开Shader文件”MultipleLighting”:

先删除所有和雾相关的代码,在这里我们不需要雾。

将之前的属性块中的代码完全迁移过来:

Properties
{
	_Tint("Tint",COLOR) = (1,1,1,1)
	_MainTex ("Albedo", 2D) = "white" {}
	//告诉Unity,金属值也需要进行Gamma校正
	[Gamma]_Metallic("Metallic",Range(0,1)) = 0//金属般的
	_Smoothness("Smoothness",Range(0,1)) = 0.1//物体表面平滑程度,越平滑越容易聚焦光线、光点越小
}

将之前的”SingleLighting_MetallicWorkflow”Shader中的Pass通道的代码完全迁移过来,然后删除Pass通道中从#include开始直到片元函数结束的部分。

Pass
{
	Tags{"LightMode"="ForwardBase"}//前向渲染

	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag

	//使用PBS
	#pragma target 3.0

	ENDCG
}

增加一个”ForwardAdd”Pass通道:

由于新增Pass通道是用于计算逐像素的次级光,次级光颜色应与基础通道中光照颜色叠加显示,所以需要加上混合指令:Blend One One,此后该Pass通道计算出的颜色就会添加到帧缓冲区与原帧缓冲区颜色混合。

在基础通道中GPU会将片元的深度信息写入深度缓冲区(Z Buffer)中,只有距离摄像机近的片元会渲染出来。这个工作我们没有必要在”ForwardAdd”通道中再做一次。通过添加指令”ZWrite Off“可以告诉GPU该Pass通道不需要进行深度写入。

Pass
{
	Tags{"LightMode"="ForwardAdd"}//前向渲染
	Blend One One//次级光和主光混合
	ZWrite Off//计算次级光时无需重复计算深度缓冲

	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag

	//使用PBS
	#pragma target 3.0

	ENDCG
}

(2)点光源

创建一个PointLight,设置为红色,此时并不能正确渲染出点光源光照:

picture4

原因在于,在第一个Pass通道,也就是”ForwardBase”通道中,以逐像素方式计算了平行光。在第二个Pass通道,也就是”ForwardAdd”Pass通道中,本应该以逐像素方式计算点光源光照,但我们没有处理点光源,而是将点光源当做平行光在处理。也就是:float3 lightDir = _WorldSpaceLightPos0.xyz;光源方向不应该是”_WorldSpaceLightPos0.xyz”,这个是平行光的光源方向,点光源的光源方向是:normalize(_WorldSpaceLightPos0.xyz - i.worldPos);。要同时处理两种光源,甚至是处理后面更多的光源,所以我们需要增加Shader变体。

Shader变体在Shader的检视面板查看:

picture5

点击”Show“:当前只有一个Shader变体。

// Total snippets: 2
// -----------------------------------------
// Snippet #0 platforms ffffffff:

1 keyword variants used in scene:

<no keywords defined>


// -----------------------------------------
// Snippet #1 platforms ffffffff:

1 keyword variants used in scene:

<no keywords defined>

增加Shader变体的方式是在第二个Pass通道加上:#pragma multi_compile_fwdadd

Pass
{
	......
	CGPROGRAM
	//使用PBS
	#pragma target 3.0

	//多个光类型定义:
	//#pragma multi_compile DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT
	//用下面这个可以代替上面定义的五种光类型
	#pragma multi_compile_fwdadd
	......
	ENDCG
}

之后再次查看该Shader的变体:

// Total snippets: 2
// -----------------------------------------
// Snippet #0 platforms ffffffff:

1 keyword variants used in scene:

<no keywords defined>


// -----------------------------------------
// Snippet #1 platforms ffffffff:
Builtin keywords used: DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT

5 keyword variants used in scene:

POINT
DIRECTIONAL
SPOT
POINT_COOKIE
DIRECTIONAL_COOKIE

此时共有5个Shader变体,分别支持了点光源、平行光、聚光灯、点光源剪影、平行光剪影。

在处理不同光源之前,我们将PBS需要的光源信息提取出来,成为一个单独的函数:

//计算PBS需要使用的光数据
UnityLight CreateLight(v2f i)
{
	UnityLight light;
	light.color = lightColor;
	light.dir = lightDir;
	light.ndotl = DotClamped(i.normal , lightDir);
	return light;
}	

//计算PBS需要使用的间接光数据
UnityIndirect CreateIndirectLight(v2f i)
{
	UnityIndirect indirectLight;
	indirectLight.diffuse = 0;//漫反射代表环境光
	indirectLight.specular = 0;//镜面反射代表环境反射
	return indirectLight;
}

在片元函数最后的PBS函数变为:

return UNITY_BRDF_PBS
(
albedo , specColor , 
oneMinusReflectivity ,_Smoothness , 
i.normal ,viewDir , 
CreateLight(i) , CreateIndirectLight(i)
);

在处理点光源的时候,光源方向应为:light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);

//计算PBS需要使用的光数据
UnityLight CreateLight(v2f i)
{
	UnityLight light;
	//定义为点光的时候:
	#if defined(POINT)
	light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
	#else
	//定义为平行光的时候:
	light.dir = _WorldSpaceLightPos0.xyz;
	#endif
	light.color = lightColor;
	light.ndotl = DotClamped(i.normal , lightDir);
	return light;
}	

此时我们需要处理光照衰减,一般情况下光照衰减使用的公式为:”光照衰减 = 1/距离的平方”;为了在光源靠近物体时光照达到最大,但不超过1,所以光照衰减公式变更为:”光照衰减 = 1/(1+距离的平方)”。

使用上述方法计算光照衰减会有一个问题,就是点光在光照范围交界处会产生光照由无到比较亮的突变。解决之法,就是使用宏UNITY_LIGHT_ATTENUATION来计算光照衰减,让光的边缘渐渐减小为0,而不是突变。

此宏会计算不同类型的光的衰减,但在计算前,需要引入库文件#include "AutoLight.cginc"、并定义光的类型:比如点光:#define POINT。之后还是会出现新问题:光源类型不匹配,所以要定义多重光类型,分别处理不同光类型,让shader生成不同pass变体。由于之前我们已经定义了,所以此处可以直接使用。

在UnityShader库中的CGIncludes文件夹中可以搜索宏UNITY_LIGHT_ATTENUATION:

#ifdef POINT
uniform sampler2D _LightTexture0;
uniform unityShadowCoord4x4 unity_WorldToLight;
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
	unityShadowCoord3 lightCoord = \
		mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
	fixed destName = \
		(tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr). \
		UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(input));
#endif

Unity在计算Point光源衰减的时候使用的是一张名为”_LightTexture0”的纹理作为查找表来在片元着色器中计算逐像素光照的衰减。优点在于节省性能;缺点在于需要预处理获得采样纹理,而且纹理的大小也会影响衰减精度。

//计算PBS需要使用的光数据
UnityLight CreateLight(v2f i)
{
	UnityLight light;
	//定义为点光的时候:
	#if defined(POINT)
	light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
	#else
	//定义为平行光的时候:
	light.dir = _WorldSpaceLightPos0.xyz;
	#endif
	//新的计算光照衰减宏:UNITY_LIGHT_ATTENUATION(光照衰减,阴影相关,世界位置)
	UNITY_LIGHT_ATTENUATION(attenuation , 0 , i.worldPos);
	light.color = _LightColor0.rgb * attenuation;
	light.ndotl = DotClamped(i.normal , light.dir);
	light.color = lightColor;
	light.ndotl = DotClamped(i.normal , lightDir);
	return light;
}	

现在点光源也通过逐像素的方式在第二个Pass通道中计算光照了。

picture6

(3)聚光灯

使用聚光灯的原理同点光源相似:

//计算PBS需要使用的光数据
UnityLight CreateLight(v2f i)
{
	UnityLight light;
	//定义为点光的时候:
	#if defined(POINT) || defined(SPOT)
	light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
	#else
	//定义为平行光的时候:
	light.dir = _WorldSpaceLightPos0.xyz;
	#endif
	//新的计算光照衰减宏:UNITY_LIGHT_ATTENUATION(光照衰减,阴影相关,世界位置)
	UNITY_LIGHT_ATTENUATION(attenuation , 0 , i.worldPos);
	light.color = _LightColor0.rgb * attenuation;
	light.ndotl = DotClamped(i.normal , light.dir);
	light.color = lightColor;
	light.ndotl = DotClamped(i.normal , lightDir);
	return light;
}	

在UnityShader库中的CGIncludes文件夹中可以搜索宏UNITY_LIGHT_ATTENUATION:

#ifdef SPOT
uniform sampler2D _LightTexture0;
uniform unityShadowCoord4x4 unity_WorldToLight;
uniform sampler2D _LightTextureB0;
inline fixed UnitySpotCookie(unityShadowCoord4 LightCoord) {
return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;
}
inline fixed UnitySpotAttenuate(unityShadowCoord3 LightCoord) {
return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx). \
	UNITY_ATTEN_CHANNEL;
}
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
unityShadowCoord4 lightCoord = \
	mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)); \
fixed destName = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * \
	UnitySpotAttenuate(lightCoord.xyz) * SHADOW_ATTENUATION(input);
#endif

同Point光源一样,Unity在计算Spot光源衰减的时候使用的也是一张名为”_LightTexture0”的纹理作为查找表来在片元着色器中计算逐像素光照的衰减。

现在聚光灯也可以通过逐像素的方式在第二个Pass通道中计算光照了。

picture7

(4)剪影(Cookies)

Unity中可以使用一张遮罩纹理作为剪影,为平行光/点光源/聚光灯光源遮挡光线。剪影的A通道被用来遮挡光线,其他通道没有作用。如下图剪影(Cookies)的RGBA的值均相同:

cookie0

将纹理导入Unity后,在纹理设置中设置纹理类型为Cookies:

picture8

设置光源类型:

picture9

将Cookies设置到Spot光源中,效果如下:

picture10

剪影(Cookies)还可以设置到平行光和点光源中,本文就不再赘述,读者可自行实验。

注意,为了支持点光源剪影,需要更改:

//计算PBS需要使用的光数据
UnityLight CreateLight(v2f i)
{
	UnityLight light;
	//定义为点光的时候:
	#if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT)
	......
}	

在UnityShader库中的CGIncludes文件夹中可以搜索宏UNITY_LIGHT_ATTENUATION:

#ifdef POINT_COOKIE
uniform samplerCUBE _LightTexture0;
uniform unityShadowCoord4x4 unity_WorldToLight;
uniform sampler2D _LightTextureB0;
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
	unityShadowCoord3 lightCoord = \
		mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
	fixed destName = \
		tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr). \
		UNITY_ATTEN_CHANNEL * texCUBE(_LightTexture0, lightCoord).w *
		SHADOW_ATTENUATION(input);
#endif

同点光源和平行光不一样的是:Unity在计算Cookies光源衰减的时候使用的是一张名为”_LightTextureB0”的纹理作为查找表来在片元着色器中计算逐像素光照的衰减。

(5)逐顶点光照

接下来我们需要给基础通道添加逐顶点光照计算支持。逐顶点光照即在顶点着色器中计算光照信息,然后通过插值寄存器将光照颜色传递给片元着色器。逐顶点光照计算出来的结果没有逐片元好,但是可以提升性能。

在顶点着色器的输出结构体中添加顶点光照颜色:

struct v2f
{
	float4 vertex : SV_POSITION;
	float2 uv : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float3 worldPos : TEXCOORD2;

	//传递顶点光照到片元函数(PointLight)
	#if defined(VERTEXLIGHT_ON)
	float3 vertexLightColor : TEXCOORD3;
	#endif
};

如果要使用此顶点光照颜色,还需在基础通道定义”VERTEXLIGHT_ON“:

Pass
{
	Tags{"LightMode"="ForwardBase"}//前向渲染

	CGPROGRAM
	//使用PBS
	#pragma target 3.0

	//点光和聚光灯 支持顶点光照(前向渲染中优化光照计算的,减少drawcall)
	#pragma multi_compile _ VERTEXLIGHT_ON

	#pragma vertex vert
	#pragma fragment frag

	#include "HTLighting_MetallicWorkflow.cginc" 

	ENDCG
}

此时,顶点光照颜色就可以存储在插值寄存器中传递给片元着色器了。

然后,我们新建一个函数,单独计算顶点光照颜色。使用Unity自带函数Shade4PointLights(...)计算四个顶点光照颜色。注意,即使场景中不足四个逐顶点光源,此处也会计算四次,不足的光源被当做黑色来计算。

在UnityShader库中的CGIncludes文件夹中可以搜索函数”Shade4PointLights“:

// Used in ForwardBase pass: Calculates diffuse lighting
// from 4 point lights, with data packed in a special way.
float3 Shade4PointLights (
	float4 lightPosX, float4 lightPosY, float4 lightPosZ,
	float3 lightColor0, float3 lightColor1,
	float3 lightColor2, float3 lightColor3,
	float4 lightAttenSq, float3 pos, float3 normal) {
	// to light vectors
	float4 toLightX = lightPosX - pos.x;
	float4 toLightY = lightPosY - pos.y;
	float4 toLightZ = lightPosZ - pos.z;
	// squared lengths
	float4 lengthSq = 0;
	lengthSq += toLightX * toLightX;
	lengthSq += toLightY * toLightY;
	lengthSq += toLightZ * toLightZ;
	// NdotL
	float4 ndotl = 0;
	ndotl += toLightX * normal.x;
	ndotl += toLightY * normal.y;
	ndotl += toLightZ * normal.z;
	// correct NdotL
	float4 corr = rsqrt(lengthSq);
	ndotl = max(float4(0,0,0,0), ndotl * corr);
	// attenuation
	float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
	float4 diff = ndotl * atten;
	// final color
	float3 col = 0;
	col += lightColor0 * diff.x;
	col += lightColor1 * diff.y;
	col += lightColor2 * diff.z;
	col += lightColor3 * diff.w;
	return col;
}

我们的计算顶点光照的函数:

//计算顶点光照
void ComputeVertexLightColor(inout v2f o)
{
	//传递顶点光照到片元函数(PointLight)
	#if defined(VERTEXLIGHT_ON)
	//四个顶点光照计算:只要开启了顶点光照计算,无论是否有4个顶点光照,都会付出4个顶点光照计算的代价;没有颜色的顶点光照会用黑色代替计算。
	o.vertexLightColor = Shade4PointLights
	(
		unity_4LightPosX0 , unity_4LightPosY0 , unity_4LightPosZ0 ,
		unity_LightColor[0].rgb , unity_LightColor[1].rgb , 
		unity_LightColor[2].rgb , unity_LightColor[3].rgb , 
		unity_4LightAtten0 , o.worldPos , o.normal
	);
	#endif
}

在顶点函数中添加计算顶点光照的这一步:

v2f vert (appdata v)
{
	......
	ComputeVertexLightColor(o);//计算顶点光照
	return o;
}

将顶点光照颜色传递给片元着色器中的PBS所需的间接光数据,顶点光照被添加到间接光照中的漫反射颜色中:

//计算PBS需要使用的间接光数据
UnityIndirect CreateIndirectLight(v2f i)
{
	UnityIndirect indirectLight;
	indirectLight.diffuse = 0;//漫反射代表环境光
	indirectLight.specular = 0;//镜面反射代表环境反射

	//传递顶点光照到片元函数(PointLight)
	#if defined(VERTEXLIGHT_ON)
	indirectLight.diffuse = i.vertexLightColor;
	#endif

	return indirectLight;
}

逐顶点光照效果:

picture11

(6)球谐函数(SH)

在UnityShader库中的CGIncludes文件夹中可以搜索函数”ShadeSH9“:

// normal should be normalized, w=1.0
half3 SHEvalLinearL0L1 (half4 normal) {
	half3 x;

	// Linear (L1) + constant (L0) polynomial terms
	x.r = dot(unity_SHAr,normal);
	x.g = dot(unity_SHAg,normal);
	x.b = dot(unity_SHAb,normal);

	return x;
}

// normal should be normalized, w=1.0
half3 SHEvalLinearL2 (half4 normal) {
	half3 x1, x2;
	// 4 of the quadratic (L2) polynomials
	half4 vB = normal.xyzz * normal.yzzx;
	x1.r = dot(unity_SHBr,vB);
	x1.g = dot(unity_SHBg,vB);
	x1.b = dot(unity_SHBb,vB);

	// Final (5th) quadratic (L2) polynomial
	half vC = normal.x * normal.x - normal.y * normal.y;
	x2 = unity_SHC.rgb * vC;

	return x1 + x2;
}

// normal should be normalized, w=1.0
// output in active color space
half3 ShadeSH9 (half4 normal) {
	// Linear + constant polynomial terms
	half3 res = SHEvalLinearL0L1(normal);

	// Quadratic polynomials
	res += SHEvalLinearL2(normal);

	if (IsGammaSpace())
		res = LinearToGammaSpace(res);

	return res;
}

将球面谐波SH光照数据添加到漫反射间接光中,并保证其不会产生负数:

//计算PBS需要使用的间接光数据
UnityIndirect CreateIndirectLight(v2f i)
{
	......
	#if defined(FORWARD_BASE_PASS)
	//球面谐波(作为间接光中的diffuse)
	indirectLight.diffuse += max(0 , ShadeSH9(float4(i.normal , 1)));
	#endif

	return indirectLight;
}

在基础通道定义”FORWARD_BASE_PASS“,以保证在”ForwardBase”Pass通道中会计算球谐光照。

Pass
{
	......
	//计算球面谐波
	#define FORWARD_BASE_PASS

	#include "HTLighting_MetallicWorkflow.cginc" 

	ENDCG
}

关闭场景中所有的灯光,不计算SH和计算SH的效果分别如下:

picture12

picture13

可见,计算SH时,场景中多了环境灯光颜色。

5.多光源的显示

若某个物体受多光源影响,如下图

picture14

我们假设光源A到H具有相同的颜色和强度,并且所有光源都具有自动渲染模式,因此它们将严格按照以下顺序排序。最亮的光源将以逐像素光照模式进行渲染(A到D),然后最多4个光源将以逐顶点光照模式渲染(D到G),最后其余光源以SH进行渲染(G到H)。

picture15

请注意,光源组会重叠;例如,最后一个逐像素光源混合到逐顶点光照模式。

GIF1

写在最后

本文在前向渲染中支持了多光源光照,但是还未处理阴影部分。对于阴影部分的处理可以见:…。

Reference

Rendering 5 Multiple Lights

UnityShader入门精要

Unity官方手册