zoukankan      html  css  js  c++  java
  • Direct3D轮回:游戏特效之晴天光晕

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

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

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

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

    其效果大致如图所示:

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

    首先还是要准备素材:

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

    以上,谢谢~

     

     

     

  • 相关阅读:
    HDU 3501 Calculation 2 ——Dirichlet积
    BZOJ 1101 [POI2007]Zap ——Dirichlet积
    BZOJ 1257 [CQOI2007]余数之和sum ——Dirichlet积
    SGU 194 Reactor Cooling ——网络流
    BZOJ 1497 [NOI2006]最大获利 ——网络流
    BZOJ 2705 [SDOI2012]Longge的问题 ——Dirichlet积
    BZOJ 1653 [Usaco2006 Feb]Backward Digit Sums ——搜索
    BZOJ 1861 [Zjoi2006]Book 书架 ——Splay
    BZOJ 3130 [Sdoi2013]费用流 ——网络流
    BZOJ 3990 [SDOI2015]排序 ——搜索
  • 原文地址:https://www.cnblogs.com/kenkao/p/2129865.html
Copyright © 2011-2022 走看看