永远相信美好的事情即将发生 😊!

Unity Shader 入门精要(冯乐乐著)学习笔记(8)——更复杂的光照

学习 Mavis 52℃ 0评论

Unity的渲染路径

渲染路径rendering path 决定了光照是如何应用的,大多数情况下一个项目使用一种渲染路径,但是如果希望使用多个渲染路径,例如相机A使用前向渲染,相机B使用延迟渲染,可以在每个相机的渲染路径中设置以覆盖全局的设置

全局设置:

相机单独设置:

设置Pass的LightMode标签来指定该Pass使用的渲染路径,LightMode支持的渲染路径如下:

前向渲染路径

原理:深度测试——可见的片元进行光照计算——更新帧缓冲

unity中的前向渲染:三种处理光照的方式(逐顶点处理,逐像素处理,球谐函数处理),光源类型(平行光或其他)和渲染模式(是否重要)决定选择哪一个方式:

  1. 最亮平行光:逐像素。
  2. 渲染模式not important的,按逐顶点或SH处理,最多有四个光源按照逐顶点方式处理。
  3. 渲染模式important的,按逐像素。
  4. 若根据上面规则,逐像素数量小于Quality Setting 中的逐像素光源数量,会有更多的光源以逐像素方式渲染。

前向渲染的两种Pass:

内置的光照变量和函数:

顶点照明渲染路径

对硬件配置要求最少,运算性能最高,效果最差的一种路径,不支持逐像素的效果(阴影,法线映射,高精度的高光反射等),使用逐顶点的方式计算光照(unity会只填充逐顶点相关的光源变量),通常使用一个Pass就够。

可访问的内置变量和函数,最多访问到8个逐顶点光源,不够的变量填充为黑色:

延迟渲染路径

原因:前向渲染会在包含大量实时光源时,性能急速下降,光源影响区域互相重叠的部分将执行多个Pass计算,每执行一个都需要重新渲染物体,导致重复计算。

原理:延迟缓冲还利用G缓冲——存储了表面(通常是最近表面)的其他信息(法线、位置、材质属性 等)。主要包含两个Pass。

第一个Pass中,只利用深度缓冲技术进行片元可见性判断,然后把可见片元的信息填充到G缓冲区。

第二个Pass中,利用G缓冲内各个片元的信息,进行光照计算。

延迟渲染的效率不依赖于场景的复杂度,而和使用的屏幕空间大小有关。

unity中的延迟渲染:这种路径需要一定的硬件支持。每个光源都按逐像素的方式处理,但也存在一定的缺点:

  1. 不支持真正的抗锯齿
  2. 不能处理半透明物体
  3. 对显卡有一定要求

默认的G缓冲区包含以下几个渲染纹理:

  1. RT0:格式ARGB32 ,RGB存储漫反射颜色,A未使用
  2. RT1:格式ARGB32,RGB存储高光反射颜色,A存储高光反射的gloss值
  3. RT2:格式ARGB2101010,RGB存储法线,A未使用
  4. RT3:格式ARGB32(非HDR)或ARGBHalf(HDR),存储自发光+lightmap+反射探针
  5. 深度缓冲和模板缓冲

在第二个Pass中计算光照时,默认情况下仅可使用内置的Standard光照模型,要使用其他的,需要替换原有的Internal-DeferredShading.shader文件。

可访问的内置变量和函数:

Unity的光源类型

支持四种光源:平行光、点光源、聚光灯、面光源(仅在烘焙时才发挥作用)

平行光

  1. 照亮范围:无限制
  2. 作用:通常作为太阳
  3. 位置:无唯一位置,可处于任意位置
  4. 方向:通过transform组件的rotation属性可以改变。
  5. 衰减:无衰减,光照强度不会随着距离而发生改变。

