天空盒

就是围绕摄像机的360°的风景照。实质其实就是围绕着摄像机的立方体,而摄像机就在这个立方体的里面。可以用来模拟无限的天空,山脉等现象。

为什么要使用天空盒

在3D游戏中,一般来说要渲染的东西会比较多,而使用天空盒会节约部分渲染的时间;而且如果不采用“天空盒”技术,或者其他技术,而直接进行渲染天空,地面以及周围场景,那么可能会因为距离把握不当,造成“露馅”。

除了在游戏中使用“天空盒”技术较多,在一些二次元动画中也会经常使用到,如MMD(miku miku dance)。

                   

unity 神秘天空盒_unity 神秘天空盒

                   

unity 神秘天空盒_unity 神秘天空盒_02

天空盒的结构

就如同一个立方体一样

                         

unity 神秘天空盒_天空盒_03

显然可知,该立方体共有8个顶点,6个面,因此最多需要6张纹理图,这6张纹理图分别覆盖该立方体的不同面。

//SkyBox's FVF
	struct SKYBOXVERTEX
	{
		float x, y, z;
		float u, v;
	};
#define D3DFVF_SKYBOX	D3DFVF_XYZ|D3DFVF_TEX1
class CSkyModel
	{
	public:
		CSkyModel(LPDIRECT3DDEVICE9 pDevice);

		virtual ~CSkyModel();

		//Data types & constants
		enum ModelType
		{
			k_box,
			k_hemisphere,
			k_sphere,
			k_dome
		};

		bool	Init_SkyBox(float Length);					//Initialize the sky box
		bool	Load_SkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile, wchar_t *pLeftTextureFile, \
			wchar_t *pRightTextureFile, wchar_t *pTopTextureFile, wchar_t *pBottomTextureFile);
		void	Render_SkyBox(D3DXMATRIX *pMatWorld, bool bRenderFrame);			//the first parameter is the sky box's world matrix,
																					//the second parameter is doing or not doing render the line (ÊÇ·ñäÖȾ³öÏß¿ò)
		bool	Init_Floor(float length);
		void	Render_Floor(bool bRenderFrame);

	private:
		ModelType m_type;
		LPD3DXMESH m_pMesh;

		D3DXHANDLE m_hUVSettings;

		LPDIRECT3DDEVICE9			m_pd3dDevice;			//D3D Device object
		LPDIRECT3DVERTEXBUFFER9		m_pVertexBuffer;		//the vertex buffer object
		LPDIRECT3DVERTEXBUFFER9		m_pFloorVerBuffer;		//the floor's vertex buffer
		LPDIRECT3DTEXTURE9			m_pTexture[6];
		float						m_Length;				//the length of skybox
	};

首先是定义天空盒的顶点格式,x,y,z分别代表坐标位置,u,v代表纹理坐标。

然后再定义其灵活顶点格式。

在类中,我声明了关于立方体长度的变量,以及存储6个纹理的数组;也声明了关于天空盒的类型,是天空盒还是天空球,或还是其他类型。在Demo中只实现了天空盒一种,且用旋转的方式进行模拟天空移动。

该类的定义:

//
//Name: CSkyModel Class
//
CSkyModel::CSkyModel(LPDIRECT3DDEVICE9 pDevice)
{
	m_pd3dDevice = pDevice;
	m_pVertexBuffer = NULL;
	for (size_t i = 0; i != 6; i++)
	{
		m_pTexture[i] = NULL;
	}
	m_Length = 0.0f;
}

CSkyModel::~CSkyModel()
{
	SAFE_RELEASE(m_pd3dDevice);
	SAFE_RELEASE(m_pVertexBuffer);
	SAFE_RELEASE(m_pFloorVerBuffer);

	for (size_t i = 0; i != 6; ++i)
	{
		SAFE_RELEASE(m_pTexture[i]);
	}
}

