DirectX11 With Windows SDK--13 動手實現一個簡易Effects框架、陰影效果繪製

来源:https://www.cnblogs.com/X-Jun/archive/2018/09/17/9665452.html
-Advertisement-
Play Games

前言 到現在為止,所有的教程項目都沒有使用Effects11框架類來管理資源。因為在D3DCompile API ( 47)版本中,如果你嘗試編譯fx_5_0的效果文件,會收到這樣的警告: 在未來的版本中,D3DCompiler可能會停止對FX11的支持,所以我們需要自行去管理各種特效,並改用HLS ...


前言

到現在為止,所有的教程項目都沒有使用Effects11框架類來管理資源。因為在D3DCompile API (#47)版本中,如果你嘗試編譯fx_5_0的效果文件,會收到這樣的警告:
X4717: Effects deprecated for D3DCompiler_47

在未來的版本中,D3DCompiler可能會停止對FX11的支持,所以我們需要自行去管理各種特效,並改用HLSL編譯器去編譯每一個著色器。同時,在閱讀本章之前,你需要先學習本系列前面的一些重點章節再繼續:

主題 版次 創建時間 修改時間
01 DirectX11初始化 第5版 2018/5/12 2018/8/18
02 頂點/像素著色器的創建、頂點緩衝區 第10版 2018/5/13 2018/8/30
03 索引緩衝區、常量緩衝區 第7版 2018/5/13 2018/8/20
09 紋理映射與採樣器狀態 第7版 2018/7/12 2018/8/11
11 混合狀態與光柵化狀態 第3版 2018/7/21 2018/8/8
12 深度/模板狀態、反射繪製 第5版 2018/7/28 2018/9/16

在DirectXTK中的Effects.h可以看到它實現了一系列Effects管理類,相比Effects11框架庫,它缺少了反射機制,並且使用的是它內部已經寫好、編譯好的著色器。DirectXTK的Effects也只不過是為了簡化游戲開發流程而設計出來的。當然,裡面的一部分源碼實現也值得我們去學習。

註意:這章經歷了一次十分大的改動,原先所使用的BasicFX類因為在後續的章節中發現很難擴展,所以進行了一次大幅度重構。並會逐漸替換掉後面教程的項目源碼所使用的BasicFX。

在這一章的學習過後,你將會理解Effects11的一部分運作機制是怎樣的。而關於它的反射機制、著色器編譯部分不會進行探討。

這篇教程還會提到用深度/模板狀態去實現簡單的陰影效果,但不會深入數學公式原理。

DirectX11 With Windows SDK完整目錄

Github項目源碼

回顧RenderStates類

目前的RenderStates類存放有比較常用的各種狀態,原來在Effects11框架下是可以在fx文件初始化各種渲染狀態,並設置到Technique11中。但現在我們只能在C++代碼層中一次性創建好各種所需的渲染狀態:

class RenderStates
{
public:
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    static bool IsInit();

    static void InitAll(ComPtr<ID3D11Device> device);
    // 使用ComPtr無需手工釋放

public:
    static ComPtr<ID3D11RasterizerState> RSWireframe;       // 光柵化器狀態:線框模式
    static ComPtr<ID3D11RasterizerState> RSNoCull;          // 光柵化器狀態:無背面裁剪模式
    static ComPtr<ID3D11RasterizerState> RSCullClockWise;   // 光柵化器狀態:順時針裁剪模式

    static ComPtr<ID3D11SamplerState> SSLinearWrap;         // 採樣器狀態:線性過濾
    static ComPtr<ID3D11SamplerState> SSAnistropicWrap;     // 採樣器狀態:各項異性過濾

    static ComPtr<ID3D11BlendState> BSNoColorWrite;     // 混合狀態:不寫入顏色
    static ComPtr<ID3D11BlendState> BSTransparent;      // 混合狀態:透明混合
    static ComPtr<ID3D11BlendState> BSAlphaToCoverage;  // 混合狀態:Alpha-To-Coverage

    static ComPtr<ID3D11DepthStencilState> DSSWriteStencil;     // 深度/模板狀態:寫入模板值
    static ComPtr<ID3D11DepthStencilState> DSSDrawWithStencil;  // 深度/模板狀態:對指定模板值的區域進行繪製
    static ComPtr<ID3D11DepthStencilState> DSSNoDoubleBlend;    // 深度/模板狀態:無二次混合區域
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTest;      // 深度/模板狀態:關閉深度測試
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWrite;     // 深度/模板狀態:僅深度測試,不寫入深度值
};

具體的設置可以參照源碼或者上一章內容。

簡易Effects框架

該Effects框架支持的功能如下:

  1. 管理/修改常量緩衝區的內容,併進行應用(Apply)
  2. 編譯HLSL著色器而不是fx文件
  3. 管理/使用四種渲染狀態
  4. 切換渲染模式(涉及到渲染管線各種資源的綁定、切換)
  5. 僅更新修改的變數所對應的常量緩衝區塊

不過它也有這樣的缺陷:

  1. 一個特效類對應一套著色器和所使用的常量緩衝區,所屬著色器代碼的變動很可能會引起對框架類的修改,因為缺乏反射機制而導致靈活性差。

文件結構

首先是文件結構:

其中能夠暴露給程式使用的只有頭文件Effects.h,裡面可以存放多套不同的特效框架類的聲明,而關於每個框架類的實現部分都應當用一個獨立的源文件存放。而EffectHelper.h則是用來幫助管理常量緩衝區的,服務於各種框架類的實現部分以及所屬的源文件,因此不應該直接使用。

理論上它也是可以做成靜態庫使用的,然後著色器代碼穩定後也不應當變動。在使用的時候只需要包含頭文件Effects.h即可。

EffectHelper.h

該頭文件包含了一些有用的東西,但它需要在包含特效類實現的源文件中使用,且必須晚於Effects.h包含。

在堆上進行類的記憶體對齊

有些類型需要在堆上按16位元組對齊,比如XMVECTORXMMATRIX,雖然說拿這些對象作為類的成員不太合適,畢竟分配在堆上的話基本上無法保證記憶體按16位元組對齊了,但還是希望能夠做到。在VS的corecrt_malloc.h(只要有包含stdlib.h, malloc.h之一的頭文件都可以)中有這樣的一個函數:_aligned_malloc,它可以指定需要分配的記憶體位元組大小以及按多少位元組對齊。其中對齊值必須為2的整數次冪的位元組數。

void * _aligned_malloc(  
    size_t size,        // [In]分配記憶體位元組數
    size_t alignment    // [In]按多少位元組記憶體來對齊
);  

若一個類中包含有已經指定記憶體對齊的成員,則需要優先把這些成員放到最前。

然後與之對應的就是_aligned_free函數了,它可以釋放之前由_aligned_malloc分配得到的記憶體。

下麵是類模板AlignedType的實現,讓需要記憶體對齊的類去繼承該類即可。它重載了operator newoperator delete的實現:

// 若類需要記憶體對齊,從該類派生
template<class DerivedType>
struct AlignedType
{
    static void* operator new(size_t size)
    {
        const size_t alignedSize = __alignof(DerivedType);

        static_assert(alignedSize > 8, "AlignedNew is only useful for types with > 8 byte alignment! Did you forget a __declspec(align) on DerivedType?");

        void* ptr = _aligned_malloc(size, alignedSize);

        if (!ptr)
            throw std::bad_alloc();

        return ptr;
    }

    static void operator delete(void * ptr)
    {
        _aligned_free(ptr);
    }
};

需要註意的是,繼承AlignedType的類或者其成員必須本身有__declspec(align)的標識。若是內部成員,在所有包含該標識的值中最大的align值 必須是2的整數次冪且必須大於8。

下麵演示了正確的和錯誤的行為:

// 錯誤!VertexPosColor按4位元組對齊!
struct VertexPosColor : AlignedType<VertexPos>
{
    XMFLOAT3 pos;
    XMFLOAT4 color;
};

// 正確!Data按16位元組對齊,因為pos本身是按16位元組對齊的。
struct Data : AlignedType<VertexPos>
{
    XMVECTOR pos;
    int val;
};

// 正確!Vector類按16位元組對齊
__declspec(align(16))
struct Vector : AlignedType<Vector>
{
    float x;
    float y;
    float z;
    float w;
};

這裡AlignedType<T>主要是用於BasicObjectFX::Impl類,因為其內部包含了XMVECTORXMMATRIX類型的成員,且該類需要分配在堆上。

常量緩衝區管理

一個常量緩衝區可能會被創建、更新或者綁定到管線。若常量緩衝區的值沒有發生變化,我們不希望它進行無意義的更新。我們可以使用一個dirty標記,確認它是否被修改過。常量緩衝區的任一內部成員發生修改的話,我們就將數據更新到常量緩衝區並恢復該標記。

首先是抽象基類CBufferBase

struct CBufferBase
{
    template<class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    bool isDirty;
    ComPtr<ID3D11Buffer> cBuffer;

    virtual void CreateBuffer(ComPtr<ID3D11Device> device) = 0;
    virtual void UpdateBuffer(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindVS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindHS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindDS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindGS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindCS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
    virtual void BindPS(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
};

這麼做是為了方便我們放入數組進行遍歷。

然後是派生類CBufferObjectstartSlot指定了HLSL對應cbuffer的索引,T則是C++對應的結構體,存儲臨時數據:

template<UINT startSlot, class T>
struct CBufferObject : CBufferBase
{
    T data;

    void CreateBuffer(ComPtr<ID3D11Device> device) override
    {
        if (cBuffer != nullptr)
            return;
        D3D11_BUFFER_DESC cbd;
        ZeroMemory(&cbd, sizeof(cbd));
        cbd.Usage = D3D11_USAGE_DEFAULT;
        cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        cbd.CPUAccessFlags = 0;
        cbd.ByteWidth = sizeof(T);
        HR(device->CreateBuffer(&cbd, nullptr, cBuffer.GetAddressOf()));
    }

    void UpdateBuffer(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        if (isDirty)
        {
            isDirty = false;
            deviceContext->UpdateSubresource(cBuffer.Get(), 0, nullptr, &data, 0, 0);
        }
    }

    void BindVS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->VSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }

    void BindHS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->HSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }

    void BindDS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->DSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }

    void BindGS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->GSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }

    void BindCS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->CSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }

    void BindPS(ComPtr<ID3D11DeviceContext> deviceContext) override
    {
        deviceContext->PSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf());
    }
};

