1,目前虚拟场景中天空建模常用的方法有天空顶(SkyDome:半球形)和天空盒(SkyBox:长方体)两种方法。其本质都是摄像机处在一个盒子中间,这个盒子通过纹理贴图形成的虚拟世界场景。其中天空盒绘制技术非常简单,因此被广泛应用。然而,有时也会存在一些问题,例如使用雾效时,如果雾被设置在观察者的旁边,天空盒将减淡甚至消失。另一个更坑爹的问题是雾会聚积在天空盒的顶点处,从而使天空盒的多边形暴露无遗。小心地调整雾化参数或细分天空盒的每一个面可以减少这些问题,但这样会大大影响性能。此时,就可用天空顶来替代天空盒了。它是个半球形场景,因此就不会有什么边界暴露问题了。本节主要介绍天空盒相关技术。
2,天空盒其实就是一个覆盖场景四周的长方体,但它的各个面上贴有表示天空的纹理图片,即四周的4面纹理的边与顶面纹理的边相连,同时四面纹理前后相连,纹理大小要是2的N次方(32,64,128,…),如下:
1,主程序:
#include "stdafx.h"
#include
#include
#include
#include
#include "Camera.h"
#include "SkyBox.h"
Camera m_Camera;
CSkyBox m_SkyBox;
void init(void)
{
/** 用户自定义的初始化过程 */
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClearDepth(1.0f);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
/** 启用纹理 */
glEnable(GL_TEXTURE_2D);
/** 初始化天空 */
if (!m_SkyBox.Init())
{
MessageBox(NULL, (LPCWSTR)"初始化天空失败!", (LPCWSTR)"错误", MB_OK);
exit(0);
}
/** 设置摄像机 */
m_Camera.setCamera(500, 35, 400, 501, 35, 400, 0, 1, 0);
}
void display(void)
{
/** 用户自定义的绘制过程 */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
/** 放置摄像机 */
m_Camera.setLook();
/** 绘制天空 */
m_SkyBox.CreateSkyBox(0, 0, 0, 1.0, 0.5, 1.0);
glFlush(); /**< 强制执行所有的OpenGL命令 */
}
void ChangeSize(int width, int height)
{
glViewport(0, 0, width, height); /**< 重新设置视口 */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 4000.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void motion(int x, int y)
{
m_Camera.setViewByMouse();
glutPostRedisplay();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
case '1':
m_Camera.setSpeed(0.2f);
break;
case '2':
m_Camera.setSpeed(1.0f);
break;
case 'w':
m_Camera.moveCamera(m_Camera.getSpeed());
break;
case 's':
m_Camera.moveCamera(-m_Camera.getSpeed());
break;
case 'a':
m_Camera.yawCamera(-m_Camera.getSpeed());
break;
case 'd':
m_Camera.yawCamera(m_Camera.getSpeed());
break;
}
glutPostRedisplay();
printf("key::%d", key);
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_RGB);
glutInitWindowSize(800, 600);
glutInitWindowPosition((GetSystemMetrics(SM_CXSCREEN) >> 1) - 400, (GetSystemMetrics(SM_CYSCREEN) >> 1) - 300);
glutCreateWindow("天空盒");
init();
glutReshapeFunc(ChangeSize);
glutDisplayFunc(display);
glutMotionFunc(motion);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
2,天空盒实现类
#ifndef __SKYBOX_H__
#define __SKYBOX_H__
#include "stdafx.h"
#include "CBMPLoader.h"
#include "Vector.h"
#include "Camera.h"
#define GL_CLAMP_TO_EDGE 0x812F
/** 天空盒类 */
class CSkyBox
{
public:
/** 构造函数 */
CSkyBox();
~CSkyBox();
/** 初始化 */
bool Init();
/** 渲染天空 */
void CreateSkyBox(float x, float y, float z,
float width, float height,
float length);
private:
CBMPLoader m_texture[6]; /**< 天空盒纹理 */
};
#endif ///__SKYBOX_H__
#include "SkyBox.h"
CSkyBox::CSkyBox()
{
}
CSkyBox::~CSkyBox()
{
/** 删除纹理对象及其占用的内存 */
for(int i =0 ;i< 6; i++)
{
m_texture[i].FreeImage();
glDeleteTextures(1,&m_texture[i].ID);
}
}
/** 天空盒初始化 */
bool CSkyBox::Init()
{
char filename[128] ; /**< 用来保存文件名 */
char *bmpName[] = { "back","front","bottom","top","left","right"};
for(int i=0; i< 6; i++)
{
sprintf(filename,"data/%s",bmpName[i]);
strcat(filename,".bmp");
if(!m_texture[i].LoadBitmap(filename)) /**< 载入位图文件 */
{
MessageBox(NULL, (LPCWSTR)"装载位图文件失败!", (LPCWSTR)"错误", MB_OK); /**< 如果载入失败则弹出对话框 */
exit(0);
}
glGenTextures(1, &m_texture[i].ID); /**< 生成一个纹理对象名称 */
glBindTexture(GL_TEXTURE_2D, m_texture[i].ID); /**< 创建纹理对象 */
/** 控制滤波: */
/*
其中GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T通常可设置为GL_REPEAT或GL_CLAMP两种方式。
当待填充的多边形大于纹理的时候,GL_REPEAT表示多余的部分用重复的方式填充;GL_CLAMP表示多余的部分用相连边缘的相邻像素填充。
在实际绘制中,我们一般采用GL_CLAMP_EDGE来处理,这就消除了接缝处的细线,增强了天空盒的真实感。
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
/** 创建纹理 */
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, m_texture[i].imageWidth,
m_texture[i].imageHeight, GL_RGB, GL_UNSIGNED_BYTE,
m_texture[i].image);
}
return true;
}
/** 构造天空盒 */
void CSkyBox::CreateSkyBox(float x, float y, float z,
float box_width, float box_height,
float box_length)
{
/** 获得场景中光照状态 */
GLboolean lp;
glGetBooleanv(GL_LIGHTING,&lp);
/** 计算天空盒长 宽 高 */
float width = MAP * box_width/8;
float height = MAP * box_height/8;
float length = MAP * box_length/8;
/** 计算天空盒中心位置 */
x = x+ MAP/8 - width / 2;
y = y+ MAP/24 - height / 2;
z = z+ MAP/8 - length / 2;
glDisable(GL_LIGHTING); /**< 关闭光照 */
/** 开始绘制 */
glPushMatrix();
glTranslatef(-x,-y,-z);
/** 绘制背面 */
glBindTexture(GL_TEXTURE_2D, m_texture[0].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(x + width, y, z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x + width, y + height, z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + height, z);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z);
glEnd();
/** 绘制前面 */
glBindTexture(GL_TEXTURE_2D, m_texture[1].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y, z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height, z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height, z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y, z + length);
glEnd();
/** 绘制底面 */
glBindTexture(GL_TEXTURE_2D, m_texture[2].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y, z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y, z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y, z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y, z);
glEnd();
/** 绘制顶面 */
glBindTexture(GL_TEXTURE_2D, m_texture[3].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height, z);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y + height, z + length);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y + height, z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height, z);
glEnd();
/** 绘制左面 */
glBindTexture(GL_TEXTURE_2D, m_texture[4].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 1.0f); glVertex3f(x, y + height, z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + height, z + length);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z + length);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x, y, z);
glEnd();
/** 绘制右面 */
glBindTexture(GL_TEXTURE_2D, m_texture[5].ID);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(0.0f, 0.0f); glVertex3f(x + width, y, z);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x + width, y, z + length);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x + width, y + height, z + length);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x + width, y + height, z);
glEnd();
glPopMatrix(); /** 绘制结束 */
if(lp) /** 恢复光照状态 */
glEnable(GL_LIGHTING);
}
注:
/* 定义地面网格 /
const unsigned int MAP_WIDTH = 1024;
const unsigned int CELL_WIDTH = 16;
const unsigned int MAP = MAP_WIDTH * CELL_WIDTH / 2;