iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

接下來要做的事情就是把前面介紹的內容串起來,組成三角形

main.cpp

#include <cstdlib>
#include <directx/d3dx12_core.h>
#include <exception>
#include <stdexcept>

#include "graphics_engine.h"
#include "my_engine.h"
#include "skyline_debugger.h"
#include "system.h"

namespace
{
    // 定義三角形單一頂點的位置資料
    struct SimpleVertex
    {
        float x, y, z;
    };

    // 初始化三角形使用的 Root Signature
    void initRootSignature(RootSignature & rs)
    {
        rs.init(D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_WRAP,
                D3D12_TEXTURE_ADDRESS_MODE_WRAP);
    }

    // 建立繪製三角形需要的 Graphics Pipeline State 描述
    D3D12_GRAPHICS_PIPELINE_STATE_DESC createTrianglePipelineStateDescription(
        ID3D12RootSignature * rootSignature, ID3DBlob * vertexShader, ID3DBlob * pixelShader)
    {
        // 確認有傳入有效的 Root Signature
        if (rootSignature == nullptr)
            throw std::invalid_argument("TrianglePipeline: Root signature is required.");

        // 確認有傳入有效的 Vertex Shader
        if (vertexShader == nullptr)
            throw std::invalid_argument("TrianglePipeline: Vertex shader is required.");

        // 確認有傳入有效的 Pixel Shader
        if (pixelShader == nullptr)
            throw std::invalid_argument("TrianglePipeline: Pixel shader is required.");

        // 定義 Vertex Buffer 中 POSITION 資料的格式
        static constexpr D3D12_INPUT_ELEMENT_DESC inputElementDescriptions[] = {
            {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
        };

        // 建立並初始化 Graphics Pipeline State 描述
        D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};

        // 設定 Vertex Shader 使用的 Input Layout
        description.InputLayout = {inputElementDescriptions, _countof(inputElementDescriptions)};

        // 設定 Pipeline 使用的 Root Signature
        description.pRootSignature = rootSignature;

        // 設定 Vertex Shader Bytecode
        description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);

        // 設定 Pixel Shader Bytecode
        description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);

        // 使用 Direct3D 12 預設 Rasterizer 設定
        description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);

        // 關閉三角形面的 Cull
        description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;

        // 使用 Direct3D 12 預設 Blend 設定
        description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);

        // 使用 Direct3D 12 預設 Depth Stencil 設定
        description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);

        // 關閉 Depth Test
        description.DepthStencilState.DepthEnable = FALSE;

        // 禁止寫入 Depth Buffer
        description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;

        // 關閉 Stencil Test
        description.DepthStencilState.StencilEnable = FALSE;

        // 允許所有 Multisampling Sample 被處理
        description.SampleMask = UINT_MAX;

        // 指定 Pipeline 使用 Triangle 類型的 Primitive
        description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;

        // 指定同時使用一個 Render Target
        description.NumRenderTargets = 1;

        // 設定第一個 Render Target 的像素格式
        description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;

        // 設定 Depth Stencil Buffer 的格式
        description.DSVFormat = DXGI_FORMAT_D32_FLOAT;

        // 設定每個 Pixel 只使用一個 Sample
        description.SampleDesc.Count = 1;

        // 回傳設定完成的 Graphics Pipeline State 描述
        return description;
    }

    // 建立並初始化三角形使用的 Pipeline State
    void initPipelineState(PipelineState & pipelineState, RootSignature & rs, Shader & vs, Shader & ps)
    {
        // 使用 Root Signature 與 Shader 建立 Graphics Pipeline State 描述
        const D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc =
            createTrianglePipelineStateDescription(rs.get(), vs.getCompiledBlob(), ps.getCompiledBlob());

        // 使用建立好的描述初始化 Pipeline State
        pipelineState.init(psoDesc);
    }
}