關於常量緩衝區臨時變數的修改則在後續的內容。

BasicObjectFX類--管理對象繪製的資源

首先是抽象基類IEffects,它僅允許被移動,並且僅包含Apply方法。

class IEffect
{
public:
    // 使用模板別名(C++11)簡化類型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    IEffect() = default;

    // 不支持複製構造
    IEffect(const IEffect&) = delete;
    IEffect& operator=(const IEffect&) = delete;

    // 允許轉移
    IEffect(IEffect&& moveFrom) = default;
    IEffect& operator=(IEffect&& moveFrom) = default;

    virtual ~IEffect() = default;

    // 更新並綁定常量緩衝區
    virtual void Apply(ComPtr<ID3D11DeviceContext> deviceContext) = 0;
};

原來的ID3DX11EffectPass包含的方法Apply用於在各個著色器階段綁定所需要的常量緩衝區、紋理等資源,並更新之前有所修改的常量緩衝區。現在我們實現Effects框架中的Apply方法也是這麼做的。

然後是派生類BasicObjectFX,從它的方法來看,包含了單例獲取、渲染狀態的切換、修改常量緩衝區某一成員的值、應用變更四個大塊:

class BasicObjectFX : public IEffect
{
public:
    // 使用模板別名(C++11)簡化類型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    BasicObjectFX();
    virtual ~BasicObjectFX() override;

