游戏中的实时光照在着色器中的表现,就是光照方程在顶点着色器中进行逐顶点光照执行,而在像素着色器中表现为逐像素光照。 因为通常像素比顶点多,这些像素彼此之间十分靠近,所以在像素着色器上执行光照方程得到的光照品质会比顶点着色器上获得的更加杰出。
现在有一定数量的算法用于表现光照。通常,计算机图形学中的渲染方程是极其复杂的,数学技巧性很强,并且你还需要做一些研究,如果你开始迈向更高级的光照主题,比如全局光照和阴影。但是本章中,我们通常只考虑光照方程的三个最常用的部分。 这就是众所周知的环境(ambient)光,散射(diffuse)光,和镜面(specular)光这三项。 我们将以最容易理解的方式介绍这些概念,使得读者至少可以在之后使用。
顾名思义,环境光用于模拟光照在周围环境中的反射效果, 在物体表面进行着色的现象。 这是一个光照方程的一个基本项,尽管它只是一个用于增加总光照效果的颜色值,可以使用 float4 表示,例如(0.3f, 0.3f, 0.3f, 1.0f)。实际上,该基本项只不过是让一个实体颜色在场景中轻微的发亮,看上去的效果十分的不真实。
环境光实现的HLSL代码:
float4 PS_Main( PS_Input frag ) : SV_TARGET { float3 ambientColor = float3( 0.3f, 0.3f, 0.3f ); float3 finalColor = ambientColor; return float4( finalColor, 1.0f ); }
光照方程的散射项部分,用来模拟光照在物体表面和观察者眼睛之间的反射效果。 这与之前的光照首先在环境中的物体之间反射并最后对物体表面进行着色的效果是不同的,散射光照项科研用来模拟多种不同的现象,例如全局光照的混合,柔和的阴影等等。
为了表现散射光照效果,我们可以使用一个基于计算两向量点积概念的方程。 我们取得物体表面的法线向量和光照方向向量,进行点积运算就给出了散射光强度。 表面的法线向量可以使用所组成的三角形的法线向量表示,而光照向量可以通过计算灯光位置和接收光照的顶点的位置(或像素的位置,当进行逐像素光照时)之差来得到。
如果灯光的位置在接收光照顶点的正上方,则光照向量和表面法线向量之间的点积等于 1.0。 因为它们的移动方向都朝向对方(你可以通过第六章中的数学部分的知识来计算点积)。 如果灯光在物体的背面,则向量方向与之前的反向,将导致点积结果小于 0。 所有其它的角度的点积结果在上界(1.0)和平行物体表面的情形(0.0)之间时, 将会有一个光照强度的变化。 我们可以直接通过散射项的值与物体表面颜色相乘,来表现散射光照对物体表面的影响。 其散射光照方程如下:
float diffuse = clamp( dot( normal, lightVec ), 0.0, 1.0 );
如果我们使用的光照颜色仅仅是表现纯光照强度(译者注:颜色分量都相同,表现为一个单值),则有如下方程:
float diffuse = clamp( dot( normal, lightVec ), 0.0, 1.0 ); float4 diffuseLight = lightColor * diffuse;
如果将散射光照颜色应用到物体表面颜色中,假设物体表面的颜色仅仅是来自于一个颜色贴图,则方程如下:
float diffuse = clamp( dot( normal, lightVec ), 0.0, 1.0 ); float4 diffuseLight = lightColor * diffuse; float4 finalColor = textureColor * diffuseLight;
反射光实现的HLSL代码:
float4 PS_Main( PS_Input frag ) : SV_TARGET { float3 lightColor = float3( 0.7f, 0.7f, 0.7f ); float3 lightVec = normalize( frag.lightVec ); float3 normal = normalize( frag.norm ); float diffuseTerm = clamp( dot( normal, lightVec ), 0.0f, 1.0f ); float3 finalColor = lightColor * diffuseTerm; return float4( finalColor, 1.0f ); }
镜面光照类似于散射光照, 只不过镜面光照模拟灯光的镜面反射,即光线在物体表面反射并且进入眼睛。 散射光照发生在粗糙的物体表面(漫反射),即当放大表面时有很多的崎岖不平的地方,将会导致光照反射到任意的方向。这就是为什么无论你是否旋转一个发生漫反射的物体或从任意角度观察它,其光照强度基本一样。
从散射光照和镜面光照的描述中可以看到,散射光照独立于观察视角,而镜面光照则依赖于观察视角。 这意味着表现镜面光照的方程将使用照相机向量来代替散射光照中的光照方向向量。 照相机方向向量可以如下计算:
float4 cameraVec = cameraPosition – vertexPosition;
从照相机方向向量中,我们可以创建之前提到过的 half vector。 可以使用如下方程计算镜面光照的影响:
float3 halfVec = normalize( lightVec + cameraVec ); float specularTerm = pow( saturate( dot( normal, halfVec ) ), 25 );
saturate,把数截取到[0,1],saturate( dot( normal, halfVec ) )效果和clamp( dot( normal, halfVec ), 0.0f, 1.0f )是一样的。
镜面光实现的HLSL代码:
float4 PS_Main( PS_Input frag ) : SV_TARGET { float3 lightColor = float3( 0.7f, 0.7f, 0.7f ); float3 lightVec = normalize( frag.lightVec ); float3 normal = normalize( frag.norm ); float diffuseTerm = clamp( dot( normal, lightVec ), 0.0f, 1.0f ); float specularTerm = 0; if( diffuseTerm > 0.0f ) { float3 viewVec = normalize( frag.viewVec ); float3 halfVec = normalize( lightVec + viewVec ); specularTerm = pow( saturate( dot( normal, halfVec ) ), 25 ); } float3 finalColor = lightColor * specularTerm; return float4( finalColor, 1.0f ); }
/* Beginning DirectX 11 Game Programming By Allen Sherrod and Wendy Jones Lighting Shader */
Texture2D colorMap : register( t0 );
SamplerState colorSampler : register( s0 );
cbuffer cbChangesEveryFrame : register( b0 )
{
matrix worldMatrix;
};
cbuffer cbNeverChanges : register( b1 )
{
matrix viewMatrix;
};
cbuffer cbChangeOnResize : register( b2 )
{
matrix projMatrix;
};
cbuffer cbCameraData : register( b3 )
{
float3 cameraPos;
};
struct VS_Input
{
float4 pos : POSITION;
float2 tex0 : TEXCOORD0;
float3 norm : NORMAL;
};
struct PS_Input
{
float4 pos : SV_POSITION;
float2 tex0 : TEXCOORD0;
float3 norm : NORMAL;
float3 lightVec : TEXCOORD1;
float3 viewVec : TEXCOORD2;
};
PS_Input VS_Main( VS_Input vertex )
{
PS_Input vsOut = ( PS_Input )0;
float4 worldPos = mul( vertex.pos, worldMatrix );
vsOut.pos = mul( worldPos, viewMatrix );
vsOut.pos = mul( vsOut.pos, projMatrix );
vsOut.tex0 = vertex.tex0;
vsOut.norm = mul( vertex.norm, (float3x3)worldMatrix );
vsOut.norm = normalize( vsOut.norm );
float3 lightPos = float3( 0.0f, 500.0f, 50.0f );
vsOut.lightVec = normalize( lightPos - worldPos );
vsOut.viewVec = normalize( cameraPos - worldPos );
return vsOut;
}
float4 PS_Main( PS_Input frag ) : SV_TARGET
{
float3 ambientColor = float3( 0.2f, 0.2f, 0.2f );
float3 lightColor = float3( 0.7f, 0.7f, 0.7f );
float3 lightVec = normalize( frag.lightVec );
float3 normal = normalize( frag.norm );
float diffuseTerm = clamp( dot( normal, lightVec ), 0.0f, 1.0f );
float specularTerm = 0;
if( diffuseTerm > 0.0f )
{
float3 viewVec = normalize( frag.viewVec );
float3 halfVec = normalize( lightVec + viewVec );
specularTerm = pow( saturate( dot( normal, halfVec ) ), 25 );
}
float3 finalColor = ambientColor + lightColor * diffuseTerm + lightColor * specularTerm;
return float4( finalColor, 1.0f );
}