《UnityShader 入门精要》更复杂的光照

news2024/11/28 3:41:34

代码&示例图见:zaizai77/Shader-Learn: 实现一些书里讲到的shader

到了这里就开启了书里的中级篇,之后会讲解 Unity 中的渲染路径,如何计算光照衰减和阴影,如何使用高级纹理和动画等一系列进阶内容

Unity 中的渲染路径

在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。我们需要为每个Pass指定它使用的渲染路径

在Unity 5.0版本之前,主要有3种:前向渲染路径(Forward Rendering Path)​、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path)​。

设置Rendering path

摄像机组件的Rendering Path中的设置可以覆盖Project Settings中的设置

如果当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如,如果一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。

        Pass  {
            Tags  {  "LightMode"  =  "ForwardBase"  }

前向渲染路径

前向渲染路径的原理

每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。伪代码描述:

        Pass  {
            for  (each  primitive  in  this  model)  {
              for  (each  fragment  covered  by  this  primitive)  {
                  if  (failed  in  depth  test)  {
                      // 如果没有通过深度测试,说明该片元是不可见的
                      discard;
                  }  else  {
                      // 如果该片元可见
                      // 就迚行光照计算
                      float4  color  =  Shading(materialInfo,  pos,  normal,  lightDir,  viewDir);
                      // 更新帧缓冲
                      writeFrameBuffer(fragment,  color);
                  }
              }
            }
        }

对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。

Unity中的前向渲染

一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。

在Unity中,前向渲染路径有3种处理光照(即照亮物体)的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics, SH)处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的(Important)​。

在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序

  • 场景中最亮的平行光总是按逐像素处理的。
  • 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。
  • 渲染模式被设置成Important的光源,会按逐像素处理。
  • 如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。

光照会在Unity Shader的Pass中进行计算。前向渲染有两种Pass:

  • Base Pass
  • Additional Pass

  • #pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd,只有分别为Bass Pass和Additional Pass使用这两个编译指令,得到一些正确的光照变量,例如光照衰减值等
  • Base Pass旁边的注释给出了Base Pass中支持的一些光照特性。例如在Base Pass中,我们可以访问光照纹理(lightmap)​。
  • Base Pass中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能)​,而Additional Pass中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的Shadow Type。但我们可以在Additional Pass中使用 #pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity在内部使用更多的Shader变种。
  • 环境光和自发光也是在Base Pass中计算的。这是因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,而如果我们在Additional Pass中计算这两种光照,就会造成叠加多次环境光和自发光,这不是我们想要的。
  • 在Additional Pass的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉之前的渲染结果,看起来就好像该物体只受该光源的影响。通常情况下,我们选择的混合模式是Blend One One
  • 对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一个Additional Pass。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外)​,而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。

(要记得东西也太多了吧。。。。)

内置的光照变量和函数

顶点照明渲染路径

顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时得到效果最差的一种类型。它仅支持逐顶点的光源计算,不支持逐像素,所以无法得到:阴影、法线映射、高精度的高光反射等效果,它是前向渲染的一个子集。

Unity 中的顶点照明渲染

顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染,在这个Pass中,我们会计算关心的光源对物体的照明,且是按照逐顶点处理的。

它是Unity中最快速的渲染路径,且具有最广泛的硬件支持(游戏机上不支持这种路径)。Unity 5以后被作为一个遗留的渲染路径,未来的版本中可能会被移除。

可访问的内置变量和函数

在Unity中一个顶点照明的Pass中最多可以访问8个逐顶点光源。

顶点照明渲染路径中可以使用的内置变量:

如果影响该物体的光源数目小于8,则数组中剩余的光源颜色会被设置成黑色。有些变量我们同样可以在前向渲染路径中使用,如:unity_LightColor,但这些变量的数组的维度和数值在不同渲染路径下的值是不同的。

顶点照明渲染路径中可以使用的内置函数:

延迟渲染路径