点光源

  1. 照亮范围:有限,由空间内一球体定义,球体半径可由面板中的Range调整。
  2. 位置:有唯一位置。
  3. 方向:点光源位置减去某点位置可得到该点的方向。
  4. 衰减:有衰减,随着物体逐渐远离点光源,光照强度也会逐渐减小。

聚光灯

  1. 照亮范围:有限,由空间内一锥形定义,锥形半径可由面板中的Range调整,锥体张开角度由Spot Angle决定。
  2. 位置:有唯一位置。
  3. 方向:聚光灯位置减去某点位置可得到该点的方向。
  4. 衰减:有衰减,随着物体逐渐远离点光源,光照强度也会逐渐减小,在锥形顶点处光照强度最强,锥形边界处强度0,衰减值可由函数定义。

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

Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
	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 {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			// Apparently need to add this declaration 
			#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 {
			// Pass for other pixel lights
			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);
				//处理不同光源的衰减,点光源和聚光灯的衰减因为设计计算复杂,使用纹理作为查找表(lookup table,LUT)
				#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"
}

效果:

将场景中的光源设置成not important,则被当成逐顶点或SH处理,但是因为没有写这部分代码,所以这些光源效果就不会显示。

左:平行光important,四个点光源not important。

右:平行光important,四个点光源important

Unity的光照衰减

使用纹理查找得到光照衰减的弊端:

  1. 需要预处理,纹理大小影响衰减精度
  2. 不直观也不方便,虽然可以一定程度上提升性能,unity默认使用纹理查找方式计算逐像素的点光源和聚光灯的衰减。

使用_LightTexture0的纹理来计算,如果对光源使用了cookie,那么要使用_LightTextureB0。采样过程为:先得到点在光源空间中的位置,然后使用这个坐标的模的平方进行采样,得到衰减值。

也可以使用数学公式计算衰减,但是由于光源的信息unity开放的并不多,因此计算结果往往不尽如人意。

Unity的阴影——阴影如何实现

实时渲染中最常使用Shadow Map技术——摄像机与光源重合,光源的阴影区域就是摄像机看不到的地方。

前向渲染路径中,如果最重要的平行光开启了阴影,unity会为该光源计算阴影映射纹理(shadowmap)——本质是一张深度图。

unity选择使用一个lightmode标记为shadowcaster的Pass得到这张shadowmap,用于向其他物体投射阴影。

传统阴影映射纹理技术的实现中,在正常渲染的Pass中把顶点位置变换到光源空间,得到点在光源空间下的位置,然后使用xy分量对阴影映射纹理采样,得到阴影映射纹理中该位置的深度信息,如果该深度值小于顶点的深度值(顶点的z),则说明这个点在阴影里。

屏幕空间的阴影映射技术,需要注意不是所有平台unity都使用这种技术,需要显卡支持MRT,而有些移动平台不支持。unity通过调用lightmode为shadowcaster的pass来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理,然后根据这两张纹理得到屏幕空间的阴影图,如果摄像机的深度纹理中记录的深度大于转换到阴影映射纹理中的深度值,就说明该点虽然可见,但是处于光源的阴影中,这样阴影图就包含了屏幕空间所有有阴影的区域。需要物体接受来自其他物体的阴影,在shader中对阴影图进行采样即可——需要注意阴影图是屏幕空间下的。

一个物体接受其他物体的阴影:在shader中对阴影映射纹理(包括屏幕空间的阴影图)采样,把采样结果和最后的光照结果相乘得到阴影效果。

一个物体向其他物体投射阴影:把该物体加入到光源的阴影映射纹理的计算中,即为该物体执行lightmode为shadowcaster的pass。

不透明物体的阴影——让物体投射阴影

V2F_SHADOW_CASTER、TRANSFER_SHADOW_CASTER_NORMALOFFSET、SHADOW_CASTER_FRAGMENT

cast shadows的开启代表unity会将该物体加入光源的阴影映射纹理的计算中——由lightmode为shadowcaster的pass计算,Receive shadow选择是否让物体接收来自其他物体的阴影。

