如何计算投影纹理坐标
Project Texture是Shadow Mapping中的重要一环。所以要先搞定Project Texture才行。:)
Project Texture是一种很常见的特效了,在OGL中Project Texture的通常做法是:设置好纹理矩阵,然后让OGL自动生成纹理坐标。
这样做虽然也能得到很好的效果,但是本着知其然,知其所以然的精神,还是让我们自己来计算一下纹理坐标吧。
Project Texture的原理我就不介绍了,不明白的话可以参阅心蓝兄翻译的一篇文章:http://xreal.51.net/Game/ProjectTexture.htm
为了使过程更明了,我们将不采用OGL函数,而是自己通过矩阵运算来得到我们需要的纹理坐标。计算中所需要的各种矩阵用如下的变量表示:
CMatrix4x4 matrixLight; // 投影仪的模型视图变换矩阵
CMatrix4x4 matrixProjModel; // 模型的投影变换矩阵
CMatrix4x4 matrixProjTexture; // 纹理的投影变换矩阵
CMatrix4x4 matrixBiasTexture; // 纹理偏移矩阵矩阵(使纹理坐标变换到[0,1]内)
CMatrix4x4 matrixTexture; // 纹理矩阵
因为矩阵变换的累加性,所以实际上只需要用两个矩阵不断的累加操作就可以了。这里拆分开是为了使过程更加明了。
第一步:初始化。
我们假设在渲染过程中摄像机和投影仪的投影方式不发生变化,所以在初始化函数中我们将一次性的计算好摄像机和投影仪的投影矩阵。
//
// 将纹理矩阵、投影矩阵与模型视图矩阵置为单位矩阵,
// 因为以下变换将全部由自定义矩阵运算实现。
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// 初始化为单位矩阵
matrixProjModel.LoadIdentity();
matrixProjTexture.LoadIdentity();
matrixBiasTexture.LoadIdentity();
// 设置投影矩阵
float fBase = 0.05f;
float fZoom = (float)SCREEN_HEIGHT / (float)SCREEN_WIDTH;
matrixProjModel.Frustum(-fBase, fBase, fZoom * -fBase, fZoom * fBase, 0.1f, 20.0f);
// 设置纹理投影矩阵
matrixProjTexture.Frustum(-0.02f, 0.02f, -0.02f, 0.02f, 0.1f, 2.0f);
// 设置纹理偏移矩阵
matrixBiasTexture.Translate(0.5f, 0.5f, 0.0f);
matrixBiasTexture.Scale(0.5f, 0.5f, 1.0f);
//
}
这里所进行的操作和
glLoadIdentity();
glFrustum( );
是一样的。
在设置投影矩阵之前我们将OGL内置的纹理矩阵、投影矩阵与模型视图矩阵置为单位矩阵,因为这些变换将由我们自己全盘实现。另一种做法是自己编写Vertex Shader,并且在Shader中不进行任何变换。:)
接着我们设置了模型的投影矩阵,纹理的投影矩阵与纹理的偏移矩阵。纹理偏移矩阵的作用我们将在后面说明。
第二步,渲染。
Display(){
//
// 我们不希望由矩阵累加变换,
// 累加操作将由一些存储输入信息的变量来实现。
matrixModel.LoadIdentity();
matrixLight.LoadIdentity();
matrixTexture.LoadIdentity();
/* 模型的模型视图变换 */
// 先将模型(或摄像机)移动到一个位置
matrixModel.Translate(0.0f, 0.0f, -2.0f);
matrixModel.Rotate(30.0f, 1.0f, 0.0f, 0.0f);
// 然后根据键盘操作来做平移、旋转变换
// inputView[0~2]中分别存储的是绕X、Y、Z轴进行旋转的角度
// inputView[3]中存储的是向Z轴方向进行平移的距离
matrixModel.Translate(0.0f, 0.0f, inputView[3]);
matrixModel.Rotate(inputView[0], 1.0f, 0.0f, 0.0f);
matrixModel.Rotate(inputView[1], 0.0f, 1.0f, 0.0f);
matrixModel.Rotate(inputView[2], 0.0f, 0.0f, 1.0f);
/* 投影点的模型视图变换 */
// 在模型变换的基础上进行投影仪变换
matrixLight = matrixModel;
// 和模型变换一样,inputLight中存储的是旋转与平移的信息
matrixLight.Translate(0.0f, 0.0f, 2.0f);
matrixLight.Translate(0.0f, 0.0f, inputLight[3]);
matrixLight.Rotate(inputLight[0], 1.0f, 0.0f, 0.0f);
matrixLight.Rotate(inputLight[1], 0.0f, 1.0f, 0.0f);
matrixLight.Rotate(inputLight[2], 0.0f, 0.0f, 1.0f);
/* 获得纹理矩阵 */
// 投影仪矩阵求逆
matrixTexture = matrixLight.GetInverse();
// 左乘纹理投影矩阵
matrixTexture = matrixProjTexture * matrixTexture;
// 左乘偏移矩阵
matrixTexture = matrixBiasTexture* matrixTexture;
/* 画模型 */
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glColor3f(0.8, 0.8, 0.8);
// CubeModel[24][4]中存储的是一个Cube模型的24个顶点坐标
for (int i = 0; i < 24; i ++){
// vertex_tmp中存储的是模型经过模型视图变换但是未经投影变换的坐标
// vertex中存储的是模型经过模型视图与投影变换后的坐标
// texture中存储的是我们计算出来的纹理坐标
GLfloat vertex_tmp[4], vertex[4], texture[4];
// 模型的模型视图矩阵乘以模型坐标,将模型坐标进行模型视图变换
mult_matrix_vector(matrixModel, CubeModel[i], vertex_tmp);
// 模型的投影矩阵乘以模型经过模型视图变换后的坐标,将模型坐标进行投影变换
mult_matrix_vector(matrixProjModel, vertex_tmp, vertex);
// 模型经过模型视图变换后的顶点坐标乘以纹理矩阵,获得纹理坐标
mult_matrix_vector(matrixTexture, vertex_tmp, texture);
// 设置模型的纹理坐标与顶点坐标
glTexCoord2f(texture[0], texture[1]);
glVertex3f(vertex[0], vertex[1], vertex[2]);
}
glEnd();
glDisable(GL_TEXTURE_2D);
//
}
这是Project Texture的核心步骤。
我们先像通常一样分别获得模型(或摄像机)与投影仪的模型视图变换矩阵,然后对模型坐标应用该矩阵,获得经过模型变换后的坐标,最后进行投影变换。这里需要注意的一点是:我们直接进行了顶点的模型视图变换与投影变换,而通常这个变换是在Vertex Shader中进行的,所以在Vertex Shader中将不再需要任何变换,只需要写上
gl_Position = gl_Vertex;
}
就可以了。
接下来进行的是我们最为关心的纹理坐标计算。在这里我累加了矩阵操作,我们把它拆分开看看:
1 经过模型视图变换后的顶点坐标左乘投影仪的逆矩阵。这里的几何意义可以这样理解:根据顶点坐标获得纹理坐标的本质是将顶点坐标投影到NDC平面上,此时投影点的平面坐标即为纹理坐标。而Frustum操作是朝向投影仪坐标系中Z轴负方向进行的,所以需要先将模型坐标变换到投影仪坐标系中。这时用经过模型视图变换后的顶点坐标左乘投影仪模型视图变换矩阵的逆矩阵即可。
2 如上文中所说,将模型坐标变换到投影仪坐标系中后,就可以进行投影变换了。此时只需直接左乘纹理的投影矩阵。
3 到这里我们已经得到了纹理坐标,不过该坐标却是在[-1, 1]范围内的,我们需要将其变换到[0, 1]范围内,可以先乘以1/2然后再给每个分量加上1/2。对此我们将结果左乘纹理偏移矩阵做最后的调整。
最后是对模型的顶点坐标应用模型视图变换矩阵、投影变换矩阵与投影纹理矩阵。代码里注释的很详细,这里就不多说了。
OK,到此为止所有的工作都做完了,最后让我们来看看结果吧。:)
最后的一点疑惑
实际上最后得到的结果和使用自动纹理坐标生成得到的结果还是有一些差异的,对比下图:
自动生成的纹理坐标
通过上述方法计算出的纹理坐标
可以清楚的看到使用上述方法得到的投影有所扭曲,而使用自动纹理坐标生成得到的结果则是正常的。
因为我采用OGL纹理坐标自动生成时传入的GL_TEXTURE矩阵也是上面计算出的matrixTexture,所以应该不是matrixTexture计算错误。那么难道是我在最终生成纹理坐标的时候少做了某些操作?
这个问题一直没有想明白,希望知道答案的高人能够指点一下。这里谢谢先了。:)