Cg Programming In Unity Diffuse Reflection(Wiki翻译自用)


本教程介绍了每个顶点的漫反射。
这是有关Unity中基本照明的一系列教程中的第一篇。 在本教程中,我们从单个方向性光源的漫反射开始,然后包括点光源和多个光源(使用多次通过)。 进一步的教程将对此进行扩展,特别是镜面反射,每个像素光照和双面光照。

漫反射

在这里插入图片描述月球几乎只表现出漫反射(也被称为兰伯特反射,),即光被反射到所有方向而没有镜面反射高光(理想的漫反射)。此类材质的其他示例是粉笔和磨砂纸。实际上,任何看起来暗淡无光的表面都是漫反射。在完全漫反射的情况下,观察到的反射光的强度取决于表面法线矢量和入射光夹角的余弦。如左图所示,通常考虑从计算照明的曲面上一点开始的归一化矢量:归一化表面法线向量N垂直于表面,归一化光照方向L指向光源。
对于观察到的漫反射光 I d i f f u s e I_{diffuse} Idiffuse 我们需要归一化表面法线向量N与光源L归一化方向之间的夹角余弦,即点积N·L,因为 两个向量a和b的点积a·b为: a ⋅ b = ∣ a ∣ ∣ b ∣ cos ⁡ ∡ ( a , b ) a\cdot b = |a||b|\cos \measuredangle(a,b) ab=abcos(a,b)
对于归一化向量,长度 ∣ a ∣ |a| a ∣ b ∣ |b| b都是1。
如果点积 N ⋅ L N·L NL为负,那么光源在表面”错误“的一侧,我们应该将反射设置为0。可以通过 m a x ( 0 , N ⋅ L ) max(0,N·L) max(0,NL)保证点积的结果不为负,当点积结果为负时固定为0。此外,反射光取决于入射光强度 I i n c o m i n g I_{incoming} Iincoming和材质反射常量 k d i f f u s e k_{diffuse} kdiffuse:对于黑色表面, k d i f f u s e = 0 k_{diffuse}=0 kdiffuse=0,对于白色表面, k d i f f u s e = 1 k_{diffuse}=1 kdiffuse=1
漫反射公式为:
*

I d i f f u s e = I i n c o m i n g k d i f f u s e m a x ( 0 , N ⋅ L ) I_{diffuse}=I_{incoming}k_{diffuse}max(0,N·L) Idiffuse=Iincomingkdiffusemax(0,NL)

对于彩色光,该方程适应于每种颜色的分量(例如,红,绿,蓝)。因此,如果 I d i f f u s e I_{diffuse} Idiffuse, I i n c o m i n g I_{incoming} Iincoming k d i f f u s e k_{diffuse} kdiffuse表示颜色矢量,并且乘法是逐分量执行的(它们用于Cg中的矢量),此等式也适用于彩色光。 这就是我们在着色器代码中实际使用的东西。

单个定向光源(平行光)的shader代码

如果我们只有一个平行光,则用于为 I d i f f u s e I_{diffuse} Idiffuse实现方程式的shader代码相对较小。为了实现方程式,我们遵循有关实现方程式的问题:

  • 应该在顶点着色器还是片元着色器中实现方程?我们在这里尝试在顶点着色器。 在“平滑镜面高光”部分,我们将介绍片段着色器中的实现。
  • 应该在哪个坐标系中实现方程式? 我们默认在Unity中使用世界空间。(事实证明,这是一个不错的选择,因为Unity在世界空间中提供了光的方向)。
  • 我们从哪里获得参数?这个答案有点长:
    我们使用一个shader属性使用户指定漫反射材质颜色 k d i f f u s e k_{diffuse} kdiffuse。我们可以通过Unity内置的参数_WorldSpaceLightPos0获得指向世界空间中光源的方向,通过内置参数_LightColor0获得光照颜色 I i n c o m i n g I_{incoming} Iincoming。如”Shading in World Space“所描述的,我们必须使用标签
    Tags {"LightMode" = "ForwardBase"}标记着色器通道,以使得这些内置的uniform参数具有正确的值。(之后我们将讨论此标签的实际含义)。我们从具有Normal语义的顶点输入参数获得在对象空间中的表面法线向量。由于我们在世界空间中实现此方程,因此必须按照”Silhouette Enhancement“一节中所讨论的一样,将法线向量变换到世界空间。
    shader代码如下:
Shader "Cg per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // make sure that all uniforms are correctly set
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         uniform float4 _LightColor0; 
            // color of light source (from "UnityLightingCommon.cginc")
 
         uniform float4 _Color; // define shader property for shaders
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject;
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            // alternative: 
            // float3 normalDirection = UnityObjectToWorldNormal(input.normal);
            float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
 
            float3 diffuseReflection = _LightColor0.rgb * _Color.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
            output.col = float4(diffuseReflection, 1.0);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
   }
   Fallback "Diffuse"
}

使用此着色器时,请确保场景中只有一个光源,该光源必须是平行光。

Fallback Shaders

shader代码中的Fallback“ Diffuse”行定义了一个内置的Fallback着色器,以防Unity找不到合适的子着色器。 对于我们的示例,如果Unity不使用前向渲染路径(请参见下文),则它将使用Fallback shader。通过为我们的着色器属性选择特定名称“ _Color”,确保此内置Fallback Shader也可以访问它。内置着色器的源代码可在Unity网站上获得。检查此源代码似乎是确定合适的Fallback Shader及其使用的属性名称的唯一方法。

多个平行(像素)光的shader代码

到目前为止,我们仅考虑了单个光源。 为了处理多个光源,Unity根据渲染和质量设置选择各种技巧。在此处的教程中,我们将仅介绍“前向渲染路径”。(此外,应将所有摄像机配置为使用播放器设置,这是默认设置。)
在本教程中,我们仅考虑Unity的所谓像素灯。 对于第一个像素光(也就是平行光),Unity调用shader Pass的标记Tags { "LightMode" = "ForwardBase" }(如上面代码所示)。对于每增加一个平行光,Unity调用shader Pass的标记Tags { "LightMode" = "ForwardAdd" },为了确保所有光源都渲染为像素光源,必须确保质量设置允许足够的像素光源:选择Edit > Project Settings > Quality,然后在您使用的任何质量设置中增加标记为Pixel Light Count的数字。如果场景中的光源多于像素光数量允许的范围,那么Untiy仅将最重要的光作为像素光进行渲染。或者,您可以将Render Mode 设置为Important,以将其渲染为像素光源。
到目前为止,对于ForwardBasePass,我们的着色器代码还可以。对于ForwardAddPass,我们需要将反射光添加到已经存储在帧缓冲区的光中。为此,我们只需要配置 blend 即可将新的片元输出颜色添加到帧缓冲区中的颜色。如“Transparency”部分所述,这是通过以下行指定的加法混合方程实现的:

Blend One One

Blend自动将所有结果固定在0到1之间,因此我们不需要担心颜色或者alpha值大于1。
总而言之,我们用于多个定向光源的新着色器为:

Shader "Cg per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
           // pass for first light source
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         uniform float4 _LightColor0; 
            // color of light source (from "UnityLightingCommon.cginc")
 
         uniform float4 _Color; // define shader property for shaders
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
 
            float3 diffuseReflection = _LightColor0.rgb * _Color.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
            output.col = float4(diffuseReflection, 1.0);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
 
      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         uniform float4 _LightColor0; 
            // color of light source (from "UnityLightingCommon.cginc")
 
         uniform float4 _Color; // define shader property for shaders
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
 
            float3 diffuseReflection = _LightColor0.rgb * _Color.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
            output.col = float4(diffuseReflection, 1.0);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
   }
   Fallback "Diffuse"
}

这似乎是一个相当长的着色器。但是,除了Tags和ForwardAdd Pass中的Blend设置之外,两个Pass都是相同的。

点光源变化

对于平行光源,_WorldSpaceLightPos0指定了光源入射方向。但是,对于点光源,_WorldSpaceLightPos0指定了光源在世界空间中的位置。我们必须计算光源方向为世界空间中,顶点坐标到光源位置的差向量。因为点的第四坐标为1(点光源),方向的第四坐标为0(平行光),我们可以轻松区分这两种情况:

			float3 lightDirection;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               lightDirection = normalize(_WorldSpaceLightPos0.xyz 
                  - mul(modelMatrix, input.vertex).xyz);
            }

