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函数即可:
{
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
来自: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.5f, 0.7f, D3DCOLOR_XRGB( 50, 25, 50), m_pFlareTex1);
m_FlareNodes[i++] = FlareNode( 0.3f, 0.4f, D3DCOLOR_XRGB(100, 225, 200), m_pFlareTex1);
m_FlareNodes[i++] = FlareNode( 1.2f, 1.0f, D3DCOLOR_XRGB(100, 50, 50), m_pFlareTex1);
m_FlareNodes[i++] = FlareNode( 1.5f, 1.5f, D3DCOLOR_XRGB( 50, 100, 50), m_pFlareTex1);
m_FlareNodes[i++] = FlareNode(-0.3f, 0.7f, D3DCOLOR_XRGB(200, 50, 50), m_pFlareTex2);
m_FlareNodes[i++] = FlareNode( 0.6f, 0.9f, D3DCOLOR_XRGB( 50, 100, 50), m_pFlareTex2);
m_FlareNodes[i++] = FlareNode( 0.7f, 0.4f, D3DCOLOR_XRGB( 50, 200, 200), m_pFlareTex2);
m_FlareNodes[i++] = FlareNode(-0.7f, 0.7f, D3DCOLOR_XRGB( 50, 100, 25), m_pFlareTex3);
m_FlareNodes[i++] = FlareNode( 0.0f, 0.6f, D3DCOLOR_XRGB( 25, 25, 25), m_pFlareTex3);
m_FlareNodes[i++] = FlareNode( 2.0f, 1.4f, D3DCOLOR_XRGB( 25, 50, 100), 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, 0, 0, 0,
0, -m_viewPortCenter.y, 0, 0,
0, 0, 1, 0,
m_viewPortCenter.x, m_viewPortCenter.y, 0, 1
);
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(1, 1, 1, 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);
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
来自: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.0f, 0);
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中要精确实现光源的可见性判断,常规的做法应当是采用“遮挡查询”机制,相关内容后续有时间的话会为大家做更进一步的讲解~
以上,谢谢~