今天是太陽的部分,主要註解跟太陽有關的實作。
// 定義 Vertex Shader 傳給 Pixel Shader 的資料
struct VSOutput
{
// 儲存頂點的裁切空間位置
float4 position : SV_POSITION;
// 儲存對應的螢幕 UV 座標
float2 uv : TEXCOORD0;
};
// 定義太陽相關參數並綁定到 b0
cbuffer SunConstants : register(b0)
{
// 儲存太陽在螢幕上的位置
float2 sunPosition;
// 儲存太陽圓盤半徑
float sunRadius;
// 儲存太陽亮度強度
float sunIntensity;
// 儲存太陽顏色
float3 sunColor;
// 儲存螢幕寬高比
float aspectRatio;
};
// 根據 Vertex ID 建立 Fullscreen Triangle
VSOutput VSMain(uint vertexID : SV_VertexID)
{
// 建立 Vertex Shader 輸出資料
VSOutput output;
// 定義覆蓋整個畫面的三角形頂點位置
float2 positions[3] =
{
float2(-1.0f, -1.0f),
float2(-1.0f, 3.0f),
float2(3.0f, -1.0f)
};
// 取得目前 Vertex ID 對應的頂點位置
float2 position = positions[vertexID];
// 將二維位置轉換成裁切空間座標
output.position = float4(position, 0.0f, 1.0f);
// 將 NDC 座標轉換成 UV 座標並反轉 Y 軸
output.uv = position * float2(0.5f, -0.5f) + 0.5f;
// 回傳 Vertex Shader 輸出
return output;
}
// 計算太陽圓盤與外圍光暈顏色
float4 PSMain(VSOutput input) : SV_TARGET
{
// 將 UV 從 [0, 1] 轉換成 [-1, 1] 的螢幕座標
float2 screenPosition = input.uv * 2.0f - 1.0f;
// 計算目前 Pixel 相對於太陽中心的位置差
float2 delta = screenPosition - sunPosition;
// 依照視窗比例修正水平距離,避免太陽在寬螢幕上被拉伸
delta.x *= aspectRatio;
// 計算目前 Pixel 到太陽中心的距離
float distanceFromSun = length(delta);
// 設定太陽圓盤邊緣的柔化範圍
float edgeSoftness = 0.005f;
// 建立具有柔和邊緣的太陽圓盤遮罩
float sunDisc = 1.0f - smoothstep(sunRadius, sunRadius + edgeSoftness, distanceFromSun);
// 建立從太陽邊緣向外逐漸衰減的光暈遮罩
float glow = 1.0f - smoothstep(sunRadius, sunRadius * 4.0f, distanceFromSun);
// 結合太陽圓盤與較弱的光暈計算最終 RGB 顏色
float3 finalColor = sunColor * (sunDisc * sunIntensity + glow * sunIntensity * 0.2f);
// 根據太陽圓盤與光暈強度計算透明度
float alpha = saturate(sunDisc + glow * 0.15f);
// 輸出太陽的最終顏色與透明度
return float4(finalColor, alpha);
}
#include <cstdlib>
#include <exception>
#include <stdexcept>
#include <directx/d3dx12_core.h>
#include "constant_buffer.h"
#include "graphics_engine.h"
#include "render_context.h"
#include "skyline_debugger.h"
#include "system.h"
#include "math/vector.h"
namespace
{
// 定義傳給太陽 Shader 的 Constant Buffer 資料
struct SunConstants
{
// 太陽在螢幕座標中的中心位置
Vector2 sunPosition;
// 太陽圓盤半徑
float sunRadius;
// 太陽亮度強度
float sunIntensity;
// 太陽顏色
Vector3 sunColor;
// 畫面寬高比,用來修正太陽形狀
float aspectRatio;
};
// 確認 C++ 結構大小與 HLSL Constant Buffer layout 一致
static_assert(sizeof(SunConstants) == 32, "SunConstants must match the HLSL constant-buffer layout.");
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);
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC createSkyPipelineStateDescription(
ID3D12RootSignature* rootSignature,
ID3DBlob* vertexShader,
ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
throw std::invalid_argument("SkyPipeline: Root signature is required.");
if (vertexShader == nullptr)
throw std::invalid_argument("SkyPipeline: Vertex shader is required.");
if (pixelShader == nullptr)
throw std::invalid_argument("SkyPipeline: 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;
}
// 建立繪製太陽使用的 Graphics Pipeline State 設定
D3D12_GRAPHICS_PIPELINE_STATE_DESC createLightStateDescription(
ID3D12RootSignature* rootSignature,
ID3DBlob* vertexShader,
ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
throw std::invalid_argument("LightPipeline: Root signature is required.");
if (vertexShader == nullptr)
throw std::invalid_argument("LightPipeline: Vertex shader is required.");
if (pixelShader == nullptr)
throw std::invalid_argument("LightPipeline: Pixel shader is required.");
D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
// Fullscreen Triangle 由 SV_VertexID 產生,因此不需要 Vertex Buffer Input Layout
description.InputLayout = {nullptr, 0};
description.pRootSignature = rootSignature;
// 設定太陽使用的 Vertex Shader 與 Pixel Shader
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;
// 使用 Alpha Blending 讓太陽與光暈疊加在天空背景上
description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
// 取得第一個 Render Target 的 Blend 設定
auto& rt = description.BlendState.RenderTarget[0];
// 啟用顏色混合
rt.BlendEnable = TRUE;
// Source 顏色乘上 Pixel Shader 輸出的 Alpha
rt.SrcBlend = D3D12_BLEND_SRC_ALPHA;
// 背景顏色乘上 1 - Source Alpha
rt.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
// 將 Source 與 Destination 顏色相加
rt.BlendOp = D3D12_BLEND_OP_ADD;
// Alpha 輸出直接使用 Source Alpha
rt.SrcBlendAlpha = D3D12_BLEND_ONE;
// 不混合 Destination Alpha
rt.DestBlendAlpha = D3D12_BLEND_ZERO;
// 使用加法產生最終 Alpha
rt.BlendOpAlpha = D3D12_BLEND_OP_ADD;
// 允許寫入 RGBA 所有 Color Channel
rt.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;
}
void initPipelineState(PipelineState& pipelineState, RootSignature& rs, Shader& vs, Shader& ps)
{
const D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc =
createSkyPipelineStateDescription(rs.get(), vs.getCompiledBlob(), ps.getCompiledBlob());
pipelineState.init(psoDesc);
}
// 初始化太陽專用的 Pipeline State
void initLightPipelineState(PipelineState& pipelineState, RootSignature& rs, Shader& vs, Shader& ps)
{
// 建立包含 Alpha Blending 設定的太陽 Pipeline 描述
const D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc =
createLightStateDescription(rs.get(), vs.getCompiledBlob(), ps.getCompiledBlob());
// 建立太陽使用的 Pipeline State Object
pipelineState.init(psoDesc);
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
try
{
SkylineDebugger::Initialize("DayX_Light");
SKYLINE_LOG(INFO) << "[DayX_Light] Initializing window...";
initWindow(hInstance, hPrevInstance, lpCmdLine, nCmdShow, TEXT("Game"));
if (g_hWnd == nullptr)
throw std::runtime_error("DayX_Light: Failed to create the application window.");
SKYLINE_LOG(INFO) << "[DayX_Light] Initializing graphics engine...";
GraphicsEngine graphicsEngine;
graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);
// 設定太陽初始位置、大小、亮度、顏色與畫面比例
SunConstants sunConstants{
Vector2{-0.55f, 0.25f},
0.08f,
1.5f,
Vector3{1.0f, 0.85f, 0.55f},
static_cast<float>(FRAME_BUFFER_W) / static_cast<float>(FRAME_BUFFER_H)
};
// 建立儲存 SunConstants 的 Constant Buffer
ConstantBuffer sunConstantBuffer;
// 將太陽初始參數寫入 Constant Buffer
sunConstantBuffer.init(sizeof(SunConstants), &sunConstants);
SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());
SKYLINE_LOG(INFO) << "[DayX_Light] Initializing root signature...";
RootSignature rootSignature;
initRootSignature(rootSignature);
SKYLINE_LOG(INFO) << "[DayX_Light] Compiling shaders...";
Shader skyVS, skyPS;
skyVS.loadVS("assets/shaders/sky_gradient.hlsl", "VSMain");
skyPS.loadPS("assets/shaders/sky_gradient.hlsl", "PSMain");
// 建立太陽使用的 Vertex Shader 與 Pixel Shader
Shader sunVS, sunPS;
// 載入產生太陽 Fullscreen Triangle 的 Vertex Shader
sunVS.loadVS("assets/shaders/sun_2D.hlsl", "VSMain");
// 載入計算太陽圓盤與光暈的 Pixel Shader
sunPS.loadPS("assets/shaders/sun_2D.hlsl", "PSMain");
SKYLINE_LOG(INFO) << "[DayX_Light] Creating pipeline state...";
PipelineState skyPipelineState;
initPipelineState(skyPipelineState, rootSignature, skyVS, skyPS);
// 建立太陽專用的 Pipeline State
PipelineState sunPipelineState;
// 使用太陽 Shader 與 Alpha Blending 設定初始化 Pipeline
initLightPipelineState(sunPipelineState, rootSignature, sunVS, sunPS);
SKYLINE_LOG(INFO) << "[DayX_Light] Initialization completed.";
RenderContext& renderContext = graphicsEngine.getRenderContext();
bool isFirstFrame = true;
while (dispatchWindowMessage())
{
if (isFirstFrame)
SKYLINE_LOG(INFO) << "[DayX_Light] Rendering first frame...";
graphicsEngine.beginRender();
// 每幀取得目前 Frame Buffer 的實際寬高比
sunConstants.aspectRatio =
static_cast<float>(graphicsEngine.getFrameBufferWidth()) /
static_cast<float>(graphicsEngine.GetFrameBufferHeight());
// 將更新後的太陽參數同步到 GPU
sunConstantBuffer.copyToVRAM(sunConstants);
renderContext.setRootSignature(rootSignature);
renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
renderContext.setPipelineState(skyPipelineState);
renderContext.draw(3);
// 切換成支援 Alpha Blending 的太陽 Pipeline
renderContext.setPipelineState(sunPipelineState);
// 將 SunConstants 綁定到 Root Parameter 0 對應的 Constant Buffer
renderContext.setGraphicsRootConstantBufferView(
0,
sunConstantBuffer.getGPUVirtualAddress());
// 繪製 Fullscreen Triangle,並由 Pixel Shader 決定太陽實際顯示區域
renderContext.draw(3);
graphicsEngine.endRender();
SkylineDebugger::LogD3D12Messages(graphicsEngine.getD3DDevice());
if (isFirstFrame)
{
SKYLINE_LOG(INFO) << "[DayX_Light] First frame presented.";
isFirstFrame = false;
}
}
SkylineDebugger::Shutdown();
return EXIT_SUCCESS;
}
catch (const std::exception& exception)
{
SkylineDebugger::ShowFatalError("DayX_Light initialization failed", exception);
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
catch (...)
{
SkylineDebugger::ShowFatalError("DayX_Light initialization failed", "An unknown fatal error occurred.");
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
}
