摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文 名“GPU编程与CG语言之阳春白雪下里巴人”
简单透明光照模型不考虑透明物体对光的第二次折射、次表面散射,以及光在穿越透明物体时的强度衰减,只是简单的使用颜色调和的方法,即我们最终所看到的颜色,是物体表面的颜色和背景颜色的叠加。
如图 33 所示,透明物体位于视点与另一个不透明物体之间,透明物体的不透明度为t ,点A 为透明物体上的一点,点光源直接照射到A 点上产生的反射光强为ia , 视线穿过透明体与另一个物体相交处的光强为ib ,则点A 处的最终可观察的光强为:
ia和 ib都可以用前面所讲的光照模型进行计算。通过透射光方向计算透射光强,首先需要进行光线和空间物体的求交运算,以确定透射光的来源,这是非常消耗时间的,如果真的这样做,其实就是演变为光线跟踪算法了。为了保证实时性,在实际使用中,通常是根据入射光方向向量和法向量求取折射光方向,然后根据折射光方向检索环境纹理上的颜色值作为ib 。 简单透明光照模型渲染效果如 图 34 所示。
简单透明光照模型的着色程序如下所示。依然是首先定义结构体,用来包含输入、输出数据流,然后在顶点着色程序中计算顶点投影坐标,并将顶点模型坐标和法向量坐标传递到片段着色程序中,然后在片段着色程序中计算折射光方向向量,并根据折射光向量检索环境贴图,最后按照公式( 11-8 )进行颜色合成。 Cg 标准函数库中的 lerp 函数可以提供颜色合成功能,具体函数使用方法参见 8.3.1 节。
注意: 11.2 节所阐述的环境贴图方法,是使用反射光方向向量检索环境贴图;而在本节中,是使用折射光方向向量检索环境贴图。尽管都是检索环境贴图,但还是有区别的。
代码 13 结构体
struct VertexIn
{
float4 position : POSITION;
float4 normal : NORMAL;
};
struct VertexScreen
{
float4 oPosition : POSITION;
float4 objectPos : TEXCOORD0;
float4 objectNormal : TEXCOORD1;
};
顶点着色程序和 片段着色程序 为:
代码 14 简单透明光照模型顶点着色程序
void main_v(VertexIn posIn,
out VertexScreen posOut,
uniform float4x4 modelViewProj)
{
posOut.oPosition = mul(modelViewProj, posIn.position);
posOut.objectPos = posIn.position;
posOut.objectNormal = posIn.normal;
}
代码 15 简单透明光照模型片段着色程序
void main_f( VertexScreen posIn,
out float4 color : COLOR,
uniform float4x4 worldMatrix,
uniform float4x4 worldMatrix_IT,
uniform float3 globalAmbient,
uniform float3 eyePosition,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 Kd,
uniform float3 Ks,
uniform float shininess,
uniform float etaRatio, // 折射系数
uniform float transmittance, // 透明度
uniform samplerCUBE environmentMap // 环境贴图
)
{
float3 worldPos = mul(worldMatrix, posIn.objectPos).xyz;
float3 N = mul(worldMatrix_IT, posIn.objectNormal).xyz;
N = normalize(N);
// 计算入射光方向 / 视线方向 / 半角向量
float3 L = normalize(lightPosition - worldPos);
float3 V = normalize(eyePosition - worldPos);
float3 H = normalize(L + V);
// 计算漫反射分量、镜面反射分量
float3 diffuseColor = Kd * globalAmbient+Kd*lightColor*max(dot(N, L), 0);
float3 specularColor = Ks * lightColor*pow(max(dot(N, H), 0), shininess);
float3 reflectColor = diffuseColor+specularColor;
// 计算折射光线的方向 , 注意 refract 的输入参数!
float3 I = normalize(worldPos - eyePosition);
float3 T = refract(I, N, etaRatio);
// 根据折射光线的方向,检索环境贴图上的颜色信息
float3 refractedColor = texCUBE(environmentMap, T).xyz;
color.xyz = lerp(reflectColor, refractedColor, transmittance);
color.w = 1;
}
光射入透明物体时会发生一次反射和折射,光从透明物体内射出时,又会发生一次反射和折射。透明光照的简单模型实际上只是通过计算了第一次反射和折射,近似的模拟光透效果。 2005 年, Wyman 在 ACM SIGGRAPH 大会上提出了在 GPU 中用近似的方法实现两次折射的透明物体绘制算法( Interactive image-space refraction of nearby geometry )。
次表面散射是光射入半透明物体后再内部发生散射,最后射出物体并进入视野中产生的现象。次表面散射材质是高质量渲染中最复杂的材质之一,一个重要原因在于此表面散射物体内部的任何一点的光照度取决于体内其他点的光照度和材质本身的透光率。抛开材质本身的性质不说,这一特性使得次表面散射的光照方程变成一个复杂的微分方程,求出此方程的准确解是十分困难的,另一方面,材质本身可能具有复杂的各向异性和不均匀密度等性质,因此计算这样的积分变得非常困难。 GPU 编程精粹第一部的第 16 章给出了一种次表面散射的实时近似模拟算法。