Direct3D轮回:游戏特效之风动、雾化
2011-08-14 09:56 独孤残云 阅读(3888) 评论(29) 编辑 收藏 举报风动和雾化都是相对而言比较简单的游戏特效,实现起来甚至比我们前一节提到的晴天光晕还要简单~
大家还记得上上节中我们为陆地种植的那片植被吗?现在我们在场景中加入风的效果,让草儿动起来 ^ ^
首先,我们来实现一个风动效果的Shader:
代码清单: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, 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 faked~ 不正是这样吗?呵呵~
以上,谢谢~