【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

本例实现了一个基于GPU加速的OpenGL+Cg实现的环境贴图例子。


环境贴图(Environment Mapping)也叫做反射贴图,这项技术由Blinn和Newell于1976年提出,可以用于模拟光滑表面对周围场景的映射效果。生活中有很多这样的例子,比如一个光滑的球体,我们可以在它表面看到周围的环境。图一是维基百科给出的例子:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图一 环境贴图实例

环境贴图基于这样一种假设:物体周围的场景一切都位于无穷远处,这样能够编码到一个被称为环境贴图的全向图像里。而且,模型只能反射环境,不能反射本身。这样也不要期望多个反射,比如两个模型相互反射的情况。

一、立方体纹理

环境贴图最常用的纹理是立方体纹理。它包括六幅正方形的纹理图像,即组成了我们用来生成环境贴图的全向图像。图二是一个环境贴图的例子。

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图二 一个环境贴图例子

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图三 立方体环境贴图示意图

访问立方体纹理需要一个三维的纹理坐标,它代表了一个三维的向量,这个向量从立方体的中心出发,向外射出,立方体的一个面相交。

需要注意的是:访问立方体所得到的纹理颜色只与这个向量的方向有关,而与大小没有关系。正因为环境映射依赖于方向而不依赖于位置,它在平的反射表面上表现得很差。例如镜子,在镜子上反射主要依赖于位置,相反,环境映射在曲面上表现得很好。

产生立方体纹理:需要得到两个方向的图像,可以把照相机摆在物体中心的位置,然后向六个方向以90度视角拍照。必须要是90度,这样6幅图像才能刚好组合成一个全向图像。

从磁盘加载图像生成纹理可参考:GLAUX纹理生成


二、环境贴图的算法步骤如下:

1、根据视线方向和法向量计算反射向量;

2、使用反射向量检索环境贴图得到纹理信息;

3、将纹理信息融合到当前像素中。


反射向量计算

图四绘制了反射向量的计算示意图。

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图四 反射向量计算

计算公式:

R = I - 2 * N * (N * I)

I是入射光线,N是法向量。

Cg语言提供了计算反射光线的库函数:

reflect(I,N)

注意:N必须被单位化,且向量必须是3维的。

计算得到的反射光线向量长度和入射光线向量长度相等。


在本次实现中,我把反射向量的计算放在顶点程序中,虽然这样效果不如把它放在片断程序中效果好,但是用片段程序计算反射向量并不被一些基本片断profile支持。

顶点Cg程序vertex.cg:

void main_v(float4 position : POSITION, float2 texCoord : TEXCOORD0,//decal texture float3 normal : NORMAL, out float4 oPosition : POSITION, out float2 oTexCoord : TEXCOORD0,//out decal texture out float3 R : TEXCOORD1,//reflective vector uniform float3 eyePositionW,//eye position in world space uniform float4x4 modelViewProj, uniform float4x4 modelToWorld ) { modelViewProj = glstate.matrix.mvp; oPosition = mul(modelViewProj,position); oTexCoord = texCoord; float3 positionW = mul(modelToWorld,position).xyz; float3 N = mul((float3x3)modelToWorld,normal); N = normalize(N); float3 I = positionW - eyePositionW;//incident vector R = reflect(I,N); }环境贴图通常是基于世界空间来确定方向的,因此我们在世界坐标系中进行计算反射向量。要实现这一点,必须把其余的顶点数据变化到世界空间。我们也需要提供一个uniform的模型变换矩阵把顶点和房法向量变换到世界空间。

模型变换矩阵的计算可能稍微有点复杂。他需要根据模型变换包括旋转平移进行计算。

在这个实例中,在绘制模型猴头之前,进行的模型变换是:先旋转、再平移。

我们先依据旋转的参数计算出旋转矩阵rotateMatrix,计算函数:

void makeRotateMatrix(float angle, float ax,float ay,float az, float m[16]) { float radians, sine, cosine, ab, bc, ca, tx, ty, tz; float axis[3]; float mag; axis[0] = ax; axis[1] = ay; axis[2] = az; mag = sqrt(axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]); if (mag) { axis[0] /= mag; axis[1] /= mag; axis[2] /= mag; } radians = angle * myPi / 180.0; sine = sin(radians); cosine = cos(radians); ab = axis[0] * axis[1] * (1 - cosine); bc = axis[1] * axis[2] * (1 - cosine); ca = axis[2] * axis[0] * (1 - cosine); tx = axis[0] * axis[0]; ty = axis[1] * axis[1]; tz = axis[2] * axis[2]; m[0] = tx + cosine * (1 - tx); m[1] = ab + axis[2] * sine; m[2] = ca - axis[1] * sine; m[3] = 0.0f; m[4] = ab - axis[2] * sine; m[5] = ty + cosine * (1 - ty); m[6] = bc + axis[0] * sine; m[7] = 0.0f; m[8] = ca + axis[1] * sine; m[9] = bc - axis[0] * sine; m[10] = tz + cosine * (1 - tz); m[11] = 0; m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1; }其数学推导:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图五 旋转变换矩阵计算

