Cg Programming/Unity/Specular Highlights镜面高光
2017-10-17 20:19
381 查看
本教程涵盖了使用Phone反射模型的逐顶点光照(也叫做高洛德着色)。它在章节“漫反射”中通过两个额外的术语扩展了着色器代码:环境光和镜面反射。这三个术语一起构成了Phone反射模型。如果你没有读过章节“漫反射”,这会是一个非常好的机会来阅读它。
想想上面卡拉瓦乔的那幅画。白裙的大部分是在阴影中的,而没有任意部分是全黑的。很明显通常有一些光从墙壁反射回来,其它物体照亮场景中的一切–至少在某种程度上。在Phong反射模型中,这种效果会通过环境光的方法考虑进去,这个取决于总的环境光强
以及漫反射材质颜色
,环境光强的等式如下:
对比“漫反射”中的漫反射等式,这个等式也可以解释为光的红色、绿色和蓝色分量的向量等式。
在Unity中,一个统一的环境光照通过从主菜单选择Window > Lighting > Scene,设置Ambient Source为Colorc以及设置Ambient Color来指定。在Unity中的Cg着色器中,这个颜色可以使用UNITY_LIGHTMODEL_AMBIENT,它是章节“世界空间中的着色器”提到的预定义uniforms中的一个。(如果你选择Gradient来代替Color,那么UNITY_LIGHTMODEL_AMBIENT和unity_AmbientSky指定天空颜色,而Equator Color和Ground Color就由unity_AmbientEquator和unity_AmbientGround来指定。)
高光反射的计算需要表面法向量N,指向光源的方向L,反射方向R和指向观察者的方向V。
如果你近距离地查看卡拉瓦乔的那副画,你将会看到一些高光:在鼻子上,在头发上,在嘴唇上,在琵琶上,在小提琴上,在碗上,在水果上,等等。Phong反射模型包括了一个镜面反射项,它可以在光亮的表面模拟高光;甚至还包括一个参数
来指定材质的光泽。这个光泽会指定高光范围的大小:反射度越大,高光范围就越小。
只有在几何反射方向R上,一个完美发光的表面才能从光源反射光。对于不完美的发光表面,光会被反射到R周围的发向上:反光度越小,高光区域就越宽。在数学上,归一化的反射方向R定义如下:
对于归一化法向量N和归一化的指向光源的方向L,在Cg中,函数float3 reflect(float3 I, float3 N) (或float4 reflect(float4 I, float4 N))会计算相同的反射向量,但只是针对从光源指向表面上点的方向I。因此,为了使用这个函数,我们必须把方向L取反。
高光反射部分会以观察者的方向V来计算高光反射。如上讨论,如果V离R越近,强度就越强,这里“越近”由反光度
来参数化。在Phong反射模型中,R和V之间夹角余弦值的
次方被用来生成不同反光度的高光。
跟漫反射的情况相似,我们应该截取负的余弦值到0。此外,对于镜面反射,镜面部分需要一个材质颜色
,它通常是白色的,这样所有高光就有入射光的颜色
。举例来说,卡拉瓦乔的画作中所有的高光都是白色的。Phong反射模型的镜面部分表示如下:
跟漫反射情况类似,如果光源在表面错误的一边的话应该忽略镜面部分;也就是如果点乘N·L的值是负数的话。
对于镜面反射的实现,我们需要世界空间中指向观察者的方向,它可以通过计算摄像机位置和顶点位置(都在世界空间)的差值得到。世界空间中摄像机的位置由Unity的统一参数_WorldSpaceCameraPos提供;顶点位置可以像章节“漫反射”中讨论的一样转换而来。世界空间中镜面部分的等式可以通过以下方式实现:
这一小段代码使用了章节“漫反射”着色器代码中相同的变量,以及另外使用了用户自定义参数_SpecColor 和_Shininess。(名字是被别指定的,这样备用着色器就能访问它们;查阅章节“漫反射”。)pow(a, b)是用来计算a的b次方
。
如果环境光被加入到第一个通道(我们只需要一次)中,以及镜面反射被加入到章节“漫反射”中完整着色器的两个通道中的话,那么着色器看起来就像这样:
在Phone反射模型中什么是环境光。
在Phone反射模型中什么是镜面反射项。
在Unity,在Cg中,这些镜面反射项如何实现。
关于着色器代码,你应该阅读章节“漫反射”。
环境光
想想上面卡拉瓦乔的那幅画。白裙的大部分是在阴影中的,而没有任意部分是全黑的。很明显通常有一些光从墙壁反射回来,其它物体照亮场景中的一切–至少在某种程度上。在Phong反射模型中,这种效果会通过环境光的方法考虑进去,这个取决于总的环境光强
以及漫反射材质颜色
,环境光强的等式如下:
对比“漫反射”中的漫反射等式,这个等式也可以解释为光的红色、绿色和蓝色分量的向量等式。
在Unity中,一个统一的环境光照通过从主菜单选择Window > Lighting > Scene,设置Ambient Source为Colorc以及设置Ambient Color来指定。在Unity中的Cg着色器中,这个颜色可以使用UNITY_LIGHTMODEL_AMBIENT,它是章节“世界空间中的着色器”提到的预定义uniforms中的一个。(如果你选择Gradient来代替Color,那么UNITY_LIGHTMODEL_AMBIENT和unity_AmbientSky指定天空颜色,而Equator Color和Ground Color就由unity_AmbientEquator和unity_AmbientGround来指定。)
镜面高光
高光反射的计算需要表面法向量N,指向光源的方向L,反射方向R和指向观察者的方向V。
如果你近距离地查看卡拉瓦乔的那副画,你将会看到一些高光:在鼻子上,在头发上,在嘴唇上,在琵琶上,在小提琴上,在碗上,在水果上,等等。Phong反射模型包括了一个镜面反射项,它可以在光亮的表面模拟高光;甚至还包括一个参数
来指定材质的光泽。这个光泽会指定高光范围的大小:反射度越大,高光范围就越小。
只有在几何反射方向R上,一个完美发光的表面才能从光源反射光。对于不完美的发光表面,光会被反射到R周围的发向上:反光度越小,高光区域就越宽。在数学上,归一化的反射方向R定义如下:
对于归一化法向量N和归一化的指向光源的方向L,在Cg中,函数float3 reflect(float3 I, float3 N) (或float4 reflect(float4 I, float4 N))会计算相同的反射向量,但只是针对从光源指向表面上点的方向I。因此,为了使用这个函数,我们必须把方向L取反。
高光反射部分会以观察者的方向V来计算高光反射。如上讨论,如果V离R越近,强度就越强,这里“越近”由反光度
来参数化。在Phong反射模型中,R和V之间夹角余弦值的
次方被用来生成不同反光度的高光。
跟漫反射的情况相似,我们应该截取负的余弦值到0。此外,对于镜面反射,镜面部分需要一个材质颜色
,它通常是白色的,这样所有高光就有入射光的颜色
。举例来说,卡拉瓦乔的画作中所有的高光都是白色的。Phong反射模型的镜面部分表示如下:
跟漫反射情况类似,如果光源在表面错误的一边的话应该忽略镜面部分;也就是如果点乘N·L的值是负数的话。
着色器代码
环境光的着色器代码很简单,就是向量和向量的乘法:float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
对于镜面反射的实现,我们需要世界空间中指向观察者的方向,它可以通过计算摄像机位置和顶点位置(都在世界空间)的差值得到。世界空间中摄像机的位置由Unity的统一参数_WorldSpaceCameraPos提供;顶点位置可以像章节“漫反射”中讨论的一样转换而来。世界空间中镜面部分的等式可以通过以下方式实现:
float3 viewDirection = normalize(_WorldSpaceCameraPos - mul(modelMatrix, input.vertex).xyz); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // 光源在“错误”的一面 { specularReflection = float3(0.0, 0.0, 0.0); // 没有镜面反射 } else // 光源在正确的一面 { specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); }
这一小段代码使用了章节“漫反射”着色器代码中相同的变量,以及另外使用了用户自定义参数_SpecColor 和_Shininess。(名字是被别指定的,这样备用着色器就能访问它们;查阅章节“漫反射”。)pow(a, b)是用来计算a的b次方
。
如果环境光被加入到第一个通道(我们只需要一次)中,以及镜面反射被加入到章节“漫反射”中完整着色器的两个通道中的话,那么着色器看起来就像这样:
Shader "Cg per-vertex lighting" { Properties { _Color ("Diffuse Material Color", Color) = (1,1,1,1) _SpecColor ("Specular Material Color", Color) = (1,1,1,1) _Shininess ("Shininess", Float) = 10 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } // 环境光和第一个光源的通道 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _LightColor0; // 光源颜色 (从"Lighting.cginc"获得) // 用户自定义参数 uniform float4 _Color; uniform float4 _SpecColor; //材质的高光反射颜色 uniform float _Shininess;//光泽度(也叫反光度) 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 = _Object2World; float3x3 modelMatrixInverse = _World2Object; float3 normalDirection = normalize( mul(input.normal, modelMatrixInverse)); float3 viewDirection = normalize(_WorldSpaceCameraPos - 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(modelMatrix, input.vertex).xyz; float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // 线性衰减 lightDirection = normalize(vertexToLightSource); } float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb; float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // 光源在错误的一面 { specularReflection = float3(0.0, 0.0, 0.0); // 没有镜面反射 } else // 光源在正确的一面 { specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); } output.col = float4(ambientLighting + diffuseReflection + specularReflection, 1.0); output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { return input.col; } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } // 额外光源的通道 Blend One One // 加性混合 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _LightColor0; // 光源颜色(从"Lighting.cginc"获得) // 用户自定义参数 uniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess; 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 = _Object2World; float3x3 modelMatrixInverse = _World2Object; float3 normalDirection = normalize( mul(input.normal, modelMatrixInverse)); float3 viewDirection = normalize(_WorldSpaceCameraPos - mul(modelMatrix, input.vertex).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)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side? { specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection } else // light source on the right side { specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); } output.col = float4(diffuseReflection + specularReflection, 1.0); // no ambient contribution in this pass output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { return input.col; } ENDCG } } Fallback "Specular" }
总结
恭喜你,刚刚学习了如何实现Phong反射模型。最后的代码我们主要实现的逐顶点的镜面高光。特别地,我们看到:在Phone反射模型中什么是环境光。
在Phone反射模型中什么是镜面反射项。
在Unity,在Cg中,这些镜面反射项如何实现。
深入阅读
如果你想知道得更多关于着色器代码,你应该阅读章节“漫反射”。
相关文章推荐
- Cg Programming/Unity/Smooth Specular Highlights平滑镜面高光
- Cg Programming/Unity/Reflecting Surfaces反射表面
- Cg Programming/Unity/Billboards广告牌
- Cg Programming/Unity/Transparency
- 《CG Programming in Unity》笔记1-基础知识
- Cg Programming/Unity
- Cg Programming/Unity/Shading in World Space世界空间中的着色器
- wiki/Cg Programming/Unity_shder/Debugging of Shaders
- Cg Programming/Unity/Soft Shadows of Spheres球体的软阴影
- wiki/Cg Programming/Unity_shder/Shading in World Space
- 解读Unity中的CG编写Shader系列十 (光滑的镜面反射(冯氏着色))
- Cg Programming/Unity/Cutaways
- Cg Programming/Unity/Two-Sided Smooth Surfaces双面平滑曲面
- wiki/Cg Programming/Unity/shder_理解准备
- Cg Programming/Unity/Transparency透明度
- Cg Programming/Unity/Order-Independent Transparency
- Cg Programming/Unity/Two-Sided Surfaces双面表面
- wiki/Cg Programming/Unity_shder/Minimal Shader
- (译)Cg Programming/Unity(Cg编程/Unity)
- Cg Programming/Unity 目录