    BasicObjectFX(BasicObjectFX&& moveFrom);
    BasicObjectFX& operator=(BasicObjectFX&& moveFrom);

    // 獲取單例
    static BasicObjectFX& Get();

    

    // 初始化Basix.fx所需資源並初始化渲染狀態
    bool InitAll(ComPtr<ID3D11Device> device);


    //
    // 渲染模式的變更
    //

    // 預設狀態來繪製
    void SetRenderDefault(ComPtr<ID3D11DeviceContext> deviceContext);
    // Alpha混合繪製
    void SetRenderAlphaBlend(ComPtr<ID3D11DeviceContext> deviceContext);
    // 無二次混合
    void SetRenderNoDoubleBlend(ComPtr<ID3D11DeviceContext> deviceContext, UINT stencilRef);
    // 僅寫入模板值
    void SetWriteStencilOnly(ComPtr<ID3D11DeviceContext> deviceContext, UINT stencilRef);
    // 對指定模板值的區域進行繪製,採用預設狀態
    void SetRenderDefaultWithStencil(ComPtr<ID3D11DeviceContext> deviceContext, UINT stencilRef);
    // 對指定模板值的區域進行繪製,採用Alpha混合
    void SetRenderAlphaBlendWithStencil(ComPtr<ID3D11DeviceContext> deviceContext, UINT stencilRef);
    // 2D預設狀態繪製
    void Set2DRenderDefault(ComPtr<ID3D11DeviceContext> deviceContext);
    // 2D混合繪製
    void Set2DRenderAlphaBlend(ComPtr<ID3D11DeviceContext> deviceContext);

    

    //
    // 矩陣設置
    //

    void XM_CALLCONV SetWorldMatrix(DirectX::FXMMATRIX W);
    void XM_CALLCONV SetViewMatrix(DirectX::FXMMATRIX V);
    void XM_CALLCONV SetProjMatrix(DirectX::FXMMATRIX P);
    void XM_CALLCONV SetWorldViewProjMatrix(DirectX::FXMMATRIX W, DirectX::CXMMATRIX V, DirectX::CXMMATRIX P);

    void XM_CALLCONV SetTexTransformMatrix(DirectX::FXMMATRIX W);

    void XM_CALLCONV SetReflectionMatrix(DirectX::FXMMATRIX R);
    void XM_CALLCONV SetShadowMatrix(DirectX::FXMMATRIX S);
    void XM_CALLCONV SetRefShadowMatrix(DirectX::FXMMATRIX RefS);
    
    //
    // 光照、材質和紋理相關設置
    //

    // 各種類型燈光允許的最大數目
    static const int maxLights = 5;

    void SetDirLight(size_t pos, const DirectionalLight& dirLight);
    void SetPointLight(size_t pos, const PointLight& pointLight);
    void SetSpotLight(size_t pos, const SpotLight& spotLight);

    void SetMaterial(const Material& material);

    void SetTexture(ComPtr<ID3D11ShaderResourceView> texture);

    void XM_CALLCONV SetEyePos(DirectX::FXMVECTOR eyePos);



    //
    // 狀態開關設置
    //

    void SetReflectionState(bool isOn);
    void SetShadowState(bool isOn);
    

    // 應用常量緩衝區和紋理資源的變更
    void Apply(ComPtr<ID3D11DeviceContext> deviceContext);
    
private:
    class Impl;
    std::unique_ptr<Impl> pImpl;
};

XM_CALLCONV即在第六章之前提到的__vectorcall__fastcall約定。

然後來到BasicObjectFX.cpp,首先包含了對應HLSL五個cbuffer的C++結構體:

#include "Effects.h"
#include "EffectHelper.h"
#include "Vertex.h"
#include <d3dcompiler.h>
#include <experimental/filesystem>
using namespace DirectX;
using namespace std::experimental;