当场景中包含大量实时光源,使用前向渲染会造成性能急剧下降。每个光源都会执行一个Pass,重复地渲染一个物体会存在很多相同的重复计算。

在这样的环境下,本是一种更古老的渲染方法的延迟渲染,又流行了起来。除了前向渲染中使用的颜色缓冲深度缓冲外,延迟渲染还使用了额外的缓冲区——G缓冲(Geometry Buffer,G-buffer)。G缓冲区存储了我们关心的表面(通常指离相机最近的表面)的其他信息,如:表面的法线、位置、用于光照计算的材质属性等。

延迟渲染的过程大致可以用下面的伪代码来描述:

        Pass  1  {
            // 第一个Pass不迚行真正的光照计算
            // 仅仅把光照计算需要的信息存储到G缓冲中

            for  (each  primitive  in  this  model)  {

              for  (each  fragment  covered  by  this  primitive)  {
                  if  (failed  in  depth  test)  {
                    // 如果没有通过深度测试,说明该片元是不可见的
                    discard;
                  }  else  {
                    // 如果该片元可见
                    // 就把需要的信息存储到G缓冲中
                    writeGBuffer(materialInfo,  pos,  normal,  lightDir,  viewDir);
                  }
              }
          }
        }

        Pass  2  {
          // 利用G缓冲中的信息迚行真正的光照计算

          for  (each  pixel  in  the  screen)  {
              if  (the  pixel  is  valid)  {
                  // 如果该像素是有效的
                  // 读取它对应的G缓冲中的信息
                  readGBuffer(pixel,  materialInfo,  pos,  normal,  lightDir,  viewDir);

                  // 根据读取到的信息迚行光照计算
                  float4  color  =  Shading(materialInfo,  pos,  normal,  lightDir,  viewDir);
                  // 更新帧缓冲
                  writeFrameBuffer(pixel,  color);
              }
          }
        }

延迟渲染使用的Pass数目通常是2个,与场景中包含的光源数目没有关系,和屏幕的大小有关。

延迟渲染适合在光源数目众多、使用前向渲染造成性能瓶颈的情况下使用,每个光源都可以按逐像素处理。但它也有缺点:

  • 不支持真正的抗锯齿功能;
  • 不能处理半透明物体;
  • 对显卡有要求,必须支持MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。

延迟渲染需要两个Pass:

  • 第一个Pass用于渲染G缓冲

把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中对每个物体来说,这个Pass仅执行一次。

  • 第二个Pass用于计算真正的光照模型

使用上一个Pass中渲染的数据来计算最终的颜色光照颜色,再存储到帧缓冲中。

这些变量都可以在UnityDeferred Library.cginc文件中找到它们的声明。

更加深入的东西就直接看官方文档吧,太深入了,写了也是白写,记不住

Unity - Manual: Introduction to rendering paths in the Built-In Render Pipeline

4种渲染路径(前向渲染路径、延迟渲染路径、遗留的延迟渲染路径和顶点照明渲染路径)的详细比较,包括它们的特性比较(是否支持逐像素光照、半透明物体、实时阴影等)​、性能比较以及平台支持

Unity 的光源类型

之前的例子中用且仅用了一个平行光光源,之后在本节中,我们将会学习如何在Unity中处理点光源(point light)和聚光灯(spot light)​。

Unity一共支持4种光源类型:平行光、点光源、聚光灯和面光源(area light)面光源仅在烘焙时才可发挥作用,所以这里不讨论

光源类型有什么影响

最常使用的光源属性有:光源的位置方向(到某点的方向)、颜色强度以及衰减(到某点的衰减,与点到光源的距离有关)。

平行光

它的几何定义是最简单的。平行光可以照亮的范围是没有限制的,它通常是作为太阳这样的角色在场景中出现的,它没有一个唯一的位置,也就是说,它可以放在场景中的任意位置,它的几何属性只有方向,光照强度不会随着距离而发生改变。

点光源

点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光

点光源是有位置属性的,同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。

聚光灯

