代码改变世界

Direct3D轮回:游戏特效之风动、雾化

2011-08-14 09:56  独孤残云  阅读(3888)  评论(29编辑  收藏  举报

风动和雾化都是相对而言比较简单的游戏特效,实现起来甚至比我们前一节提到的晴天光晕还要简单~

大家还记得上上节中我们为陆地种植的那片植被吗?现在我们在场景中加入风的效果,让草儿动起来 ^ ^

首先,我们来实现一个风动效果的Shader:

Wind.fx
/*-------------------------------------

代码清单:Wind.fx
来自:
http://www.cnblogs.com/kenkao/

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

uniform matrix  wvp;                              
// 世界·摄影·投影变换
uniform texture tex;                              // 活动纹理
uniform float   time;                             // 全局时间
        float3  winddir = {1.0f-1.0f-1.0f};   // 默认风向

sampler TexSampler 
= sampler_state
{
    Texture   
= <tex>;
    MinFilter 
= LINEAR;
    MagFilter 
= LINEAR;
    MipFilter 
= LINEAR;
};

struct VS_INPUT
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float4 color    : COLOR;
};

struct PS_INPUT
{
    float4 color    : COLOR;
    float2 texcoord : TEXCOORD;
};

struct PS_OUTPUT
{
    float4 color : COLOR;
};

VS_OUTPUT WindVS(VS_INPUT input)
{
    VS_OUTPUT output 
= (VS_OUTPUT)0
    output.position 
= mul(input.position,wvp);
    
if(input.texcoord.y < 0.1)
        output.position 
+= cos(time)/2;        // 整个Shader唯一起作用的代码——顶点位置随全局时间呈现余弦波动,借以虚拟“风动”效果 ^_^
    output.texcoord = input.texcoord;
    output.color 
= 1.0f;
    
return output;
}

PS_OUTPUT WindPS(PS_INPUT input)
{
    PS_OUTPUT output 
= (PS_OUTPUT)0
    output.color 
= 1.2 * input.color * tex2D(TexSampler, input.texcoord);
    
return output;
}

technique WindTech
{
    pass P0
    {
        VertexShader 
= compile vs_2_0 WindVS();
        PixelShader  
= compile ps_2_0 WindPS();
        AlphaRef 
= 150;
        AlphaTestEnable 
= TRUE;
        AlphaFunc 
= GREATEREQUAL;
        CullMode 
= NONE;
    } 
}

原理非常简单,只有一句代码起到了实质性的作用,它使得所有效果应用顶点随时间呈现余弦状波动效果。

而其他所有的代码其实就相当于一个没有任何特效的固定管线~

下面我们应用它来改造原有的CPlantCollect类,其实也就是在其初始化时加载风动特效,而后绘制所有植被节点时应用之即可~

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

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

-------------------------------------
*/
bool CPlantCollect::LoadContent()
{
    
// 创建纹理
    HRESULT hr = D3DXCreateTextureFromFile(g_pD3DDevice,"grass.png",&m_pGrassTexture);
    
if(FAILED(hr))
        
return false;

    
// 加载风动效果
    m_pWindEffect = new CD3DEffect;
    
if(!m_pWindEffect->LoadEffect("Wind.fx"))
        
return false;

    m_hWindWVP  
= m_pWindEffect->GetEffect()->GetParameterByName(0,"wvp");
    m_hWindTime 
= m_pWindEffect->GetEffect()->GetParameterByName(0,"time");
    m_pWindEffect
->GetEffect()->SetTechnique("WindTech");
    m_pWindEffect
->GetEffect()->SetTexture("tex",m_pGrassTexture);

    
return true;
}
void CPlantCollect::Draw(float gameTick)
{
    
// 禁用背面剔除
    g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

    
// 启用Alpha通道
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    g_pD3DDevice
->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    g_pD3DDevice
->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

    
// 开启Alpha检测
    g_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
    g_pD3DDevice
->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
    g_pD3DDevice
->SetRenderState(D3DRS_ALPHAREF, 150);

    
// 设置纹理、顶点缓冲区、索引缓冲区及顶点格式
    if(!m_pWindEffect)
        g_pD3DDevice
->SetTexture(0,m_pGrassTexture);
    g_pD3DDevice
->SetStreamSource(0, m_pVB, 0sizeof(VertexPositionTex));
    g_pD3DDevice
->SetIndices(m_pIB);
    g_pD3DDevice
->SetFVF(VertexPositionTex::FVF);

    
// 如果风动特效存在,则设置相应参数并开启特效
    if(m_pWindEffect)
    {
        D3DXMATRIX matWorld;
        g_pD3DDevice
->GetTransform(D3DTS_WORLD,&matWorld);
        D3DXMATRIX matView;
        g_pD3DDevice
->GetTransform(D3DTS_VIEW,&matView);
        D3DXMATRIX matProj;
        g_pD3DDevice
->GetTransform(D3DTS_PROJECTION,&matProj);
        m_pWindEffect
->GetEffect()->SetMatrix(m_hWindWVP,&(matWorld*matView*matProj));
        m_pWindEffect
->GetEffect()->SetFloat(m_hWindTime,gameTick/500.0f);

        UINT NumPasses;
        m_pWindEffect
->BeginEffect(NumPasses);
        m_pWindEffect
->GetEffect()->BeginPass(0);
    }

    
// 绘制顶点
    g_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 00, m_vertexNum, 0, m_indexNum/3);

    
if(m_pWindEffect)
    {
        m_pWindEffect
->GetEffect()->EndPass();
        m_pWindEffect
->EndEffect();
    }

    
// 关闭Alpha检测
    g_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

    
// 禁用Alpha通道
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);

    
// 重用背面剔除
    g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}