bool CSkyModel::Init_SkyBox(float Length)
{
	m_Length = Length;
	//1.创建。创建顶点缓存
	m_pd3dDevice->CreateVertexBuffer(24 * sizeof(SKYBOXVERTEX), 0,
		D3DFVF_SKYBOX, D3DPOOL_MANAGED, &m_pVertexBuffer, 0);

	//用一个结构体把顶点数据先准备好
	SKYBOXVERTEX vertices[] =
	{
		//前面的四个顶点
		{ -m_Length / 2, 0.0f,    m_Length / 2, 0.0f, 1.0f, },
		{ -m_Length / 2, m_Length / 2,   m_Length / 2, 0.0f, 0.0f, },
		{ m_Length / 2, 0.0f,    m_Length / 2, 1.0f, 1.0f, },
		{ m_Length / 2, m_Length / 2,   m_Length / 2, 1.0f, 0.0f, },

		//背面的四个顶点
		{ m_Length / 2, 0.0f,   -m_Length / 2, 0.0f, 1.0f, },
		{ m_Length / 2, m_Length / 2,  -m_Length / 2, 0.0f, 0.0f, },
		{ -m_Length / 2, 0.0f,   -m_Length / 2, 1.0f, 1.0f, },
		{ -m_Length / 2, m_Length / 2,  -m_Length / 2, 1.0f, 0.0f, },

		//左面的四个顶点
		{ -m_Length / 2, 0.0f,   -m_Length / 2, 0.0f, 1.0f, },
		{ -m_Length / 2, m_Length / 2,  -m_Length / 2, 0.0f, 0.0f, },
		{ -m_Length / 2, 0.0f,    m_Length / 2, 1.0f, 1.0f, },
		{ -m_Length / 2, m_Length / 2,   m_Length / 2, 1.0f, 0.0f, },

		//右面的四个顶点
		{ m_Length / 2, 0.0f,   m_Length / 2, 0.0f, 1.0f, },
		{ m_Length / 2, m_Length / 2,  m_Length / 2, 0.0f, 0.0f, },
		{ m_Length / 2, 0.0f,  -m_Length / 2, 1.0f, 1.0f, },
		{ m_Length / 2, m_Length / 2, -m_Length / 2, 1.0f, 0.0f, },

		//上面的四个顶点
		{ m_Length / 2, m_Length / 2, -m_Length / 2, 1.0f, 0.0f, },
		{ m_Length / 2, m_Length / 2,  m_Length / 2, 1.0f, 1.0f, },
		{ -m_Length / 2, m_Length / 2, -m_Length / 2, 0.0f, 0.0f, },
		{ -m_Length / 2, m_Length / 2,  m_Length / 2, 0.0f, 1.0f, },

		//bottom
		{-m_Length / 2,0,m_Length / 2,0,0},
		{-m_Length / 2,0,-m_Length / 2,0,1},
		{m_Length / 2,0,m_Length / 2,1,0},
		{m_Length / 2,0,-m_Length / 2,1,1},
	};

	//准备填充顶点数据
	void *pVertices;

	//Lock
	m_pVertexBuffer->Lock(0, 0, (void**)&pVertices, 0);
	//access 把结构体中的数据直接拷到顶点缓冲区中
	memcpy(pVertices, vertices, sizeof(vertices));

	//UnLock
	m_pVertexBuffer->Unlock();

	//Floor
	float _length = 5000.0f;
	this->Init_Floor(_length);

	return true;
}

bool CSkyModel::Init_Floor(float tLength)
{
	//1.创建。创建顶点缓存
	m_pd3dDevice->CreateVertexBuffer(4 * sizeof(SKYBOXVERTEX), 0,
		D3DFVF_SKYBOX, D3DPOOL_MANAGED, &m_pFloorVerBuffer, 0);

	//用一个结构体把顶点数据先准备好
	SKYBOXVERTEX vertices[] =
	{
		//底部
		{ -tLength,0.0f,-tLength,0.0f,30.0f },
		{ -tLength,0.0f,tLength,0.0f,0.0f },
		{ tLength,0.0f,-tLength,30.0f,30.0f },
		{ tLength,0.0f,tLength,30.0f,0.0f },
	};
	//准备填充顶点数据
	void *pVertices;

	//Lock
	m_pFloorVerBuffer->Lock(0, 0, (void**)&pVertices, 0);
	//access 把结构体中的数据直接拷到顶点缓冲区中
	memcpy(pVertices, vertices, sizeof(vertices));

	//UnLock
	m_pVertexBuffer->Unlock();

	return true;
}

