代码改变世界

Direct3D轮回:游戏特效之晴天光晕

2011-08-07 11:15  独孤残云  阅读(4468)  评论(7编辑  收藏  举报

基本的场景元素具备之后,本节我们来为已构建的场景新增一些特殊效果~

玩过摄影的朋友应该都知道,当我们把镜头对准某一发光体的时候,会有光晕现象产生,这种光晕现象也称作“镜头眩光”。

在晴天的场景中实现这样一个光晕特效,往往会有意想不到的效果~

晴天光晕特效实现的原理其实挺简单,它可以由一个主光晕加一系列镜头七彩光环组成。主光晕以光源为中心,镜头光环则散列在由光源及镜头中心决定的直线上~

其效果大致如图所示:

下面,我们来着手实现这样一个效果~

首先还是要准备素材:

  glow.png

               flare1.png

                     flare2.png

    flare3.png

 

其中,glow为主光晕素材,透明底色、半透明主色,由于我的blog背景的关系,看不太明显,大家可以右击另存为,放大看一下效果。一系列flare则为黑底、大小各异的光环~

由于最终是以2D精灵的形式绘制各个光晕素材,因此需要借助于之前构建的CSpriteBatch。

下面,我们为之前构建的CSpriteBatch新增一些功能:

1>定义blend模式

#define SPRITEBLENDMODE_NONE            0
#define SPRITEBLENDMODE_ALPHABLEND 1
#define SPRITEBLENDMODE_ADDITIVE      2

SPRITEBLENDMODE_NONE代表没有blend状态;

SPRITEBLENDMODE_ALPHABLEND模式开启Alpha通道,支持半透明绘制,这里用于主光晕的绘制;

SPRITEBLENDMODE_ADDITIVE模式则采用叠加的方式,将精灵的各个像素颜色直接与背景各像素颜色叠加,镜头光环采用这种方式绘制可以达到非常好的效果。

2>ALPHABLEND模式的实现

m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

开启Alpha通道的常规程序,没什么可说的~

3>ADDITIVE模式的实现

m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

该模式下,我们直接将D3DRS_DESTBLEND渲染状态置为D3DBLEND_ONE,代表背景不丢失,前景直接与其叠加即可~

各模式仅仅改变设备即时的渲染状态,因此,我们只需在原有基础上完善CSpriteBatch的Begin与End函数即可:

void CSpriteBatch::Begin(DWORD Flags, CD3DEffect* pD3DEffect)

    m_Flags 
= Flags;
    
// 获得原始状态
    m_pDevice->GetTransform(D3DTS_VIEW,       &m_OriViewMatrix);
    m_pDevice
->GetTransform(D3DTS_PROJECTION, &m_OriProjMatrix);
    
// 设置单位摄影矩阵及正交投影矩阵
    m_pDevice->SetTransform(D3DTS_VIEW,       &m_ViewMatrix);
    m_pDevice
->SetTransform(D3DTS_PROJECTION, &m_ProjMatrix);
    
// 如果存在特效则应用之
    if(pD3DEffect)
    {
        
this->m_pD3DEffect=pD3DEffect;
        m_pD3DEffect
->BeginEffect(m_NumPasses);
    }
    
// 如果是ALPHABLEND模式
    if(Flags == SPRITEBLENDMODE_ALPHABLEND)
    {
        m_pDevice
->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
        m_pDevice
->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
        m_pDevice
->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    }
    
// 如果是ADDITIVE模式
    else if(Flags == SPRITEBLENDMODE_ADDITIVE)
    {
        m_pDevice
->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
        m_pDevice
->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
        m_pDevice
->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    }
}
void CSpriteBatch::End()
{
    
// 结束绘制之前Flush一次全部节点
    Flush();
    
// 禁用Alpha通道
    if(m_Flags!=SPRITEBLENDMODE_NONE)
        m_pDevice
->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
    
// 如果存在特效则结束之
    if(m_pD3DEffect)
        m_pD3DEffect
->EndEffect();
    
// 还原原始摄影矩阵及投影矩阵
    m_pDevice->SetTransform(D3DTS_VIEW,       &m_OriViewMatrix);
    m_pDevice
->SetTransform(D3DTS_PROJECTION, &m_OriProjMatrix);
}

4>新增重载Draw函数

void CSpriteBatch::Draw(CTexture2D* pTexture,const D3DXVECTOR2& Pos,const D3DXVECTOR2& Origin,const float& Scale,D3DCOLOR Color,const float& layerDepth)
{
 D3DXVECTOR2 offset = Origin * Scale;
 RECT rect;
 rect.left   = Pos.x - offset.x;
 rect.top    = Pos.y - offset.y;
 rect.right  = rect.left + pTexture->GetWidth()  * Scale;
 rect.bottom = rect.top  + pTexture->GetHeight() * Scale;
 Draw(pTexture,rect,Color,layerDepth);
}

结合自身的需要,我们新增这样一个重载的Draw函数。两个陌生的参数Origin与Scale分别代表精灵的原始中心(默认为左上角)与缩放比率,该函数的构建思想参照了Xna4.0下SpriteBatch的实现。