然后进行平移变换矩阵translateMatrix的计算:

//compute translation transformation matrix static void makeTranslateMatrix(float x, float y, float z, float m[16]) { m[0] = 1; m[1] = 0; m[2] = 0; m[3] = x; m[4] = 0; m[5] = 1; m[6] = 0; m[7] = y; m[8] = 0; m[9] = 0; m[10] = 1; m[11] = z; m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1; }其数学依据:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

图六 平移变换矩阵计算

然后得到模型变换矩阵modelMatrix = tranlateMatrix * rotateMatrix(注意顺序:先旋转变换,再平移变换)。


片断Cg程序fragment.cg:

void main_f(float2 texCoord : TEXCOORD0, float3 R : TEXCOORD1, out float4 color : COLOR, uniform float reflectivity, uniform sampler2D decalMap, uniform samplerCUBE environmentMap) { // Fetch reflected environment color float4 reflectedColor = texCUBE(environmentMap, R); // Fetch the decal base color float4 decalColor = tex2D(decalMap, texCoord); color = lerp(decalColor, reflectedColor, reflectivity); }

注意:我们最后需要将环境贴图所得颜色与模型本身颜色进行混合,这样做的原因是更加贴近现实,因为没有完全反射的物体。

模型本身的颜色是通过访问一个二维纹理得到的,这个二维纹理也是从磁盘加载一幅图像生成。


程序所载入的模型是一个猴头,其数据放在一个.h头文件中,由定点坐标、法向量以及三角形定点索引等数组组成。

程序运行效果:
【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

我所使用的用来构建环境贴图的6幅图:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

用于模型本身纹理映射的图:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)


折射效果的实现

折射的物理依据:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)

我们一般假设光线只进行一次这是,即忽略光线从介质射出时发生的折射,这样是为了简化计算,而且人眼也不会感觉到和现实有明显的区别。


折射光线计算

Cg提供了计算折射光线的函数refract:

float3 refract (float3 I, float3 N, float etaRatio) { float cosI = dot(-I, N); float cosT2 = 1.0f - etaRatio * etaRatio * (1.0f – cosI * cosI); float3 T = etaRatio * I + ((etaRatio * cosI - sqrt(abs(cosT2))) * N); return T * (float3)(cosT2 > 0); }这样,和上述反射环境贴图的区别就是:用折射光线向量去访问立方体环境贴图。

顶点Cg程序:veretx.cg:

void main_v(float4 position : POSITION, float2 texCoord : TEXCOORD0,//decal texture float3 normal : NORMAL, out float4 oPosition : POSITION, out float2 oTexCoord : TEXCOORD0,//out decal texture out float3 T : TEXCOORD1,//refractive vector uniform float etaRatio,//the ratio of the indices of refraction uniform float3 eyePositionW,//eye position in world space uniform float4x4 modelViewProj, uniform float4x4 modelToWorld ) { modelViewProj = glstate.matrix.mvp; oPosition = mul(modelViewProj,position); oTexCoord = texCoord; float3 positionW = mul(modelToWorld,position).xyz; float3 N = mul((float3x3)modelToWorld,normal); N = normalize(N); float3 I = normalize(positionW - eyePositionW);//incident vector T = refract(I,N,etaRatio); }
片断Cg程序fragment.cg:

void main_f(float2 texCoord : TEXCOORD0, float3 T : TEXCOORD1, out float4 color : COLOR, uniform float transmittance, uniform sampler2D decalMap, uniform samplerCUBE environmentMap) { //fetch reflected environment color float4 refractedColor = texCUBE(environmentMap,T); //fetch the decal base coloe float4 decalColor = tex2D(decalMap,texCoord); //color = refractedColor; color = lerp(refractedColor,decalColor,transmittance); }
运行效果:

【GPU编程】《The Cg Tutorial》学习之环境贴图(Environment Mapping)