参考Nehe教程第7课
在上节基础上继续。
-----------------------------------------------------
这一课我会教您如何使用三种不同的纹理滤波方式。教您如何使用键盘来移动场景中的对象,还会教您在OpenGL场景中应用简单的光照。这一课包含了很多内容,如果您对前面的课程有疑问的话,先回头复习一下。进入后面的代码之前,很好的理解基础知识十分重要。
程序开始,我们先加上几个新的变量。
下面几行是新的。我们增加一个布尔变量。 m_bLight 变量跟踪光照是否打开。
BOOL m_bLight;// 光源的开/关现在设置5个变量来控制绕x轴和y轴旋转角度的步长,以及绕x轴和y轴的旋转速度。另外还创建了一个m_fZ来控制进入屏幕深处的距离。
GLfloat m_fXRot;//X旋转 GLfloat m_fYRot; GLfloat m_fZRot; GLfloat m_fXSpeed;// X 旋转速度 GLfloat m_fYSpeed;// Y 旋转速度
GLfloat m_fZ; // 深入屏幕的距离初始化View
COpenglbaseView::COpenglbaseView() { // TODO: add construction code here m_hRC=NULL; m_pDC=NULL; m_fRotQuadrangle=20.0f; m_fRotTriangle=7.0f; m_fXRot=20.0f; m_fYRot=10.0f; m_fZRot=1.0f; m_fXSpeed=1.0f; m_fYSpeed=1.0f; m_fZ=-5.0f; // 深入屏幕的距离 }
m_nFilter 变量跟踪显示时所采用的纹理类型。
第一种纹理(texture 0) 使用gl_nearest(不光滑)滤波方式构建。
第二种纹理 (texture 1) 使用gl_linear(线性滤波) 方式,离屏幕越近的图像看起来就越光滑。
第三种纹理 (texture 2) 使用 mipmapped滤波方式,这将创建一个外观十分优秀的纹理。
根据我们的使用类型,m_nFilter 变量的值分别等于 0, 1 或 2 。下面我们从第一种纹理开始。
GLuint texture[3] 为三种不同纹理分配储存空间。它们分别位于在 texture[0], texture[1] 和 texture[2]中。
int m_nFilter;//纹理滤波方式
GLuint texture[3]; // 保存3个纹理标志
m_nFilter=0;
这段代码调用前面的代码载入位图,并将其转换成3个纹理。Status 变量跟踪纹理是否已载入并被创建了。
////////////////////////////////////////////////////////////////////////// //加载纹理 ////////////////////////////////////////////////////////////////////////// int COpenglbaseView::LoadGLTextures() { int Status=FALSE; AUX_RGBImageRec *TextureImage[1]; // 创建保存1个纹理的数据结构 memset(TextureImage,0,sizeof(void *)*1); // 初始化 if (TextureImage[0]=LoadBMP("Data/logo.bmp")) // 加载纹理0 { Status=TRUE; glGenTextures(3, &texture[0]); // 创建3个纹理,纹理存放首地址为&texture[0] //创建 Nearest 滤波贴图.我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 GL_LINEAR.MIN_FILTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始尺寸时采用。 glBindTexture(GL_TEXTURE_2D, texture[0]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // 创建线性滤波纹理 glBindTexture(GL_TEXTURE_2D, texture[1]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); ////////////////////////////////////////////////////////////////////////// /* 下面是创建纹理的新方法Mipmapping! 您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失。刚才还很不错的图案变得很难看。 当您告诉OpenGL创建一个 mipmapped的纹理后,OpenGL将尝试创建不同尺寸的高质量纹理。 当您向屏幕绘制一个 mipmapped纹理的时候,OpenGL将选择它已经创建的外观最佳的纹理(带有更多细节)来绘制, 而不仅仅是缩放原先的图像(这将导致细节丢失)。 我曾经说过有办法可以绕过OpenGL对纹理宽度和高度所加的限制——64、128、256,等等。办法就是 gluBuild2DMipmaps。 据我的发现,您可以使用任意的位图来创建纹理。OpenGL将自动将它缩放到正常的大小。 因为是第三个纹理,我们将它存到texture[2]。这样本课中的三个纹理全都创建好了。 */ ////////////////////////////////////////////////////////////////////////// // 创建 MipMapped 纹理 glBindTexture(GL_TEXTURE_2D, texture[2]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); ////////////////////////////////////////////////////////////////////////// /*下面一行生成 mipmapped 纹理。我们使用三种颜色(红,绿,蓝)来生成一个2D纹理。 TextureImage[0]->sizeX 是位图宽度,extureImage[0]->sizeY 是位图高度,GL_RGB意味着我们依次使用RGB色彩。 GL_UNSIGNED_BYTE 意味着纹理数据的单位是字节。TextureImage[0]->data指向我们创建纹理所用的位图。 */ ////////////////////////////////////////////////////////////////////////// } if (TextureImage[0]) // 纹理是否存在 { if (TextureImage[0]->data) // 纹理图像是否存在 { free(TextureImage[0]->data); // 释放纹理图像占用的内存 } free(TextureImage[0]); // 释放图像结构 } return Status; }
接着应该载入纹理并初始化OpenGL设置了。myInit()函数的第一行使用上面的代码载入纹理。创建纹理之后,我们调用
glEnable(GL_TEXTURE_2D)启用2D纹理映射。阴影模式设为平滑阴影( smooth shading )。背景色设为黑色,我们启用深度测试,然后我们启用优化透视计算。
void COpenglbaseView::SetLight() { // Lights properties float ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};// 环境光参数 float diffuseProperties[] = {0.8f, 0.8f, 0.8f, 1.0f};// 漫射光参数 float specularProperties[] = {1.0f, 1.0f, 1.0f, 1.0f}; GLfloat position[] = {0.0f, 0.0f, 2.0f, 1.0f}; // 光源位置 glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties); glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties); glLightfv( GL_LIGHT0, GL_SPECULAR, specularProperties); glLightfv( GL_LIGHT0, GL_POSITION, position);//设置光源参数,v表示参数用矢量形式position被设定 // 最后,我们启用0号光源。我们还没有启用GL_LIGHTING,所以您看不见任何光线。 //记住:只对光源进行设置、定位、甚至启用,光源都不会工作。除非我们启用GL_LIGHTING。 glEnable(GL_LIGHT0); glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0); // back material float MatAmbientBack[] = {0.0f, 0.0f, 0.0f, 0.0f}; glMaterialfv(GL_BACK,GL_AMBIENT,MatAmbientBack); // SetMaterial(m_glMat[0],false); float ambient[] = {0.0f,0.0f,0.0f,1.0f}; float diffuse[] = {0.0f,0.0f,0.0f,1.0f}; float specular[] = {0.0f,0.0f,0.0f,1.0f}; float emission[] = {0.3f,0.3f,0.3f,1.0f}; float shininess[] = {0.0f}; // Change // if(string == "Silver") { // Ambient ambient[0] = 0.19225f; ambient[1] = 0.19225f; ambient[2] = 0.19225f; ambient[3] = 1.0f; // Diffuse diffuse[0] = 0.50754f; diffuse[1] = 0.50754f; diffuse[2] = 0.50754f; diffuse[3] = 1.0f; // Specular specular[0] = 0.508273f; specular[1] = 0.508273f; specular[2] = 0.508273f; specular[3] = 1.0f; // Shininess shininess[0] = 51.2f; } // apply glMaterialfv( GL_FRONT, GL_AMBIENT, ambient); glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse); glMaterialfv( GL_FRONT, GL_SPECULAR, specular); glMaterialfv( GL_FRONT, GL_SHININESS, shininess); glMaterialfv( GL_FRONT, GL_EMISSION, emission); }
绘制立方体
BOOL COpenglbaseView::RenderScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存 glLoadIdentity(); // 重置当前的模型观察矩阵
glTranslatef(0.0f,0.0f,z)将立方体沿着Z轴移动m_fZ单位。
glRotatef(m_fXRot,1.0f,0.0f,0.0f);将立方体绕X轴旋转m_fXRot。
glRotatef(m_fYRot,0.0f,1.0f,0.0f);将立方体绕Y轴旋转m_fYRot。
glRotatef(m_fXRot,1.0f,0.0f,0.0f); // 绕X轴旋转 glRotatef(m_fYRot,0.0f,1.0f,0.0f); // 绕Y轴旋转 glRotatef(m_fZRot,0.0f,0.0f,1.0f); // 绕Z轴旋转
下一行我们绑定的纹理是texture[m_nFilter],而不是上一课中的texture[0]。任何时候,我们按下F键,m_nFilter的值就会增加。如果这个数值大于2,变量filter 将被重置为0。程序初始时,变量m_nFilter的值也将设为0。使用变量m_nFilter我们就可以选择三种纹理中的任意一种。
glBindTexture(GL_TEXTURE_2D, texture[m_nFilter]); // 选择纹理
glBegin(GL_QUADS);
glNormal3f是这一课的新东西。Normal就是法线的意思,所谓法线是指经过面(多边形)上的一点且垂直于这个面(多边形)的直线。使用光源的时候必须指定一条法线。法线告诉OpenGL这个多边形的朝向,并指明多边形的正面和背面。如果没有指定法线,什么怪事情都可能发生:不该照亮的面被照亮了,多边形的背面也被照亮....。对了,法线应该指向多边形的外侧。
看着木箱的前面您会注意到法线与Z轴正向同向。这意味着法线正指向观察者-您自己。这正是我们所希望的。对于木箱的背面,也正如我们所要的,法线背对着观察者。如果立方体沿着X或Y轴转个180度的话,前侧面的法线仍然朝着观察者,背面的法线也还是背对着观察者。换句话说,不管是哪个面,只要它朝着观察者这个面的法线就指向观察者。由于光源紧邻观察者,任何时候法线对着观察者时,这个面就会被照亮。并且法线越朝着光源,就显得越亮一些。如果您把观察点放到立方体内部,你就会法线里面一片漆黑。因为法线是向外指的。如果立方体内部没有光源的话,当然是一片漆黑。
// 前侧面 glNormal3f( 0.0f, 0.0f, 1.0f); // 法线指向观察者 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 // 后侧面 glNormal3f( 0.0f, 0.0f,-1.0f); // 法线背向观察者 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的左下 // 顶面 glNormal3f( 0.0f, 1.0f, 0.0f); // 法线向上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 // 底面 glNormal3f( 0.0f,-1.0f, 0.0f); // 法线朝下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 // 右侧面 glNormal3f( 1.0f, 0.0f, 0.0f); // 法线朝右 glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 // 左侧面 glNormal3f(-1.0f, 0.0f, 0.0f); // 法线朝左 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glEnd();
m_fXRot+=m_fXSpeed; // X 轴旋转 m_fYRot+=m_fYSpeed; // Y 轴旋转-----------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------
现在转入OnDraw()函数,判断是否开启了光照
void COpenglbaseView::OnDraw(CDC* pDC) { COpenglbaseDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here if(m_bLight) { glEnable(GL_LIGHTING);//用当前光参数计算颜色值 glEnable(GL_LIGHT0); } else { glDisable(GL_LIGHTING); glDisable(GL_LIGHT0); }
代码将检查L键是否按下,切换光照开关。Nehe设置了两个变量来判断L是否按下是为了防止L键被按住后,这段代码被反复执行,并导致窗体不停闪烁。这里仅有m_bLight。
void COpenglbaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { switch(nChar) { case VK_ESCAPE: //获取主框架窗口指针(app文件openbase.cpp包含了mainfrm.h) CMainFrame * pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd; //调用主窗口类的自定义函数EndFullScreen,退出全屏显示状态 pFrame->EndFullScreen(); Invalidate(); break; //灯光控制 case 'L': m_bLight=!m_bLight; Invalidate(); break; } CView::OnKeyDown(nChar, nRepCnt, nFlags); }
void COpenglbaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { switch(nChar) { case VK_ESCAPE: { //获取主框架窗口指针(app文件openbase.cpp包含了mainfrm.h) CMainFrame * pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd; //调用主窗口类的自定义函数EndFullScreen,退出全屏显示状态 pFrame->EndFullScreen(); Invalidate(); break; } //灯光控制 case 'L': { m_bLight=!m_bLight; Invalidate(); break; } //滤波方式 case 'F': { m_nFilter++; if (m_nFilter>2) { m_nFilter=0; } Invalidate(); break; } case VK_PRIOR: // PageUp按下了? { m_fZ-=0.02f; // 若按下,将木箱移向屏幕内部 } Invalidate(); break; //接着四行检查PageDown键是否按下,若是的话,增加z变量的值。 case VK_NEXT: // PageDown按下了么 { m_fZ+=0.02f; // 若按下的话,将木箱移向观察者 } Invalidate(); break; //现在检查方向键。 case VK_UP: // Up方向键按下了么? { m_fXSpeed-=0.01f; // 若是,减少xspeed } Invalidate(); break; case VK_DOWN: // Down方向键按下了么? { m_fXSpeed+=0.01f; // 若是,增加xspeed } Invalidate(); break; case VK_RIGHT: // Right方向键按下了么? { m_fYSpeed+=0.01f; // 若是,增加yspeed } Invalidate(); break; case VK_LEFT: // Left方向键按下了么? { m_fYSpeed-=0.01f; // 若是, 减少yspeed } Invalidate(); break; } CView::OnKeyDown(nChar, nRepCnt, nFlags); }初始化
m_fXSpeed=1.0f; m_fYSpeed=1.0f;
奇怪,每次按键都会出现闪烁呢,需要把Invalidate参数改为FALSE
void COpenglbaseView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { switch(nChar) { case VK_ESCAPE: { //获取主框架窗口指针(app文件openbase.cpp包含了mainfrm.h) CMainFrame * pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd; //调用主窗口类的自定义函数EndFullScreen,退出全屏显示状态 pFrame->EndFullScreen(); Invalidate(FALSE); break; } //灯光控制 case 'L': { m_bLight=!m_bLight; Invalidate(FALSE); break; } //滤波方式 case 'F': { m_nFilter++; if (m_nFilter>2) { m_nFilter=0; } Invalidate(FALSE); break; } case VK_PRIOR: // PageUp按下了? { m_fZ-=0.02f; // 若按下,将木箱移向屏幕内部 } Invalidate(FALSE); break; //接着四行检查PageDown键是否按下,若是的话,增加z变量的值。 case VK_NEXT: // PageDown按下了么 { m_fZ+=0.02f; // 若按下的话,将木箱移向观察者 } Invalidate(FALSE); break; //现在检查方向键。 case VK_UP: // Up方向键按下了么? { m_fXSpeed-=0.01f; // 若是,减少xspeed } Invalidate(FALSE); break; case VK_DOWN: // Down方向键按下了么? { m_fXSpeed+=0.01f; // 若是,增加xspeed } Invalidate(FALSE); break; case VK_RIGHT: // Right方向键按下了么? { m_fYSpeed+=0.01f; // 若是,增加yspeed } Invalidate(FALSE); break; case VK_LEFT: // Left方向键按下了么? { m_fYSpeed-=0.01f; // 若是, 减少yspeed } Invalidate(FALSE); break; } CView::OnKeyDown(nChar, nRepCnt, nFlags); }
http://download.csdn.net/detail/whucv/4756840