• 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两个方法)

    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.0f0);
        
    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.0f0.0f-1.0f1.0f);
        g_pD3DEffect 
    -> GetEffect() -> SetVector(g_vecLightDir,&vLightDirection);
        D3DXVECTOR4 vColorDiffuse 
    = D3DXVECTOR4(0.8f0.0f0.0f1.0f);
        D3DXVECTOR4 vColorSpecular 
    = D3DXVECTOR4(1.0f1.0f1.0f1.0f);
        D3DXVECTOR4 vColorAmbient 
    = D3DXVECTOR4(0.1f0.1f0.1f1.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中,大家可以留心一下两个函数的调用时机。
    最后是效果图:

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

     

  • 相关阅读:
    Openjudge-NOI题库-简单算术表达式求值
    洛谷-求同构数的个数-NOIP2013提高组复赛
    洛谷-拼数-NOIP1998提高组复赛
    洛谷-统计数字-NOIP2007提高组复赛
    洛谷-谁拿了最多奖学金-NOIP2005提高组复赛
    Openjudge-NOI题库-字符串移位包含问题
    洛谷-乘积最大-NOIP2000提高组复赛
    NOIP2015-普及组复赛-第一题-金币
    Tyvj-超级书架
    Openjudge-NOI题库-出书最多
  • 原文地址:https://www.cnblogs.com/kenkao/p/2094454.html
Copyright © 2020-2023  润新知