纹理坐标
加载纹理只是在几何图形上应用纹理的第一步。最低限度我们必须同时提供纹理坐标,并设置纹理坐标环绕模式和纹理过滤。
最后,我们可以选择对纹理进行Mip贴图
,以提高纹理贴图性能和/或视觉质量。
- 范围:x和y轴上0到1之间的范围(2D纹理图片)。
- 采样(Sampling):使用纹理坐标获取纹理颜色。
- 起止:纹理坐标起始于(0,0)也就是纹理图片的左下角,终结于纹理图片的右上角(1,1)。
下面的图片展示了我们是如何把纹理坐标映射到三角形上的。
我们为三角形准备了3个纹理坐标点。如上图所示,我们希望三角形的左下角对应纹理的左下角,因此我们把三角左下角的顶点的纹理坐标设置为(0,0);三角形的上顶点对应于图片的中间所以我们把它的纹理坐标设置为(0.5,1.0);同理右下方的顶点设置为(1.0,0)。我们只要传递这三个纹理坐标给顶点着色器就行了,接着片段着色器会为每个片段生成纹理坐标的插值。
纹理坐标看起来就像这样:
GLfloat texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 顶部位置
};
如图,一维、二维、三维纹理坐标(s、t、r、q与x、y、z、w相类似)(q为缩放因子)
纹理参数
设置参数的方法
/*
参数1:target,指定这些参数将要应用在那个纹理模式上,⽐如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
参数2:pname,指定需要设置那个纹理参数
参数3:param,设定特定的纹理参数的值
*/
glTexParameterf(GLenum target,GLenum pname,GLfloat param);
glTexParameteri(GLenum target,GLenum pname,GLint param);
glTexParameterfv(GLenum target,GLenum pname,GLfloat * params);
glTexParameteriv(GLenum target,GLenum pname,GLint *params);
-
过滤
根据一个拉伸或收缩的纹理贴图计算颜色片段的过程。临近过滤是我们能够选择的最简单、最快速的过滤方法,其最显著的特征就是当纹理被拉伸到特别大的时候所出现的大片斑驳状像素。
1.临近过滤
//最近邻过滤用于GL_TEXTURE_2D,为放大和缩小过滤器设置纹理过滤器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_FILTER,GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)
2.线性过滤
更接近真实,没有人工操作的痕迹
//简单设置线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_FILTER,GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)
注:
- 纹理缩⼩时,使⽤邻近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
- 纹理放大时,使⽤线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
-
环绕
纹理采样有几种不同的插值方式。我们需要自己告诉OpenGL在纹理中采用哪种采样方式。
纹理坐标通常的范围是从(0, 0)到(1, 1),如果我们把纹理坐标设置为范围以外会发生什么?OpenGL默认的行为是重复这个纹理图像(我们简单地忽略浮点纹理坐标的整数部分),
但OpenGL提供了更多的选择:
环绕方式:
-
GL_REPEAT:
纹理的默认行为,重复纹理图像 -
GL_MIRRORED_REPEAT:
和GL_REPEAT一样,除了重复的图片是镜像放置的 -
GL_CLAMP_TO_EDGE:
纹理坐标会在0到1之间,超出的部分会重复纹理坐标的边缘,就是边缘被拉伸 -
GL_CLAMP_TO_BORDER:
超出的部分是用户指定的边缘的颜色
当纹理坐标超出默认范围时,每个值都有不同的视觉效果输出。我们来看看这些纹理图像的例子:
可以使用glTexParameter
函数单独设置每个坐标轴s、t(如果是使用3D纹理那么还有一个r)
它们和x、y(z)是相等的:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个参数:指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。
第二个参数:需要我们去告知我们希望去设置哪个纹理轴。我们打算设置的是WRAP选项,并且指定S和T轴。
第三个参数:需要我们传递放置方式。
如果我们选择GL_CLAMP_TO_BORDER
选项,我们还要指定一个边缘的颜色。
这次使用glTexParameter
函数的fv后缀形式,加上GL_TEXTURE_BORDER_COLOR
作为选项,这个函数需要我们传递一个边缘颜色的float数组作为颜色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
综合应用
// Main.cpp
// OpenGL ZhangQiang
#include // OpenGL toolkit
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__
#include
#else
#define FREEGLUT_STATIC
#include
#endif
/////////////////////////////////////////////////////////////////////////////////
// An assortment of needed classes
GLShaderManager shaderManager;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;//投影矩阵堆栈
GLFrame cameraFrame;//全局照相机实例
GLFrame objectFrame;
GLFrustum viewFrustum;//投影矩阵
GLBatch pyramidBatch;
GLuint textureID;
GLGeometryTransform transformPipeline;// 变换管线
M3DMatrix44f shadowMatrix;
void MakePyramid(GLBatch& pyramidBatch)
{
//6个三角形18个顶点,1代表在这个批次中将应用一个纹理
pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
// Bottom of pyramid
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);//向批次中添加一个表面法线,代表表面(顶点)面对的方向。在大多数光照模式下是必须的
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);//添加一个纹理坐标
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);//添加了顶点的位置
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3f(1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, 1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);
M3DVector3f vApex = { 0.0f, 1.0f, 0.0f }; // Vector of three floats(x, y, z)
M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f };
M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f };
M3DVector3f n;
// Front of Pyramid
//glTool函数库包含了一个函数,专门用于根据一个多边形上的3个点计算一条法线向量:
//该方法的第一个参数用于存储求得的法线向量,还要另外向它传递3个向量,表示取自多边形或三角形上的点
//(以逆时针环绕方向指定)。注意,该方法返回的法线向量并不一定是单位长度的
m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex); // Apex
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft); // Front left corner
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight); // Front right corner
m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex); // Apex
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft); // Back left corner
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft); // Front left corner
m3dFindNormal(n, vApex, vFrontRight, vBackRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex); // Apex
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight); // Front right corner
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight); // Back right cornder
m3dFindNormal(n, vApex, vBackRight, vBackLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex); // Apex
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight); // Back right cornder
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft); // Back left corner
pyramidBatch.End();
}
// Load a TGA as a 2D Texture. Completely initialize the state
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// Read the texture bits
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
///////////////////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering context.
// This is the first opportunity to do any OpenGL related tasks.
void SetupRC()
{
// Black background
glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
//调用之前使用
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glGenTextures(1, &textureID);//分配一些纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);//绑定一种纹理状态
LoadTGATexture("stone.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
MakePyramid(pyramidBatch);
cameraFrame.MoveForward(-7.0f);
}
///////////////////////////////////////////////////////////////////////////////
// Cleanup... such as deleting texture objects
void ShutdownRC(void)
{
glDeleteTextures(1, &textureID);
}
///////////////////////////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
{
static GLfloat vLightPos[] = { 1.0f, 1.0f, 0.0f };
static GLfloat vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };
// Clear the window with current clearing color
//glClear()函数的作用是用当前缓冲区清除值,
//也就是glClearColor或者glClearDepth、glClearIndex、glClearStencil、glClearAccum等函数所指定的值来清除指定的缓冲区,
//也可以使用glDrawBuffer一次清除多个颜色缓存。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//保存当前模型视图矩阵
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
modelViewMatrix.MultMatrix(mObjectFrame);
glBindTexture(GL_TEXTURE_2D, textureID);//绑定纹理对象
//绘制背景,点光源
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightPos, vWhite, 0);
pyramidBatch.Draw();
modelViewMatrix.PopMatrix();
// Flush drawing commands
glutSwapBuffers();
}
// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
if (key == GLUT_KEY_UP)
//void RotateWorld(float fAngle, float x, float y, float z)
//在世界坐标系中旋转
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_DOWN)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_LEFT)
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
glutPostRedisplay();
//glutPostRedisplay 标记当前窗口需要重新绘制。
//通过glutMainLoop下一次循环时,窗口显示将被回调以重新显示窗口的正常面板。
}
///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
//创建投影矩阵并把它载入到投影矩阵堆栈中
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//设置变换管线以使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Pyramid");
glutReshapeFunc(ChangeSize);
glutSpecialFunc(SpecialKeys);
glutDisplayFunc(RenderScene);//OpenGL渲染代码
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}