本文主要是介绍Cg Programming In Unity Diffuse Reflection(Wiki翻译自用),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Contents
- 漫反射
- 单个定向光源(平行光)的shader代码
- Fallback Shaders
- 多个平行(像素)光的shader代码
- 点光源变化
- 聚光灯变化
- 总结
本教程介绍了每个顶点的漫反射。
这是有关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) a⋅b=∣a∣∣b∣cos∡(a,b) 。
对于归一化向量,长度 ∣ a ∣ |a| ∣a∣和 ∣ b ∣ |b| ∣b∣都是1。
如果点积 N ⋅ L N·L N⋅L为负,那么光源在表面”错误“的一侧,我们应该将反射设置为0。可以通过 m a x ( 0 , N ⋅ L ) max(0,N·L) max(0,N⋅L)保证点积的结果不为负,当点积结果为负时固定为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_{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 setCGPROGRAM#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 shadersstruct 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
,以将其渲染为像素光源。
到目前为止,对于ForwardBase
Pass,我们的着色器代码还可以。对于ForwardAdd
Pass,我们需要将反射光添加到已经存储在帧缓冲区的光中。为此,我们只需要配置 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 sourceCGPROGRAM#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 shadersstruct 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 sourcesBlend 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 shadersstruct 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 sourceCGPROGRAM#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 shadersstruct 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 attenuationlightDirection = 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 sourcesBlend 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 shadersstruct 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 attenuationlightDirection = 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"
}
请注意,ForwardBase
Pass中的光源始终是定向光。 因此,实际上可以简化第一遍的代码。 另一方面,在两次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,N⋅L))。
- 如何在着色器中为单个平行光实现漫反射。
- 如何扩展具有线性衰减的点光源的着色器。
- 如何进一步扩展着色器以处理多个逐像素的光照。
这篇关于Cg Programming In Unity Diffuse Reflection(Wiki翻译自用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!