https://www.cnblogs.com/kenkao/archive/2011/08/14/2137726.html
风动和雾化都是相对而言比较简单的游戏特效,实现起来甚至比我们前一节提到的晴天光晕还要简单~
大家还记得上上节中我们为陆地种植的那片植被吗?现在我们在场景中加入风的效果,让草儿动起来 ^ ^
首先,我们来实现一个风动效果的Shader:
Wind.fx
原理非常简单,只有一句代码起到了实质性的作用,它使得所有效果应用顶点随时间呈现余弦状波动效果。
而其他所有的代码其实就相当于一个没有任何特效的固定管线~
下面我们应用它来改造原有的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, 0, sizeof(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, 0, 0, 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 来自: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.0f, 0); 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 fake