bool CSkyModel::Load_SkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile, wchar_t *pLeftTextureFile, \
	wchar_t *pRightTextureFile, wchar_t *pTopTextureFile, wchar_t *pBottomTextureFile)
{
	//从文件加载五张纹理
	D3DXCreateTextureFromFileW(m_pd3dDevice, pFrontTextureFile, &m_pTexture[0]);  //前面
	D3DXCreateTextureFromFileW(m_pd3dDevice, pBackTextureFile, &m_pTexture[1]);  //后面
	D3DXCreateTextureFromFileW(m_pd3dDevice, pLeftTextureFile, &m_pTexture[2]);  //左面
	D3DXCreateTextureFromFileW(m_pd3dDevice, pRightTextureFile, &m_pTexture[3]);  //右面
	D3DXCreateTextureFromFileW(m_pd3dDevice, pTopTextureFile, &m_pTexture[4]);  //上面
	D3DXCreateTextureFromFileW(m_pd3dDevice, pBottomTextureFile, &m_pTexture[5]); //底部
	return TRUE;
}

//--------------------------------------------------------------------------------------
// Name: SkyBoxClass::RenderSkyBox()
// Desc: 绘制出天空盒,可以通过第二个参数选择是否绘制出线框
//--------------------------------------------------------------------------------------
void CSkyModel::Render_SkyBox(D3DXMATRIX *pMatWorld, bool bRenderFrame)
{
	m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);  //将纹理颜色混合的第一个参数的颜色值用于输出
	m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);   //纹理颜色混合的第一个参数的值就取纹理颜色值
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);

	m_pd3dDevice->SetTransform(D3DTS_WORLD, pMatWorld);  //设置世界矩阵
	m_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(SKYBOXVERTEX));    //把包含的几何体信息的顶点缓存和渲染流水线相关联  
	m_pd3dDevice->SetFVF(D3DFVF_SKYBOX);  //设置FVF灵活顶点格式

										  //一个for循环,将6个面绘制出来
	for (int i = 0; i != 6; i++)
	{
		m_pd3dDevice->SetTexture(0, m_pTexture[i]);
		m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i * 4, 2);
	}

	//对是否渲染线框的处理代码
	if (bRenderFrame)  //如果要渲染出线框的话
	{
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); //把填充模式设为线框填充
																		 //一个for循环,将5个面的线框绘制出来
		for (int i = 0; i != 6; i++)
		{
			m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i * 4, 2);	//绘制顶点 
		}

		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);	//把填充模式调回实体填充
	}
}


void CSkyModel::Render_Floor(bool bRenderFrame)
{
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

	m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	D3DXMATRIX matWorld;
	D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f);
	m_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);

	m_pd3dDevice->SetStreamSource(0, m_pFloorVerBuffer, 0, sizeof(SKYBOXVERTEX));
	m_pd3dDevice->SetFVF(D3DFVF_SKYBOX);
	m_pd3dDevice->SetTexture(0, m_pTexture[5]);
	m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

	//对是否渲染线框的处理代码
	if (bRenderFrame)  //如果要渲染出线框的话
	{
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); //把填充模式设为线框填充

		m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);	//绘制顶点 

		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);	//把填充模式调回实体填充
	}
}

最后演示结果

                    

unity 神秘天空盒_天空盒_04


天空盒实现还有其他办法,不用手动进行设置立方体的大小,可以通过导入一个立方体的模型,如.X文件,然后通过HLSL进行顶点着色器的编写,也可以实现。

如果想多了解关于3D场景以及地形渲染可以参考

Real Time 3D Terrain Engines Using C++ And Dx9