代码改变世界

Direct3D轮回:游戏场景之天空

2011-07-09 12:23  独孤残云  阅读(3710)  评论(8编辑  收藏  举报

游戏场景中的天空效果实现起来其实很简单,我们使用一个称之为“天空盒”的技术即可~

如下是一个天空盒对象的简单实现:

/*-------------------------------------

代码清单:Skybox.h
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
"Texture2D.h"

#pragma once

// 天空盒顶点缓冲结构定义
struct VertexSkybox{
    VertexSkybox(){}
    VertexSkybox(
float x, float y, float z, float nx, float ny, float nz, D3DCOLOR color, float u, float v){
        _x 
= x; _y = y; _z = z;
        _nx 
= nx; _ny = ny; _nz = nz;
        _color 
= color;
        _u 
= u; _v = v;
    }
    
float _x, _y, _z;
    
float _nx, _ny, _nz;
    D3DCOLOR _color;
    
float _u, _v;
    
static const DWORD FVF;
};

class CSkybox
{
public:
    CSkybox(
void);
    
~CSkybox(void);
public:
    
bool Create(                                                // 构建天空盒
        char* szFrontTex,                                       // 前、后、左、右、上、下 纹理路径
        char* szBackTex,
        
char* szLeftTex,
        
char* szRightTex,
        
char* szTopTex,
        
char* szBottomTex
        );
    
void Draw();                                                // 绘制天空盒
    void Release();                                             // 释放天空盒
private:
    
void InitVertices();                                        // 初始化顶点及索引缓冲区
private:
    CTexture2D
**            m_ppTextureArray;                   // 天空盒纹理数组
    VertexSkybox*           m_pVertex;                          // 顶点缓冲
    UINT16*                 m_pIndices;                         // 索引缓冲
    D3DXMATRIX              m_WorldTransMatrix;                 // 当前世界变换矩阵
    D3DXMATRIX              m_OriWorldTransMatrix;              // 原始世界变换矩阵
};

 

Skybox.cpp
/*-------------------------------------

代码清单:Skybox.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
"StdAfx.h"
#include 
"Skybox.h"
#include 
"D3DGame.h"
#include 
"D3DCamera.h"

extern IDirect3DDevice9 *g_pD3DDevice;
extern CD3DCamera       *g_pD3DCamera;

// 顶点格式的初始化放到.cpp里,避免重定义错误
const DWORD VertexSkybox::FVF = (D3DFVF_XYZ | D3DFVF_NORMAL |D3DFVF_DIFFUSE | D3DFVF_TEX1);

CSkybox::CSkybox(
void) : m_ppTextureArray(NULL),
                         m_pVertex(NULL),
                         m_pIndices(NULL)
{
}

CSkybox::
~CSkybox(void)
{
}

bool CSkybox::Create(char* szFrontTex, char* szBackTex, char* szLeftTex,
                         
char* szRightTex, char* szTopTex,  char* szBottomTex)
{
    
// 初始化顶点及索引缓冲区
    InitVertices();
    
// 初始化天空盒纹理
    m_ppTextureArray = new CTexture2D*[6];
    
for (int ti=0;ti<6;ti++)
    {
        m_ppTextureArray[ti] 
= new CTexture2D;
    }
    
// 如果初始化过程中失败,则立即释放已有资源
    if!m_ppTextureArray[0]->LoadTexture(szFrontTex)||
        
!m_ppTextureArray[1]->LoadTexture(szBackTex) ||
        
!m_ppTextureArray[2]->LoadTexture(szLeftTex) ||
        
!m_ppTextureArray[3]->LoadTexture(szRightTex)||
        
!m_ppTextureArray[4]->LoadTexture(szTopTex)  ||
        
!m_ppTextureArray[5]->LoadTexture(szBottomTex)
        )
    {
        Release();
        
return false;
    }
    
return true;
}

void CSkybox::InitVertices()
{
    
// 初始化索引缓冲
    m_pIndices = new UINT16[6];

    m_pIndices[
0= 0;
    m_pIndices[
1= 1;
    m_pIndices[
2= 2;
    m_pIndices[
3= 0;
    m_pIndices[
4= 2;
    m_pIndices[
5= 3;

    
// 初始化顶点缓冲
    const float t = 1.0f;
    
const float o = 0.0f;
    m_pVertex 
= new VertexSkybox[24];

    
// 前
    m_pVertex[0]  = VertexSkybox(-1,-1,-10,01, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
1]  = VertexSkybox( 1,-1,-10,01, D3DXCOLOR_WHITE, o, t);
    m_pVertex[
2]  = VertexSkybox( 11,-10,01, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
3]  = VertexSkybox(-11,-10,01, D3DXCOLOR_WHITE, t, o);

    
// 后
    m_pVertex[4]  = VertexSkybox( 1,-110,0,-1, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
5]  = VertexSkybox(-1,-110,0,-1, D3DXCOLOR_WHITE, o, t);
    m_pVertex[
6]  = VertexSkybox(-1110,0,-1, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
7]  = VertexSkybox( 1110,0,-1, D3DXCOLOR_WHITE, t, o);

    
// 左
    m_pVertex[8]  = VertexSkybox( 1,-1,-1-1,0,0, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
9]  = VertexSkybox( 1,-11-1,0,0, D3DXCOLOR_WHITE, o, t);
    m_pVertex[
10= VertexSkybox( 111-1,0,0, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
11= VertexSkybox( 11,-1-1,0,0, D3DXCOLOR_WHITE, t, o);

    
// 右
    m_pVertex[12= VertexSkybox(-1,-11,  1,0,0, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
13= VertexSkybox(-1,-1,-1,  1,0,0, D3DXCOLOR_WHITE, o, t);
    m_pVertex[
14= VertexSkybox(-11,-1,  1,0,0, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
15= VertexSkybox(-111,  1,0,0, D3DXCOLOR_WHITE, t, o);

    
// 上
    m_pVertex[16= VertexSkybox( 11,-10,-1,0, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
17= VertexSkybox( 1110,-1,0, D3DXCOLOR_WHITE, o, t);
    m_pVertex[
18= VertexSkybox(-1110,-1,0, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
19= VertexSkybox(-11,-10,-1,0, D3DXCOLOR_WHITE, t, o);

    
// 下
    m_pVertex[20= VertexSkybox( 1,-1101,0, D3DXCOLOR_WHITE, o, o);
    m_pVertex[
21= VertexSkybox( 1,-1,-101,0, D3DXCOLOR_WHITE, t, o);
    m_pVertex[
22= VertexSkybox(-1,-1,-101,0, D3DXCOLOR_WHITE, t, t);
    m_pVertex[
23= VertexSkybox(-1,-1101,0, D3DXCOLOR_WHITE, o, t);

}

void CSkybox::Release()
{
    
// 释放天空盒纹理
    for (int ti=0;ti<6;ti++)
    {
        ReleaseCOM(m_ppTextureArray[ti]);
    }
    delete[] m_ppTextureArray;
    
// 释放索引缓冲
    delete[] m_pIndices;
    
// 释放顶点缓冲
    delete[] m_pVertex;
}

void CSkybox::Draw()
{
    
// 绘制之前,根据摄影机位置,更新世界矩阵
    D3DXVECTOR3 pos = g_pD3DCamera->GetCameraPos();
    D3DXMatrixTranslation(
&m_WorldTransMatrix,pos.x,pos.y,pos.z);
    g_pD3DDevice
->GetTransform(D3DTS_WORLD, &m_OriWorldTransMatrix);
    g_pD3DDevice
->SetTransform(D3DTS_WORLD, &m_WorldTransMatrix);

    
// 禁用深度缓冲
    g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
    
// 更改采样方式,平滑纹理间过度
    g_pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
    g_pD3DDevice
->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

    
// 分6次绘制天空盒6个面
    for (int i=0;i<6;i++)
    {
        
// 应用纹理
        g_pD3DDevice->SetTexture(0, m_ppTextureArray[i]->GetTexture());
        
// 应用顶点格式
        g_pD3DDevice->SetFVF(VertexSkybox::FVF);
        
// 绘制一个面的4个顶点
        g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 042&m_pIndices[0],
            D3DFMT_INDEX16, 
&m_pVertex[i * 4], sizeof(VertexSkybox));
    }

    
// 还原默认采样方式
    g_pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
    g_pD3DDevice
->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
    
// 重用深度缓冲
    g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
    
    
// 还原世界矩阵
    g_pD3DDevice->SetTransform(D3DTS_WORLD, &m_OriWorldTransMatrix);

}

 

原理很简单,我们首先绘制一个盒子,并在其6个表面分别贴上对应的天空纹理,而后绘制即可~

需要注意的地方有三点:

1> 虚拟出的天空效果要给人一种“无限远”的视觉体验,因此,我们要保证摄影机始终处在天空盒的中央位置,即天空盒随摄影机的移动而移动;

2> 我们不可能真的做一个无限大的天空盒出来,为了使有限大的天空盒不会遮挡其他物体,绘制时要关闭深度缓冲,且先于其他物体绘制;

3> 绘制时,我们需要临时更改纹理采样状态,以平滑面与面纹理间的过渡~

另外,关于天空盒的绘制,其实还有部分优化的余地~

Irrlicht引擎的做法是,当摄影机呈直角角度,即当摄影机完全正对其中一个面的时候,采用类似CSpriteBatch的方法,仅绘制这个面即可。这个做法是有必要的,不过出现的概率并不大。

我们延续Irrlicht的思路,还可以找到另一种优化的方法,即:无论任何时刻,正常状态下,我们只能看到天空盒6个面其中的3个~

不过,相应的条件判断都要由CPU来完成,各类方法的优劣还未详细测试。大家感兴趣的话不妨一试~

然后是主体代码:

 

D3DGame.cpp
/*-------------------------------------

代码清单:D3DGame.cpp
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

#include 
"StdAfx.h"
#include 
"D3DGame.h"
#include 
"D3DCamera.h"
#include 
"D3DEffect.h"
#include 
"CoordCross.h"
#include 
"SimpleXMesh.h"
#include 
"Texture2D.h"
#include 
"D3DSprite.h"
#include 
"Skybox.h"
#include 
"SpriteBatch.h"
#include 
<stdio.h>

//---通用全局变量

HINSTANCE  g_hInst;
HWND       g_hWnd;
D3DXMATRIX g_matProjection;

//---D3D全局变量

IDirect3D9       
*g_pD3D           = NULL;
IDirect3DDevice9 
*g_pD3DDevice     = NULL;
CMouseInput      
*g_pMouseInput    = NULL;
CKeyboardInput   
*g_pKeyboardInput = NULL;
CD3DCamera       
*g_pD3DCamera     = NULL;
CCoordCross      
*g_pCoordCross    = NULL;
CSimpleXMesh     
*g_pSimpleXMesh   = NULL;
CD3DEffect       
*g_pD3DEffect     = NULL;
CD3DSprite       
*g_pD3DSprite     = NULL;
CTexture2D       
*g_pTexture2D     = NULL;
CSpriteBatch     
*g_SpriteBatch    = NULL;
CTexture2D       
*g_pTexture2D2    = NULL;
CD3DEffect       
*g_pD3DEffect2    = NULL;
CSkybox          
*g_pSkybox        = NULL;


//---HLSL全局变量句柄

D3DXHANDLE   g_CurrentTechHandle 
= NULL;
D3DXHANDLE   g_matWorldViewProj  
= NULL;  
D3DXHANDLE   g_matWorld          
= NULL;
D3DXHANDLE   g_vecEye            
= NULL;
D3DXHANDLE   g_vecLightDir       
= NULL;
D3DXHANDLE   g_vDiffuseColor     
= NULL;
D3DXHANDLE   g_vSpecularColor    
= NULL;
D3DXHANDLE   g_vAmbient          
= NULL;

D3DXHANDLE   g_CurrentTechHandle2 
= NULL;
D3DXHANDLE   g_Scale              
= NULL;

// HLSL特效参数设置
void GetParameters();
void SetParameters();


void Initialize(HINSTANCE hInst, HWND hWnd)
{
    g_hInst 
= hInst;
    g_hWnd  
= hWnd;
    InitD3D(
&g_pD3D, &g_pD3DDevice, g_matProjection, hWnd);
    g_pMouseInput 
= new CMouseInput;
    g_pMouseInput
->Initialize(hInst,hWnd);
    g_pKeyboardInput 
= new CKeyboardInput;
    g_pKeyboardInput
->Initialize(hInst,hWnd);
    g_pD3DCamera 
= new CD3DCamera;
}

void LoadContent()
{
    g_pCoordCross 
= new CCoordCross;

    g_pD3DCamera
->SetCameraPos(D3DXVECTOR3(0.5f,0.5f,-5.0f));

    g_pSimpleXMesh 
= new CSimpleXMesh;
    g_pSimpleXMesh
->LoadXMesh("teapot.X");

    g_pD3DEffect 
= new CD3DEffect;
    g_pD3DEffect2 
= new CD3DEffect;
    g_pD3DEffect
->LoadEffect("Light.fx");
    g_pD3DEffect2
->LoadEffect("Thunder.fx");

    GetParameters();

    g_pD3DSprite 
= new CD3DSprite(g_pD3DDevice);
    g_SpriteBatch 
= new CSpriteBatch(g_pD3DDevice);

    g_pTexture2D 
= new CTexture2D;
    g_pTexture2D
->LoadTexture("img.jpg");
    g_pTexture2D2 
= new CTexture2D;
    g_pTexture2D2
->LoadTexture("img2.jpg");

    g_pSkybox 
= new CSkybox;
    g_pSkybox
->Create("Skybox_0.JPG","Skybox_1.JPG","Skybox_2.JPG"
        ,
"Skybox_3.JPG","Skybox_4.JPG","Skybox_5.JPG");
}

void Update()
{
    g_pMouseInput
->GetState();
    g_pKeyboardInput
->GetState();
    g_pD3DCamera
->Update();
}

void Draw()
{
    
// 参数设定
    SetParameters();
    g_pD3DDevice
->SetTransform(D3DTS_VIEW,&g_pD3DCamera->GetViewMatrix());

    POINT pos;
    pos.x
=0;
    pos.y
=0;

    POINT pos2;
    pos2.x 
= 440;
    pos2.y 
= 260;

    g_pD3DDevice
->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f0);
    
if(SUCCEEDED(g_pD3DDevice->BeginScene())) 
    {
        
// 天空处在无限远处,因此必须最先绘制天空盒
        g_pSkybox->Draw();

        
//g_pCoordCross->Draw();

        
//// 开始绘制并应用特效
        //g_SpriteBatch->Begin(g_pD3DEffect2);

        
//// CSpriteBatch绘制
        //g_SpriteBatch->Draw(g_pTexture2D2,pos);
        
//g_SpriteBatch->Draw(g_pTexture2D,pos2);

        
//// 结束绘制并终止特效
        //g_SpriteBatch->End();

        UINT numPasses;
        
// 开启特效
        g_pD3DEffect->BeginEffect(numPasses);
        
for(UINT i=0;i<numPasses;i++)
        {
            
// 开启路径
            g_pD3DEffect->GetEffect()->BeginPass(i);
            
for(DWORD j=0;j<g_pSimpleXMesh->GetMaterialNum();j++)
            {
                g_pSimpleXMesh
->DrawXMeshSubset(j);
            }
            
// 路径结束
            g_pD3DEffect->GetEffect()->EndPass();
        }
        
// 特效结束
        g_pD3DEffect->EndEffect();
    
        g_pD3DDevice
->EndScene();
    }
    g_pD3DDevice
->Present(NULL, NULL, NULL, NULL);
}

void UnloadContent()
{
    ReleaseCOM(g_pSkybox);
    ReleaseCOM(g_pTexture2D2);
    ReleaseCOM(g_pTexture2D);
    ReleaseCOM(g_SpriteBatch);
    ReleaseCOM(g_pD3DSprite);
    ReleaseCOM(g_pD3DEffect2);
    ReleaseCOM(g_pD3DEffect);
    ReleaseCOM(g_pSimpleXMesh);
    ReleaseCOM(g_pCoordCross);
}

void Dispose()
{
    ReleaseCOM(g_pD3DCamera);
    ReleaseCOM(g_pKeyboardInput);
    ReleaseCOM(g_pMouseInput);
    ReleaseCOM(g_pD3DDevice);
    ReleaseCOM(g_pD3D);
}

void GetParameters()
{
    
// 获得HLSL中各个全局变量句柄
    g_CurrentTechHandle = g_pD3DEffect -> GetEffect() -> GetTechniqueByName("SpecularLight");
    g_matWorldViewProj  
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"matWorldViewProj");
    g_matWorld          
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"matWorld");
    g_vecEye            
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"vecEye");
    g_vecLightDir       
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"vecLightDir");
    g_vDiffuseColor     
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"vDiffuseColor");
    g_vSpecularColor    
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"vSpecularColor");
    g_vAmbient          
= g_pD3DEffect -> GetEffect() -> GetParameterByName(0"vAmbient");

    g_CurrentTechHandle2 
= g_pD3DEffect2 -> GetEffect() -> GetTechniqueByName("Technique1");
    g_Scale              
= g_pD3DEffect2 -> GetEffect() -> GetParameterByName(0"Scale");
}

void SetParameters()
{
    
// 设定当前技术
    g_pD3DEffect -> GetEffect() -> SetTechnique(g_CurrentTechHandle);
    
// 设定HLSL中的各个参数
    D3DXMATRIX worldMatrix;
    D3DXMatrixTranslation(
&worldMatrix,0.0f,0.0f,0.0f);
    g_pD3DEffect 
-> GetEffect() -> SetMatrix(g_matWorldViewProj,&(worldMatrix*g_pD3DCamera->GetViewMatrix()*g_matProjection));
    g_pD3DEffect 
-> GetEffect() -> SetMatrix(g_matWorld,&worldMatrix);
    D3DXVECTOR3 cameraPos 
= g_pD3DCamera->GetCameraPos();
    D3DXVECTOR4 vecEye 
= D3DXVECTOR4(cameraPos.x,cameraPos.y,cameraPos.z,0.0f);
    g_pD3DEffect 
-> GetEffect() -> SetVector(g_vecEye,&vecEye);
    D3DXVECTOR4 vLightDirection 
= D3DXVECTOR4(0.0f0.0f-1.0f1.0f);
    g_pD3DEffect 
-> GetEffect() -> SetVector(g_vecLightDir,&vLightDirection);
    D3DXVECTOR4 vColorDiffuse 
= D3DXVECTOR4(0.8f0.0f0.0f1.0f);
    D3DXVECTOR4 vColorSpecular 
= D3DXVECTOR4(1.0f1.0f1.0f1.0f);
    D3DXVECTOR4 vColorAmbient 
= D3DXVECTOR4(0.1f0.1f0.1f1.0f);
    g_pD3DEffect 
-> GetEffect() -> SetVector(g_vDiffuseColor,&vColorDiffuse);
    g_pD3DEffect 
-> GetEffect() -> SetVector(g_vSpecularColor,&vColorSpecular);
    g_pD3DEffect 
-> GetEffect() -> SetVector(g_vAmbient,&vColorAmbient);

    g_pD3DEffect2 
-> GetEffect() -> SetTechnique(g_CurrentTechHandle2);
    g_pD3DEffect2 
-> GetEffect() -> SetFloat(g_Scale,0.8f);
}

 

最后是实际的效果图:

 

大家可以尝试变动一下HLSL中光源的方向,使其朝向天空纹理中的太阳,会有更加真实的视觉体验哦^ ^