它的照亮空间同样是有限的,但不再是简单的球体,而是由空间中的一块锥形区域定义的。聚光灯可以用于表示由一个特定位置出发、向特定方向延伸的光。

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

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

        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编译指令。#pragma multi_compile_fwdbase指令可以保证我们在Shader中使用光照衰减等光照变量可以被正确赋值。

我们在Base Pass中处理了场景中的最重要的平行光。在这个例子中,场景中只有一个平行光。如果场景中包含了多个平行光,Unity会选择最亮的平行光传递给Base Pass进行逐像素处理,其他平行光会按照逐顶点或在Additional Pass中按逐像素的方式处理。如果场景中没有任何平行光,那么Base Pass会当成全黑的光源处理

我们可以使用__WorldSpaceLightPos0来得到这个平行光的方向(位置对平行光来说没有意义)​,使用_LightColor0来得到它的颜色和强度(_LightColor0已经是颜色和强度相乘后的结果)​,由于平行光可以认为是没有衰减的,因此这里我们直接令衰减值为1.0。

        //  Compute  diffuse  term
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

        ...

        //  Compute  specular  term
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)),
        _Gloss);

        //  The  attenuation  of  directional  light  is  always  1
        fixed  atten  =  1.0;

        return  fixed4(ambient  +  (diffuse  +  specular)  *  atten,  1.0);

Additional Pass 

        Pass  {
            //  Pass  for  other  pixel  lights
            Tags  {  "LightMode"="ForwardAdd"  }

            Blend  One  One

            CGPROGRAM

            //  Apparently  need  to  add  this  declaration
            #pragma  multi_compile_fwdadd

与Base Pass不同的是,我们还使用Blend命令开启和设置了混合模式。这是因为,我们希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加。如果没有使用Blend命令的话,Additional Pass会直接覆盖掉之前的光照结果。

        #ifdef  USING_DIRECTIONAL_LIGHT
              fixed3  worldLightDir  =  normalize(_WorldSpaceLightPos0.xyz);
        #else
              fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
        #endif

在上面的代码中,我们首先判断了当前处理的逐像素光源的类型,这是通过使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT来得到的。如果当前前向渲染Pass处理的光源类型是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT。如果判断得知是平行光的话,光源方向可以直接由_WorldSpaceLightPos0.xyz得到;如果是点光源或聚光灯,那么_WorldSpaceLightPos0.xyz表示的是世界空间下的光源位置,而想要得到光源方向的话,我们就需要用这个位置减去世界空间下的顶点位置。

处理不同光源的衰减

        #ifdef  USING_DIRECTIONAL_LIGHT
            fixed  atten  =  1.0;
        #else
            float3  lightCoord  =  mul(_LightMatrix0,  float4(i.worldPosition,  1)).xyz;
            fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
        #endif

我们可以使用数学表达式来计算给定点相对于点光源和聚光灯的衰减,但这些计算往往涉及开根号、除法等计算量相对较大的操作,因此Unity选择了使用一张纹理作为查找表(Lookup Table, LUT)​,以在片元着色器中得到光源的衰减。我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值

Shader "Custom/Chapter9/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 {
				// 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);

				#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
		}
	}
}

对于场景中的一个物体,如果它不在一个光源的光照范围内,Unity是不会为这个物体调用Pass来处理这个光源的

我们可以通过把光源的Render Mode设为Not Important来告诉Unity,我们不希望把该光源当成逐像素处理。

因为shader 中没有写逐顶点光照处理,所以被设置为Not Important 的点光源不会对胶囊体产生影响

Unity 的光照衰减

上面的示例中使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减。这样的好处在于,计算衰减不依赖于数学公式的复杂性,我们只要使用一个参数值去纹理中采样即可。但使用纹理查找来计算衰减也有一些弊端

  • 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度。
  • 不直观,同时也不方便,因此一旦把数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。

用于光照衰减的纹理

Unity在内部使用一张名为_LightTexture0的纹理来计算光源衰减,我们通常只关心_LightTexture0对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。例如,(0, 0)点表明了与光源位置重合的点的衰减值,而(1, 1)点表明了在光源空间中所关心的距离最远的点的衰减。