当然,不要忘记当对象Release时调用ReleaseCOM(m_pWindEffect)释放掉风动效果就可以了 ^ ^

风动效果通过截图表现不出来,建议大家在前几节的基础上自己动手尝试一下,感觉会非常不错~

 

之后我们来看D3D中雾化效果的实现。

雾化特效是D3D设备自身携带的一种效果,我们只需打开几个状态开关并设置一下相应的参数即可~

我们新建一个CD3DFog类,不过因为雾化特性来自于D3D本身,因此直接写成全局函数就可以了 ^ ^

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

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

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

#include 
"D3DInit.h"

// 雾化类型枚举
typedef enum _D3DFOGTYPE
{
    D3DFOGTYPE_NONE 
= 0,      // 无雾化效果
    D3DFOGTYPE_VERTEX = 140,  // 顶点雾化
    D3DFOGTYPE_PIXEL = 35     // 像素雾化
}D3DFOGTYPE;

// 重置雾化特效
void ResetFog( D3DFOGTYPE FogType,   // 雾化类型
               D3DFOGMODE FogMode,   // 雾化模式
               D3DCOLOR FogColor,    // 雾化颜色
               float Param0,         // 参数零
               float Param1 = 0.0f   // 参数一
               );

void BeginFog(IDirect3DDevice9 *pD3DDevice);  // 开启雾化
void EndFog(IDirect3DDevice9 *pD3DDevice);    // 关闭雾化

 

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

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

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

#include 
"StdAfx.h"
#include 
"D3DFog.h"

D3DFOGTYPE  g_FogType  
= D3DFOGTYPE_VERTEX;  // 默认雾化类型为顶点雾化
D3DFOGMODE  g_FogMode  = D3DFOG_LINEAR;      // 默认雾化模式为线性模式
D3DCOLOR    g_FogColor = D3DXCOLOR_WHITE;    // 默认雾化颜色为白色
float       g_FogStart = 0.0f;               // 默认线性雾化起始深度为0.0f
float       g_FogEnd   = 100.0f;             // 默认线性雾化终止深度为100.0f
float       g_FogDensity = 0.0f;             // 默认指数雾化参数为0.0f

