OpenGL实现3D游戏编程【连载5】——纹理坐标、纹理贴图
欢迎来到zhooyu的专栏。
个人主页:【zhooyu】
文章专栏:【OpenGL实现3D游戏编程】
本专栏内容:
我们从游戏的角度出发,用C++去了解一下游戏中的功能都是怎么实现的。这一切还是要从自己玩游戏开始说起,此前就玩过一下3D游戏,当时就被游戏里的一些画面和设置深深的吸引了,同时游戏里还有很多很有趣的设定,比如,玩家的视角是怎么移动的?崎岖不平的地图是怎样制作的?人物和物体、地面的碰撞是怎样检测的?鼠标是怎样选中眼前的物体的?魔法技能是怎样释放的?不用加载进度条的无缝世界地图是怎么实现的?带着这些疑问,我们走进了一个OpenGL世界的3D世界。
- 纹理
在OpenGL中,纹理是一种常用的技术,用于将图像或图案映射到3D模型的表面上,以增加图形的细节和真实感。那么我们上一节已经做好了纹理的准备工作,将需要的纹理图片加载并绑定到了纹理编号中,我们这一节就探讨一下怎样将对应的纹理图片显示到我们的程序中去。
1、纹理坐标
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。下面的图片展示了我们是如何把纹理坐标映射到三维图形上的。
2、最简单的纹理贴图
那么先准备一个最简单的贴图。我们需要自定义一个整型变量texExampleBox用来存储我们的纹理编号,随后在消息函数的WM_CREATE中加载我们的纹理图片,加载函数直接用我们上节课的Texture_LoadFromFile_2或Texture_LoadFromFile_3即可,参数为我们图片在工程文件夹中的相对路径。
//自定义纹理编号
GLuint texExampleBox;
case WM_CREATE:
......
//加载纹理
texExampleBox=Texture_LoadFromFile_3("Image/Box1693.bmp");
......
接下来,我们就可以在WM_PAINT消息中添加我们显示纹理的操作。我们先创建一个垂直用地面的矩形,然后在每个glVertex3f函数前添加glTexCoord2f函数,将图片的纹理信息与正方形图形的坐标顶点对应,就可以将纹理显示在正方形图形中了。
//启用二维纹理,调用特定编号的纹理
glEnable(GL_TEXTURE_2D);
//绑定箱子的纹理
glBindTexture(GL_TEXTURE_2D,texExampleBox);
//显示长方形
glBegin(GL_QUADS);
//显示长方形的四个顶点
glTexCoord2f(0,0);glVertex3f(-2.0f,-2.0f,0.0f);
glTexCoord2f(1,0);glVertex3f(2.0f,-2.0f,0.0f);
glTexCoord2f(1,1);glVertex3f(2.0f,2.0f,0.0f);
glTexCoord2f(0,1);glVertex3f(-2.0f,2.0f,0.0f);
glEnd();
//关闭纹理贴图
glDisable(GL_TEXTURE_2D);
添加纹理前我们的正方形是白色的,没有任何纹理:
添加纹理坐标后,我们的正方形显示如下:
3、三角形的纹理贴图
要想把纹理映射到三角形上,必须指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联一个纹理坐标(Texture Coordinate),表明顶点对应纹理图像的哪个位置的片段采样,之后图形的其它位置片段系统会自动进行片段插值(Fragment interpolation)。
//启用二维纹理,调用特定编号的纹理
glEnable(GL_TEXTURE_2D);
//绑定箱子的纹理
glBindTexture(GL_TEXTURE_2D,texExampleBox);
//显示长方形
glBegin(GL_TRIANGLES);
//显示长方形的四个顶点
glTexCoord2f(0,0);glVertex3f(-2.0f,-2.0f,0.0f);
glTexCoord2f(1,0);glVertex3f(2.0f,-2.0f,0.0f);
glTexCoord2f(0.5,1);glVertex3f(0.0f,2.0f,0.0f);
glEnd();
//关闭纹理贴图
glDisable(GL_TEXTURE_2D);
4、圆形纹理贴图
处理了简单的纹理贴图,我们来看下复杂的圆形贴图,深刻理解一下纹理坐标和顶点坐标的关系。我们准备了一个圆形的图片,但该图片在PNG文件中仍然是以矩形的图片保存的。如果我们想将这个图片纹理显示到一个圆形的图形中,由于圆形不存在正方形那样的四个角,我们无法向正方形那样将纹理坐标进行关联,那怎么处理这种纹理贴图呢?
首先我们需要明确的是,我们这里的圆形是以正多边形的方式显示出来的,当正多边形的边数较多时,下面正多边形的边数CircleFrame我们设置为36,就能较为圆滑的显示出圆形的轮廓。那我们就需要在圆弧上画出36个点,并连接中心点形成36个三角形。那我们只需要将中心点和36个圆弧上的点找到对应的纹理坐标即可。
//启用二维纹理,调用特定编号的纹理
glEnable(GL_TEXTURE_2D);
//绑定箱子的纹理
glBindTexture(GL_TEXTURE_2D,texExampleCircle);
//显示长方形
glBegin(GL_TRIANGLE_STRIP);
//设定自定义圆的精度
int CircleFrame=36;
//用于计算圆时的常量
float PI=3.1415926f;
//显示长方形的四个顶点
for(int i=0;i<=CircleFrame;i++)
{
//圆心的纹理坐标
glTexCoord2f(0.5f,0.5f);
//圆心的三维坐标
glVertex3f(0.0f,0.0f,0.0f);
//圆弧上点的纹理坐标
glTexCoord2f(0.5f+0.5f*cos(2*PI/CircleFrame*i),0.5f+0.5f*sin(2*PI/CircleFrame*i));
//圆弧的三维坐标
glVertex3f(2.0f*cos(2*PI/CircleFrame*i),2.0f*sin(2*PI/CircleFrame*i),0.0f);
}
glEnd();
//关闭纹理贴图
glDisable(GL_TEXTURE_2D);
经过纹理编号关联后,我们可以看到纹理已将完美的显示在了圆形上,效果如下:
5、球形贴图
我们现在需要显示一个更为复杂的纹理贴图,将下边这张世界地图纹理显示到一个圆球上,让圆球真正变成一个小型的地球仪。
首先,我们需要在三维空间中创建一个圆形的模型,如下图。我们这里为了方便演示,采用双for循环和sin、cos函数相配合时时获取球体各个顶点的方法,方便大家直观的理解。比如我们下边(x0,y0,z0)和(x1,y1,z1)就是临时计算出来的顶点。
同样,我们也可以时时的计算出以上顶点对应的纹理坐标,并将纹理图片显示到圆球体上。这里我们要说个题外话,初中地理课时,老不明白地图展开图为什么越靠近两极面积越大,比如俄罗斯地图为什么那么大,现在了解了纹理贴图后,终于好像明白了一点,原因在于在纹理贴图的时候越靠近两极的部分纹理图片会被逐步压缩,在两极点上整个图片宽度会被压缩到极点上,这也就说明了展开图特性。
//设置线的宽度
glLineWidth(1.0f);
//启用二维纹理,调用特定编号的纹理
glEnable(GL_TEXTURE_2D);
//绑定地球的纹理
glBindTexture(GL_TEXTURE_2D,texExampleEarth);
//设定自定义圆的精度
int EarthFrame=18;
//用于计算圆时的常量
float PI=3.1415926f;
float EarthSize=2.0f;
//显示地球框架顶点:纬度点
if(1)for(int i=0;i<EarthFrame-1;i++)
{
//按照划分的各个区域逐个显示纹理
glBegin(GL_QUAD_STRIP);
//显示地球框架顶点:经度点
for(int j=0;j<EarthFrame;j++)
{
//当前维度的点
float x0=EarthSize*cos(2*PI/(float)(EarthFrame-1)*j)*cos(PI/(float)(EarthFrame-1)*i-PI/2);
float z0=EarthSize*sin(2*PI/(float)(EarthFrame-1)*j)*cos(PI/(float)(EarthFrame-1)*i-PI/2);
float y0=EarthSize*sin(PI/(float)(EarthFrame-1)*i-PI/2);
//下一个维度的点
float x1=EarthSize*cos(2*PI/(float)(EarthFrame-1)*j)*cos(PI/(float)(EarthFrame-1)*(i+1)-PI/2);
float z1=EarthSize*sin(2*PI/(float)(EarthFrame-1)*j)*cos(PI/(float)(EarthFrame-1)*(i+1)-PI/2);
float y1=EarthSize*sin(PI/(float)(EarthFrame-1)*(i+1)-PI/2);
//计算纹理坐标并显示纹理
glTexCoord2f(1/(float)(EarthFrame-1)*(j),1/(float)(EarthFrame-1)*((i+0)));
glVertex3d(x0,y0,z0);
glTexCoord2f(1/(float)(EarthFrame-1)*(j),1/(float)(EarthFrame-1)*((i+1)));
glVertex3d(x1,y1,z1);
}
glEnd();
}
//关闭纹理贴图
glDisable(GL_TEXTURE_2D);
完成纹理贴图后圆球体变成如下样式:
动态图效果如下: