您的位置:首页 > 移动开发 > Unity3D

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的值是负数的话。

着色器代码

环境光的着色器代码很简单,就是向量和向量的乘法:

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语言