當然看過前面的程式後,除了有重複的程式外,也開始會發現有順序問題。像是太陽光最強的地方會蓋過天空色、在漸漸減弱時天空色會再次顯現、甚至後面範例的雲會混合更多的色層。
在這邊為了順利傳遞需要的數值到下一個渲染,就需要 Pass 的概念了。
一般的引擎都會有渲染順序的設定,這邊拿 UE 的 Rendering 教學當作範例。
Begin Play | Rendering | Tutorial
但這些常見的引擎都已經把相關的實作包的非常上層,底層這些 Pass 是怎麼傳遞的得直接開 RenderDoc 才看的到,譬如以下的文章。
雖然是 2017 的文章了,不過可以看看他是如何分析裡面內容的
不過這專案畢竟是用來熟悉基礎的,先從簡單的設定開始比較好。
因此在這裡,再把前面的程式重新重構一次
render_pass.h
#pragma once
#include <d3d12.h>
class GraphicsEngine;
class RenderContext;
class RootSignature;
//初始化渲染階段時所需的共用資源
struct RenderPassInitContext
{
GraphicsEngine& graphicsEngine;
RootSignature& rootSignature;
};
//執行單一畫格的渲染階段時所需的資料
struct RenderFrameContext
{
RenderContext& renderContext;
UINT width;
UINT height;
};
//所有渲染階段的共用介面
class RenderPass
{
public:
virtual ~RenderPass() = default;
//建立此渲染階段所需的固定資源與 pipeline 狀態
virtual void init(const RenderPassInitContext& context) = 0;
//將此渲染階段的命令記錄至目前 frame 的 RenderContext
virtual void execute(const RenderFrameContext& context) = 0;
};
common_pass.h
#pragma once
#include "constant_buffer.h"
#include "math/matrix.h"
#include "pipeline_state.h"
#include "render_pass.h"
#include "shader.h"
#include "math/vector.h"
class CommonPass final : public RenderPass
{
public:
void init(const RenderPassInitContext& context) override;
void execute(const RenderFrameContext& context) override;
private:
struct CameraConstants
{
Matrix inverseView;
Matrix inverseProjection;
Vector3 cameraPosition;
float padding;
};
static_assert(sizeof(CameraConstants) == 144,
"CameraConstants must match the HLSL constant-buffer layout.");
void updateCameraConstants(UINT width, UINT height);
Shader m_vertexShader;
Shader m_pixelShader;
PipelineState m_pipelineState;
ConstantBuffer m_cameraConstantBuffer;
CameraConstants m_cameraConstants{};
};
common_pass.cpp
#include "common_pass.h"
#include "cloudscape_camera.h"
#include <stdexcept>
#include <directx/d3dx12_core.h>
#include "graphics_engine.h"
#include "root_signature.h"
#include "render_context.h"
namespace
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC createPipelineStateDescription(
ID3D12RootSignature* rootSignature, ID3DBlob* vertexShader, ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
{
throw std::invalid_argument("CommonPass: Root signature is required.");
}
if (vertexShader == nullptr)
{
throw std::invalid_argument("CommonPass: Vertex shader is required.");
}
if (pixelShader == nullptr)
{
throw std::invalid_argument("CommonPass: Pixel shader is required.");
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
description.InputLayout = {nullptr, 0};
description.pRootSignature = rootSignature;
description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);
description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);
description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
description.DepthStencilState.DepthEnable = FALSE;
description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
description.DepthStencilState.StencilEnable = FALSE;
description.SampleMask = UINT_MAX;
description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
description.NumRenderTargets = 1;
description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
description.DSVFormat = DXGI_FORMAT_D32_FLOAT;
description.SampleDesc.Count = 1;
return description;
}
}
void CommonPass::updateCameraConstants(UINT width, UINT height)
{
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
const Vector3& cameraPosition = CloudscapeCamera::position;
const Vector3& cameraTarget = CloudscapeCamera::target;
const Vector3& worldUp = CloudscapeCamera::up;
Matrix view;
view.MakeLookAt(cameraPosition, cameraTarget, worldUp);
Matrix projection;
projection.MakeProjectionMatrix(Math::PI / 3.0f, aspectRatio, 0.1f, 1000.0f);
// HLSL 以 mul(vector, matrix) 使用矩陣,因此上傳前需要轉置。
m_cameraConstants.inverseView.Inverse(view);
m_cameraConstants.inverseView.Transpose();
m_cameraConstants.inverseProjection.Inverse(projection);
m_cameraConstants.inverseProjection.Transpose();
m_cameraConstants.cameraPosition = cameraPosition;
m_cameraConstants.padding = 0.0f;
}
void CommonPass::init(const RenderPassInitContext& context)
{
m_vertexShader.loadVS("assets/shaders/common/common.hlsl", "VSMain");
m_pixelShader.loadPS("assets/shaders/common/common.hlsl", "PSMain");
const D3D12_GRAPHICS_PIPELINE_STATE_DESC description = createPipelineStateDescription(
context.rootSignature.get(), m_vertexShader.getCompiledBlob(), m_pixelShader.getCompiledBlob());
m_pipelineState.init(description);
const UINT width = context.graphicsEngine.getFrameBufferWidth();
const UINT height = context.graphicsEngine.GetFrameBufferHeight();
if (width == 0 || height == 0)
{
throw std::runtime_error("CommonPass: Frame-buffer dimensions must be non-zero during initialization.");
}
updateCameraConstants(width, height);
m_cameraConstantBuffer.init(sizeof(CameraConstants), &m_cameraConstants);
}
void CommonPass::execute(const RenderFrameContext& context)
{
if (context.width == 0 || context.height == 0)
{
return;
}
updateCameraConstants(context.width, context.height);
m_cameraConstantBuffer.copyToVRAM(m_cameraConstants);
context.renderContext.setPipelineState(m_pipelineState);
context.renderContext.setGraphicsRootConstantBufferView(
0, m_cameraConstantBuffer.getGPUVirtualAddress());
context.renderContext.draw(3);
}
light_pass.h
#pragma once
#include "constant_buffer.h"
#include "common.h"
#include "math/matrix.h"
#include "pipeline_state.h"
#include "render_pass.h"
#include "shader.h"
#include "math/vector.h"
class LightPass final : public RenderPass
{
public:
explicit LightPass(const CloudSettings& settings);
void init(const RenderPassInitContext& context) override;
void execute(const RenderFrameContext& context) override;
void setSettings(const CloudSettings& settings);
private:
struct SunConstants
{
Matrix inverseView;
Matrix inverseProjection;
Vector3 sunDirection;
float sunRadius;
Vector3 sunColor;
float sunIntensity;
};
static_assert(sizeof(SunConstants) == 160, "SunConstants must match the HLSL constant-buffer layout.");
void updateCameraConstants(UINT width, UINT height);
Shader m_vertexShader;
Shader m_pixelShader;
PipelineState m_pipelineState;
ConstantBuffer m_constantBuffer;
SunConstants m_constants{};
};
light_pass.cpp
#include "light_pass.h"
#include "cloudscape_camera.h"
#include <stdexcept>
#include <directx/d3dx12_core.h>
#include "graphics_engine.h"
#include "root_signature.h"
#include "render_context.h"
namespace
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC createPipelineStateDescription(ID3D12RootSignature* rootSignature,
ID3DBlob* vertexShader, ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
{
throw std::invalid_argument("LightPass: Root signature is required.");
}
if (vertexShader == nullptr)
{
throw std::invalid_argument("LightPass: Vertex shader is required.");
}
if (pixelShader == nullptr)
{
throw std::invalid_argument("LightPass: Pixel shader is required.");
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
description.InputLayout = {nullptr, 0};
description.pRootSignature = rootSignature;
description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);
description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);
description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
D3D12_RENDER_TARGET_BLEND_DESC& renderTarget = description.BlendState.RenderTarget[0];
renderTarget.BlendEnable = TRUE;
renderTarget.SrcBlend = D3D12_BLEND_SRC_ALPHA;
renderTarget.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
renderTarget.BlendOp = D3D12_BLEND_OP_ADD;
renderTarget.SrcBlendAlpha = D3D12_BLEND_ONE;
renderTarget.DestBlendAlpha = D3D12_BLEND_ZERO;
renderTarget.BlendOpAlpha = D3D12_BLEND_OP_ADD;
renderTarget.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
description.DepthStencilState.DepthEnable = FALSE;
description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
description.DepthStencilState.StencilEnable = FALSE;
description.SampleMask = UINT_MAX;
description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
description.NumRenderTargets = 1;
description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
description.DSVFormat = DXGI_FORMAT_D32_FLOAT;
description.SampleDesc.Count = 1;
return description;
}
}
LightPass::LightPass(const CloudSettings& settings)
{
setSettings(settings);
}
void LightPass::setSettings(const CloudSettings& settings)
{
m_constants.sunDirection = settings.sunDirection;
m_constants.sunDirection.Normalize();
m_constants.sunRadius = 0.055f;
m_constants.sunColor = settings.sunColor;
m_constants.sunIntensity = settings.sunIntensity;
}
void LightPass::updateCameraConstants(UINT width, UINT height)
{
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
const Vector3& cameraPosition = CloudscapeCamera::position;
const Vector3& cameraTarget = CloudscapeCamera::target;
const Vector3& worldUp = CloudscapeCamera::up;
Matrix view;
view.MakeLookAt(cameraPosition, cameraTarget, worldUp);
Matrix projection;
projection.MakeProjectionMatrix(Math::PI / 3.0f, aspectRatio, 0.1f, 1000.0f);
// HLSL 以 mul(vector, matrix) 使用矩陣,因此上傳前需要轉置。
m_constants.inverseView.Inverse(view);
m_constants.inverseView.Transpose();
m_constants.inverseProjection.Inverse(projection);
m_constants.inverseProjection.Transpose();
}
void LightPass::init(const RenderPassInitContext& context)
{
m_vertexShader.loadVS("assets/shaders/sun.hlsl", "VSMain");
m_pixelShader.loadPS("assets/shaders/sun.hlsl", "PSMain");
const D3D12_GRAPHICS_PIPELINE_STATE_DESC description = createPipelineStateDescription(
context.rootSignature.get(), m_vertexShader.getCompiledBlob(), m_pixelShader.getCompiledBlob());
m_pipelineState.init(description);
const UINT width = context.graphicsEngine.getFrameBufferWidth();
const UINT height = context.graphicsEngine.GetFrameBufferHeight();
if (width == 0 || height == 0)
throw std::runtime_error("LightPass: Frame-buffer dimensions must be non-zero during initialization.");
updateCameraConstants(width, height);
m_constantBuffer.init(sizeof(SunConstants), &m_constants);
}
void LightPass::execute(const RenderFrameContext& context)
{
if (context.height == 0)
{
return;
}
updateCameraConstants(context.width, context.height);
m_constantBuffer.copyToVRAM(m_constants);
context.renderContext.setPipelineState(m_pipelineState);
context.renderContext.setGraphicsRootConstantBufferView(0, m_constantBuffer.getGPUVirtualAddress());
context.renderContext.draw(3);
}
//renderer.h
#pragma once
#include <memory>
#include <vector>
#include "root_signature.h"
class GraphicsEngine;
class LightPass;
class RenderPass;
class Renderer
{
public:
explicit Renderer(GraphicsEngine& graphicsEngine);
~Renderer();
void init();
void render();
void clearRenderItems();
private:
GraphicsEngine& m_graphicsEngine;
RootSignature m_rootSignature;
std::vector<std::unique_ptr<RenderPass>> m_passes;
LightPass* m_lightPass = nullptr;
bool m_initialized = false;
};
//renderer.cpp
#include "renderer.h"
#include <stdexcept>
#include "common_pass.h"
#include "graphics_engine.h"
#include "light_pass.h"
#include "render_context.h"
#include "render_pass.h"
// 保存 GraphicsEngine 的參考供 Renderer 後續使用
Renderer::Renderer(GraphicsEngine& graphicsEngine)
: m_graphicsEngine(graphicsEngine)
{
// Renderer 建立前必須確認 D3D12 Device 已完成初始化
if (m_graphicsEngine.getD3DDevice() == nullptr)
{
throw std::runtime_error("Graphics engine is not initialized");
}
}
// 使用編譯器產生的預設解構行為
Renderer::~Renderer() = default;
// 初始化 Renderer 所需的 Root Signature 與各個 Render Pass
void Renderer::init()
{
// 防止同一個 Renderer 被重複初始化
if (m_initialized)
{
throw std::logic_error(
"Renderer: Renderer is already initialized.");
}
// 初始化 Root Signature 使用的 Static Sampler 設定
m_rootSignature.init(
D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP);
// 將 GraphicsEngine 與 Root Signature 包裝成 Render Pass 初始化環境
RenderPassInitContext initContext{
m_graphicsEngine,
m_rootSignature
};
// 建立處理共用繪製工作的 Common Pass
auto commonPass = std::make_unique<CommonPass>();
// 初始化 Common Pass 所需的 GPU 資源
commonPass->init(initContext);
// 將 Common Pass 所有權移交給 Renderer 統一管理
m_passes.push_back(std::move(commonPass));
// 建立 Light Pass
auto lightPass = std::make_unique<LightPass>();
// 保存非擁有型指標,供 Renderer 後續使用
m_lightPass = lightPass.get();
// 初始化 Light Pass 所需的 GPU 資源
lightPass->init(initContext);
// 將 Light Pass 所有權移交給 Renderer 統一管理
m_passes.push_back(std::move(lightPass));
// 標記 Renderer 已成功完成初始化
m_initialized = true;
}
// 依照 Render Pass 順序執行目前影格的所有繪製工作
void Renderer::render()
{
// 取得目前 GraphicsEngine 使用的 RenderContext
RenderContext& renderContext =
m_graphicsEngine.getRenderContext();
// 將此 Renderer 的 Root Signature 綁定至 Command List
renderContext.setRootSignature(m_rootSignature);
// 將圖元拓樸設定為 Triangle List
renderContext.setPrimitiveTopology(
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 建立本影格共用的繪製環境並帶入 Frame Buffer 尺寸
RenderFrameContext frameContext{
renderContext,
m_graphicsEngine.getFrameBufferWidth(),
m_graphicsEngine.GetFrameBufferHeight()
};
// 依照加入順序執行所有 Render Pass
for (const auto& pass : m_passes)
{
// 讓目前 Pass 使用本影格的 RenderContext 執行繪製
pass->execute(frameContext);
}
}
main 會像這樣
//main.cpp
#include <cstdlib>
#include <exception>
#include "graphics_engine.h"
#include "my_engine.h"
#include "render_pass/renderer.h"
#include "skyline_debugger.h"
#include "system.h"
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, LPWSTR commandLine, int showCommand)
{
try
{
SkylineDebugger::Initialize("DayX_Pass");
initWindow(instance, previous, commandLine, showCommand, TEXT("DayX Pass"));
GraphicsEngine graphicsEngine;
graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);
SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());
Renderer renderer(graphicsEngine);
renderer.init();
while (dispatchWindowMessage())
{
graphicsEngine.beginRender();
renderer.render();
graphicsEngine.endRender();
SkylineDebugger::LogD3D12Messages(graphicsEngine.getD3DDevice());
}
SkylineDebugger::Shutdown();
return EXIT_SUCCESS;
}
catch (const std::exception& error)
{
SkylineDebugger::ShowFatalError("DayX_Pass initialization failed", error);
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
catch (...)
{
SkylineDebugger::ShowFatalError("DayX_Pass initialization failed",
"An unknown fatal error occurred.");
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
}
DirectX 12の魔導書 3Dレンダリングの基礎からMMDモデルを踊らせるまで
Begin Play | Rendering | Tutorial