Direct3D轮回:基于HLSL实现D3D中的光照特效

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设备事先为你提供的那些特效方法。

然后,我们在主体代码中加载并应用灯光特效到网格,替代我们上一节使用到的固定功能流水线(BeginEffectEndEffect两个方法)

Direct3D轮回:基于HLSL实现D3D中的光照特效 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 
< 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用于获得参数句柄;SetMatrixSetVectorSetXXX函数则借由实现获得的句柄将特定的数值送入HLSL相应的变量中。
相关句柄的获得及内容设置被我封装到GetParametersSetParameters中,大家可以留心一下两个函数的调用时机。
最后是效果图:

Direct3D轮回:基于HLSL实现D3D中的光照特效

怎么样,效果还算不错吧?呵呵~
其实就我个人观点,相较于固定功能流水线,HLSL更能反映3D渲染管道的本质:顶点3D位置变换(顶点着色器)+逐像素颜色输出(像素着色器)
建议大家尽量掌握一些简单的HLSL特效编写,并在日常应用中尽量使用其替代原有固定功能流水线~这将为大家后续的D3D学习带来诸多益处~

 

你可能感兴趣的:(DI)