虽然平行光没有光的衰减,但我们应该增加点光源随距离的衰减。当光从一个点在三个维度上扩散时,它以更大的距离覆盖了更大的虚拟球体。由于这些球体的表面随半径的增加而呈平方增加,因此,每个区域的光量随与点光源的距离增加而呈二次方减少。因此,我们应将光源的强度除以到顶点的距离的平方。
由于二次衰减相当快,因此我们使用随距离变化的线性衰减,即将强度除以距离而不是平方距离。 代码可以是:

			float3 lightDirection;
			float attenuation;
			if(0.0 == _WorldSpaceLightPos0.w)
			{
				attenuation = 1.0;
				lightDirection = normalize(_WorldSpaceLightPos0.xyz);
			}
			else
			{
				float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
					- mul(unity_ObjectToWorld,input.vertex).xyz;
				float distance = length(vertexToLightSource );
				attenuation = 1.0/distance;
				lightDirection = normalize(vertexToLightSource);
			}

然后将衰减因子attenuation 乘以_LightColor0来计算入射光。请参见下面的着色器代码。请注意,点光源具有其他功能,这些功能超出了本教程的范围。
另外请注意,此代码不太可能为您提供最佳性能,因为任何if通常都非常昂贵。由于_WorldSpaceLightPos0.w为0或1,因此实际上不必重写代码即可避免使用if并进一步优化代码:

			float3 vertexToLightSource = 
				_WorldSpaceLightPos0.xyz - mul(unity_ObjectToWorld, 
				input.vertex * _WorldSpaceLightPos0.w).xyz ;
			float one_over_distance = 1.0 / length (vertexToLightSource);
			float attenuation = lerp(1.0, one_over_distance, _WorldSpaceLightPos0.w); 
			float3 lightDirection =  vertexToLightSource * one_over_distance;

但是,为清楚起见,我们将使用带有if的版本。
多个定向和点光源的完整着色器代码为:

Shader "Cg per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
           // pass for first light source
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         uniform float4 _LightColor0; 
            // color of light source (from "UnityLightingCommon.cginc")
 
         uniform float4 _Color; // define shader property for shaders
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = _WorldSpaceLightPos0.xyz 
                  - mul(modelMatrix, input.vertex).xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
            output.col = float4(diffuseReflection, 1.0);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
 
      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
 
         uniform float4 _LightColor0; 
            // color of light source (from "UnityLightingCommon.cginc")
 
         uniform float4 _Color; // define shader property for shaders
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 col : COLOR;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject;
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = _WorldSpaceLightPos0.xyz 
                  - mul(modelMatrix, input.vertex).xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb
               * max(0.0, dot(normalDirection, lightDirection));
  
            output.col = float4(diffuseReflection, 1.0);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }
 
         ENDCG
      }
   }
   Fallback "Diffuse"
}

请注意,ForwardBasePass中的光源始终是定向光。 因此,实际上可以简化第一遍的代码。 另一方面,在两次Pass中使用相同的Cg代码,可以更轻松地将代码从一个Pass复制并粘贴到另一个Pass,以防万一我们必须编辑着色器代码。

聚光灯变化

Unity借助“ Cookies”部分中所述的cookie纹理实现聚光灯; 但是,这有些超前。 在这里,我们将聚光灯视为点光源。

总结

您刚刚了解了Unity的每个像素灯光如何工作。 对于以后有关更高级照明的教程,这是必不可少的。 我们还看到了:

  • 什么是漫反射以及如何进行数学描述( I d i f f u s e = I i n c o m i n g k d i f f u s e m a x ( 0 , N ⋅ L ) I_{diffuse}=I_{incoming}k_{diffuse}max(0,N·L) Idiffuse=Iincomingkdiffusemax(0,NL))。
  • 如何在着色器中为单个平行光实现漫反射。
  • 如何扩展具有线性衰减的点光源的着色器。
  • 如何进一步扩展着色器以处理多个逐像素的光照。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值