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


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

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

    首先,我们来实现一个风动效果的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~ 不正是这样吗?呵呵~

    以上,谢谢~

  • 相关阅读:
    Thrift在微服务中的使用
    MySQL 必知必会
    IDEA 内使用 git
    分布式锁
    LeetCode 图
    LeetCode 位运算
    LeetCode 数组
    LeetCode 字符串
    LeetCode 哈希表
    LeetCode 栈和队列
  • 原文地址:https://www.cnblogs.com/kenkao/p/2137726.html
Copyright © 2020-2023  润新知