准备工作做完了,下面我们来着手完成这个CLensFlare对象的构建:

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

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

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

#include 
"SpriteBatch.h"

#pragma once

// 光晕节点
struct FlareNode{
    
float       _Position;  // 位置(镜头中心为原点)
    
float       _Scale;     // 缩放比率
    D3DXCOLOR   _Color;     // 色相
    CTexture2D
* _pTexture;  // 纹理指针
    FlareNode(){}
    FlareNode(
float Position, float Scale, D3DXCOLOR Color, CTexture2D* pTexture){
        _Position 
= Position;  _Scale = Scale;  _Color = Color;  _pTexture = pTexture;
    };
};

class CLensFlare
{
public:
    CLensFlare(
void);
    
~CLensFlare(void);
public:
    
bool Create(                            // 创建光晕
        D3DXVECTOR3 lightPos,               // 光源位置(绝对)
        D3DXVECTOR2 viewPortSize);          // 视口尺寸
    void Draw();                            // 绘制光晕
    void Release();                         // 释放资源
private:
    
void CalRelativeLightPos(int &iCoordX, 
        
int &iCoordY, int &iCoordW);        // 计算光源的相对位置
    bool LoadContent();                     // 加载纹理
    bool IsVisible();                       // 光源是否可见
private:
    CTexture2D
* m_pFlareTex1;               // 光晕纹理一
    CTexture2D* m_pFlareTex2;               // 光晕纹理二
    CTexture2D* m_pFlareTex3;               // 光晕纹理三
    CTexture2D* m_pGlowTex;                 // 主光晕纹理
private:
    D3DXVECTOR3 m_lightPosition;            
// 光源位置(绝对)
    D3DXVECTOR2 m_relativeLightPos;         // 光源位置(相对)
    D3DXVECTOR2 m_viewPortCenter;           // 视口尺寸
    FlareNode   m_FlareNodes[10];           // 光晕节点列表
    float       m_occlusionAlpha;           // 光源Alpha系数
};
LensFlare.cpp
/*-------------------------------------

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

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

#include 
"StdAfx.h"
#include 
"LensFlare.h"
#include 
"D3DGame.h"
#include 
"SpriteBatch.h"
#include 
"D3DCamera.h"

extern IDirect3DDevice9 *g_pD3DDevice;
extern CSpriteBatch     *g_pSpriteBatch;
extern CD3DCamera       *g_pD3DCamera;

extern D3DXMATRIX       g_matWorld;
extern D3DXMATRIX       g_matProjection;

CLensFlare::CLensFlare(
void) : m_pFlareTex1(NULL),
                               m_pFlareTex2(NULL),
                               m_pFlareTex3(NULL),
                               m_pGlowTex(NULL),
                               m_lightPosition(D3DXVECTOR3_ZERO),
                               m_relativeLightPos(D3DXVECTOR2_ZERO),
                               m_viewPortCenter(D3DXVECTOR2(
400,300)),
                               m_occlusionAlpha(
0.0f)
{

}

CLensFlare::
~CLensFlare(void)
{
}

void CLensFlare::Release()
{
    ReleaseCOM(m_pFlareTex1);
    ReleaseCOM(m_pFlareTex2);
    ReleaseCOM(m_pFlareTex3);
    ReleaseCOM(m_pGlowTex);
}

bool CLensFlare::Create(D3DXVECTOR3 lightPos, D3DXVECTOR2 viewPortSize)
{
    
// 获得光源位置与视口中心位置
    m_lightPosition  = lightPos;
    m_viewPortCenter 
= D3DXVECTOR2(viewPortSize.x/2, viewPortSize.y/2);

    
if(!LoadContent())
    {
        Release();
        
return false;
    }
    
return true;
}

bool CLensFlare::LoadContent()
{
    
// 加载纹理
    m_pFlareTex1 = new CTexture2D;
    m_pFlareTex2 
= new CTexture2D;
    m_pFlareTex3 
= new CTexture2D;
    m_pGlowTex   
= new CTexture2D;

    
if!m_pFlareTex1->LoadTexture("flare1.png")||
        
!m_pFlareTex2->LoadTexture("flare2.png")||
        
!m_pFlareTex3->LoadTexture("flare3.png")||
        
!m_pGlowTex  ->LoadTexture("glow.png"  ))
        
return false;

    
// 初始化一系列光晕节点,并赋予不同的位置、缩放比率与颜色
    int i = 0;
    m_FlareNodes[i
++= FlareNode(-0.5f0.7f, D3DCOLOR_XRGB( 50,  25,  50), m_pFlareTex1);
    m_FlareNodes[i
++= FlareNode( 0.3f0.4f, D3DCOLOR_XRGB(100225200), m_pFlareTex1);
    m_FlareNodes[i
++= FlareNode( 1.2f1.0f, D3DCOLOR_XRGB(100,  50,  50), m_pFlareTex1);
    m_FlareNodes[i
++= FlareNode( 1.5f1.5f, D3DCOLOR_XRGB( 50100,  50), m_pFlareTex1);

    m_FlareNodes[i
++= FlareNode(-0.3f0.7f, D3DCOLOR_XRGB(200,  50,  50), m_pFlareTex2);
    m_FlareNodes[i
++= FlareNode( 0.6f0.9f, D3DCOLOR_XRGB( 50100,  50), m_pFlareTex2);
    m_FlareNodes[i
++= FlareNode( 0.7f0.4f, D3DCOLOR_XRGB( 50200200), m_pFlareTex2);

    m_FlareNodes[i
++= FlareNode(-0.7f0.7f, D3DCOLOR_XRGB( 50100,  25), m_pFlareTex3);
    m_FlareNodes[i
++= FlareNode( 0.0f0.6f, D3DCOLOR_XRGB( 25,  25,  25), m_pFlareTex3);
    m_FlareNodes[i
++= FlareNode( 2.0f1.4f, D3DCOLOR_XRGB( 25,  50100), m_pFlareTex3);

    
return true;
}

void CLensFlare::CalRelativeLightPos(int &iCoordX, int &iCoordY, int &iCoordW)
{
    
// 计算光源在2D屏幕中的相对位置,固定公式
    D3DXMATRIX matWorld, matView, matConcat, matViewportScale;
    D3DXVECTOR4 vResult;

    matViewportScale 
= D3DXMATRIX(
        m_viewPortCenter.x, 
000,
        
0-m_viewPortCenter.y, 00,
        
0010,
        m_viewPortCenter.x, m_viewPortCenter.y, 
01
        );

    matView 
= g_pD3DCamera->GetViewMatrix();
    D3DXMatrixIdentity(
&matWorld);
    matConcat 
= matWorld;
    matConcat 
*= matView;
    matConcat 
*= g_matProjection;
    matConcat 
*= matViewportScale;

    D3DXVECTOR3 resultLightPos 
= m_lightPosition + g_pD3DCamera->GetCameraPos();
    D3DXVec3Transform(
&vResult, &resultLightPos, &matConcat);

    iCoordX 
= vResult.x/vResult.w;
    iCoordY 
= vResult.y/vResult.w;
    iCoordW 
= vResult.w;
}

bool CLensFlare::IsVisible()
{
    
// 判断光源是否可见(目前仅粗略判断)
    int X,Y,W = 0;
    CalRelativeLightPos(X,Y,W);
    
if( W > 0.0f &&
        X 
>= -100 && X < m_viewPortCenter.x * 2 + 100 &&
        Y 
>= -100 && Y < m_viewPortCenter.y * 2 + 100 )
    {
        m_relativeLightPos.x 
= X;
        m_relativeLightPos.y 
= Y;
        m_occlusionAlpha     
= W;
        
return true;
    }
    
else
        
return false;
}

void CLensFlare::Draw()
{
    
if(IsVisible())
    {
        
// 以ALPHABLEND模式绘制主光晕
        g_pSpriteBatch->Begin(SPRITEBLENDMODE_ALPHABLEND);
        D3DXCOLOR color 
= D3DCOLOR_COLORVALUE(111, m_occlusionAlpha);
        D3DXVECTOR2 glowOrigin 
= D3DXVECTOR2(m_pGlowTex->GetWidth(),m_pGlowTex->GetHeight())/2;
        
float scale = 400 * 2 / m_pGlowTex->GetWidth();
        g_pSpriteBatch 
-> Draw(m_pGlowTex,m_relativeLightPos,glowOrigin,scale);
        g_pSpriteBatch
->End();

        
// 以ADDITIVE模式绘制其他镜头光晕
        g_pSpriteBatch->Begin(SPRITEBLENDMODE_ADDITIVE);
        // 镜头中心与光源2D屏幕位置作差得到相应的倾斜向量
        D3DXVECTOR2 flareVector 
= m_viewPortCenter - m_relativeLightPos;
        
for(int i=0;i<10;i++)
        {
            D3DXVECTOR2 flarePos 
= m_relativeLightPos + flareVector * m_FlareNodes[i]._Position;
            D3DXVECTOR2 flareOrgin 
= D3DXVECTOR2(m_FlareNodes[i]._pTexture->GetWidth(),m_FlareNodes[i]._pTexture->GetHeight())/2;
            g_pSpriteBatch
->Draw(m_FlareNodes[i]._pTexture,flarePos,flareOrgin,m_FlareNodes[i]._Scale,m_FlareNodes[i]._Color);
        }
        g_pSpriteBatch
->End();
    }
}

值得留意的是CalRelativeLightPos函数的计算过程,这是由3D位置推导2D屏幕位置的固定计算公式,建议大家一定要记住~

下面看主体代码:

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

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())) 
    {
        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();
        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);
}

最后是效果图:

是否有一种身临其境的感觉呢?呵呵~

最后需要顺带一提的是,镜头光晕应当仅在光源可见时才进行绘制。而本节中实现的CLensFlare类下的IsVisible方法还存在很大的瑕疵,并不能精确判断光源的可见性。

D3D中要精确实现光源的可见性判断,常规的做法应当是采用“遮挡查询”机制,相关内容后续有时间的话会为大家做更进一步的讲解~

以上,谢谢~