// Windows 應用程式的主進入點
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    try
    {
        // 初始化 Skyline Debugger
        SkylineDebugger::Initialize("DayX_Triangle");

        // 建立應用程式視窗
        initWindow(hInstance, hPrevInstance, lpCmdLine, nCmdShow, TEXT("Game"));

        // 確認應用程式視窗建立成功
        if (g_hWnd == nullptr)
            throw std::runtime_error("DayX_Triangle: Failed to create the application window.");

        // 建立 Graphics Engine
        GraphicsEngine graphicsEngine;

        // 使用視窗與 Frame Buffer 大小初始化 Graphics Engine
        graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);

        // 將 D3D12 Device 設定給 Skyline Debugger
        SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());

        // 建立 Root Signature
        RootSignature rootSignature;

        // 初始化三角形使用的 Root Signature
        initRootSignature(rootSignature);

        // 建立 Vertex Shader 與 Pixel Shader 物件
        Shader vs, ps;

        // 編譯 triangle.hlsl 中的 VSMain
        vs.loadVS("assets/shaders/triangle.hlsl", "VSMain");

        // 編譯 triangle.hlsl 中的 PSMain
        ps.loadPS("assets/shaders/triangle.hlsl", "PSMain");

        // 建立 Pipeline State
        PipelineState pipelineState;

        // 初始化三角形使用的 Graphics Pipeline State
        initPipelineState(pipelineState, rootSignature, vs, ps);

        // 建立三角形的三個頂點位置
        SimpleVertex vertices[] = {
            {-0.5f, -0.5f, 0.0f},
            {0.0f, 0.5f, 0.0f},
            {0.5f, -0.5f, 0.0f},
        };

        // 建立三角形使用的 Vertex Buffer
        VertexBuffer triangleVB;

        // 依照頂點數量與單一頂點大小初始化 Vertex Buffer
        triangleVB.init(_countof(vertices), sizeof(vertices[0]));

        // 將三角形頂點資料複製到 Vertex Buffer
        triangleVB.copy(vertices);

        // 定義三角形使用的三個 Index
        uint16_t indices[] = {0, 1, 2};

        // 建立三角形使用的 Index Buffer
        IndexBuffer triangleIB;

        // 依照 Index 數量與單一 Index 大小初始化 Index Buffer
        triangleIB.init(_countof(indices), sizeof(indices[0]));

        // 將三角形 Index 資料複製到 Index Buffer
        triangleIB.copy(indices);

        // 取得 Graphics Engine 使用的 Render Context
        RenderContext& renderContext = graphicsEngine.getRenderContext();

        // 將目前 Graphics Engine 的 Command List 設定給 Render Context
        renderContext.SetCommandList(graphicsEngine.getCommandList());

        // 持續處理視窗訊息並執行每一幀的繪製
        while (dispatchWindowMessage())
        {
            // 開始目前 Frame 的 Rendering
            graphicsEngine.beginRender();

            // 設定 Graphics Pipeline 使用的 Root Signature
            renderContext.setRootSignature(rootSignature);

            // 設定目前使用的 Graphics Pipeline State
            renderContext.setPipelineState(pipelineState);

            // 設定 Input Assembler 使用 Triangle List
            renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

            // 將三角形 Vertex Buffer 綁定到 Input Assembler
            renderContext.setVertexBuffer(triangleVB);

            // 將三角形 Index Buffer 綁定到 Input Assembler
            renderContext.setIndexBuffer(triangleIB);

            // 使用三個 Index 繪製三角形
            renderContext.drawIndexed(3);

            // 結束目前 Frame 並提交繪製結果
            graphicsEngine.endRender();

            // 將 Direct3D 12 Debug Layer 訊息輸出到 Debugger
            SkylineDebugger::LogD3D12Messages(graphicsEngine.getD3DDevice());
        }

        // 關閉 Skyline Debugger
        SkylineDebugger::Shutdown();

        // 程式正常結束
        return EXIT_SUCCESS;
    }
    catch (const std::exception& exception)
    {
        // 顯示標準例外造成的致命錯誤
        SkylineDebugger::ShowFatalError("DayX_Triangle initialization failed", exception);

        // 關閉 Skyline Debugger
        SkylineDebugger::Shutdown();

        // 回傳程式執行失敗
        return EXIT_FAILURE;
    }
    catch (...)
    {
        SkylineDebugger::ShowFatalError("DayX_Triangle initialization failed", "An unknown fatal error occurred.");
        SkylineDebugger::Shutdown();

        return EXIT_FAILURE;
    }
}

最後要輸出三角形前,需要設定對應的 shader,之後會介紹 HLSL 的語法,這邊先簡單帶過

//triangle.hlsl

// 定義 Vertex Shader 接收的輸入資料
struct VSInput
{
    float4 position : POSITION; // 接收 Vertex Buffer 傳入的頂點位置
};

// 定義 Vertex Shader 傳出的輸出資料
struct VSOutput
{
    float4 position : SV_POSITION; // 輸出頂點經過 Vertex Shader 處理後的位置
};

// 接收頂點資料並輸出處理後的位置
VSOutput VSMain(VSInput input)
{
    // 建立 VSOutput 並將所有欄位初始化為 0
    VSOutput vsOut = (VSOutput) 0;

    // 將輸入的頂點位置直接傳給輸出位置
    vsOut.position = input.position;

    // 回傳處理完成的 Vertex Shader 輸出資料
    return vsOut;
}

// 接收 Vertex Shader 輸出並決定 Pixel 的最終顏色
float4 PSMain(VSOutput vsOut) : SV_Target0
{
    // 輸出不透明的紅色
    return float4(1.0f, 0.0f, 0.0f, 1.0f);
}

整個流程是這種感覺

Win32 Window
    │
    ▼
GraphicsEngine::init()
    │
    ├─ DXGI Factory
    ├─ D3D12 Device
    ├─ Command Queue
    ├─ Command Allocator
    ├─ Command List
    ├─ Swap Chain
    ├─ RTV / Back Buffer
    ├─ DSV / Depth Buffer
    └─ Fence
    │
    ▼
Root Signature
    │
    ▼
VS / PS
    │
    ▼
Pipeline State Object
    │
    ▼
Vertex Buffer + Index Buffer
    │
    ▼
Main Loop
    │
    ├─ beginRender()
    │
    ├─ Set Root Signature
    ├─ Set PSO
    ├─ Set Primitive Topology
    ├─ Set Vertex Buffer
    ├─ Set Index Buffer
    ├─ DrawIndexed()
    │
    └─ endRender()
         │
         ├─ Close Command List
         ├─ ExecuteCommandLists()
         ├─ Present()
         └─ Fence

最終結果

參考資料

DirectX 12の魔導書 3Dレンダリングの基礎からMMDモデルを踊らせるまで


上一篇
Day 9 :Graphics Pipeline 介紹(5) - Shader 設定
下一篇
Day 11:基本 HLSL 介紹
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言