//
// 這些結構體對應HLSL的結構體,僅供該文件使用。需要按16位元組對齊
//

struct CBChangesEveryDrawing
{
    DirectX::XMMATRIX world;
    DirectX::XMMATRIX worldInvTranspose;
    DirectX::XMMATRIX texTransform;
    Material material;
};

struct CBDrawingStates
{
    int isReflection;
    int isShadow;
    DirectX::XMINT2 pad;
};

struct CBChangesEveryFrame
{
    DirectX::XMMATRIX view;
    DirectX::XMVECTOR eyePos;
};

struct CBChangesOnResize
{
    DirectX::XMMATRIX proj;
};


struct CBChangesRarely
{
    DirectX::XMMATRIX reflection;
    DirectX::XMMATRIX shadow;
    DirectX::XMMATRIX refShadow;
    DirectionalLight dirLight[BasicObjectFX::maxLights];
    PointLight pointLight[BasicObjectFX::maxLights];
    SpotLight spotLight[BasicObjectFX::maxLights];
};

EffectHelper.h需要放在Effects.h之後。

這5個結構體都放在源文件是因為這些結構體僅限於在該文件種使用。

BasicObjectFX::Impl類

之前在BasicObjectFX中聲明瞭Impl類,主要目的是為了將類的成員和方法定義都轉移到源文件中。不僅可以減少BasicObjectFX類的壓力,還可以避免暴露上面的五個結構體。

BasicObjectFX::Impl類包含一切所需資源,以及一個編譯著色器的方法:

//
// BasicObjectFX::Impl 需要先於BasicObjectFX的定義
//

class BasicObjectFX::Impl : public AlignedType<BasicObjectFX::Impl>
{
public:
    // 必須顯式指定
    Impl() = default;
    ~Impl() = default;

    // objFileNameInOut為編譯好的著色器二進位文件(.*so),若有指定則優先尋找該文件並讀取
    // hlslFileName為著色器代碼,若未找到著色器二進位文件則編譯著色器代碼
    // 編譯成功後,若指定了objFileNameInOut,則保存編譯好的著色器二進位信息到該文件
    // ppBlobOut輸出著色器二進位信息
    HRESULT CreateShaderFromFile(const WCHAR* objFileNameInOut, const WCHAR* hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** ppBlobOut);

public:
    // 需要16位元組對齊的優先放在前面
    CBufferObject<0, CBChangesEveryDrawing> cbDrawing;      // 每次對象繪製的常量緩衝區
    CBufferObject<1, CBDrawingStates>       cbStates;       // 每次繪製狀態變更的常量緩衝區
    CBufferObject<2, CBChangesEveryFrame>   cbFrame;        // 每幀繪製的常量緩衝區
    CBufferObject<3, CBChangesOnResize>     cbOnResize;     // 每次視窗大小變更的常量緩衝區
    CBufferObject<4, CBChangesRarely>       cbRarely;       // 幾乎不會變更的常量緩衝區
    BOOL isDirty;                                           // 是否有值變更
    std::vector<CBufferBase*> cBufferPtrs;                  // 統一管理上面所有的常量緩衝區


    ComPtr<ID3D11VertexShader> vertexShader3D;              // 用於3D的頂點著色器
    ComPtr<ID3D11PixelShader>  pixelShader3D;               // 用於3D的像素著色器
    ComPtr<ID3D11VertexShader> vertexShader2D;              // 用於2D的頂點著色器
    ComPtr<ID3D11PixelShader>  pixelShader2D;               // 用於2D的像素著色器

    ComPtr<ID3D11InputLayout>  vertexLayout2D;              // 用於2D的頂點輸入佈局
    ComPtr<ID3D11InputLayout>  vertexLayout3D;              // 用於3D的頂點輸入佈局

    ComPtr<ID3D11ShaderResourceView> texture;               // 用於繪製的紋理

};

著色器的編譯方法這裡不再贅述。

構造/析構/單例

這裡用一個匿名空間保管單例對象的指針。當有一個實例被構造出來的時候就會給其賦值。後續就不允許再被實例化了,可以使用Get方法獲取該單例。

namespace
{
    // BasicObjectFX單例
    static BasicObjectFX * pInstance = nullptr;
}

BasicObjectFX::BasicObjectFX()
{
    if (pInstance)
        throw std::exception("BasicObjectFX is a singleton!");
    pInstance = this;
    pImpl = std::make_unique<BasicObjectFX::Impl>();
}

BasicObjectFX::~BasicObjectFX()
{
}

BasicObjectFX::BasicObjectFX(BasicObjectFX && moveFrom)
{
    pImpl.swap(moveFrom.pImpl);
}

BasicObjectFX & BasicObjectFX::operator=(BasicObjectFX && moveFrom)
{
    pImpl.swap(moveFrom.pImpl);
    return *this;
}

BasicObjectFX & BasicObjectFX::Get()
{
    if (!pInstance)
        throw std::exception("BasicObjectFX needs an instance!");
    return *pInstance;
}

BasicObjectFX::InitAll方法

BasicObjectFX::InitAll方法負責創建出所有的著色器和常量緩衝區,以及所有的渲染狀態:

bool BasicObjectFX::InitAll(ComPtr<ID3D11Device> device)
{
    if (!device)
        return false;

    ComPtr<ID3DBlob> blob;

    // 創建頂點著色器(2D)
    HR(pImpl->CreateShaderFromFile(L"HLSL\\BasicObject_VS_2D.vso", L"HLSL\\BasicObject_VS_2D.hlsl", "VS", "vs_5_0", blob.GetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->vertexShader2D.GetAddressOf()));
    // 創建頂點佈局(2D)
    HR(device->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout),
        blob->GetBufferPointer(), blob->GetBufferSize(), pImpl->vertexLayout2D.GetAddressOf()));

    // 創建像素著色器(2D)
    HR(pImpl->CreateShaderFromFile(L"HLSL\\BasicObject_PS_2D.pso", L"HLSL\\BasicObject_PS_2D.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->pixelShader2D.GetAddressOf()));

    // 創建頂點著色器(3D)
    HR(pImpl->CreateShaderFromFile(L"HLSL\\BasicObject_VS_3D.vso", L"HLSL\\BasicObject_VS_3D.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->vertexShader3D.GetAddressOf()));
    // 創建頂點佈局(3D)
    HR(device->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout),
        blob->GetBufferPointer(), blob->GetBufferSize(), pImpl->vertexLayout3D.GetAddressOf()));

    // 創建像素著色器(3D)
    HR(pImpl->CreateShaderFromFile(L"HLSL\\BasicObject_PS_3D.pso", L"HLSL\\BasicObject_PS_3D.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->pixelShader3D.GetAddressOf()));


    // 初始化
    RenderStates::InitAll(device);

    pImpl->cBufferPtrs.assign({
        &pImpl->cbDrawing, 
        &pImpl->cbFrame, 
        &pImpl->cbStates, 
        &pImpl->cbOnResize, 
        &pImpl->cbRarely});

    // 創建常量緩衝區
    for (auto& pBuffer : pImpl->cBufferPtrs)
    {
        pBuffer->CreateBuffer(device);
    }

    return true;
}

各種渲染狀態的切換

下麵所有的渲染模式使用的是線性Wrap採樣器。

BasicFX::SetRenderDefault方法--預設渲染

BasicObjectFX::SetRenderDefault方法使用了預設的3D像素著色器和頂點著色器,並且其餘各狀態都保留使用預設狀態:

void BasicObjectFX::SetRenderDefault()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

BasicObjectFX::SetRenderAlphaBlend方法--Alpha透明混合渲染

該繪製模式關閉了光柵化裁剪,並採用透明混合方式。

void BasicObjectFX::SetRenderAlphaBlend()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

BasicObjectFX::SetRenderNoDoubleBlend方法--無重覆混合(單次混合)

該繪製模式用於繪製陰影,防止過度混合。需要指定繪製區域的模板值。

void BasicObjectFX::SetRenderNoDoubleBlend(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDoubleBlend.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

BasicObjectFX::SetWriteStencilOnly方法--僅寫入模板值

該模式用於向模板緩衝區寫入用戶指定的模板值,並且不寫入到深度緩衝區和後備緩衝區。

void BasicObjectFX::SetWriteStencilOnly(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSWriteStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSNoColorWrite.Get(), nullptr, 0xFFFFFFFF);
}

BasicObjectFX::SetRenderDefaultWithStencil方法--對指定模板值區域進行常規繪製

該模式下,僅對模板緩衝區的模板值和用戶指定的相等的區域進行常規繪製。

void BasicObjectFX::SetRenderDefaultWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSCullClockWise.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

BasicObjectFX::SetRenderAlphaBlendWithStencil方法--對指定模板值區域進行Alpha透明混合繪製

該模式下,僅對模板緩衝區的模板值和用戶指定的相等的區域進行Alpha透明混合繪製。

void BasicObjectFX::SetRenderAlphaBlendWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

BasicObjectFX::Set2DRenderDefault方法--2D預設繪製

該模式使用的是2D頂點著色器和像素著色器,並修改為2D輸入佈局。

void BasicObjectFX::Set2DRenderDefault()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout2D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader2D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader2D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

BasicObjectFX::Set2DRenderAlphaBlend方法--2D透明混合繪製

相比上面,多了透明混合狀態。

void BasicObjectFX::Set2DRenderAlphaBlend()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout2D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader2D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader2D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

更新常量緩衝區

下麵這些所有的方法會更新CBufferObject中的臨時數據,數據臟標記被設為true

void XM_CALLCONV BasicObjectFX::SetWorldMatrix(DirectX::FXMMATRIX W)
{
    auto& cBuffer = pImpl->cbDrawing;
    cBuffer.data.world = W;
    cBuffer.data.worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, W));
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetViewMatrix(FXMMATRIX V)
{
    auto& cBuffer = pImpl->cbFrame;
    cBuffer.data.view = V;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetProjMatrix(FXMMATRIX P)
{
    auto& cBuffer = pImpl->cbOnResize;
    cBuffer.data.proj = P;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetWorldViewProjMatrix(FXMMATRIX W, CXMMATRIX V, CXMMATRIX P)
{
    pImpl->cbDrawing.data.world = W;
    pImpl->cbDrawing.data.worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, W));
    pImpl->cbFrame.data.view = V;
    pImpl->cbOnResize.data.proj = P;

    auto& pCBuffers = pImpl->cBufferPtrs;
    pCBuffers[0]->isDirty = pCBuffers[1]->isDirty = pCBuffers[3]->isDirty = true;
    pImpl->isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetTexTransformMatrix(FXMMATRIX W)
{
    auto& cBuffer = pImpl->cbDrawing;
    cBuffer.data.texTransform = W;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetReflectionMatrix(FXMMATRIX R)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.reflection = R;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetShadowMatrix(FXMMATRIX S)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.shadow = S;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void XM_CALLCONV BasicObjectFX::SetRefShadowMatrix(DirectX::FXMMATRIX RefS)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.refShadow = RefS;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetDirLight(size_t pos, const DirectionalLight & dirLight)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.dirLight[pos] = dirLight;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetPointLight(size_t pos, const PointLight & pointLight)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.pointLight[pos] = pointLight;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetSpotLight(size_t pos, const SpotLight & spotLight)
{
    auto& cBuffer = pImpl->cbRarely;
    cBuffer.data.spotLight[pos] = spotLight;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetMaterial(const Material & material)
{
    auto& cBuffer = pImpl->cbDrawing;
    cBuffer.data.material = material;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetTexture(ComPtr<ID3D11ShaderResourceView> texture)
{
    pImpl->texture = texture;
}

void XM_CALLCONV BasicObjectFX::SetEyePos(FXMVECTOR eyePos)
{
    auto& cBuffer = pImpl->cbFrame;
    cBuffer.data.eyePos = eyePos;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetReflectionState(bool isOn)
{
    auto& cBuffer = pImpl->cbStates;
    cBuffer.data.isReflection = isOn;
    pImpl->isDirty = cBuffer.isDirty = true;
}

void BasicObjectFX::SetShadowState(bool isOn)
{
    auto& cBuffer = pImpl->cbStates;
    cBuffer.data.isShadow = isOn;
    pImpl->isDirty = cBuffer.isDirty = true;
}

BasicObjectFX::Apply方法--應用緩衝區、紋理資源併進行更新

BasicObjectFX::Apply首先將所需要用到的緩衝區綁定到渲染管線上,並設置紋理,然後才是視情況更新常量緩衝區。

下麵的緩衝區數組索引值同時也對應了之前編譯期指定的startSlot值。

首先檢驗總的臟標記是否為true,若有任意數據被修改,則檢驗每個常量緩衝區的臟標記,並根據該標記決定是否要更新常量緩衝區。

void BasicObjectFX::Apply(ComPtr<ID3D11DeviceContext> deviceContext)
{
    auto& pCBuffers = pImpl->cBufferPtrs;
    // 將緩衝區綁定到渲染管線上
    pCBuffers[0]->BindVS(deviceContext);
    pCBuffers[1]->BindVS(deviceContext);
    pCBuffers[2]->BindVS(deviceContext);
    pCBuffers[3]->BindVS(deviceContext);
    pCBuffers[4]->BindVS(deviceContext);

    pCBuffers[0]->BindPS(deviceContext);
    pCBuffers[1]->BindPS(deviceContext);
    pCBuffers[2]->BindPS(deviceContext);
    pCBuffers[4]->BindPS(deviceContext);

    // 設置紋理
    deviceContext->PSSetShaderResources(0, 1, pImpl->texture.GetAddressOf());

    if (pImpl->isDirty)
    {
        pImpl->isDirty = false;
        for (auto& pCBuffer : pCBuffers)
        {
            pCBuffer->UpdateBuffer(deviceContext);
        }
    }
}

當然,目前BasicFX能做的事情還是比較有限的,並且還需要隨著HLSL代碼的變動而隨之調整。更多的功能會在後續教程中實現。

繪製平面陰影

使用XMMatrixShadow可以生成陰影矩陣,根據光照類型和位置對幾何體投影到平面上的。

XMMATRIX XMMatrixShadow(
    FXMVECTOR ShadowPlane,      // 平面向量(nx, ny, nz, d)
    FXMVECTOR LightPosition);   // w = 0時表示平行光方向, w = 1時表示光源位置

通常指定的平面會稍微比實際平面高那麼一點點,以避免深度緩衝區資源爭奪導致陰影顯示有問題。

使用模板緩衝區防止過度混合

一個物體投影到平面上時,投影區域的某些位置可能位於多個三角形之內,這會導致這些位置會有多個像素通過測試併進行混合操作,渲染的次數越多,顯示的顏色會越黑。

我們可以使用模板緩衝區來解決這個問題。

  1. 在之前的例子中,我們用模板值為0的區域表示非鏡面反射區,模板值為1的區域表示為鏡面反射區;
  2. 使用RenderStates::DSSNoDoubleBlend的深度模板狀態,當給定的模板值和深度/模板緩衝區的模板值一致時,通過模板測試並對模板值加1,繪製該像素的混合,然後下一次由於給定的模板值比深度/模板緩衝區的模板值小1,不會再通過模板測試,也就阻擋了後續像素的繪製;
  3. 應當先繪製鏡面的陰影區域,再繪製正常的陰影區域。

著色器代碼的變化

Basic_PS_2D.hlsl文件變化如下:

#include "Basic.fx"

// 像素著色器(2D)
float4 PS_2D(Vertex2DOut pIn) : SV_Target
{
    float4 color = tex.Sample(sam, pIn.Tex);
    clip(color.a - 0.1f);
    return color;
}

Basic_PS_3D.hlsl文件變化如下:

#include "Basic.fx"

// 像素著色器(3D)
float4 PS_3D(Vertex3DOut pIn) : SV_Target
{
    // 提前進行裁剪,對不符合要求的像素可以避免後續運算
    float4 texColor = tex.Sample(sam, pIn.Tex);
    clip(texColor.a - 0.1f);

    // 標準化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 頂點指向眼睛的向量
    float3 toEyeW = normalize(gEyePosW - pIn.PosW);

    // 初始化為0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);
    int i;


    // 強制展開迴圈以減少指令數
    [unroll]
    for (i = 0; i < gNumDirLight; ++i)
    {
        ComputeDirectionalLight(gMaterial, gDirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    
    [unroll]
    for (i = 0; i < gNumPointLight; ++i)
    {
        PointLight pointLight = gPointLight[i];
        // 若當前在繪製反射物體,需要對光照進行反射矩陣變換
        [flatten]
        if (gIsReflection)
        {
            pointLight.Position = (float3) mul(float4(pointLight.Position, 1.0f), gReflection);
        }

        ComputePointLight(gMaterial, pointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    
    [unroll]
    for (i = 0; i < gNumSpotLight; ++i)
    {
        SpotLight spotLight = gSpotLight[i];
        // 若當前在繪製反射物體,需要對光照進行反射矩陣變換
        [flatten]
        if (gIsReflection)
        {
            spotLight.Position = (float3) mul(float4(spotLight.Position, 1.0f), gReflection);
        }

        ComputeSpotLight(gMaterial, spotLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    

    
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * gMaterial.Diffuse.a;
    return litColor;
}

Basic_VS_2D.hlsl變化如下:

#include "Basic.fx"

// 頂點著色器(2D)
Vertex2DOut VS_2D(Vertex2DIn pIn)
{
    Vertex2DOut pOut;
    pOut.PosH = float4(pIn.Pos, 1.0f);
    pOut.Tex = mul(float4(pIn.Tex, 0.0f, 1.0f), gTexTransform).xy;
    return pOut;
}

Basic_VS_3D.hlsl變化如下:

#include "Basic.fx"

// 頂點著色器(3D)
Vertex3DOut VS_3D(Vertex3DIn pIn)
{
    Vertex3DOut pOut;
    
    float4 posW = mul(float4(pIn.PosL, 1.0f), gWorld);
    // 若當前在繪製反射物體,先進行反射操作
    [flatten]
    if (gIsReflection)
    {
        posW = mul(posW, gReflection);
    }
    // 若當前在繪製陰影,先進行投影操作
    [flatten]
    if (gIsShadow)
    {
        posW = (gIsReflection ? mul(posW, gRefShadow) : mul(posW, gShadow));
    }

    pOut.PosH = mul(mul(posW, gView), gProj);
    pOut.PosW = mul(float4(pIn.Pos, 1.0f), gWorld).xyz;
    pOut.NormalW = mul(pIn.NormalL, (float3x3) gWorldInvTranspose);
    pOut.Tex = mul(float4(pIn.Tex, 0.0f, 1.0f), gTexTransform).xy;
    return pOut;
}

GameObject類與BasicObjectFX類的對接

由於GameObject類也承擔了繪製方法,那麼最後的Apply也需要交給游戲對象來調用。因此GameObject::Draw方法變更如下:

void GameObject::Draw(ComPtr<ID3D11DeviceContext> deviceContext, BasicObjectFX& effect)
{
    // 設置頂點/索引緩衝區
    UINT strides = sizeof(VertexPosNormalTex);
    UINT offsets = 0;
    deviceContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &strides, &offsets);
    deviceContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);

    // 更新數據並應用
    effect.SetWorldMatrix(XMLoadFloat4x4(&mWorldMatrix));
    effect.SetTexTransformMatrix(XMLoadFloat4x4(&mTexTransform));
    effect.SetTexture(mTexture);
    effect.SetMaterial(mMaterial);
    effect.Apply(deviceContext);

    deviceContext->DrawIndexed(mIndexCount, 0, 0);
}

場景繪製

現在場景只有牆體、地板、木箱和鏡面。

第1步: 鏡面區域寫入模板緩衝區

// *********************
// 1. 給鏡面反射區域寫入值1到模板緩衝區
// 

mBasicObjectFX.SetWriteStencilOnly(md3dImmediateContext, 1);
mMirror.Draw(md3dImmediateContext, mBasicObjectFX);

第2步: 繪製不透明的反射物體

// ***********************
// 2. 繪製不透明的反射物體
//

// 開啟反射繪製
mBasicObjectFX.SetReflectionState(true);
mBasicObjectFX.SetRenderDefaultWithStencil(md3dImmediateContext, 1);

mWalls[2].Draw(md3dImmediateContext, mBasicObjectFX);
mWalls[3].Draw(md3dImmediateContext, mBasicObjectFX);
mWalls[4].Draw(md3dImmediateContext, mBasicObjectFX);
mFloor.Draw(md3dImmediateContext, mBasicObjectFX);
mWoodCrate.Draw(md3dImmediateContext, mBasicObjectFX);

第3步: 繪製不透明反射物體的陰影

// ***********************
// 3. 繪製不透明反射物體的陰影
//

mWoodCrate.SetMaterial(mShadowMat);
mBasicObjectFX.SetShadowState(true);    // 反射開啟,陰影開啟            
mBasicObjectFX.SetRenderNoDoubleBlend(md3dImmediateContext, 1);

mWoodCrate.Draw(md3dImmediateContext, mBasicObjectFX);

// 恢復到原來的狀態
mBasicObjectFX.SetShadowState(false);
mWoodCrate.SetMaterial(mWoodCrateMat);

第4步: 繪製透明鏡面

// ***********************
// 4. 繪製透明鏡面
//

// 關閉反射繪製
mBasicObjectFX.SetReflectionState(false);
mBasicObjectFX.SetRenderAlphaBlendWithStencil(md3dImmediateContext, 1);

mMirror.Draw(md3dImmediateContext, mBasicObjectFX);

第5步:繪製不透明的正常物體

// ************************
// 5. 繪製不透明的正常物體
//
mBasicObjectFX.SetRenderDefault(md3dImmediateContext);

for (auto& wall : mWalls)
    wall.Draw(md3dImmediateContext, mBasicObjectFX);
mFloor.Draw(md3dImmediateContext, mBasicObjectFX);
mWoodCrate.Draw(md3dImmediateContext, mBasicObjectFX);

第6步:繪製不透明正常物體的陰影

// ************************
// 6. 繪製不透明正常物體的陰影
//
mWoodCrate.SetMaterial(mShadowMat);
mBasicObjectFX.SetShadowState(true);    // 反射關閉,陰影開啟
mBasicObjectFX.SetRenderNoDoubleBlend(md3dImmediateContext, 0);

mWoodCrate.Draw(md3dImmediateContext, mBasicObjectFX);

mBasicObjectFX.SetShadowState(false);       // 陰影關閉
mWoodCrate.SetMaterial(mWoodCrateMat);

最終繪製效果如下:

註意該樣例只生成點光燈到地板的陰影。你可以用各種攝像機模式來進行測試。

DirectX11 With Windows SDK完整目錄

Github項目源碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 開發現狀 隨著技術的發展,移動開發設備遍地開花,各種各樣的移動設備不如人們的家庭。移動設備不局限於手機、IPA等設備。由於Android開源,可在條款允許範圍內修改代碼二次開發,結合物聯網技術的迅速普及,各種Android應用進入物聯網之中。智能手機、智能電視等各種擁有交互需求的設備大部分都是基於A ...
  • 概述 ScreenMatch是根據你的需要,生成需要適配的尺寸的文件,手機會根據屏幕相關參數自動尋找合適的尺寸文件 添加插件 如圖,打開Android Studio的Settings設置,找到Plugins,點擊Browse Repositories,在彈出的輸入框里填入screenMatch,就可 ...
  • https://www.cnblogs.com/chenqf/p/6386163.html 前言 Http 緩存機製作為 web 性能優化的重要手段,對於從事 Web 開發的同學們來說,應該是知識體系庫中的一個基礎環節,同時對於有志成為前端架構師的同學來說是必備的知識技能。但是對於很多前端同學來說, ...
  • 1)名字VS值 名字和記憶體(存儲)位置相關聯。 名字—(環境)———>位置——(狀態)——>值 這兩個映射都在隨著程式的運行而改變。 2)環境VS狀態 環境是指一個名字到存儲位置映射,也可以說是名字到變數(左值)的映射,環境的改變需要遵守語言的作用於與規則; 狀態是一個從記憶體位置到它的值的映射,即把 ...
  • 上篇介紹了Util的開發環境,並讓你把Demo運行起來。本文將介紹該Demo的前端Angular運行機制以及目錄結構。 目錄結構 在VS上打開Util Demo,會看見如下的目錄結構。 現代前端通常採用VS Code開發,不過我們為了使用TagHelper,需要採用VS開發,這為你提供了更多的選擇。 ...
  • 題意 題目鏈接 求出把$n$分解為斐波那契數的方案數,方案兩兩不同的定義是分解出來的數不完全相同 Sol 這種題,直接爆搜啊。。。 打表後不難發現$<=1e18$的fib數只有88個 最先想到的應該是直接把$n$加入到搜索狀態里,然後枚舉能被分成哪些 但是這樣分解出來的數可能會有重覆的,因此我們還要 ...
  • lambda 表達式 介紹 問題:假設有個需求是,在vector\找出所有長度大於等於4的元素。標準庫find_if函數的第三參數是函數指針,但是這個函數指針指向的函數只能接受一個參數,這個參數是vector\里的元素。這時問題就來了,長度4無法作為參數傳遞, 腫麽辦??? 解決辦法:使用lambd ...
  • Java當中JVM 01 在使用控制面板時的實質: 使用 ,然後變成為 通過運行 這個命令,在類載入器中(含有載入,驗證,準備,解析,初始化,使用,卸載),到 為Java虛擬機中運行,在 中有方法區,堆記憶體,線程棧,本地方法棧, 計數器。 類載入器: 1. 載入, 2. 驗證, 文件的版本是否能相容 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...