OpenGL中有两种方法为顶点(Vertex)指定纹理坐标:
1.由人工给每个顶点分配坐标。可以通过函数glTexCord*()来完成。
2.由OpenGL自动为每个顶点分配坐标。这个任务由函数glTexGen*()来完成。
要完成自动纹理坐标的生成,首先要指定以什么样的模式(既什么样的算法)来生成纹理坐标。可以指定三种纹理坐标生成模式:GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP,下面就分析这些模式的具体含义:
1.GL_OBJECT_LINEAR
在这个模式下,其纹理生成函数是顶点坐标(x0,y0,z0,w0)的线性组合:
生成的坐标=p0*x0+p1*y0+p2*z0+p3*w0;
假如我们以(p0,p1,p2,p3)为参数构造一个平面:p0*X+p1*Y+p2*Z+p3=0;
若(p0,p1,p2)已经被归一化,则任何一点到这个平面的距离为:
(p0*x+p1*y+p2*z+p3)/sqrt(p0*p0+p1*p1+p2*p2)=p0*x+p1*y+p2*z+p3;
可以看出:此模式下生成的坐标相当于顶点坐标到特定平面的距离。
网上提供的一份伪代码:
for(i=0; i { myTexCoord[i].s = dot4D(myVertex[i], myPlane_S); myTexCoord[i].t = dot4D(myVertex[i], myPlane_T); } |
个人的一些疑惑:根据上面提供的资料属实,我们假设这样一种情况。如果图元上的顶点跟以(p0,p1,p2,p3)为参数构造的平面平行,那么图元上的每一个顶点到构造平面的距离应该是一致的,那么他们生成的坐标也应该是一样的。例如:我们将矩形的z = 1,同时我们设置(p0, p1,p2,p3)= (0,0,1,0),相当于z=0的平面。根据上面的理解。该矩形到参考平面的距离应该均为1,即生成的坐标应该都是1。下面我们在代码上实现他:
//将矩形z轴的值均设为 float frontVertexs[] = { // -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; //设置以(p0,p1,p2,p3)为参数的平面为z=0的平面 float zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f}; //设置纹理坐标自动生成模式为GL_OBJECT_LINEAR glTexGeni(GL_S,GL_TEXTURE_ENV_MODE,GL_OBJECT_LINEAR); glTexGeni(GL_T,GL_TEXTURE_ENV_MODE,GL_OBJECT_LINEAR); //设置GL_OBJECT_LINEAR的参考平面为zPlane glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane); //开启ST方向的纹理坐标自动生成 glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); //关闭ST方向的纹理坐标自动生成 glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); |
效果图:
从效果图可以看出:生成的坐标纹理点明显并非不是1,跟我们上述猜测的结果并不一致。原因未知。
2.GL_EYE_LINEAR
在这个模式下,其纹理生成函数也是顶点的人眼坐标(xe,ye,ze,we)的线性组合:
生成的坐标=p0'*xe+p1'*ye+p2'*ze+p3'*we;
其中:(p0',p1',p2',p3')=(p0,p1,p2,p3)*inverse(M)
可以看出:GL_EYE_LINEAR和GL_OBJECT_LINEAR模式具有类似的纹理生成函数,唯一的差别在于GL_OBJECT_LINEAR是物体空间内,在视发生变化的情况下,纹理坐标不会随着视的改变而改变;而GL_EYE_LINEAR是在视空间内,在视发生变化的情况下,纹理坐标会随着视的改变而改变,用而改变贴图后的效果。
3.GL_SPHERE_MAP
在这个模式下,其纹理坐标生成算法如下:
以u标识在视坐标系下原点到顶点的一个单位向量;
以n'标识在视坐标系下顶点的法向量;
以r=trans(rx,ry,rz)标识顶点的反射向量,则t可以由以下公式计算得到:
r=u-2*trans(n')(n'u);
另m=2*sqrt(rx*rx+ry*ry+(rz+1)*(rz+1)),则生成的坐标:
(S,T)=( rx/m+0.5, ry/m+0.5 ); //在这个情况下,指定R,Q坐标是非法的。
可以看出,这个模式是在平面上模拟了球面的反射效果。
以下是网上找的一份具体实现方法:
首先,我们将(x, y, z, 1)和其法向量(nx, ny, nz, 0)(已归一化)转换到摄像机坐标系中,得到(x', y', z', 1)和(nx', ny', nz', 0),再根据摄像机的朝向(0, 0, 1)计算出反射向量(rx, ry, rz, 0),反射向量的计算可参阅后附的参考资料。(rx, ry, rz)表示包围球上的坐标。将球面上坐标转换为2d坐标有多种方法,OpenGL采用的方法是另p = sqrt(rx^2 + ry^2 + (rz + 1)^2),然后令s = rx / (2p) + 1/2,t = ry / (2p) + 1/2。这个方法对所有相同z值的坐标,令其rx和ry除以的相同的数量,来将坐标投影到二维空间中去。这个变换把球面上一圈一圈的圆环,将其按某种比例缩放后,投影到二维平面上的一个圆圈上去了。
相应的伪代码:
for(i=0; i { myEyeVertex = MatrixTimesVector(ModelviewMatrix, myVertex[i]); myEyeVertex = Normalize(myEyeVertex); myEyeNormal = VectorTimesMatrix(myNormal[i], InverseModelviewMatrix); reflectionVector = myEyeVertex - myEyeNormal * 2.0 * dot3D(myEyeVertex, myEyeNormal); reflectionVector.z += 1.0; m = 1.0 / (2.0 * sqrt(dot3D(reflectionVector, reflectionVector))); //I am emphasizing that we write to s and t. Used to sample a 2D texture. myTexCoord[i].s = reflectionVector.x * m + 0.5; myTexCoord[i].t = reflectionVector.y * m + 0.5; } |
个人根据以上方法所实行的自动纹理生成代码(最终跟预想效果不一致,原因未知)
float* getTexCoord() { float matrixView[16] = {0}; memset(matrixView, 0, sizeof(float) * 16); glGetFloatv(GL_MODELVIEW_MATRIX, matrixView); int size = 4; for (int i = 0; i < size; ++i) { float vertex[4] = {0.0f, 0.0f, 0.0f, 1.0f}; float cameraVertex[4] = {0.0f, 0.0f, 0.0f, 1.0f}; float normalValue[3] = {0}; float cameraNormal[3] = {0}; float reflectVector[4] = {0}; // 获取第i个顶点的坐标 vertex[0] = frontVertexs[3 * i]; vertex[1] = frontVertexs[3 * i + 1]; vertex[2] = frontVertexs[3 * i + 2]; // 将顶点坐标转换到摄像机坐标系中 cameraVertex[0] = matrixView[0] * vertex[0] + matrixView[4] * vertex[1] + matrixView[8] * vertex[2] + matrixView[12] * vertex[3]; cameraVertex[1] = matrixView[1] * vertex[0] + matrixView[5] * vertex[1] + matrixView[9] * vertex[2] + matrixView[13] * vertex[3]; cameraVertex[2] = matrixView[2] * vertex[0] + matrixView[6] * vertex[1] + matrixView[10] * vertex[2] + matrixView[14] * vertex[3]; // 归一化 float lenVertex = sqrt(cameraVertex[0] * cameraVertex[0] + cameraVertex[1] * cameraVertex[1] + cameraVertex[2] * cameraVertex[2]); if (lenVertex > 0) { cameraVertex[0] = cameraVertex[0] / lenVertex; cameraVertex[1] = cameraVertex[1] / lenVertex; cameraVertex[2] = cameraVertex[2] / lenVertex; } //获取法向量坐标 normalValue[0] = frontNormals[3 * i]; normalValue[1] = frontNormals[3 * i + 1]; normalValue[2] = frontNormals[3 * i + 2]; cameraNormal[0] = matrixView[0] * normalValue[0] + matrixView[4] * normalValue[1] + matrixView[8] * normalValue[2] + matrixView[12] * normalValue[3]; cameraNormal[1] = matrixView[1] * normalValue[0] + matrixView[5] * normalValue[1] + matrixView[9] * normalValue[2] + matrixView[13] * normalValue[3]; cameraNormal[2] = matrixView[2] * normalValue[0] + matrixView[6] * normalValue[1] + matrixView[10] * normalValue[2] + matrixView[14] * normalValue[3]; // 法向量归一化 float lenNormal = sqrt(cameraNormal[0] * cameraNormal[0] + cameraNormal[1] * cameraNormal[1] + cameraNormal[2] * cameraNormal[2]); if (lenNormal > 0) { cameraNormal[0] = cameraNormal[0] / lenNormal; cameraNormal[1] = cameraNormal[1] / lenNormal; cameraNormal[2] = cameraNormal[2] / lenNormal; } // 求反射向量 // 求顶点和法向量的点乘值 float dot3D = cameraVertex[0] * cameraNormal[0] + cameraVertex[1] * cameraNormal[1] + cameraVertex[2] * cameraNormal[2]; cameraNormal[0] = cameraNormal[0] * 2.0f * dot3D; cameraNormal[1] = cameraNormal[1] * 2.0f * dot3D; cameraNormal[2] = cameraNormal[2] * 2.0f * dot3D; reflectVector[0] = cameraVertex[0] - cameraNormal[0]; reflectVector[1] = cameraVertex[1] - cameraNormal[1]; reflectVector[2] = cameraVertex[2] - cameraNormal[2]; reflectVector[3] = cameraVertex[3] - cameraNormal[3]; // 求中间值m reflectVector[2] = reflectVector[2] + 1.0f; float m = 2.0f * sqrt(reflectVector[0] * reflectVector[0] + reflectVector[1] * reflectVector[1] + reflectVector[2] * reflectVector[2]); if(m > 0) { m = 1.0f / m; } // 求纹理坐标 texCoords[2 * i ] = reflectVector[0] * m + 0.5f; texCoords[2 * i + 1] = reflectVector[1] * m + 0.5f; } return texCoords; } |
4.以上几种都是OpenGL提供的自动纹理坐标生成的算法,但根据以上数学原理均无法得到预期效果。以下是根据永久,尹凡提供的思路自己写的一套自动纹理坐标生成算法。在一些较规则的图元上能有一定效果,但并不精确。
实现原理为:获取绘制图元的所有点,得到图元上所有顶点x,y的最大最小值。计算x,y最大最小值的间距。然后依次遍历所有顶点,计算该顶点的x,y值与最小值的差,然后除以该间距,这样就能得出每个顶点在该轴所占的百分比,也就是我们所要计算的归一化的纹理坐标。
示例代码如下:
float* glGetTex(float* vertexPoint, int size,int count, GLenum mode) { //获取x,y最大最小值 float maxX = *vertexPoint; float minX = *vertexPoint; float maxY = *(vertexPoint + 1); float minY = *(vertexPoint + 1); for (int i = 1; i < count; ++i) { float tempX = *(vertexPoint + i * size); float tempY = *(vertexPoint + i * size + 1); if(maxX < tempX) maxX = tempX; if (minX > tempX) minX = tempX; if (maxY < tempY) maxY = tempY; if (minY > tempY) minY = tempY; } float strideX = maxX - minX; float strideY = maxY - minX; float* pTex = new float[count * 2]; for (int i = 0; i < count; ++i) { pTex[2 * i] = (vertexPoint[i * size] - minX) / strideX; pTex[2 * i + 1] = (vertexPoint[i * size + 1] - minY) / strideY; } return pTex; } |
参考文档:
http://hi.baidu.com/mincomp/blog/item/7d5ea2db254695d4b7fd485d.html
http://blog.csdn.net/houdy/archive/2004/10/21/145444.aspx
http://www.opengl.org/wiki/Mathematics_of_glTexGen
-------------------------------------------------------------------------------------------------------------------------------------------------
此文章为我原创作品,若要转载,请和本人联系,或注明出处。
欢迎大家对文章内容提出宝贵意见,同时希望大家及时指出文中的错误之处,这样我可以及时更正。