ArchieOpenGL基础教程第九课:光照和键盘

参考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;


现在载入一个位图,并用它创建三种不同的纹理。这一课使用glaux辅助库来载入位图,因此在编译时您应该确认是否包含了glaux库。

这段代码调用前面的代码载入位图,并将其转换成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 )。背景色设为黑色,我们启用深度测试,然后我们启用优化透视计算。

光照设置及光源设置

现在开始设置光源。下面一行设置环境光的发光量,光源light1开始发光。这一课的开始处我们我们将环境光的发光量存放在LightAmbient数组中。现在我们就使用此数组(半亮度环境光)。在int InitGL(GLvoid)函数中添加下面的代码。

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_fYRot的旋转值分别增加m_fXSpeed和m_fYSpeed个单位。m_fXSpeed和m_fYSpeed的值越大,立方体转得就越快。

	m_fXRot+=m_fXSpeed;								// X 轴旋转
	m_fYRot+=m_fYSpeed;								// Y 轴旋转
-----------------------------------------------------------------------------------------------------------------------------------------------
现在转入OnDraw()主函数。我们将在这里增加开关光源、旋转木箱、切换过滤方式以及将木箱移近移远的控制代码。


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

光照控制

现在转入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);
	}

检查按键消息OnKeyDown

代码将检查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
 


你可能感兴趣的:(ArchieOpenGL基础教程第九课:光照和键盘)