摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU编程与CG语言之阳春白雪下里巴人”
天地开辟 日月重光
--- 司马懿,征东辽歌
这一章起两个作用,其一阐述经典光照模型算法,其二,阐述如何使用CG 语言实现这些算法,让读者在实践中学习到CG 编程的方法 。这种原理加算法程序实现的讲述方法,同样会用在下面的章节中。原理阐述的过程难免是枯燥的,我会尽量在逻辑安排和讲解方式上减轻这种感觉。
当光照射到物体表面时,一部分被物体表面吸收,另一部分被反射,对于透明物体而言,还有一部分光穿过透明体,产生透射光。被物体吸收的光能转化为热量,只有反射光和透射光能够进入眼睛,产生视觉效果。通过反射和透射产生的光波(光具有波粒二相性)决定了物体呈现的亮度和颜色,即反射和投射光的强度决定了物体表面的亮度,而它们含有的不同波长光的比例决定了物体表面的色彩。
所以,物体表面光照颜色由入射光、物体材质,以及材质和光的交互规律共同决定。
光与物体最基本的交互方式就是反射,遵循反射定律:反射光与入射光位于表面法向两侧,对理想反射面(如镜面),入射角等于反射角,观察者只能在表面法向的反射方向一侧才能看到反射光。 不过世界上并不存在真正的理想反射体,正如物理学中绝对的匀速状态是不存在的。
环境光 (Ambient Light): 从物体表面所产生的反射光的统一照明,称为环境光或背景光(计算机图形学第二版 389 页)。例如房间里面并没有受到灯光或者太阳光的直接照射,而是由 墙壁、天花板、地板及室内各物体之间光的多次反射进行自然照明。通常我们认为理想的环境光具有如下特性:没有空间或方向性;在所有方向上和所有物体表面上投射的环境光强度是统一的恒定值。
由于环境光给予物体各个点的光照强度相同,且没有方向之分,所以在只有环境光的情况下,同一物体各点的明暗程度均一样,因此,只有环境光是不能产生具有真实感的图形效果。
环境光是对光照现象的最简单抽象,局限性很大。它仅能描述光线在空间中无方向并均匀散布时的状态。真实的情况是:光线通常都有方向。点光源是发光体的最简单的模型,光线由光源出发向四周发散。还有一种是平行光,即光线都从同一个方向照射。通过模拟方向光和物体表面的交互模式,可以渲染出具有高真实感(明暗变化、镜面反射等)的三维场景。
粗糙的物体表面向各个方向等强度地反射光,这种等同地向各个方向散射的现象称为光的漫反射( diffuse reflection )。产生光的漫反射现象的物体表面称为理想漫反射体,也称为朗伯( Lambert )反射体。
对于仅暴露在环境光下的朗伯反射体,可以用公式 (9-1) 表示某点处漫反射的光强:
其中 Ia表示环境光强度(简称光强), Kd(0< Kd <1 )为材质对环境光的反射系数, Iambdiff是漫反射体与环境光交互反射的光强。
即使一个理想的漫反射体在所有方向上具有等量的反射光线,但是表面光强还依赖于光线的入射方向(方向光)。例如,入射光方向垂直的表面与入射光方向成斜角的表面相比,其光强要大的多。这种现象可以用Lambert 定律进行数学上的量化。
即,当方向光照射到朗伯反射体上时,漫反射光的光强与入射光的方向和入射点表面法向夹角的余弦成正比,这称之为Lambert 定律,并由此构造出 Lambert 漫反射模型:
Il是点光源强度, @是入射光方向与顶点法线的夹角,称为入射(0≤@ ≤90°) , Iidiff是漫反射体与方向光交互反射的光强。入射角为零时,说明光线垂直于物体表面,漫反射光强最大;90° 时光线与物体表面平行,物体接收不到任何光线。
若N 为顶点单位法向量, L表示从顶点指向光源的单位向量(注意,是由顶点指向光源,不要弄反了),则 cos@等价于N 与L 的点积。所以公式(9-2) 可以表示为公式(9-3) :
综合考虑 环境光和方向来,Lambert 光照模型可写为:
这一节给出漫反射模型的渲染代码。正如前面所言,着色程序分为顶点着色程序和片段着色程序,顶点着色程序工作在顶点着色器上,只对传入的顶点数据进行处理,也就是说 “ 不对面片的内部点进行处理 ” 。顶点着色程序和片段着色程序可以写在同一个文件中,也可以写在不同的文件中。也可以只有顶点着色程序,而没有片段着色程序。
由于这是本书的第一个着色程序,也就是 Hello Cg World ,所以首先只使用顶点着色程序进行漫反射光照渲染,待读者掌握顶点着色程序的写法后,写片段着色程序就会感觉比较轻松。本节同时也会给出在 Cg 语言中使用结构体的编码方法。 漫反射光照模型的顶点着色代码如下所示:
代码 1 漫反射光照模型顶点着色程序
void main_v(float4 position : POSITION,
float4 normal : NORMAL,
out float4 oPosition : POSITION,
out float4 color : COLOR,
uniform float4x4 modelViewProj,
uniform float4x4 worldMatrix,
uniform float4x4 worldMatrix_IT,
uniform float3 globalAmbient,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 Kd)
{
oPosition = mul(modelViewProj, position);
float3 worldPos = mul(worldMatrix, position).xyz;
float3 N = mul(worldMatrix_IT, normal).xyz;
N = normalize(N);
// 计算入射光方向
float3 L = lightPosition - worldPos;
L = normalize(L);
// 计算方向光漫反射光强
float3 diffuseColor = Kd*lightColor*max(dot(N, L), 0);
// 计算环境光漫反射光强
float3 ambientColor = Kd*globalAmbient;
color.xyz = diffuseColor+ambientColor;
color.w = 1;
}
图 17 展示了使用漫反射光照模型的渲染效果。
下面给出使用结构体的代码形式:
代码 2 漫反射光照模型顶点着色程序(使用结构体)
struct VertexIn
{
float4 position : POSITION;
float4 normal : NORMAL;
};
struct VertexScreen
{
float4 oPosition : POSITION;
float4 color : COLOR;
};
void main_v(VertexIn posIn,
out VertexScreen posOut,
uniform float4x4 modelViewProj,
uniform float4x4 worldMatrix,
uniform float4x4 worldMatrix_IT,
uniform float3 globalAmbient,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 Kd)
{
posOut.oPosition = mul(modelViewProj, posIn.position);
float3 worldPos = mul(worldMatrix, posIn.position).xyz;
float3 N = mul(worldMatrix_IT, posIn.normal).xyz;
N = normalize(N);
// 计算入射光方向
float3 L = lightPosition - worldPos;
L = normalize(L);
// 计算方向光漫反射光强
float3 diffuseColor = Kd*lightColor*max(dot(N, L), 0);
// 计算环境光漫反射光强
float3 ambientColor = Kd*globalAmbient;
posOut.color.xyz = diffuseColor+ambientColor;
posOut.color.w = 1;
}
前面已经说过,在着色程序中使用结构体会使得代码易编写、易维护。本书下面的代码中都会使用结构体形式。