课程论文
课程名称:计算机图形学
专业班级:计科班
学生姓名:
学号:
指导教师:
课程设计时间:2011年6月 10日
1课程题目:
基于OPENGL 的纹理贴图以其漫游的实现
2算法设计:
2.1 几何图元与场景表达
如上的形式,绘制图形在glbegin和glend这两个函数之间,而绘制的几何图元的方式也是多种多样的。上面的分别是画多边形和画点,这是最基本的绘图的方式,当然了,OPENGL 有一个辅助的函数库GLUT。它里面有一些基本的图形的绘制函数
2.2OpenGL中的变换
这是一个非常重要的内容,一定要理解和掌握它,这样在以后就不会出现莫名奇妙的错误了,我在学这个编程的时候,就是会经常出现不能显示我们所绘制图形的问题,这个过程花费了我好多的时间来解决。这是将现实世界的东西转换到计算机所能表达非常重要的一
步。它包括模型与视点的变换(移,旋转,及比例变换)、投影变换(平行投影,透视投影)
2.3 OpenGL中的照明
OpenGL中使用简单光照模型实现顶点颜色的计算,然后通过着色模式的设置,决定内部象素颜色确定方式。
OpenGL中有两种颜色着色模式:单一着色(flat)和渐变着色(smooth)。通过函数glShadeModel(GLenum mode);
进行设置。其中:mode参数为GL_FLAT或GL_SMOOTH,分别表示使用单一着色模式和渐变颜色模式。
在使用渐变颜色模式时,OpenGL使用Gouraud模型对颜色进行插值:线段内部使用顶点颜色的线性插值,多边形内部使用顶点颜色的双线性插值。
1)启动和关闭光照
glEnable(GL_LIGHTING); //之后使用光照模型计算顶点颜色。
glDisable(GL_LIGHTING);//之后顶点颜色为当前颜色,当前颜色可以通过glColor*函数指定。
(2)启动和关闭特定光源
glEnable(GL_LIGHTi);
glDisable(GL_LIGHTi);
其中:GL_LIGHTi为GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7、…,光源的最大数目为GL_MAX_LIGHTS,但至少有8个可用光源
2.4 OPENGL中使用纹理
这个内容是我研究的重点,在OPENGL的学习过程中这方面的内容一直伴随着,这也是最基本的实现三维可视化的过程。
物体的表面细节称为纹理(Texture)。OpenGL使用图像表示纹理,这也是最常用的表示纹理的方式。二维纹理用得最多,一维和三维纹理也各有其用途。那么生成纹理的操作步骤是什么呢?下面我将要介绍它
(1)图像准备:既可以在程序中生成图像,也可以读取图像文件。
(2)生成纹理号:适用于多个纹理反复切换时,一个纹理时不需要。
(3)设置当前纹理:切换纹理,又称为纹理绑定。
(4)定义纹理:指定当前纹理的图像。
(5)设置纹理参数:指定纹理的重叠方式和插值方式。
(6)设置纹理环境参数:决定怎样使用纹理颜色,例如是否与光照色合成。
(7)启动纹理功能,绘制场景,给出顶点的纹理坐标和几何坐标
注:纹理坐标可以直接指定,也可以启用自动计算纹理坐标功能。
(8)退出前删除纹理。
用到的函数:1)生成纹理号的函数:
Void glGenTextures (GLsizei n, GLuint *textures);
同时生成多个纹理号。
(2)设置当前纹理:
void glBindTexture( GLenum target, GLuint texture );
target:GL_TEXTURE_1D、GL_TEXTURE_2D
texture:纹理号
(3)删除纹理
void glDeleteTextures (GLsizei n, GLuint *textures);
注:删除的纹理必须存在
4)定义纹理
void glTexImage2D(GLenum target, GLint level, GLint components,
GLsizei width, glsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
void glTexImage1D(GLenum target, GLint level, GLint components,
GLsizei width, GLint border, GLenum format, GLenum type,
const GLvoid *pixels);
说明:
1)target:纹理维数。例如:GL_TEXTURE_1D、GL_TEXTURE_2D
2)level:纹理层次,0层表示最底层纹理(使用原始图像)
3)components:1-4的整数,说明象素有几个分量
4)width、height:象素宽度和高度,一定是二次幂+两个边界宽度
5)border:边界宽度,0或1
4)format:说明各个分量的意义,例如:GL_RGB
5)type:每个分量的类型,例如:GL_UNSIGNED_BYTE
6)pixels:数据地址
当然了,OPENGL 的内容远不止这些,这的内容不是一时三刻可以说清楚的,像OPENGL 的双缓存机制,还有它的效果处理(如融合,雾化,渲染等方面),显示列表,选择模式,状态机制等等。
本实验要求实现建立一个具有三维特征的地势起伏不定的山形,然后在这个山上随机种植四种不同的树,它们的位置没有固定。可以在我们建立的山中视景漫游。同时我们可以看到不同的天气变化。真实的模拟自然界
2.5 模块设计
我们在这里建立三个模块,我们知道一个好的程序要求高内聚低耦合。在这里我用了四
个模块,一个是视景建立模块,一个是OPENGL的窗体建立模块,另一个是WIN32的窗体
建立模块,最后一个当然是输出模块了,这四个模块分别是:
视景(object)OPENGL
Win32窗体
可视化(输出)
3源程序
3.1头文件
#include
#include
#include"stdafx.h"
#include"贴图漫游.h"
#include"OpenGL.h"
//////////////////////////////////////////////////////////
OpenGL* m_OpenGL;
HDC hDC; // GDI设备句柄,将窗口连接到GDI( 图形设备接口)
HGLRC hRC=NULL; // 渲染描述句柄,将OpenGL调用连接到设备描述表
HWND hWnd=NULL; // 保存Windows 分配给程序的窗口句柄
int Width = 800;// 窗口宽
int Height= 600;// 窗口高
int bits = 32; // 颜色深度
LRESULT WINAPI MsgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM
lParam )// 消息处理
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,INT )// WinMain程
序入口
3.2 纹理映射技术
按照前面我们所了解到的有关纹理映射的知识,在加载纹理之前,我们需要获取图像,通过在BITMAP1类中的LoadBitmapFileWithAlpha函数可以完成,那么如何读取一个BITMAP 图像呢?了解一个BITMAP图像是非常重要的。
BITMAP图像的图像格式有RGB的也有RGBA格式的,但无论是那种格式,一幅BITMAP 图像它的结构有文件头,长和宽,格式类型。明白了它的结构之后,我们就可以像文件一个读写它了,下面是一个读取BITMAP图像的代码:
unsigned char * LoadBitmapFile(char *filename, BITMAPINFOHEADER
*bitmapInfoHeader)
{
FILE *filePtr; //文件的指针
BITMAPFILEHEADER bitmapFileHeader; // 文件头,它是一个BITMAPFILEHEADER结构
unsigned char *bitmapImage; // 文件的数据,以字节的方式存储它
unsigned int imageIdx = 0; // image index counter
unsigned char tempRGB; // RGB格式
filePtr = fopen(filename, "rb"); // 以二进制的方式打开图像if (filePtr == NULL) // 判断是否为空,对于指针这一步是不可少的
return NULL;
//读取一个文件头
fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
// verify that this is a bitmap by checking for the universal bitmap id
if (bitmapFileHeader.bfType != BITMAP_ID)
{
fclose(filePtr);
return NULL;
}
// 读取文件头信息
fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
// 移动指针
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
//生成一个结点
bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);
// verify memory allocation
if (!bitmapImage)
{
free(bitmapImage);
fclose(filePtr);
return NULL;
}
// read in the bitmap image data
fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
// make sure bitmap image data was read
if (bitmapImage == NULL)
{
fclose(filePtr);
return NULL;
}
// swap the R and B values to get RGB since the bitmap color format is in BGR
for(imageIdx= 0; imageIdx< bitmapInfoHeader->biSizeImage; imageIdx+=3) {
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}
// 关闭文件并且返回读取的数据
fclose(filePtr);
return bitmapImage;
}
LoadBitmapFileWithAlpha函数 ,Alpha代表的是透明度.
接下来我们是绑定纹理的工作:
以函数LOAD16为例:
bool object::LoadT8(char *filename, GLuint &texture)
{ AUX_RGBImageRec *pImage = NULL;
pImage = auxDIBImageLoad(filename);
if(pImage == NULL) return false;
glGenTextures(1, &texture); //这个是生成纹理的个数
glBindTexture (GL_TEXTURE_2D,texture); //绑定纹理
gluBuild2DMipmaps(GL_TEXTURE_2D,4, pImage->sizeX,
pImage->sizeY,GL_RGB,
GL_UNSIGNED_BYTE,pImage->data);//这个是生成纹理,当然也可以用GlTexImage2D,它是
二维纹理的定义,由于OPENGL的纹理的实现都是以矩阵变换的方式实现的,所以它的图像的像素必须是2的整数倍,而对于一般的图像它绑定纹理的工作就不能实现了,但用gluBuild2DMipmaps就可以,因为它细化了图像
free(pImage->data);
free(pImage);
return true;
}
应用纹理到一个图原意味着由纹理图像空间到帧缓冲区图像空间的一次映射。一般来说,定义纹理映射方式是指设定纹理图像映射到目标区以获得最终RGBA值的方式。OPENGL 提供了丰富的纹理映射的手段.
void object::texture(UINT textur)
{ glBindTexture (GL_TEXTURE_2D, textur);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);//清除边界过渡
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST);
}
3.3从高度图建立地形
创建地形有两种方法,一种是通过三角形格网的形式,这种形式建立的地形图比较固定,如何你想改变它的地形,你要重新写函数,不容易更新;二是通过一个高度图来建立地形,高度图是一个以raw为扩展名的文件,一幅没有经过任何处理的图像就是这种格式的,由于它占的内硬盘容量比较大,很少用。现在我们不用RAW来建立一个地形文件,而是用BMP文件还建立地形,这样的话我们的地形就可以是任意的,在建天空盒的时候,没有画底面,因为底面我们的地形我们画的纹理没有三维信息。是通过高度图建立的地形,代码如下。
unsigned char *g_imageData;
BITMAPINFOHEADER g_bit;
g_imageData = LoadBit("data/Terrain1.bmp",&g_bit); //调等高地形图
然后初始化地形
void object::InitTerrain()
{ int index = 0;
int Vertex;
for (int z = 0; z < MAP_W; z++)
for (int x = 0; x < MAP_W; x++)
{ Vertex = z * MAP_W + x;
g_terrain [Vertex][0] = float(x)*MAP_SCALE;
g_terrain [Vertex][1] = (float)(g_imageData[(z*MAP_W+x)*3]/3);
g_terrain [Vertex][2] = -float(z)*MAP_SCALE;
g_texcoord[Vertex][0] = (float) x;//纹理坐标
g_texcoord[Vertex][1] = (float) z;
g_index [index++] = Vertex;//索引坐标
g_index [index++] = Vertex+ MAP_W;
}
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,0,g_terrain);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2,GL_FLOAT,0,g_texcoord);
}
void object::DrawSand()
{
glBindTexture(GL_TEXTURE_2D, g_cactus[0]);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);//纹理环境
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST);
for (int z = 0; z < MAP_W-1; z++)
glDrawElements(GL_TRIANGLE_STRIP,MAP_W*2,GL_UNSIGNED_INT,&g_index[z*MAP_W*2]);/ /渲染数组数据中的图元
}
这样,地形就建好了,最后一步绘树的过程要获取地形的高度。获取地形高度的代码如下:
float object::GetHeight(float x, float z)
{ float CameraX = x/MAP_SCALE;
float CameraZ =-z/MAP_SCALE;
int Col0 = int(CameraX);
int Row0 = int(CameraZ);
int Col1 = Col0 + 1;
int Row1 = Row0 + 1;
if (Col1 > MAP_W) Col1 = 0;
if (Row1 > MAP_W) Row1 = 0;
float h00=g_terrain[Col0 + Row0*MAP_W][1];
float h01=g_terrain[Col1 + Row0*MAP_W][1];
float h11=g_terrain[Col1 + Row1*MAP_W][1];
float h10=g_terrain[Col0 + Row1*MAP_W][1];
float tx =CameraX - int(CameraX);
float ty =CameraZ - int(CameraZ);
float txty = tx * ty;
return h00*(1.0f-ty-tx+txty)
+ h01*(tx-txty)
+ h11*txty
+ h10*(ty-txty);
}
再获取高度之后,我们就要绘树了,绘树用到了一个融合的技术
glEnable(GL_BLEND);//启动蒙板
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//启动蒙板回调,其实是削除背景黑
glEnable(GL_ALPHA_TEST);//透明测试
glAlphaFunc(GL_GREATER, 0);
这几个函数就是剔除了树的黑色背景,为了不影响其它的绘制效果,绘完树之后要禁用蒙板和ALPHA测试
3.4漫游实现
BOOL object::DisplayScene()
{ float speed=0.5f;
float x=g_eye[0],y=g_eye[2],z=g_eye[2];// 视角
if (KEY_DOWN(VK_SHIFT)) speed =speed*2;//如果按下SHIFT键,则观看的速度加倍
if (KEY_DOWN(VK_LEFT)) g_Angle-=speed*2;//方向左键按下,则速度减速
if (KEY_DOWN(VK_RIGHT)) g_Angle+=speed*2;//方向左键按下,则速度减速
rad_xz = float (3.13149* g_Angle/180.0f);
if (KEY_DOWN(33)) g_elev +=speed;//page up 键
if (KEY_DOWN(34)) g_elev -=speed;//page down 键
if (g_elev<-360) g_elev =-360;
if (g_elev> 360) g_elev = 360;
if (KEY_DOWN(VK_UP))
{ g_eye[2]+=(float)sin(rad_xz)*speed;
g_eye[0]+=(float)cos(rad_xz)*speed;
}
if (KEY_DOWN(VK_DOWN))
{ g_eye[2]-=(float)sin(rad_xz)*speed;
g_eye[0]-=(float)cos(rad_xz)*speed;}
BOOL AT=FALSE;
if(KEY_DOWN('A')&& AT==FALSE){ AT=TRUE;glEnable(GL_FOG);}
else
{glDisable(GL_FOG);AT=FALSE;}
if(g_eye[0]< MAP_SCALE) g_eye[0]= MAP_SCALE;
if(g_eye[0]> (MAP_W-2)*MAP_SCALE) g_eye[0]= (MAP_W-2)*MAP_SCALE;
if(g_eye[2]<-(MAP_W-2)*MAP_SCALE) g_eye[2]=-(MAP_W-2)*MAP_SCALE;
if(g_eye[2]> -MAP_SCALE) g_eye[2]= -MAP_SCALE;
g_eye[1] =GetHeight((float)g_eye[0],(float)g_eye[2])+gao;
g_look[0] = (float)(g_eye[0] +100*cos(rad_xz));
g_look[2] = (float)(g_eye[2] +100*sin(rad_xz));
g_look[1] = g_eye[1] +g_elev;
gluLookAt(g_eye[0],g_eye[1],g_eye[2],
g_look[0],g_look[1],g_look[2],
0.0,1.0,0.0 ); return TRUE;} 4.运行结果
雾化效果
5.心得体会:
本学期的计算机图形学也结束了,总有意犹未尽的感觉,因为我发现这里面的好玩的东西才刚刚开始,课后我也写了写程序,在做这些程序的过程我也遇到了许多的困难,当然有许多的是自己的知识也不关,对原理的理解不清楚。
感谢老师和同学们的帮助,在老师的指导下我得以事半功倍,还是那句话,这非一朝一夕功夫,之后的编程之路还很久远,谢谢老师把我带进图形学的世界里,上了图形学的课才懂得了编程的趣味性在哪里..