为了对_LightTexture0纹理采样得到给定点到该光源的衰减值,我们首先需要得到该点在光源空间中的位置,这是通过_LightMatrix0变换矩阵得到的,_LightMatrix0可以把顶点从世界空间变换到光源空间

        float3  lightCoord  =  mul(_LightMatrix0,  float4(i.worldPosition,  1)).xyz;

然后,我们可以使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值:

        fixed atten = tex2D(_LightTexture0, dot(lightCoord,  lightCoord).rr).UNITY_ATTEN_CHANNEL;

可以发现,在上面的代码中,我们使用了光源空间中顶点距离的平方(通过dot函数来得到)来对纹理采样,之所以没有使用距离值来采样是因为这种方法可以避免开方操作。然后,我们使用宏UNITY_ATTEN_CHANNEL来得到衰减纹理中衰减值所在的分量,以得到最终的衰减值。

Unity 中的阴影

为了让场景看起来更加真实,具有深度信息,我们通常希望光源可以把一些物体的阴影投射在其他物体上

阴影是如何产生的

当一个光源发射的一条光线遇到一个不透明物体时,这条光线就不可以再继续照亮其他物体(这里不考虑光线反射)​。因此,这个物体就会向它旁边的物体投射阴影,那些阴影区域的产生是因为光线无法到达这些区域。

在实时渲染中,我们最常使用的是一种名为Shadow Map的技术。这种技术理解起来非常简单,它会首先把摄像机的位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。而Unity就是使用的这种技术。

在前向渲染路径中,如果场景中最重要的平行光开启了阴影,Unity就会为该光源计算它的阴影映射纹理(shadowmap)​。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置(深度信息)​。

Unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是LightMode标签被设置为ShadowCaster的Pass。这个Pass的渲染目标不是帧缓存,而是阴影映射纹理(或深度纹理)​。

因此当开启了光源的阴影效果后,底层渲染会优先找当前渲染物体的Unity Shader中LightMode标签为ShadowCaster的Pass,如果没有,会继续在Fallback中找,如果仍然没有找到,该物体就不会向其他物体投射阴影。

  • 传统的阴影映射纹理实现过程:

在正常渲染的Pass中把顶点位置变换到光源空间下,得到光源空间中顶点的位置,使用xy分量对阴影映射纹理进行采样得到记录的深度值,如果它小于这个顶点的z分量,则说明这个顶点在阴影中。

  • Unity 5及以后使用的屏幕空间的阴影映射技术(Screenspace Shadow Map):

调用LightMode为ShadowCaster的Pass得到光源的阴影映射纹理和相机的深度纹理,如果相机深度纹理记录的深度大于转换到光源阴影映射纹理中的深度值,则说明这个物体表面的该点处于阴影中。通过这样的方式,生成的阴影图包含了屏幕空间中所有有阴影的区域。我们只需要对这个阴影图进行采样就可以计算场景中哪些物体会呈现阴影了。

关于阴影,其实涉及投射接收两个过程。举个例子,在太阳光下物体A挡住了物体B,在物体B上产生了阴影。那么对于物体A,就是投射阴影的过程,对于物体B就是接收阴影的过程。

光源的阴影映射纹理的生成过程会计算投射物体在光源空间中的位置;

接收物体的Shader实现中会对光源的阴影映射纹理进行采样,以确认它是否会被挡住并产生阴影。

不透明物体的阴影

Mesh Renderer组件的Cast Shadows和Receive Shadows属性可以控制该物体是否投射/接收阴影 

让物体接收阴影

首先,我们在Base Pass中包含进一个新的内置文件:

        #include "AutoLight.cginc"。

这是因为,我们下面计算阴影时所用的宏都是在这个文件中声明的。

我们在顶点着色器的输出结构体v2f中添加了一个内置宏SHADOW_COORDS:

        struct  v2f  {
            float4  pos  :  SV_POSITION;
            float3  worldNormal  :  TEXCOORD0;
            float3  worldPos  :  TEXCOORD1;
            SHADOW_COORDS(2)
        };

这个宏的作用很简单,就是声明一个用于对阴影纹理采样的坐标。需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2。

然后,我们在顶点着色器返回之前添加另一个内置宏TRANSFER_SHADOW:

        v2f  vert(a2v  v)  {
            v2f  o;
            ...
            //  Pass  shadow  coordinates  to  pixel  shader
            TRANSFER_SHADOW(o);

            return  o;
        }

这个宏用于在顶点着色器中计算上一步中声明的阴影纹理坐标。

接着,我们在片元着色器中计算阴影值,这同样使用了一个内置宏SHADOW_ATTENUATION:

        //  Use  shadow  coordinates  to  sample  shadow  map
        fixed  shadow  =  SHADOW_ATTENUATION(i);

透明物体的阴影​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用nextjs开发时遇到的问题

这几天已经基本把node后端的接口全部写完了,在前端开发时考虑时博客视频类型,考虑了ssr,于是选用了nextJs,用的是nextUi,tailwincss,目前碰到两个比较难受的事情。 1.nextUI个别组件无法在服务器段渲染 目前简单的解决方法&…

Golang项目:实现一个内存缓存系统

