OpenGL之自动纹理坐标生成

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

-------------------------------------------------------------------------------------------------------------------------------------------------

此文章为我原创作品,若要转载,请和本人联系,或注明出处。
欢迎大家对文章内容提出宝贵意见,同时希望大家及时指出文中的错误之处,这样我可以及时更正。

你可能感兴趣的:(OpenGL)