上图中cube使用的shader并没有lightmode为shadowcaster的pass,但是由于调用了fallback,所以unity在内置的shader中可以找到,因此正方形可以正确投射阴影。如果去掉fallback结果如下:

但是还存在一个问题,场景中右边的竖平面,即使与cube一样开启了投射和接收,但是并没有在其他物体上投射出阴影,这是因为在计算光源的阴影映射纹理的时候会剔除物体的背面,对于内置的平面来说,只会有一个面,,由于右侧平面在光源空间下没有正面,所以不加入阴影映射纹理计算,这时需要将cast shadows设置成two sided才行:

除此以外,最下面的平面可以接收阴影是因为使用了内置shader,而cube使用的前面写的前向渲染的shader,没有进行接收阴影的相关操作,所以无效果。

不透明物体的阴影——让物体接收阴影

SHADOW_COORDS、TRANSFER_SHADOW、SHADOW_ATTENUATION

Shader "Unity Shaders Book/Chapter 9/Shadow" {
	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 {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			// Apparently need to add this declaration 
			#pragma multi_compile_fwdbase	
			#pragma vertex vert
			#pragma fragment frag
			// Need these files to get built-in macros
			#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;
				SHADOW_COORDS(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;
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);//用于计算之前声明的阴影纹理坐标。
			 	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;
				fixed shadow = SHADOW_ATTENUATION(i);//计算阴影值
				return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
			}
			ENDCG
		}
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			Blend One One
			CGPROGRAM
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
//			#pragma multi_compile_fwdadd_fullshadows
			#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 position : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			v2f vert(a2v v) {
			 	v2f o;
			 	o.position = 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
					float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
					fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				#endif
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

效果:

统一管理光照衰减和阴影

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)

Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
	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 {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			// Apparently need to add this declaration
			#pragma multi_compile_fwdbase	
			#pragma vertex vert
			#pragma fragment frag
			// Need these files to get built-in macros
			#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;
				SHADOW_COORDS(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;
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	return o;
			}
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				//第一个参数用来存储光照衰减和阴影值相乘的结果
				//第二个参数会传给之前使用的SHADOW_ATTENUATION来计算阴影值
				//第三个参数用于计算光源空间下的坐标,再对光照衰减纹理采样得到光照衰减。
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			ENDCG
		}
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			Blend One One
			CGPROGRAM
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
//			#pragma multi_compile_fwdadd_fullshadows
			#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;
				SHADOW_COORDS(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;
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	return o;
			}
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//不需要判断光源类型
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

透明物体的阴影

对于大多数不透明物体,将fallback设置成VertexLit就可以正确向其他物体投射阴影,但对于透明的物体需要小心处理。

透明度测试的物体投射阴影

在之前的透明度测试中加上将fallback设置成VertexLit,得到的效果是错误的(镂空部分投射出了阴影——因为vertexlit中未做片元舍弃的操作):

将fallback设置成进行了透明度测试的”Transparent/Cutout/VertexLit”,即可得到正确结果——回调的shader中使用了_Cutoff的属性进行透明度测试,所以shader中必须提供名为_Cutoff的属性:

但可以观察有些地方不应该透光——某些面完全背对光源,因此未加入阴影映射纹理的计算,应该将castshadows属性设置为two shadows强制计算所有面,得到正确的结果:

透明度混合的物体投射阴影

所有内置的透明度混合的shader都没有包含阴影投射的pass。也就是这些物体不会向其他物体投射阴影,也不会接受其他物体的阴影。

FallBack “Transparent/VertexLit”——无阴影效果:


FallBack “VertexLit”——强制阴影效果:

强制的图是有问题的,右侧阴影不会穿透立方体投射到下方的平面上。


Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Unity Shader 入门精要(冯乐乐著)学习笔记(8)——更复杂的光照
喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址