天空盒
就是围绕摄像机的360°的风景照。实质其实就是围绕着摄像机的立方体,而摄像机就在这个立方体的里面。可以用来模拟无限的天空,山脉等现象。
为什么要使用天空盒
在3D游戏中,一般来说要渲染的东西会比较多,而使用天空盒会节约部分渲染的时间;而且如果不采用“天空盒”技术,或者其他技术,而直接进行渲染天空,地面以及周围场景,那么可能会因为距离把握不当,造成“露馅”。
除了在游戏中使用“天空盒”技术较多,在一些二次元动画中也会经常使用到,如MMD(miku miku dance)。
天空盒的结构
就如同一个立方体一样
显然可知,该立方体共有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); //把填充模式调回实体填充
}
}
最后演示结果
天空盒实现还有其他办法,不用手动进行设置立方体的大小,可以通过导入一个立方体的模型,如.X文件,然后通过HLSL进行顶点着色器的编写,也可以实现。
如果想多了解关于3D场景以及地形渲染可以参考
Real Time 3D Terrain Engines Using C++ And Dx9