void ResetFog(D3DFOGTYPE FogType, D3DFOGMODE FogMode, D3DCOLOR FogColor, float Param0, float Param1)
{
    g_FogType 
= FogType;
    g_FogMode 
= FogMode;
    g_FogColor 
= FogColor;
    
// 如果雾化类型为线性雾化,则参数零、参数一分别代表线性雾化的起始与终止深度
    if(g_FogMode == D3DFOG_LINEAR)
    {
        g_FogStart 
= Param0;
        g_FogEnd   
= Param1;
    }
    
// 如果雾化类型为指数雾化,则参数零代表指数参数,参数一无效
    else
        g_FogDensity 
= Param0;
}

void BeginFog(IDirect3DDevice9 *pD3DDevice)
{
    
if(pD3DDevice && g_FogType != D3DFOGTYPE_NONE && g_FogMode != D3DFOG_NONE)
    {
        
// 开启雾化
        pD3DDevice->SetRenderState(D3DRS_FOGENABLE, TRUE);
        
// 设置雾化颜色
        pD3DDevice->SetRenderState(D3DRS_FOGCOLOR, g_FogColor);
        
// 设置雾化类型
        pD3DDevice->SetRenderState((D3DRENDERSTATETYPE)g_FogType, g_FogMode);
        
// 设置雾化参数
        if (g_FogMode == D3DFOG_LINEAR)
        {
            pD3DDevice
->SetRenderState(D3DRS_FOGSTART, *(DWORD*)&g_FogStart);
            pD3DDevice
->SetRenderState(D3DRS_FOGEND, *(DWORD*)&g_FogEnd);
        }
        
else
            pD3DDevice
->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&g_FogDensity);
    }
}

void EndFog(IDirect3DDevice9 *pD3DDevice)
{
    
if(pD3DDevice && g_FogMode != D3DFOG_NONE)
    {
        
// 结束雾化
        pD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
    }
}

这里值得一提的是代码中提到的雾化类型及雾化模式。

雾化类型主要分顶点级和像素级。顶点级雾化仅针对于各顶点,比较省性能,但不够精确;像素级雾化针对所有像素,效果与顶点级雾化相反。

雾化模式则主要分为线性雾化、一次指数雾化及二次指数雾化,用以控制雾化效果由近及远、有稀薄到厚重的变换过程。以下是三种雾化模式相应的计算公式:

 

如下为主体代码:

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 
"BaseTerrain.h"
#include 
"Water.h"
#include 
"PlantCollect.h"
#include 
"LensFlare.h"
#include 
"D3DFog.h"
#include 
<stdio.h>
#include 
<time.h>

//---通用全局变量

HINSTANCE  g_hInst;
HWND       g_hWnd;
D3DXMATRIX g_matWorld;
D3DXMATRIX g_matProjection;
D3DPRESENT_PARAMETERS g_D3DPP;

//---D3D全局变量

IDirect3D9       
*g_pD3D           = NULL;
IDirect3DDevice9 
*g_pD3DDevice     = NULL;
CMouseInput      
*g_pMouseInput    = NULL;
CKeyboardInput   
*g_pKeyboardInput = NULL;
CD3DCamera       
*g_pD3DCamera     = NULL;
CSpriteBatch     
*g_pSpriteBatch   = NULL;
CSkybox          
*g_pSkybox        = NULL;
CBaseTerrain     
*g_pBaseTerrain   = NULL;
CWater           
*g_pWater         = NULL;
CPlantCollect    
*g_pPlant         = NULL;
CSimpleXMesh     
*g_pMesh          = NULL;
CLensFlare       
*g_pFlare         = NULL;

// 场景绘制
void DrawScene(bool isReflect,bool isRefract);

