Direct3D轮回:基于HLSL实现D3D中的光照特效
2011-06-30 14:51 独孤残云 阅读(3727) 评论(8) 编辑 收藏 举报HLSL(High-Level Shading Language,高级着色语言),即大家口中经常提到的Shader。
相较于固定功能流水线,使用HLSL的优势是不言而喻的。
使用HLSL编写的模块工作于GPU之上,取代了原有的固定功能流水线,从而使得我们从那些事先定义好的固定运算中解脱出来,在特效编写过程中获得巨大的灵活度。
Xna中更是干脆完全舍弃了D3D中旧有的固定功能流水线。
下面我们来看如何在D3D中使用HLSL编写光照特效:
D3D中特效的应用主要依赖于ID3DXEffect接口。如下类在此接口的基础上做了简单的封装,以实现特效的加载、应用及释放~
/*-------------------------------------
代码清单:D3DEffect.h
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
#include "D3DInit.h"
#pragma once
class CD3DEffect
{
public:
CD3DEffect(void);
virtual ~CD3DEffect(void);
public:
virtual bool LoadEffect(char* szFxFileName, char* ErrMsg); //加载特效
virtual void BeginEffect(UINT& numPasses); //开启特效
virtual void EndEffect(); //终止特效
virtual void Release(); //释放特效
public:
ID3DXEffect* GetEffect(){return m_pEffect;} //获得特效指针
protected:
ID3DXEffect* m_pEffect; //特效指针
};
/*-------------------------------------
代码清单:D3DEffect.cpp
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
#include "StdAfx.h"
#include "D3DEffect.h"
#include "D3DGame.h"
extern IDirect3DDevice9 *g_pD3DDevice;
CD3DEffect::CD3DEffect(void):m_pEffect(NULL)
{
}
CD3DEffect::~CD3DEffect(void)
{
}
bool CD3DEffect::LoadEffect(char* szFxFileName, char* ErrMsg)
{
HRESULT hr = 0;
ID3DXBuffer* errorBuffer = 0;
hr = D3DXCreateEffectFromFile(
g_pD3DDevice, // D3D设备
szFxFileName, // 特效文件
0, // no preprocessor definitions
0, // no ID3DXInclude interface
D3DXSHADER_DEBUG, // 编译标志
0, // don't share parameters
&m_pEffect, // 特效对象
&errorBuffer // 错误缓冲
);
// 错误反馈
if( errorBuffer )
{
ErrMsg = (char*)errorBuffer->GetBufferPointer();
return FALSE;
}
if(FAILED(hr))
{
ErrMsg = "D3DXCreateEffectFromFile() - FAILED";
return FALSE;
}
return TRUE;
}
void CD3DEffect::BeginEffect(UINT& numPasses)
{
m_pEffect->Begin(&numPasses, 0);
}
void CD3DEffect::EndEffect()
{
m_pEffect->End();
}
void CD3DEffect::Release()
{
ReleaseCOM(m_pEffect);
}
没有多少内容,需要注意的只有特效加载函数,直接参考龙书即可~
接下来,我们来看特效文件(.fx)的内容:
/*-------------------------------------------------------------
代码清单:Light.fx
引自:上海八中物理组--->Xna游戏编程--->Shader教程系列
http://shiba.hpe.sh.cn/jiaoyanzu/WULI/soft/xna.aspx?classId=4
-------------------------------------------------------------*/
float4x4 matWorldViewProj; //世界*摄影*投影
float4x4 matWorld; //世界坐标系
float4 vecLightDir; //光照方向向量
float4 vecEye; //视点坐标(摄影机位置)
float4 vDiffuseColor; //漫反射光颜色
float4 vSpecularColor; //镜面高光颜色
float4 vAmbient; //环境光颜色
struct VToP
{
float4 Pos : POSITION;
float3 L : TEXCOORD0;
float3 N : TEXCOORD1;
float3 V : TEXCOORD2;
};
VToP VS(float4 Pos : POSITION, float3 N : NORMAL)
{
VToP vtop = (VToP)0;
vtop.Pos = mul(Pos, matWorldViewProj); //---变换后位置
vtop.N = mul(N, matWorld); //---变换后法线(仅仅收世界坐标支配)
float4 PosWorld = mul(Pos, matWorld); //---变换后位置(仅仅受世界坐标支配)
vtop.L = vecLightDir; //---光照方向向量
vtop.V = vecEye - PosWorld; //---相对视点坐标
return vtop;
}
float4 PS(VToP vtop) : COLOR
{
float3 Normal = normalize(vtop.N);
float3 LightDir = normalize(vtop.L);
float3 ViewDir = normalize(vtop.V); //---向量相关计算,大多仅用到其方向特性,需要事先单位化。
//---若是根据其长度考虑衰减,则另当别论,这个在后续点光源的实现中还要介绍。
float Diff = saturate(dot(Normal, LightDir)); //---光照方向与法线求点积=衰减
//---正面照过来,物体表面反射出的光就多一些;侧面照过来,物体表面反射的光就少一些。这个很好理解~
float3 Reflect = normalize(reflect(-LightDir, Normal));
float Specular = pow(saturate(dot(Reflect, ViewDir)), 10); //---关于镜面高光的实现思路,关键要看物体表面镜面反射(入射光与反射光对称于顶点法线)之后的反射光是否射入人眼。
//---我们不太好要求反射光线完全垂直射入人眼(观察向量=反射向量),允许其存在一定的偏向(观察向量与反射向量的夹角小于某一值)。
//---pow(saturate(dot(Reflect, ViewDir)), 10)即是作此处理,大家可以试着改动下10这个参数,观察效果~
return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular; //---最后的颜色 = 环境光 + 漫射光 + 镜面高光
}
technique SpecularLight
{
pass P0
{
VertexShader = compile vs_1_1 VS();
PixelShader = compile ps_2_0 PS();
}
}
环境光、漫射光与镜面高光的实现机理比较简单,我在HLSL代码中给出了部分注释。如果大家依然不明白的话,可以参看老师为大家翻译的这篇文章:http://shiba.hpe.sh.cn/jiaoyanzu/WULI/showArticle.aspx?articleId=326&classId=4
从顶点着色器及像素着色器的相关计算即可看出,各个顶点的位置变换及颜色输出完全由你掌控,从而无需再受制于D3D设备事先为你提供的那些特效方法。
然后,我们在主体代码中加载并应用灯光特效到网格,替代我们上一节使用到的固定功能流水线(BeginEffect、EndEffect两个方法)。
代码清单: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 <stdio.h>
//---通用全局变量
HINSTANCE g_hInst;
HWND g_hWnd;
D3DXMATRIX g_matProjection;
//---D3D全局变量
IDirect3D9 *g_pD3D = NULL;
IDirect3DDevice9 *g_pD3DDevice = NULL;
CMouseInput *g_pMouseInput = NULL;
CKeyboardInput *g_pKeyboardInput = NULL;
CD3DCamera *g_pD3DCamera = NULL;
CCoordCross *g_pCoordCross = NULL;
CSimpleXMesh *g_pSimpleXMesh = NULL;
CD3DEffect *g_pD3DEffect = NULL;
//---HLSL全局变量句柄
D3DXHANDLE g_CurrentTechHandle = NULL;
D3DXHANDLE g_matWorldViewProj = NULL;
D3DXHANDLE g_matWorld = NULL;
D3DXHANDLE g_vecEye = NULL;
D3DXHANDLE g_vecLightDir = NULL;
D3DXHANDLE g_vDiffuseColor = NULL;
D3DXHANDLE g_vSpecularColor = NULL;
D3DXHANDLE g_vAmbient = NULL;
// HLSL特效参数设置
void GetParameters();
void SetParameters();
void Initialize(HINSTANCE hInst, HWND hWnd)
{
g_hInst = hInst;
g_hWnd = hWnd;
InitD3D(&g_pD3D, &g_pD3DDevice, 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;
}
void LoadContent()
{
g_pCoordCross = new CCoordCross;
// 设置摄影机位置
g_pD3DCamera->SetCameraPos(D3DXVECTOR3(0.5f,0.5f,-5.0f));
g_pSimpleXMesh = new CSimpleXMesh;
// 加载X网格
g_pSimpleXMesh->LoadXMesh("teapot.X");
g_pD3DEffect = new CD3DEffect;
char ErrMsg[60];
// 加载fx特效
if(!g_pD3DEffect->LoadEffect("Light.fx",ErrMsg))
::MessageBox(g_hWnd,ErrMsg,0,0);
// 获得句柄
GetParameters();
}
void Update()
{
g_pMouseInput->GetState();
g_pKeyboardInput->GetState();
g_pD3DCamera->Update();
}
void Draw()
{
// 参数设定
SetParameters();
g_pD3DDevice->SetTransform(D3DTS_VIEW,&g_pD3DCamera->GetViewMatrix());
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f, 0);
if(SUCCEEDED(g_pD3DDevice->BeginScene()))
{
g_pCoordCross->Draw();
UINT numPasses;
// 开启特效
g_pD3DEffect->BeginEffect(numPasses);
for(UINT i=0;i<numPasses;i++)
{
// 开启路径
g_pD3DEffect->GetEffect()->BeginPass(i);
for(DWORD j=0;j<g_pSimpleXMesh->GetMaterialNum();j++)
{
g_pSimpleXMesh->DrawXMeshSubset(j);
}
// 路径结束
g_pD3DEffect->GetEffect()->EndPass();
}
// 特效结束
g_pD3DEffect->EndEffect();
g_pD3DDevice->EndScene();
}
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
void UnloadContent()
{
ReleaseCOM(g_pD3DEffect);
ReleaseCOM(g_pSimpleXMesh);
ReleaseCOM(g_pCoordCross);
}
void Dispose()
{
ReleaseCOM(g_pD3DCamera);
ReleaseCOM(g_pKeyboardInput);
ReleaseCOM(g_pMouseInput);
ReleaseCOM(g_pD3DDevice);
ReleaseCOM(g_pD3D);
}
void GetParameters()
{
// 获得HLSL中各个全局变量句柄
g_CurrentTechHandle = g_pD3DEffect -> GetEffect() -> GetTechniqueByName("SpecularLight");
g_matWorldViewProj = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "matWorldViewProj");
g_matWorld = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "matWorld");
g_vecEye = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vecEye");
g_vecLightDir = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vecLightDir");
g_vDiffuseColor = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vDiffuseColor");
g_vSpecularColor = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vSpecularColor");
g_vAmbient = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vAmbient");
}
void SetParameters()
{
// 设定当前技术
g_pD3DEffect -> GetEffect() -> SetTechnique(g_CurrentTechHandle);
// 设定HLSL中的各个参数
D3DXMATRIX worldMatrix;
D3DXMatrixTranslation(&worldMatrix,0.0f,0.0f,0.0f);
g_pD3DEffect -> GetEffect() -> SetMatrix(g_matWorldViewProj,&(worldMatrix*g_pD3DCamera->GetViewMatrix()*g_matProjection));
g_pD3DEffect -> GetEffect() -> SetMatrix(g_matWorld,&worldMatrix);
D3DXVECTOR3 cameraPos = g_pD3DCamera->GetCameraPos();
D3DXVECTOR4 vecEye = D3DXVECTOR4(cameraPos.x,cameraPos.y,cameraPos.z,0.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vecEye,&vecEye);
D3DXVECTOR4 vLightDirection = D3DXVECTOR4(0.0f, 0.0f, -1.0f, 1.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vecLightDir,&vLightDirection);
D3DXVECTOR4 vColorDiffuse = D3DXVECTOR4(0.8f, 0.0f, 0.0f, 1.0f);
D3DXVECTOR4 vColorSpecular = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
D3DXVECTOR4 vColorAmbient = D3DXVECTOR4(0.1f, 0.1f, 0.1f, 1.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vDiffuseColor,&vColorDiffuse);
g_pD3DEffect -> GetEffect() -> SetVector(g_vSpecularColor,&vColorSpecular);
g_pD3DEffect -> GetEffect() -> SetVector(g_vAmbient,&vColorAmbient);
}
GetTechniqueByName用于获得技术句柄;SetTechnique用于设置当前技术。
GetParameterByName用于获得参数句柄;SetMatrix、SetVector等SetXXX函数则借由实现获得的句柄将特定的数值送入HLSL相应的变量中。
相关句柄的获得及内容设置被我封装到GetParameters和SetParameters中,大家可以留心一下两个函数的调用时机。
最后是效果图:
怎么样,效果还算不错吧?呵呵~
其实就我个人观点,相较于固定功能流水线,HLSL更能反映3D渲染管道的本质:顶点3D位置变换(顶点着色器)+逐像素颜色输出(像素着色器)。
建议大家尽量掌握一些简单的HLSL特效编写,并在日常应用中尽量使用其替代原有固定功能流水线~这将为大家后续的D3D学习带来诸多益处~