要求 支持设定过期时间,精确到秒支持设定最大内存,当内存超过时做出合适的处理支持并发安全按照以下接口安全 type Cache interface{//size : 1KB 100KB 1MB 2MB 1GBSetMaxMemory(size string )bool//将value写入缓存Set(key string, val interface{},e…

Softing线上研讨会 | Ethernet-APL:推动数字时代的过程自动化

| (免费)线上研讨会时间:2024年11月19日 16:00~16:30 / 23:00~23:30 Ethernet-APL以10Mb/s的传输速率为过程工业中的现场设备带来了无缝以太网连接和本质安全电源,这不仅革新了新建工厂,也适用于改造现有工厂。 与现…

《Deep Multimodal Learning with Missing Modality: A Survey》中文校对版

文章汉化系列目录 文章目录 文章汉化系列目录摘要1 引言2 方法论分类:概述2.1 数据处理方面2.2 策略设计方面 3 数据处理方面的方法3.1 模态填充3.1.1 模态组合方法3.1.2 模态生成方法 3.2 面向表示的模型3.2.1 协调表示方法3.2.2 表示组合方法。3.2.3 表示生成方法…

python爬虫案例——猫眼电影数据抓取之字体解密,多套字体文件解密方法(20)

文章目录 1、任务目标2、网站分析3、代码编写1、任务目标 目标网站:猫眼电影(https://www.maoyan.com/films?showType=2) 要求:抓取该网站下,所有即将上映电影的预约人数,保证能够获取到实时更新的内容;如下: 2、网站分析 进入目标网站,打开开发者模式,经过分析,我…

鸿蒙安全控件之位置控件简介

位置控件使用直观且易懂的通用标识,让用户明确地知道这是一个获取位置信息的按钮。这满足了授权场景需要匹配用户真实意图的需求。只有当用户主观愿意,并且明确了解使用场景后点击位置控件,应用才会获得临时的授权,获取位置信息并…

MATLAB矩阵元素的修改及删除

利用等号赋值来进行修改 A ( m , n ) c A(m,n)c A(m,n)c将将矩阵第 m m m行第 n n n列的元素改为 c c c,如果 m m m或 n n n超出原来的行或列,则会自动补充行或列,目标元素改为要求的,其余为 0 0 0 A ( m ) c A(m)c A(m)c将索引…

网络安全之内网安全

下面给出了应对企业内网安全挑战的10种策略。这10种策略即是内网的防御策略,同时也是一个提高大型企业网络安全的策略。 1、注意内网安全与网络边界安全的不同 内网安全的威胁不同于网络边界的威胁。网络边界安全技术防范来自Internet上的攻击,主要是防…

Python 爬虫入门教程:从零构建你的第一个网络爬虫

网络爬虫是一种自动化程序,用于从网站抓取数据。Python 凭借其丰富的库和简单的语法,是构建网络爬虫的理想语言。本文将带你从零开始学习 Python 爬虫的基本知识,并实现一个简单的爬虫项目。 1. 什么是网络爬虫? 网络爬虫&#x…

solr 远程命令执行 (CVE-2019-17558)

目录 漏洞描述 执行漏洞py脚本,取得shell连接 EXP 漏洞描述 Apache Velocity是一个基于Java的模板引擎,它提供了一个模板语言去引用由Java代码定义的对象。Velocity是Apache基金会旗下的一个开源软件项目,旨在确保Web应用程序在表示层和业…

数据库中的视图

数据库中的视图 什么是视图创建视图使⽤视图修改数据注意事项 删除视图视图的优点 什么是视图 视图是⼀个虚拟的表,它是基于⼀个或多个基本表或其他视图的查询结果集。视图本⾝不存储数 据,⽽是通过执⾏查询来动态⽣成数据。⽤户可以像操作普通表⼀样使…

爬虫实战:采集知乎XXX话题数据

目录 反爬虫的本意和其带来的挑战目标实战开发准备代码开发发现问题1. 发现问题[01]2. 发现问题[02] 解决问题1. 解决问题[01]2. 解决问题[02] 最终结果 结语 反爬虫的本意和其带来的挑战 在这个数字化时代社交媒体已经成为人们表达观点的重要渠道,对企业来说&…

springboot-vue excel上传导出

数据库 device_manage表 字段,id,workshop,device_number,device_name,device_model,warn_time,expired_time device_warn表 字段,id,warn_time,expired_time 后端 实体类格式 device_manage Data TableName("device_manage"…

【简单好抄保姆级教学】javascript调用本地exe程序(谷歌,edge,百度,主流浏览器都可以使用....)

javascript调用本地exe程序 详细操作步骤结果 详细操作步骤 在本地创建一个txt文件依次输入 1.指明所使用注册表编程器版本 Windows Registry Editor Version 5.00这是脚本的第一行,指明了所使用的注册表编辑器版本。这是必需的,以确保脚本能够被正确解…

Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)

目录 一、Zset有序集合类型介绍 二、常见命令 1、ZADD 2、ZCARD 3、ZCOUNT 4、ZRANGE 5、ZREVRANGE 6、ZRANGEBYSCORE 7、ZREVRANGEBYSCORE 8、ZPOPMAX 9、ZPOPMIN 10、ZRANK 11、ZREVRANK 12、ZSCORE 13、ZREM 14、ZREMRANGEBYRANK 15、ZREMRANGEBYSCORE 16…

设计模式之 责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,旨在将多个处理对象通过链式结构连接起来,形成一条处理请求的链条。每个处理对象都有机会处理请求,或者将请求传递给链中的下一个对象。这样&#x…

新版布谷直播软件源码开发搭建功能更新明细

即将步入2025年也就是山东布谷科技专注直播系统开发,直播软件源码出售开发搭建等业务第9年,山东布谷科技不断更新直播软件功能,以适应当前新市场环境下的新要求。山东布谷科技始终秉承初心,做一款符合广大客户需求的直播系统软件。支持广大客户提交更多个…

VITE+VUE3+TS环境搭建

前言(与搭建项目无关): 可以安装一个node管理工具,比如nvm,这样可以顺畅的切换vue2和vue3项目,以免出现项目跑不起来的窘境。我使用的nvm,当前node 22.11.0 目录 搭建项目 添加状态管理库&…

HTML飞舞的爱心

目录 系列文章 写在前面 完整代码 代码分析 写在后面 系列文章 序号目录1HTML满屏跳动的爱心(可写字)2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心(简易版)7HTML粒子爱心8HTML蓝色…

英伟达推出了全新的小型语言模型家族——Hymba 1.5B

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…