void Initialize(HINSTANCE hInst, HWND hWnd)
{
    g_hInst 
= hInst;
    g_hWnd  
= hWnd;
    InitD3D(
&g_pD3D, &g_pD3DDevice, g_D3DPP, 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;
    g_pSpriteBatch 
= new CSpriteBatch(g_pD3DDevice);
    srand(time(
0));
}

CTexture2D
* debugTexture = NULL;

void LoadContent()
{
    g_pD3DCamera
->SetCameraPos(D3DXVECTOR3(600.0f,0.0f,600.0f));

    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");

    g_pBaseTerrain 
= new CBaseTerrain;
    g_pBaseTerrain
->Create(128,128,10,"HeightData_128x128.raw","Grass.dds");

    g_pWater 
= new CWater;
    g_pWater
->Create(1280,1280,0.0f,0.0f,40.0f);

    g_pPlant 
= new CPlantCollect;
    g_pPlant
->Create(60,90,15);

    g_pMesh 
= new CSimpleXMesh;
    g_pMesh
->LoadXMesh("WindMill.x");

    g_pFlare 
= new CLensFlare;
    g_pFlare
->Create(D3DXVECTOR3(-1600,700,600),D3DXVECTOR2(800,600));

    ResetFog(D3DFOGTYPE_PIXEL,D3DFOG_EXP2,D3DXCOLOR_WHITE,
0.001f);
}

void Update(float gameTick)
{
    g_pMouseInput
->GetState();
    g_pKeyboardInput
->GetState();

    
// 更新摄影机高度
    D3DXVECTOR3 CameraPos = g_pD3DCamera->GetCameraPos();
    
float roleHeight = 25.0f;
    
float Ty = g_pBaseTerrain->GetExactHeightAt(CameraPos.x,CameraPos.z) + roleHeight;
    g_pD3DCamera
->SetCameraPos(D3DXVECTOR3(
        CameraPos.x,
        Ty,
        CameraPos.z));

    g_pD3DCamera
->Update();
}

void Draw(float gameTick)
{
    g_pD3DDevice
->GetTransform(D3DTS_WORLD, &g_matWorld);
    g_pD3DDevice
->SetTransform(D3DTS_VIEW,  &g_pD3DCamera->GetViewMatrix());
    
if(SUCCEEDED(g_pD3DDevice->BeginScene())) 
    {
        BeginFog(g_pD3DDevice);

        g_pWater
->BeginReflect();
        DrawScene(
true,false);
        g_pWater
->EndReflect();

        g_pWater
->BeginRefract();
        DrawScene(
false,true);
        g_pWater
->EndRefract();

        g_pD3DDevice
->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f0);
        DrawScene(
false,false);
        g_pWater
->Draw(gameTick);
        g_pPlant
->Draw(gameTick);
        g_pFlare
->Draw();

        EndFog(g_pD3DDevice);

        g_pD3DDevice
->EndScene();
    }
    g_pD3DDevice
->Present(NULL, NULL, NULL, NULL);
}

void DrawScene(bool isReflect,bool isRefract)
{
    g_pSkybox
->Draw(isReflect,isRefract);
    g_pBaseTerrain
->Draw();

    D3DXMATRIX scalTrans;
    D3DXMatrixScaling(
&scalTrans,5.0f,5.0f,5.0f);
    D3DXMATRIX movTrans;
    D3DXMatrixTranslation(
&movTrans,366,g_pBaseTerrain->GetExactHeightAt(366,190)-5.0f,190);
    g_pMesh
->DrawXMesh(scalTrans * movTrans);
}

void UnloadContent()
{
    ReleaseCOM(g_pFlare);
    ReleaseCOM(g_pPlant);
    ReleaseCOM(g_pWater);
    ReleaseCOM(g_pBaseTerrain);
    ReleaseCOM(g_pSkybox);
}

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

最后是效果图:

配合合适的纹理,近处的景色与远处的天空完全浑然一体~

想到一句图形学领域的至理名言:Everything can be faked~ 不正是这样吗?呵呵~

以上,谢谢~