refactor: 重构项目结构为 MVS 多示例版本,添加 README 文档

This commit is contained in:
2026-03-13 15:07:03 +08:00
parent faf99dfc68
commit 5efa171050
76 changed files with 23925 additions and 53 deletions

View File

@@ -0,0 +1,511 @@
#include "BattleFireDirect.h"
ID3D12Device* gD3D12Device = nullptr;
ID3D12CommandQueue* gCommandQueue = nullptr;
IDXGISwapChain3* gSwapChain = nullptr;
ID3D12Resource* gDSRT = nullptr, * gColorRTs[2];
int gCurrentRTIndex = 0;
ID3D12DescriptorHeap* gSwapChainRTVHeap = nullptr;
ID3D12DescriptorHeap* gSwapChainDSVHeap = nullptr;
UINT gRTVDescriptorSize = 0;
UINT gDSVDescriptorSize = 0;
ID3D12CommandAllocator* gCommandAllocator = nullptr;
ID3D12GraphicsCommandList* gCommandList = nullptr;
ID3D12Fence* gFence = nullptr;
HANDLE gFenceEvent = nullptr;
UINT64 gFenceValue = 0;
ID3D12RootSignature* InitRootSignature() {
//1110001110101111111111111111111111
D3D12_ROOT_PARAMETER rootParameters[4];
rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
rootParameters[1].Constants.RegisterSpace = 0;
rootParameters[1].Constants.ShaderRegister = 0;
rootParameters[1].Constants.Num32BitValues = 4;
rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[0].Descriptor.RegisterSpace = 0;
rootParameters[0].Descriptor.ShaderRegister = 1;//cbv
D3D12_DESCRIPTOR_RANGE descriptorRange[1];
descriptorRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
descriptorRange[0].RegisterSpace = 0;
descriptorRange[0].BaseShaderRegister = 0;//t0
descriptorRange[0].NumDescriptors = 1;
descriptorRange[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
rootParameters[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
rootParameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
rootParameters[2].DescriptorTable.pDescriptorRanges = descriptorRange;
rootParameters[2].DescriptorTable.NumDescriptorRanges = _countof(descriptorRange);//cbv
rootParameters[3].ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV;
rootParameters[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[3].Descriptor.RegisterSpace = 1;
rootParameters[3].Descriptor.ShaderRegister = 0;//srv
D3D12_STATIC_SAMPLER_DESC samplerDesc[1];
memset(samplerDesc, 0,sizeof(D3D12_STATIC_SAMPLER_DESC)*_countof(samplerDesc));
samplerDesc[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
samplerDesc[0].MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc[0].RegisterSpace = 0;
samplerDesc[0].ShaderRegister = 0;//s0
samplerDesc[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.NumParameters = _countof(rootParameters);
rootSignatureDesc.pParameters = rootParameters;
rootSignatureDesc.NumStaticSamplers = _countof(samplerDesc);
rootSignatureDesc.pStaticSamplers = samplerDesc;
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
//64 DWORD -> float 128 WORD -> 16bit
ID3DBlob* signature;
HRESULT hResult = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, nullptr);
ID3D12RootSignature* d3d12RootSignature;
gD3D12Device->CreateRootSignature(
0, signature->GetBufferPointer(), signature->GetBufferSize(),
IID_PPV_ARGS(&d3d12RootSignature));
return d3d12RootSignature;
}
void CreateShaderFromFile(
LPCTSTR inShaderFilePath,
const char* inMainFunctionName,
const char* inTarget,//"vs_5_0","ps_5_0","vs_4_0"
D3D12_SHADER_BYTECODE* inShader) {
ID3DBlob* shaderBuffer = nullptr;
ID3DBlob* errorBuffer = nullptr;
HRESULT hResult = D3DCompileFromFile(inShaderFilePath, nullptr, nullptr,
inMainFunctionName, inTarget, D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
0, &shaderBuffer, &errorBuffer);
if (FAILED(hResult)) {
char szLog[1024] = {0};
strcpy(szLog, (char*)errorBuffer->GetBufferPointer());
printf("CreateShaderFromFile error : [%s][%s]:[%s]\n", inMainFunctionName, inTarget, szLog);
errorBuffer->Release();
return;
}
inShader->pShaderBytecode = shaderBuffer->GetBufferPointer();
inShader->BytecodeLength = shaderBuffer->GetBufferSize();
}
ID3D12Resource* CreateConstantBufferObject(int inDataLen) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inDataLen;
d3d12ResourceDesc.Height = 1;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* bufferObject = nullptr;
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&bufferObject)
);
return bufferObject;
}
void UpdateConstantBuffer(ID3D12Resource* inCB, void* inData, int inDataLen) {
D3D12_RANGE d3d12Range = { 0 };
unsigned char* pBuffer = nullptr;
inCB->Map(0, &d3d12Range, (void**)&pBuffer);
memcpy(pBuffer, inData, inDataLen);
inCB->Unmap(0, nullptr);
}
ID3D12PipelineState* CreatePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader,
D3D12_SHADER_BYTECODE inGSShader) {
D3D12_INPUT_ELEMENT_DESC vertexDataElementDesc[] = {
{"POSITION",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,0,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"TEXCOORD",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 4,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"NORMAL",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 8,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"TANGENT",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 12,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0}
};
D3D12_INPUT_LAYOUT_DESC vertexDataLayoutDesc = {};
vertexDataLayoutDesc.NumElements = 4;
vertexDataLayoutDesc.pInputElementDescs = vertexDataElementDesc;
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = inID3D12RootSignature;
psoDesc.VS = inVertexShader;
psoDesc.GS = inGSShader;
psoDesc.PS = inPixelShader;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
psoDesc.SampleDesc.Count = 1;
psoDesc.SampleDesc.Quality = 0;
psoDesc.SampleMask = 0xffffffff;
psoDesc.InputLayout = vertexDataLayoutDesc;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
psoDesc.RasterizerState.DepthClipEnable = TRUE;
psoDesc.DepthStencilState.DepthEnable = true;
psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
psoDesc.BlendState = { 0 };
D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc = {
FALSE,FALSE,
D3D12_BLEND_SRC_ALPHA,D3D12_BLEND_INV_SRC_ALPHA,D3D12_BLEND_OP_ADD,
D3D12_BLEND_SRC_ALPHA,D3D12_BLEND_INV_SRC_ALPHA,D3D12_BLEND_OP_ADD,
D3D12_LOGIC_OP_NOOP,
D3D12_COLOR_WRITE_ENABLE_ALL,
};
for (int i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
psoDesc.BlendState.RenderTarget[i] = rtBlendDesc;
psoDesc.NumRenderTargets = 1;
ID3D12PipelineState* d3d12PSO = nullptr;
HRESULT hResult = gD3D12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&d3d12PSO));
if (FAILED(hResult)) {
return nullptr;
}
return d3d12PSO;
}
bool InitD3D12(HWND inHWND, int inWidth, int inHeight) {
HRESULT hResult;
UINT dxgiFactoryFlags = 0;
#ifdef _DEBUG
{
ID3D12Debug* debugController = nullptr;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
debugController->EnableDebugLayer();
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
IDXGIFactory4* dxgiFactory;
hResult = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&dxgiFactory));
if (FAILED(hResult)) {
return false;
}
IDXGIAdapter1* adapter;
int adapterIndex = 0;
bool adapterFound = false;
while (dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND) {
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
continue;
}
hResult = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr);
if (SUCCEEDED(hResult)) {
adapterFound = true;
break;
}
adapterIndex++;
}
if (false == adapterFound) {
return false;
}
hResult = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&gD3D12Device));
if (FAILED(hResult)) {
return false;
}
D3D12_COMMAND_QUEUE_DESC d3d12CommandQueueDesc = {};
hResult = gD3D12Device->CreateCommandQueue(&d3d12CommandQueueDesc, IID_PPV_ARGS(&gCommandQueue));
if (FAILED(hResult)) {
return false;
}
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc = {};
swapChainDesc.BufferDesc.Width = inWidth;
swapChainDesc.BufferDesc.Height = inHeight;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = inHWND;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
IDXGISwapChain* swapChain = nullptr;
dxgiFactory->CreateSwapChain(gCommandQueue, &swapChainDesc, &swapChain);
gSwapChain = static_cast<IDXGISwapChain3*>(swapChain);
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inWidth;
d3d12ResourceDesc.Height = inHeight;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE dsClearValue = {};
dsClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsClearValue.DepthStencil.Depth = 1.0f;
dsClearValue.DepthStencil.Stencil = 0;
gD3D12Device->CreateCommittedResource(&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&dsClearValue,
IID_PPV_ARGS(&gDSRT)
);
//RTV,DSV,alloc
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescRTV = {};
d3dDescriptorHeapDescRTV.NumDescriptors = 2;
d3dDescriptorHeapDescRTV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
gD3D12Device->CreateDescriptorHeap(&d3dDescriptorHeapDescRTV, IID_PPV_ARGS(&gSwapChainRTVHeap));
gRTVDescriptorSize = gD3D12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescDSV = {};
d3dDescriptorHeapDescDSV.NumDescriptors = 1;
d3dDescriptorHeapDescDSV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
gD3D12Device->CreateDescriptorHeap(&d3dDescriptorHeapDescDSV, IID_PPV_ARGS(&gSwapChainDSVHeap));
gDSVDescriptorSize = gD3D12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapStart = gSwapChainRTVHeap->GetCPUDescriptorHandleForHeapStart();
for (int i = 0; i < 2; i++) {
gSwapChain->GetBuffer(i, IID_PPV_ARGS(&gColorRTs[i]));
D3D12_CPU_DESCRIPTOR_HANDLE rtvPointer;
rtvPointer.ptr = rtvHeapStart.ptr + i * gRTVDescriptorSize;
gD3D12Device->CreateRenderTargetView(gColorRTs[i], nullptr, rtvPointer);
}
D3D12_DEPTH_STENCIL_VIEW_DESC d3dDSViewDesc = {};
d3dDSViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
d3dDSViewDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
gD3D12Device->CreateDepthStencilView(gDSRT, &d3dDSViewDesc, gSwapChainDSVHeap->GetCPUDescriptorHandleForHeapStart());
gD3D12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&gCommandAllocator));
gD3D12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, gCommandAllocator, nullptr, IID_PPV_ARGS(&gCommandList));
gD3D12Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&gFence));
gFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
return true;
}
ID3D12CommandAllocator* GetCommandAllocator() {
return gCommandAllocator;
}
ID3D12GraphicsCommandList* GetCommandList() {
return gCommandList;
}
void WaitForCompletionOfCommandList() {
if (gFence->GetCompletedValue() < gFenceValue) {
gFence->SetEventOnCompletion(gFenceValue, gFenceEvent);
WaitForSingleObject(gFenceEvent, INFINITE);
}
}
void EndCommandList() {
gCommandList->Close();//
ID3D12CommandList* ppCommandLists[] = { gCommandList };
gCommandQueue->ExecuteCommandLists(1, ppCommandLists);
//CommandList
gFenceValue += 1;
gCommandQueue->Signal(gFence, gFenceValue);//
}
void BeginRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList) {
gCurrentRTIndex = gSwapChain->GetCurrentBackBufferIndex();
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(gColorRTs[gCurrentRTIndex], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
inCommandList->ResourceBarrier(1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE colorRT, dsv;
dsv.ptr = gSwapChainDSVHeap->GetCPUDescriptorHandleForHeapStart().ptr;
colorRT.ptr = gSwapChainRTVHeap->GetCPUDescriptorHandleForHeapStart().ptr + gCurrentRTIndex * gRTVDescriptorSize;
inCommandList->OMSetRenderTargets(1, &colorRT, FALSE, &dsv);
D3D12_VIEWPORT viewport = { 0.0f,0.0f,1280.0f,720.0f };
D3D12_RECT scissorRect = { 0,0,1280,720 };
inCommandList->RSSetViewports(1, &viewport);
inCommandList->RSSetScissorRects(1, &scissorRect);
const float clearColor[] = { 0.0f,0.0f,0.0f,1.0f };
inCommandList->ClearRenderTargetView(colorRT, clearColor, 0, nullptr);
inCommandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
}
void EndRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList) {
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(gColorRTs[gCurrentRTIndex], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
inCommandList->ResourceBarrier(1, &barrier);
}
void SwapD3D12Buffers() {
gSwapChain->Present(0, 0);
}
ID3D12Resource* CreateBufferObject(ID3D12GraphicsCommandList* inCommandList,
void* inData, int inDataLen, D3D12_RESOURCE_STATES inFinalResourceState) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;//gpu
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inDataLen;
d3d12ResourceDesc.Height = 1;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* bufferObject = nullptr;
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&bufferObject)
);
d3d12ResourceDesc = bufferObject->GetDesc();
UINT64 memorySizeUsed = 0;
UINT64 rowSizeInBytes = 0;
UINT rowUsed = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT subresourceFootprint;
gD3D12Device->GetCopyableFootprints(&d3d12ResourceDesc, 0, 1, 0,
&subresourceFootprint, &rowUsed, &rowSizeInBytes, &memorySizeUsed);
// 3 x 4 x 4 = 48bytes,32bytes,24bytes + 24bytes
ID3D12Resource* tempBufferObject = nullptr;
d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&tempBufferObject)
);
BYTE* pData;
tempBufferObject->Map(0, nullptr, reinterpret_cast<void**>(&pData));
BYTE* pDstTempBuffer = reinterpret_cast<BYTE*>(pData + subresourceFootprint.Offset);
const BYTE* pSrcData = reinterpret_cast<BYTE*>(inData);
for (UINT i = 0; i < rowUsed; i++) {
memcpy(pDstTempBuffer + subresourceFootprint.Footprint.RowPitch * i, pSrcData + rowSizeInBytes * i, rowSizeInBytes);
}
tempBufferObject->Unmap(0, nullptr);
inCommandList->CopyBufferRegion(bufferObject, 0, tempBufferObject, 0, subresourceFootprint.Footprint.Width);
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(bufferObject, D3D12_RESOURCE_STATE_COPY_DEST, inFinalResourceState);
inCommandList->ResourceBarrier(1, &barrier);
return bufferObject;
}
D3D12_RESOURCE_BARRIER InitResourceBarrier(
ID3D12Resource* inResource, D3D12_RESOURCE_STATES inPrevState,
D3D12_RESOURCE_STATES inNextState) {
D3D12_RESOURCE_BARRIER d3d12ResourceBarrier;
memset(&d3d12ResourceBarrier, 0, sizeof(d3d12ResourceBarrier));
d3d12ResourceBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
d3d12ResourceBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
d3d12ResourceBarrier.Transition.pResource = inResource;
d3d12ResourceBarrier.Transition.StateBefore = inPrevState;
d3d12ResourceBarrier.Transition.StateAfter = inNextState;
d3d12ResourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
return d3d12ResourceBarrier;
}
ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
const void* inPixelData, int inDataSizeInBytes, int inWidth, int inHeight,
DXGI_FORMAT inFormat) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inWidth;
d3d12ResourceDesc.Height = inHeight;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = inFormat;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* texture = nullptr;
gD3D12Device->CreateCommittedResource(&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
d3d12ResourceDesc = texture->GetDesc();
UINT64 memorySizeUsed = 0;
UINT64 rowSizeInBytes = 0;
UINT rowUsed = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT subresourceFootprint;
gD3D12Device->GetCopyableFootprints(&d3d12ResourceDesc, 0, 1, 0,
&subresourceFootprint, &rowUsed, &rowSizeInBytes, &memorySizeUsed);
// 3 x 4 x 4 = 48bytes,32bytes,24bytes + 24bytes
ID3D12Resource* tempBufferObject = nullptr;
D3D12_HEAP_PROPERTIES d3dTempHeapProperties = {};
d3dTempHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
D3D12_RESOURCE_DESC d3d12TempResourceDesc = {};
d3d12TempResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12TempResourceDesc.Alignment = 0;
d3d12TempResourceDesc.Width = memorySizeUsed;
d3d12TempResourceDesc.Height = 1;
d3d12TempResourceDesc.DepthOrArraySize = 1;
d3d12TempResourceDesc.MipLevels = 1;
d3d12TempResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12TempResourceDesc.SampleDesc.Count = 1;
d3d12TempResourceDesc.SampleDesc.Quality = 0;
d3d12TempResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12TempResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
gD3D12Device->CreateCommittedResource(
&d3dTempHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12TempResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&tempBufferObject)
);
BYTE* pData;
tempBufferObject->Map(0, nullptr, reinterpret_cast<void**>(&pData));
BYTE* pDstTempBuffer = reinterpret_cast<BYTE*>(pData + subresourceFootprint.Offset);
const BYTE* pSrcData = reinterpret_cast<const BYTE*>(inPixelData);
for (UINT i = 0; i < rowUsed; i++) {
memcpy(pDstTempBuffer + subresourceFootprint.Footprint.RowPitch * i, pSrcData + rowSizeInBytes * i, rowSizeInBytes);
}
tempBufferObject->Unmap(0, nullptr);
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = texture;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = tempBufferObject;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = subresourceFootprint;
inCommandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(texture,
D3D12_RESOURCE_STATE_COPY_DEST,D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
inCommandList->ResourceBarrier(1, &barrier);
return texture;
}
ID3D12Device* GetD3DDevice() {
return gD3D12Device;
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <d3d12.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include <stdio.h>
D3D12_RESOURCE_BARRIER InitResourceBarrier(
ID3D12Resource* inResource, D3D12_RESOURCE_STATES inPrevState,
D3D12_RESOURCE_STATES inNextState);
ID3D12RootSignature* InitRootSignature();
void CreateShaderFromFile(
LPCTSTR inShaderFilePath,
const char* inMainFunctionName,
const char* inTarget,//"vs_5_0","ps_5_0","vs_4_0"
D3D12_SHADER_BYTECODE* inShader);
ID3D12Resource* CreateConstantBufferObject(int inDataLen);
void UpdateConstantBuffer(ID3D12Resource* inCB, void* inData, int inDataLen);
ID3D12Resource* CreateBufferObject(ID3D12GraphicsCommandList* inCommandList,
void* inData, int inDataLen, D3D12_RESOURCE_STATES inFinalResourceState);
ID3D12PipelineState* CreatePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader,
D3D12_SHADER_BYTECODE inGSShader);
bool InitD3D12(HWND inHWND, int inWidth, int inHeight);
ID3D12GraphicsCommandList* GetCommandList();
ID3D12CommandAllocator* GetCommandAllocator();
void WaitForCompletionOfCommandList();
void EndCommandList();
void BeginRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList);
void EndRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList);
void SwapD3D12Buffers();
ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
const void*inPixelData,int inDataSizeInBytes,int inWidth,int inHeight,
DXGI_FORMAT inFormat);
ID3D12Device* GetD3DDevice();

114
MVS/HelloEarth/README.md Normal file
View File

@@ -0,0 +1,114 @@
# HelloEarth
基础 DirectX 12 渲染示例项目,展示如何搭建基本的渲染管线。
## 简介
HelloEarth 是 XCEngine 项目的入门级示例,通过渲染一个带纹理的球体,帮助理解 DirectX 12 的核心概念和渲染流程。
## 技术栈
- **渲染 API**: DirectX 12
- **语言**: C++17
- **构建系统**: CMake
- **依赖库**: DirectX 12 SDK, stb_image
## 项目结构
```
HelloEarth/
├── main.cpp # 程序入口
├── BattleFireDirect.cpp/h # DirectX 12 核心渲染实现
├── StaticMeshComponent.cpp/h # 静态网格组件
├── Utils.cpp/h # 工具函数
├── stbi/ # 图像加载库
│ ├── stb_image.h
│ └── stb_image.cpp
└── Res/ # 资源文件
├── Shader/
│ ├── gs.hlsl # 几何着色器
│ └── ndctriangle.hlsl # 三角形着色器
├── Model/
│ └── Sphere.lhsm # 球体模型
└── Image/
└── earth_d.jpg # 地球纹理
```
## 构建方法
### 前置要求
- Windows 10/11
- Visual Studio 2019 或更高版本
- CMake 3.15+
### 构建步骤
```bash
# 创建并进入构建目录
mkdir build && cd build
# 配置项目
cmake ..
# 编译
cmake --build . --config Release
```
### 运行
编译完成后运行 `HelloEarth.exe`(如果生成了可执行文件)
## 功能特性
### 渲染管线
- DirectX 12 渲染环境初始化
- 命令队列和命令列表管理
- 资源转换和同步
### 着色器
- 顶点着色器VS
- 几何着色器GS
- 像素着色器PS
### 资源管理
- 静态网格加载和渲染
- 纹理加载(支持 JPG/PNG
- 常量缓冲区管理
- 着色器资源视图SRV
### 数学运算
- 投影矩阵Perspective Projection
- 视图矩阵View Matrix
- 模型矩阵Model Matrix
- 法线矩阵计算
## 核心概念
### 渲染流程
1. 初始化 D3D12 设备和命令队列
2. 创建命令分配器和命令列表
3. 加载着色器(编译 HLSL
4. 创建根签名和 PSO 管道状态
5. 加载网格模型和纹理
6. 创建常量缓冲区并更新数据
7. 渲染循环:
- 重置命令分配器和列表
- 设置渲染目标
- 设置根签名和 PSO
- 设置描述符堆
- 绑定常量缓冲和纹理
- 提交绘制命令
- 呈现交换链
### 关键接口
- `InitD3D12` - 初始化 DirectX 12
- `CreateShaderFromFile` - 从文件加载着色器
- `CreatePSO` - 创建管道状态对象
- `StaticMeshComponent::Render` - 渲染网格
## 资源说明
- 模型文件格式:`.lhsm`(自定义格式)
- 纹理支持PNG、JPG
- 着色器HLSLShader Model 5.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

View File

@@ -0,0 +1,99 @@
struct VertexData{
float4 position:POSITION;
float4 texcoord:TEXCOORD0;
float4 normal:NORMAL;
float4 tangent:TANGENT;
};
struct VSOut{
float4 position:SV_POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
static const float PI=3.141592;
cbuffer globalConstants:register(b0){
float4 misc;
};
Texture2D T_DiffuseTexture:register(t0);
SamplerState samplerState:register(s0);
struct MaterialData{
float r;
};
StructuredBuffer<MaterialData> materialData:register(t0,space1);
cbuffer DefaultVertexCB:register(b1){
float4x4 ProjectionMatrix;
float4x4 ViewMatrix;
float4x4 ModelMatrix;
float4x4 IT_ModelMatrix;
float4x4 ReservedMemory[1020];
};
VSOut MainVS(VertexData inVertexData){
VSOut vo;
vo.normal=mul(IT_ModelMatrix,inVertexData.normal);
float4 positionWS=mul(ModelMatrix,inVertexData.position);
float4 positionVS=mul(ViewMatrix,positionWS);
vo.position=mul(ProjectionMatrix,positionVS);
//vo.position=float4(positionWS.xyz+vo.normal.xyz*sin(misc.x)*0.2f,1.0f);
vo.texcoord=inVertexData.texcoord;
return vo;
}
[maxvertexcount(4)]
void MainGS(triangle VSOut inPoint[3],uint inPrimitiveID:SV_PrimitiveID,
inout TriangleStream<VSOut> outTriangleStream){
outTriangleStream.Append(inPoint[0]);
outTriangleStream.Append(inPoint[1]);
outTriangleStream.Append(inPoint[2]);
/*VSOut vo;
float3 positionWS=inPoint[0].position.xyz;
float3 N=normalize(inPoint[0].normal.xyz);
vo.normal=float4(N,0.0f);
float3 helperVec=abs(N.y)>0.999?float3(0.0f,0.0f,1.0f):float3(0.0f,1.0f,0.0f);
float3 tangent=normalize(cross(N,helperVec));//u
float3 bitangent=normalize(cross(tangent,N));//v
float scale=materialData[inPrimitiveID].r;
float3 p0WS=positionWS-(bitangent*0.5f-tangent*0.5f)*scale;//left bottom
float4 p0VS=mul(ViewMatrix,float4(p0WS,1.0f));
vo.position=mul(ProjectionMatrix,p0VS);
vo.texcoord=float4(0.0f,1.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p1WS=positionWS-(bitangent*0.5f+tangent*0.5f)*scale;//right bottom
float4 p1VS=mul(ViewMatrix,float4(p1WS,1.0f));
vo.position=mul(ProjectionMatrix,p1VS);
vo.texcoord=float4(1.0f,1.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p2WS=positionWS+(bitangent*0.5f+tangent*0.5f)*scale;//left top
float4 p2VS=mul(ViewMatrix,float4(p2WS,1.0f));
vo.position=mul(ProjectionMatrix,p2VS);
vo.texcoord=float4(0.0f,0.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p3WS=positionWS+(bitangent*0.5f-tangent*0.5f)*scale;//right top
float4 p3VS=mul(ViewMatrix,float4(p3WS,1.0f));
vo.position=mul(ProjectionMatrix,p3VS);
vo.texcoord=float4(1.0f,0.0f,0.0f,0.0f);
outTriangleStream.Append(vo);*/
}
float4 MainPS(VSOut inPSInput):SV_TARGET{
float3 N=normalize(inPSInput.normal.xyz);
float3 bottomColor=float3(0.1f,0.4f,0.6f);
float3 topColor=float3(0.7f,0.7f,0.7f);
float theta=asin(N.y);//-PI/2 ~ PI/2
theta/=PI;//-0.5~0.5
theta+=0.5f;//0.0~1.0
float ambientColorIntensity=1.0;
float3 ambientColor=lerp(bottomColor,topColor,theta)*ambientColorIntensity;
float4 diffuseColor=T_DiffuseTexture.Sample(samplerState,inPSInput.texcoord.xy);
float3 surfaceColor=diffuseColor.rgb;
return float4(surfaceColor,1.0f);
}

View File

@@ -0,0 +1,65 @@
struct VertexData{
float4 position:POSITION;
float4 texcoord:TEXCOORD0;
float4 normal:NORMAL;
float4 tangent:TANGENT;
};
struct VSOut{
float4 position:SV_POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 positionWS:TEXCOORD1;
};
static const float PI=3.141592;
cbuffer globalConstants:register(b0){
float4 misc;
};
cbuffer DefaultVertexCB:register(b1){
float4x4 ProjectionMatrix;
float4x4 ViewMatrix;
float4x4 ModelMatrix;
float4x4 IT_ModelMatrix;
float4x4 ReservedMemory[1020];
};
VSOut MainVS(VertexData inVertexData){
VSOut vo;
vo.normal=mul(IT_ModelMatrix,inVertexData.normal);
float3 positionMS=inVertexData.position.xyz+vo.normal*sin(misc.x);
float4 positionWS=mul(ModelMatrix,float4(positionMS,1.0));
float4 positionVS=mul(ViewMatrix,positionWS);
vo.position=mul(ProjectionMatrix,positionVS);
vo.positionWS=positionWS;
vo.texcoord=inVertexData.texcoord;
return vo;
}
float4 MainPS(VSOut inPSInput):SV_TARGET{
float3 N=normalize(inPSInput.normal.xyz);
float3 bottomColor=float3(0.1f,0.4f,0.6f);
float3 topColor=float3(0.7f,0.7f,0.7f);
float theta=asin(N.y);//-PI/2 ~ PI/2
theta/=PI;//-0.5~0.5
theta+=0.5f;//0.0~1.0
float ambientColorIntensity=0.2;
float3 ambientColor=lerp(bottomColor,topColor,theta)*ambientColorIntensity;
float3 L=normalize(float3(1.0f,1.0f,-1.0f));
float diffuseIntensity=max(0.0f,dot(N,L));
float3 diffuseLightColor=float3(0.1f,0.4f,0.6f);
float3 diffuseColor=diffuseLightColor*diffuseIntensity;
float3 specularColor=float3(0.0f,0.0f,0.0f);
if(diffuseIntensity>0.0f){
float3 cameraPositionWS=float3(0.0f,0.0f,0.0f);
float3 V=normalize(cameraPositionWS.xyz-inPSInput.positionWS.xyz);
float3 R=normalize(reflect(-L,N));
float specularIntensity=pow(max(0.0f,dot(V,R)),128.0f);
specularColor=float3(1.0f,1.0f,1.0f)*specularIntensity;
}
float3 surfaceColor=ambientColor+diffuseColor+specularColor;
return float4(surfaceColor,1.0f);
}

View File

@@ -0,0 +1,90 @@
#include "StaticMeshComponent.h"
#include "BattleFireDirect.h"
#include <stdio.h>
void StaticMeshComponent::SetVertexCount(int inVertexCount) {
mVertexCount = inVertexCount;
mVertexData = new StaticMeshComponentVertexData[inVertexCount];
memset(mVertexData, 0, sizeof(StaticMeshComponentVertexData)*inVertexCount);
}
void StaticMeshComponent::SetVertexPosition(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mPosition[0] = inX;
mVertexData[inIndex].mPosition[1] = inY;
mVertexData[inIndex].mPosition[2] = inZ;
mVertexData[inIndex].mPosition[3] = inW;
}
void StaticMeshComponent::SetVertexTexcoord(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mTexcoord[0] = inX;
mVertexData[inIndex].mTexcoord[1] = inY;
mVertexData[inIndex].mTexcoord[2] = inZ;
mVertexData[inIndex].mTexcoord[3] = inW;
}
void StaticMeshComponent::SetVertexNormal(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mNormal[0] = inX;
mVertexData[inIndex].mNormal[1] = inY;
mVertexData[inIndex].mNormal[2] = inZ;
mVertexData[inIndex].mNormal[3] = inW;
}
void StaticMeshComponent::SetVertexTangent(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mTangent[0] = inX;
mVertexData[inIndex].mTangent[1] = inY;
mVertexData[inIndex].mTangent[2] = inZ;
mVertexData[inIndex].mTangent[3] = inW;
}
void StaticMeshComponent::InitFromFile(ID3D12GraphicsCommandList* inCommandList, const char* inFilePath) {
FILE* pFile = nullptr;
errno_t err = fopen_s(&pFile, inFilePath, "rb");
if (err == 0) {
int temp = 0;
fread(&temp, 4, 1, pFile);
mVertexCount = temp;
mVertexData = new StaticMeshComponentVertexData[mVertexCount];
fread(mVertexData, 1, sizeof(StaticMeshComponentVertexData) * mVertexCount, pFile);
mVBO=CreateBufferObject(inCommandList,mVertexData,
sizeof(StaticMeshComponentVertexData) * mVertexCount,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
mVBOView.BufferLocation = mVBO->GetGPUVirtualAddress();
mVBOView.SizeInBytes = sizeof(StaticMeshComponentVertexData) * mVertexCount;
mVBOView.StrideInBytes = sizeof(StaticMeshComponentVertexData);
while (!feof(pFile)) {
fread(&temp, 4, 1, pFile);
if (feof(pFile)) {
break;
}
char name[256] = {0};
fread(name, 1, temp, pFile);
fread(&temp, 4, 1, pFile);
SubMesh* submesh = new SubMesh;
submesh->mIndexCount = temp;
unsigned int *indexes = new unsigned int[temp];
fread(indexes, 1, sizeof(unsigned int) * temp, pFile);
submesh->mIBO = CreateBufferObject(inCommandList, indexes,
sizeof(unsigned int) * temp,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
submesh->mIBView.BufferLocation = submesh->mIBO->GetGPUVirtualAddress();
submesh->mIBView.SizeInBytes = sizeof(unsigned int) * temp;
submesh->mIBView.Format = DXGI_FORMAT_R32_UINT;
mSubMeshes.insert(std::pair<std::string, SubMesh*>(name,submesh));
delete[]indexes;
}
fclose(pFile);
}
}
void StaticMeshComponent::Render(ID3D12GraphicsCommandList* inCommandList) {
D3D12_VERTEX_BUFFER_VIEW vbos[] = {
mVBOView
};
inCommandList->IASetVertexBuffers(0, 1, vbos);
if (mSubMeshes.empty()) {
inCommandList->DrawInstanced(mVertexCount, 1, 0, 0);
}
else {
for (auto iter = mSubMeshes.begin();
iter != mSubMeshes.end(); iter++) {
inCommandList->IASetIndexBuffer(&iter->second->mIBView);
inCommandList->DrawIndexedInstanced(iter->second->mIndexCount, 1, 0, 0, 0);
}
}
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <d3d12.h>
#include <unordered_map>
#include <string>
struct StaticMeshComponentVertexData {
float mPosition[4];
float mTexcoord[4];
float mNormal[4];
float mTangent[4];
};
struct SubMesh {
ID3D12Resource* mIBO;
D3D12_INDEX_BUFFER_VIEW mIBView;
int mIndexCount;
};
class StaticMeshComponent{
public:
ID3D12Resource* mVBO;
D3D12_VERTEX_BUFFER_VIEW mVBOView;
StaticMeshComponentVertexData* mVertexData;
int mVertexCount;
std::unordered_map<std::string, SubMesh*> mSubMeshes;
void SetVertexCount(int inVertexCount);
void SetVertexPosition(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexTexcoord(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexNormal(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexTangent(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void InitFromFile(ID3D12GraphicsCommandList*inCommandList,const char* inFilePath);
void Render(ID3D12GraphicsCommandList* inCommandList);
};

10
MVS/HelloEarth/Utils.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "Utils.h"
#include <math.h>
#include <algorithm>
float srandom() {
float number = float(rand())/float(RAND_MAX);//0.0~1.0f
number *= 2.0f;//0.0~2.0
number -= 1.0f;//-1.0f~1.0f;
return number;
}

3
MVS/HelloEarth/Utils.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
float srandom();//-1.0f~1.0f

188
MVS/HelloEarth/main.cpp Normal file
View File

@@ -0,0 +1,188 @@
#include <windows.h>
#include "BattleFireDirect.h"
#include "StaticMeshComponent.h"
#include "stbi/stb_image.h"
#include "Utils.h"
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"dxgi.lib")
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib,"winmm.lib")
LPCTSTR gWindowClassName = L"BattleFire";
LRESULT CALLBACK WindowProc(HWND inHWND, UINT inMSG, WPARAM inWParam, LPARAM inLParam) {
switch (inMSG) {
case WM_CLOSE:
PostQuitMessage(0);//enqueue WM_QUIT
break;
}
return DefWindowProc(inHWND, inMSG, inWParam, inLParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int inShowCmd) {
//register
WNDCLASSEX wndClassEx;
wndClassEx.cbSize = sizeof(WNDCLASSEX);
wndClassEx.style = CS_HREDRAW | CS_VREDRAW;
wndClassEx.cbClsExtra = NULL;//class
wndClassEx.cbWndExtra = NULL;//instance
wndClassEx.hInstance = hInstance;
wndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClassEx.hbrBackground = NULL;
wndClassEx.lpszMenuName = NULL;
wndClassEx.lpszClassName = gWindowClassName;
wndClassEx.lpfnWndProc = WindowProc;
if (!RegisterClassEx(&wndClassEx)) {
MessageBox(NULL, L"Register Class Failed!", L"Error", MB_OK | MB_ICONERROR);
return -1;
}
//create
int viewportWidth = 1280;
int viewportHeight = 720;
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = viewportWidth;
rect.bottom = viewportHeight;
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
HWND hwnd = CreateWindowEx(NULL,
gWindowClassName,
L"My Render Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
windowWidth, windowHeight,
NULL,
NULL,
hInstance,
NULL);
if (!hwnd) {
MessageBox(NULL, L"Create Window Failed!", L"Error", MB_OK | MB_ICONERROR);
return -1;
}
//show
InitD3D12(hwnd, 1280, 720);
ID3D12GraphicsCommandList* commandList = GetCommandList();
ID3D12CommandAllocator* commandAllocator = GetCommandAllocator();
StaticMeshComponent staticMeshComponent;
staticMeshComponent.InitFromFile(commandList, "Res/Model/Sphere.lhsm");
ID3D12RootSignature* rootSignature = InitRootSignature();
D3D12_SHADER_BYTECODE vs,gs,ps;
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainVS", "vs_5_1", &vs);
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainGS", "gs_5_1", &gs);
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainPS", "ps_5_1", &ps);
ID3D12PipelineState*pso=CreatePSO(rootSignature, vs, ps, gs);
ID3D12Resource* cb = CreateConstantBufferObject(65536);//1024x64(4x4)
DirectX::XMMATRIX projectionMatrix=DirectX::XMMatrixPerspectiveFovLH(
(45.0f*3.141592f)/180.0f,1280.0f/720.0f,0.1f,1000.0f);
DirectX::XMMATRIX viewMatrix = DirectX::XMMatrixIdentity();
DirectX::XMMATRIX modelMatrix = DirectX::XMMatrixTranslation(0.0f,0.0f,5.0f);
//modelMatrix *= DirectX::XMMatrixRotationZ(90.0f*3.141592f/180.0f);
DirectX::XMFLOAT4X4 tempMatrix;
float matrices[64];
DirectX::XMStoreFloat4x4(&tempMatrix, projectionMatrix);
memcpy(matrices, &tempMatrix, sizeof(float) * 16);
DirectX::XMStoreFloat4x4(&tempMatrix, viewMatrix);
memcpy(matrices+16, &tempMatrix, sizeof(float) * 16);
DirectX::XMStoreFloat4x4(&tempMatrix, modelMatrix);
memcpy(matrices + 32, &tempMatrix, sizeof(float) * 16);;
DirectX::XMVECTOR determinant;
DirectX::XMMATRIX inverseModelMatrix = DirectX::XMMatrixInverse(&determinant, modelMatrix);
if (DirectX::XMVectorGetX(determinant) != 0.0f) {
DirectX::XMMATRIX normalMatrix = DirectX::XMMatrixTranspose(inverseModelMatrix);
DirectX::XMStoreFloat4x4(&tempMatrix, modelMatrix);
memcpy(matrices + 48, &tempMatrix, sizeof(float) * 16);;
}
UpdateConstantBuffer(cb, matrices, sizeof(float) * 64);
ID3D12Resource* sb = CreateConstantBufferObject(65536);//1024x64(4x4)
struct MaterialData {
float r;
};
MaterialData* materialDatas = new MaterialData[3000];
for (int i=0;i<3000;i++){
materialDatas[i].r = srandom() * 0.1f + 0.1f;//0.0~1.0
}
UpdateConstantBuffer(sb, materialDatas, sizeof(MaterialData) * 3000);
int imageWidth, imageHeight,imageChannel;
//stbi_set_flip_vertically_on_load(true);
stbi_uc* pixels = stbi_load("Res/Image/earth_d.jpg", &imageWidth, &imageHeight, &imageChannel, 4);
ID3D12Resource* texture = CreateTexture2D(commandList, pixels,
imageWidth * imageHeight * imageChannel, imageWidth, imageHeight,DXGI_FORMAT_R8G8B8A8_UNORM);
delete[]pixels;
ID3D12Device* d3dDevice = GetD3DDevice();
ID3D12DescriptorHeap* srvHeap = nullptr;
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescSRV = {};
d3dDescriptorHeapDescSRV.NumDescriptors = 3;
d3dDescriptorHeapDescSRV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
d3dDescriptorHeapDescSRV.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
d3dDevice->CreateDescriptorHeap(&d3dDescriptorHeapDescSRV, IID_PPV_ARGS(&srvHeap));
ID3D12DescriptorHeap* descriptorHeaps[] = {srvHeap};
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
D3D12_CPU_DESCRIPTOR_HANDLE srvHeapPtr = srvHeap->GetCPUDescriptorHandleForHeapStart();
d3dDevice->CreateShaderResourceView(texture, &srvDesc, srvHeapPtr);
srvHeapPtr.ptr += d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
EndCommandList();
WaitForCompletionOfCommandList();
ShowWindow(hwnd, inShowCmd);
UpdateWindow(hwnd);
float color[] = {0.5f,0.5f,0.5f,1.0f};
MSG msg;
DWORD last_time = timeGetTime();
DWORD appStartTime = last_time;
while (true){
ZeroMemory(&msg, sizeof(MSG));
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
//rendering
WaitForCompletionOfCommandList();
DWORD current_time = timeGetTime();//ms
DWORD frameTime = current_time - last_time;
DWORD timeSinceAppStartInMS = current_time - appStartTime;
last_time = current_time;
float frameTimeInSecond = float(frameTime) / 1000.0f;//second
float timeSinceAppStartInSecond = float(timeSinceAppStartInMS) / 1000.0f;
color[0] = timeSinceAppStartInSecond;
commandAllocator->Reset();
commandList->Reset(commandAllocator, nullptr);
BeginRenderToSwapChain(commandList);
//draw
commandList->SetPipelineState(pso);
commandList->SetGraphicsRootSignature(rootSignature);
commandList->SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
commandList->SetGraphicsRootConstantBufferView(0, cb->GetGPUVirtualAddress());
commandList->SetGraphicsRoot32BitConstants(1, 4, color, 0);
commandList->SetGraphicsRootDescriptorTable(2, srvHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootShaderResourceView(3, sb->GetGPUVirtualAddress());
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
staticMeshComponent.Render(commandList);
EndRenderToSwapChain(commandList);
EndCommandList();
SwapD3D12Buffers();
}
}
return 0;
}

View File

@@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,673 @@
#include "BattleFireDirect.h"
ID3D12Device* gD3D12Device = nullptr;
ID3D12CommandQueue* gCommandQueue = nullptr;
IDXGISwapChain3* gSwapChain = nullptr;
ID3D12Resource* gDSRT = nullptr, * gColorRTs[2];
int gCurrentRTIndex = 0;
ID3D12DescriptorHeap* gSwapChainRTVHeap = nullptr;
ID3D12DescriptorHeap* gSwapChainDSVHeap = nullptr;
UINT gRTVDescriptorSize = 0;
UINT gDSVDescriptorSize = 0;
ID3D12CommandAllocator* gCommandAllocator = nullptr;
ID3D12GraphicsCommandList* gCommandList = nullptr;
ID3D12Fence* gFence = nullptr;
HANDLE gFenceEvent = nullptr;
UINT64 gFenceValue = 0;
ID3D12RootSignature* InitRootSignature() {
//1110001110101111111111111111111111
D3D12_ROOT_PARAMETER rootParameters[4];
rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
rootParameters[1].Constants.RegisterSpace = 0;
rootParameters[1].Constants.ShaderRegister = 0;
rootParameters[1].Constants.Num32BitValues = 4;
rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[0].Descriptor.RegisterSpace = 0;
rootParameters[0].Descriptor.ShaderRegister = 1;//cbv
D3D12_DESCRIPTOR_RANGE descriptorRange[1];
descriptorRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
descriptorRange[0].RegisterSpace = 0;
descriptorRange[0].BaseShaderRegister = 0;//t0
descriptorRange[0].NumDescriptors = 1;
descriptorRange[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
rootParameters[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
rootParameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
rootParameters[2].DescriptorTable.pDescriptorRanges = descriptorRange;
rootParameters[2].DescriptorTable.NumDescriptorRanges = _countof(descriptorRange);//cbv
rootParameters[3].ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV;
rootParameters[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[3].Descriptor.RegisterSpace = 1;
rootParameters[3].Descriptor.ShaderRegister = 0;//srv
D3D12_STATIC_SAMPLER_DESC samplerDesc[1];
memset(samplerDesc, 0,sizeof(D3D12_STATIC_SAMPLER_DESC)*_countof(samplerDesc));
samplerDesc[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
samplerDesc[0].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
samplerDesc[0].MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc[0].RegisterSpace = 0;
samplerDesc[0].ShaderRegister = 0;//s0
samplerDesc[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.NumParameters = _countof(rootParameters);
rootSignatureDesc.pParameters = rootParameters;
rootSignatureDesc.NumStaticSamplers = _countof(samplerDesc);
rootSignatureDesc.pStaticSamplers = samplerDesc;
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
//64 DWORD -> float 128 WORD -> 16bit
ID3DBlob* signature;
HRESULT hResult = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, nullptr);
ID3D12RootSignature* d3d12RootSignature;
gD3D12Device->CreateRootSignature(
0, signature->GetBufferPointer(), signature->GetBufferSize(),
IID_PPV_ARGS(&d3d12RootSignature));
return d3d12RootSignature;
}
ID3D12RootSignature* InitVolumeRootSignature() {
D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[0].Descriptor.RegisterSpace = 0;
rootParameters[0].Descriptor.ShaderRegister = 1;
rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV;
rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameters[1].Descriptor.RegisterSpace = 0;
rootParameters[1].Descriptor.ShaderRegister = 1;
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.NumParameters = _countof(rootParameters);
rootSignatureDesc.pParameters = rootParameters;
rootSignatureDesc.NumStaticSamplers = 0;
rootSignatureDesc.pStaticSamplers = nullptr;
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ID3DBlob* signature;
HRESULT hResult = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, nullptr);
ID3D12RootSignature* d3d12RootSignature;
gD3D12Device->CreateRootSignature(
0, signature->GetBufferPointer(), signature->GetBufferSize(),
IID_PPV_ARGS(&d3d12RootSignature));
return d3d12RootSignature;
}
void CreateShaderFromFile(
LPCTSTR inShaderFilePath,
const char* inMainFunctionName,
const char* inTarget,//"vs_5_0","ps_5_0","vs_4_0"
D3D12_SHADER_BYTECODE* inShader) {
ID3DBlob* shaderBuffer = nullptr;
ID3DBlob* errorBuffer = nullptr;
HRESULT hResult = D3DCompileFromFile(inShaderFilePath, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE,
inMainFunctionName, inTarget, D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
0, &shaderBuffer, &errorBuffer);
if (FAILED(hResult)) {
char szLog[1024] = {0};
if (errorBuffer) {
strcpy(szLog, (char*)errorBuffer->GetBufferPointer());
} else {
strcpy(szLog, "Unknown error");
}
printf("CreateShaderFromFile error : [%s][%s]:[%s]\n", inMainFunctionName, inTarget, szLog);
errorBuffer->Release();
return;
}
inShader->pShaderBytecode = shaderBuffer->GetBufferPointer();
inShader->BytecodeLength = shaderBuffer->GetBufferSize();
}
ID3D12Resource* CreateConstantBufferObject(int inDataLen) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inDataLen;
d3d12ResourceDesc.Height = 1;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* bufferObject = nullptr;
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&bufferObject)
);
return bufferObject;
}
void UpdateConstantBuffer(ID3D12Resource* inCB, void* inData, int inDataLen) {
D3D12_RANGE d3d12Range = { 0 };
unsigned char* pBuffer = nullptr;
inCB->Map(0, &d3d12Range, (void**)&pBuffer);
memcpy(pBuffer, inData, inDataLen);
inCB->Unmap(0, nullptr);
}
ID3D12PipelineState* CreatePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader,
D3D12_SHADER_BYTECODE inGSShader) {
D3D12_INPUT_ELEMENT_DESC vertexDataElementDesc[] = {
{"POSITION",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,0,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"TEXCOORD",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 4,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"NORMAL",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 8,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"TANGENT",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,sizeof(float) * 12,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0}
};
D3D12_INPUT_LAYOUT_DESC vertexDataLayoutDesc = {};
vertexDataLayoutDesc.NumElements = 4;
vertexDataLayoutDesc.pInputElementDescs = vertexDataElementDesc;
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = inID3D12RootSignature;
psoDesc.VS = inVertexShader;
psoDesc.GS = inGSShader;
psoDesc.PS = inPixelShader;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
psoDesc.SampleDesc.Count = 1;
psoDesc.SampleDesc.Quality = 0;
psoDesc.SampleMask = 0xffffffff;
psoDesc.InputLayout = vertexDataLayoutDesc;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
psoDesc.RasterizerState.DepthClipEnable = TRUE;
psoDesc.DepthStencilState.DepthEnable = true;
psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
psoDesc.BlendState = { 0 };
D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc = {
FALSE,FALSE,
D3D12_BLEND_SRC_ALPHA,D3D12_BLEND_INV_SRC_ALPHA,D3D12_BLEND_OP_ADD,
D3D12_BLEND_SRC_ALPHA,D3D12_BLEND_INV_SRC_ALPHA,D3D12_BLEND_OP_ADD,
D3D12_LOGIC_OP_NOOP,
D3D12_COLOR_WRITE_ENABLE_ALL,
};
for (int i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
psoDesc.BlendState.RenderTarget[i] = rtBlendDesc;
psoDesc.NumRenderTargets = 1;
ID3D12PipelineState* d3d12PSO = nullptr;
HRESULT hResult = gD3D12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&d3d12PSO));
if (FAILED(hResult)) {
return nullptr;
}
return d3d12PSO;
}
ID3D12PipelineState* CreateVolumePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader) {
D3D12_INPUT_ELEMENT_DESC vertexDataElementDesc[] = {
{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0}
};
D3D12_INPUT_LAYOUT_DESC vertexDataLayoutDesc = {};
vertexDataLayoutDesc.NumElements = 1;
vertexDataLayoutDesc.pInputElementDescs = vertexDataElementDesc;
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = inID3D12RootSignature;
psoDesc.VS = inVertexShader;
psoDesc.PS = inPixelShader;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
psoDesc.SampleDesc.Count = 1;
psoDesc.SampleDesc.Quality = 0;
psoDesc.SampleMask = 0xffffffff;
psoDesc.InputLayout = vertexDataLayoutDesc;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
psoDesc.RasterizerState.FrontCounterClockwise = FALSE;
psoDesc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
psoDesc.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
psoDesc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
psoDesc.RasterizerState.DepthClipEnable = TRUE;
psoDesc.RasterizerState.MultisampleEnable = FALSE;
psoDesc.RasterizerState.AntialiasedLineEnable = FALSE;
psoDesc.RasterizerState.ForcedSampleCount = 0;
psoDesc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
psoDesc.DepthStencilState.DepthEnable = TRUE;
psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.BlendState.AlphaToCoverageEnable = FALSE;
psoDesc.BlendState.IndependentBlendEnable = FALSE;
for (int i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
psoDesc.BlendState.RenderTarget[i].BlendEnable = FALSE;
psoDesc.BlendState.RenderTarget[i].LogicOpEnable = FALSE;
psoDesc.BlendState.RenderTarget[i].SrcBlend = D3D12_BLEND_ONE;
psoDesc.BlendState.RenderTarget[i].DestBlend = D3D12_BLEND_ZERO;
psoDesc.BlendState.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
psoDesc.BlendState.RenderTarget[i].SrcBlendAlpha = D3D12_BLEND_ONE;
psoDesc.BlendState.RenderTarget[i].DestBlendAlpha = D3D12_BLEND_ZERO;
psoDesc.BlendState.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
psoDesc.BlendState.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
psoDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}
psoDesc.NumRenderTargets = 1;
ID3D12PipelineState* d3d12PSO = nullptr;
HRESULT hResult = gD3D12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&d3d12PSO));
if (FAILED(hResult)) {
printf("CreateVolumePSO failed: 0x%08X\n", hResult);
return nullptr;
}
return d3d12PSO;
}
ID3D12PipelineState* CreateQuadPSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader) {
D3D12_INPUT_ELEMENT_DESC vertexDataElementDesc[] = {
{"POSITION",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
{"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,8,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0}
};
D3D12_INPUT_LAYOUT_DESC vertexDataLayoutDesc = {};
vertexDataLayoutDesc.NumElements = 2;
vertexDataLayoutDesc.pInputElementDescs = vertexDataElementDesc;
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = inID3D12RootSignature;
psoDesc.VS = inVertexShader;
psoDesc.PS = inPixelShader;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
psoDesc.SampleDesc.Count = 1;
psoDesc.SampleDesc.Quality = 0;
psoDesc.SampleMask = 0xffffffff;
psoDesc.InputLayout = vertexDataLayoutDesc;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
psoDesc.RasterizerState.FrontCounterClockwise = FALSE;
psoDesc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
psoDesc.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
psoDesc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
psoDesc.RasterizerState.DepthClipEnable = TRUE;
psoDesc.RasterizerState.MultisampleEnable = FALSE;
psoDesc.RasterizerState.AntialiasedLineEnable = FALSE;
psoDesc.RasterizerState.ForcedSampleCount = 0;
psoDesc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
psoDesc.DepthStencilState.DepthEnable = TRUE;
psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.BlendState.AlphaToCoverageEnable = FALSE;
psoDesc.BlendState.IndependentBlendEnable = FALSE;
for (int i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
psoDesc.BlendState.RenderTarget[i].BlendEnable = TRUE;
psoDesc.BlendState.RenderTarget[i].LogicOpEnable = FALSE;
psoDesc.BlendState.RenderTarget[i].SrcBlend = D3D12_BLEND_SRC_ALPHA;
psoDesc.BlendState.RenderTarget[i].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
psoDesc.BlendState.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
psoDesc.BlendState.RenderTarget[i].SrcBlendAlpha = D3D12_BLEND_ONE;
psoDesc.BlendState.RenderTarget[i].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
psoDesc.BlendState.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
psoDesc.BlendState.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
psoDesc.BlendState.RenderTarget[i].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}
psoDesc.NumRenderTargets = 1;
ID3D12PipelineState* d3d12PSO = nullptr;
HRESULT hResult = gD3D12Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&d3d12PSO));
if (FAILED(hResult)) {
printf("CreateQuadPSO failed: 0x%08X\n", hResult);
return nullptr;
}
return d3d12PSO;
}
bool InitD3D12(HWND inHWND, int inWidth, int inHeight) {
HRESULT hResult;
UINT dxgiFactoryFlags = 0;
#ifdef _DEBUG
{
ID3D12Debug* debugController = nullptr;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
debugController->EnableDebugLayer();
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
IDXGIFactory4* dxgiFactory;
hResult = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&dxgiFactory));
if (FAILED(hResult)) {
return false;
}
IDXGIAdapter1* adapter;
int adapterIndex = 0;
bool adapterFound = false;
while (dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND) {
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
continue;
}
hResult = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr);
if (SUCCEEDED(hResult)) {
adapterFound = true;
break;
}
adapterIndex++;
}
if (false == adapterFound) {
return false;
}
hResult = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&gD3D12Device));
if (FAILED(hResult)) {
return false;
}
D3D12_COMMAND_QUEUE_DESC d3d12CommandQueueDesc = {};
hResult = gD3D12Device->CreateCommandQueue(&d3d12CommandQueueDesc, IID_PPV_ARGS(&gCommandQueue));
if (FAILED(hResult)) {
return false;
}
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc = {};
swapChainDesc.BufferDesc.Width = inWidth;
swapChainDesc.BufferDesc.Height = inHeight;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = inHWND;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
IDXGISwapChain* swapChain = nullptr;
dxgiFactory->CreateSwapChain(gCommandQueue, &swapChainDesc, &swapChain);
gSwapChain = static_cast<IDXGISwapChain3*>(swapChain);
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inWidth;
d3d12ResourceDesc.Height = inHeight;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE dsClearValue = {};
dsClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsClearValue.DepthStencil.Depth = 1.0f;
dsClearValue.DepthStencil.Stencil = 0;
gD3D12Device->CreateCommittedResource(&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&dsClearValue,
IID_PPV_ARGS(&gDSRT)
);
//RTV,DSV,alloc
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescRTV = {};
d3dDescriptorHeapDescRTV.NumDescriptors = 2;
d3dDescriptorHeapDescRTV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
gD3D12Device->CreateDescriptorHeap(&d3dDescriptorHeapDescRTV, IID_PPV_ARGS(&gSwapChainRTVHeap));
gRTVDescriptorSize = gD3D12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescDSV = {};
d3dDescriptorHeapDescDSV.NumDescriptors = 1;
d3dDescriptorHeapDescDSV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
gD3D12Device->CreateDescriptorHeap(&d3dDescriptorHeapDescDSV, IID_PPV_ARGS(&gSwapChainDSVHeap));
gDSVDescriptorSize = gD3D12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapStart = gSwapChainRTVHeap->GetCPUDescriptorHandleForHeapStart();
for (int i = 0; i < 2; i++) {
gSwapChain->GetBuffer(i, IID_PPV_ARGS(&gColorRTs[i]));
D3D12_CPU_DESCRIPTOR_HANDLE rtvPointer;
rtvPointer.ptr = rtvHeapStart.ptr + i * gRTVDescriptorSize;
gD3D12Device->CreateRenderTargetView(gColorRTs[i], nullptr, rtvPointer);
}
D3D12_DEPTH_STENCIL_VIEW_DESC d3dDSViewDesc = {};
d3dDSViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
d3dDSViewDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
gD3D12Device->CreateDepthStencilView(gDSRT, &d3dDSViewDesc, gSwapChainDSVHeap->GetCPUDescriptorHandleForHeapStart());
gD3D12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&gCommandAllocator));
gD3D12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, gCommandAllocator, nullptr, IID_PPV_ARGS(&gCommandList));
gD3D12Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&gFence));
gFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
return true;
}
ID3D12CommandAllocator* GetCommandAllocator() {
return gCommandAllocator;
}
ID3D12GraphicsCommandList* GetCommandList() {
return gCommandList;
}
void WaitForCompletionOfCommandList() {
if (gFence->GetCompletedValue() < gFenceValue) {
gFence->SetEventOnCompletion(gFenceValue, gFenceEvent);
WaitForSingleObject(gFenceEvent, INFINITE);
}
}
void EndCommandList() {
gCommandList->Close();//
ID3D12CommandList* ppCommandLists[] = { gCommandList };
gCommandQueue->ExecuteCommandLists(1, ppCommandLists);
//CommandList
gFenceValue += 1;
gCommandQueue->Signal(gFence, gFenceValue);//
}
void BeginRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList) {
gCurrentRTIndex = gSwapChain->GetCurrentBackBufferIndex();
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(gColorRTs[gCurrentRTIndex], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
inCommandList->ResourceBarrier(1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE colorRT, dsv;
dsv.ptr = gSwapChainDSVHeap->GetCPUDescriptorHandleForHeapStart().ptr;
colorRT.ptr = gSwapChainRTVHeap->GetCPUDescriptorHandleForHeapStart().ptr + gCurrentRTIndex * gRTVDescriptorSize;
inCommandList->OMSetRenderTargets(1, &colorRT, FALSE, &dsv);
D3D12_VIEWPORT viewport = { 0.0f,0.0f,1280.0f,720.0f };
D3D12_RECT scissorRect = { 0,0,1280,720 };
inCommandList->RSSetViewports(1, &viewport);
inCommandList->RSSetScissorRects(1, &scissorRect);
const float clearColor[] = { 0.0f,0.0f,0.0f,1.0f };
inCommandList->ClearRenderTargetView(colorRT, clearColor, 0, nullptr);
inCommandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
}
void EndRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList) {
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(gColorRTs[gCurrentRTIndex], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
inCommandList->ResourceBarrier(1, &barrier);
}
void SwapD3D12Buffers() {
gSwapChain->Present(0, 0);
}
ID3D12Resource* CreateBufferObject(ID3D12GraphicsCommandList* inCommandList,
void* inData, int inDataLen, D3D12_RESOURCE_STATES inFinalResourceState) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;//gpu
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inDataLen;
d3d12ResourceDesc.Height = 1;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* bufferObject = nullptr;
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&bufferObject)
);
d3d12ResourceDesc = bufferObject->GetDesc();
UINT64 memorySizeUsed = 0;
UINT64 rowSizeInBytes = 0;
UINT rowUsed = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT subresourceFootprint;
gD3D12Device->GetCopyableFootprints(&d3d12ResourceDesc, 0, 1, 0,
&subresourceFootprint, &rowUsed, &rowSizeInBytes, &memorySizeUsed);
// 3 x 4 x 4 = 48bytes,32bytes,24bytes + 24bytes
ID3D12Resource* tempBufferObject = nullptr;
d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
gD3D12Device->CreateCommittedResource(
&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&tempBufferObject)
);
BYTE* pData;
tempBufferObject->Map(0, nullptr, reinterpret_cast<void**>(&pData));
BYTE* pDstTempBuffer = reinterpret_cast<BYTE*>(pData + subresourceFootprint.Offset);
const BYTE* pSrcData = reinterpret_cast<BYTE*>(inData);
for (UINT i = 0; i < rowUsed; i++) {
memcpy(pDstTempBuffer + subresourceFootprint.Footprint.RowPitch * i, pSrcData + rowSizeInBytes * i, rowSizeInBytes);
}
tempBufferObject->Unmap(0, nullptr);
inCommandList->CopyBufferRegion(bufferObject, 0, tempBufferObject, 0, subresourceFootprint.Footprint.Width);
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(bufferObject, D3D12_RESOURCE_STATE_COPY_DEST, inFinalResourceState);
inCommandList->ResourceBarrier(1, &barrier);
return bufferObject;
}
D3D12_RESOURCE_BARRIER InitResourceBarrier(
ID3D12Resource* inResource, D3D12_RESOURCE_STATES inPrevState,
D3D12_RESOURCE_STATES inNextState) {
D3D12_RESOURCE_BARRIER d3d12ResourceBarrier;
memset(&d3d12ResourceBarrier, 0, sizeof(d3d12ResourceBarrier));
d3d12ResourceBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
d3d12ResourceBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
d3d12ResourceBarrier.Transition.pResource = inResource;
d3d12ResourceBarrier.Transition.StateBefore = inPrevState;
d3d12ResourceBarrier.Transition.StateAfter = inNextState;
d3d12ResourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
return d3d12ResourceBarrier;
}
ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
const void* inPixelData, int inDataSizeInBytes, int inWidth, int inHeight,
DXGI_FORMAT inFormat) {
D3D12_HEAP_PROPERTIES d3dHeapProperties = {};
d3dHeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC d3d12ResourceDesc = {};
d3d12ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
d3d12ResourceDesc.Alignment = 0;
d3d12ResourceDesc.Width = inWidth;
d3d12ResourceDesc.Height = inHeight;
d3d12ResourceDesc.DepthOrArraySize = 1;
d3d12ResourceDesc.MipLevels = 1;
d3d12ResourceDesc.Format = inFormat;
d3d12ResourceDesc.SampleDesc.Count = 1;
d3d12ResourceDesc.SampleDesc.Quality = 0;
d3d12ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
d3d12ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* texture = nullptr;
gD3D12Device->CreateCommittedResource(&d3dHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12ResourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
d3d12ResourceDesc = texture->GetDesc();
UINT64 memorySizeUsed = 0;
UINT64 rowSizeInBytes = 0;
UINT rowUsed = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT subresourceFootprint;
gD3D12Device->GetCopyableFootprints(&d3d12ResourceDesc, 0, 1, 0,
&subresourceFootprint, &rowUsed, &rowSizeInBytes, &memorySizeUsed);
// 3 x 4 x 4 = 48bytes,32bytes,24bytes + 24bytes
ID3D12Resource* tempBufferObject = nullptr;
D3D12_HEAP_PROPERTIES d3dTempHeapProperties = {};
d3dTempHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;//cpu,gpu
D3D12_RESOURCE_DESC d3d12TempResourceDesc = {};
d3d12TempResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
d3d12TempResourceDesc.Alignment = 0;
d3d12TempResourceDesc.Width = memorySizeUsed;
d3d12TempResourceDesc.Height = 1;
d3d12TempResourceDesc.DepthOrArraySize = 1;
d3d12TempResourceDesc.MipLevels = 1;
d3d12TempResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
d3d12TempResourceDesc.SampleDesc.Count = 1;
d3d12TempResourceDesc.SampleDesc.Quality = 0;
d3d12TempResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
d3d12TempResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
gD3D12Device->CreateCommittedResource(
&d3dTempHeapProperties,
D3D12_HEAP_FLAG_NONE,
&d3d12TempResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&tempBufferObject)
);
BYTE* pData;
tempBufferObject->Map(0, nullptr, reinterpret_cast<void**>(&pData));
BYTE* pDstTempBuffer = reinterpret_cast<BYTE*>(pData + subresourceFootprint.Offset);
const BYTE* pSrcData = reinterpret_cast<const BYTE*>(inPixelData);
for (UINT i = 0; i < rowUsed; i++) {
memcpy(pDstTempBuffer + subresourceFootprint.Footprint.RowPitch * i, pSrcData + rowSizeInBytes * i, rowSizeInBytes);
}
tempBufferObject->Unmap(0, nullptr);
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = texture;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = tempBufferObject;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = subresourceFootprint;
inCommandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
D3D12_RESOURCE_BARRIER barrier = InitResourceBarrier(texture,
D3D12_RESOURCE_STATE_COPY_DEST,D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
inCommandList->ResourceBarrier(1, &barrier);
return texture;
}
ID3D12Device* GetD3DDevice() {
return gD3D12Device;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <d3d12.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include <stdio.h>
D3D12_RESOURCE_BARRIER InitResourceBarrier(
ID3D12Resource* inResource, D3D12_RESOURCE_STATES inPrevState,
D3D12_RESOURCE_STATES inNextState);
ID3D12RootSignature* InitRootSignature();
ID3D12RootSignature* InitVolumeRootSignature();
void CreateShaderFromFile(
LPCTSTR inShaderFilePath,
const char* inMainFunctionName,
const char* inTarget,//"vs_5_0","ps_5_0","vs_4_0"
D3D12_SHADER_BYTECODE* inShader);
ID3D12Resource* CreateConstantBufferObject(int inDataLen);
void UpdateConstantBuffer(ID3D12Resource* inCB, void* inData, int inDataLen);
ID3D12Resource* CreateBufferObject(ID3D12GraphicsCommandList* inCommandList,
void* inData, int inDataLen, D3D12_RESOURCE_STATES inFinalResourceState);
ID3D12PipelineState* CreatePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader,
D3D12_SHADER_BYTECODE inGSShader);
ID3D12PipelineState* CreateVolumePSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader);
ID3D12PipelineState* CreateQuadPSO(ID3D12RootSignature* inID3D12RootSignature,
D3D12_SHADER_BYTECODE inVertexShader, D3D12_SHADER_BYTECODE inPixelShader);
bool InitD3D12(HWND inHWND, int inWidth, int inHeight);
ID3D12GraphicsCommandList* GetCommandList();
ID3D12CommandAllocator* GetCommandAllocator();
void WaitForCompletionOfCommandList();
void EndCommandList();
void BeginRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList);
void EndRenderToSwapChain(ID3D12GraphicsCommandList* inCommandList);
void SwapD3D12Buffers();
ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
const void*inPixelData,int inDataSizeInBytes,int inWidth,int inHeight,
DXGI_FORMAT inFormat);
ID3D12Device* GetD3DDevice();

View File

@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.15)
project(XCVolumeRenderer VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(SOURCES
main.cpp
BattleFireDirect.cpp
StaticMeshComponent.cpp
Utils.cpp
stbi/stb_image.cpp
NanoVDBLoader.cpp
)
set(HEADERS
BattleFireDirect.h
StaticMeshComponent.h
Utils.h
stbi/stb_image.h
)
add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${HEADERS})
target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE)
target_compile_options(${PROJECT_NAME} PRIVATE /utf-8 /MT)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
d3d12.lib
dxgi.lib
d3dcompiler.lib
winmm.lib
kernel32.lib
user32.lib
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME} PRIVATE _DEBUG)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE NDEBUG)
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/Res
$<TARGET_FILE_DIR:${PROJECT_NAME}>/Res
)

View File

@@ -0,0 +1,134 @@
#define NOMINMAX
#include "NanoVDBLoader.h"
#include <windows.h>
#include "BattleFireDirect.h"
#include <nanovdb/io/IO.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/HostBuffer.h>
#include <cstring>
#include <iostream>
bool LoadNanoVDB(const char* filePath, NanoVDBData& outData, ID3D12GraphicsCommandList* cmdList, ID3D12CommandAllocator* cmdAlloc) {
try {
if (cmdAlloc && cmdList) {
cmdAlloc->Reset();
cmdList->Reset(cmdAlloc, nullptr);
}
nanovdb::GridHandle<nanovdb::HostBuffer> gridHandle = nanovdb::io::readGrid(filePath);
const uint64_t byteSize = gridHandle.buffer().bufferSize();
const uint64_t elementCount = byteSize / sizeof(uint32_t);
void* bufferData = malloc(byteSize);
if (!bufferData) {
std::cerr << "Failed to allocate memory for NanoVDB" << std::endl;
return false;
}
memcpy(bufferData, gridHandle.buffer().data(), byteSize);
double* bboxData = (double*)((char*)bufferData + 560);
for (int i = 0; i < 6; i++) {
outData.worldBBox[i] = bboxData[i];
}
double* voxelSizeData = (double*)((char*)bufferData + 608);
printf("[NanoVDB] Voxel size: [%.6f, %.6f, %.6f]\n", voxelSizeData[0], voxelSizeData[1], voxelSizeData[2]);
outData.cpuData = bufferData;
outData.byteSize = byteSize;
outData.elementCount = elementCount;
ID3D12Device* device = GetD3DDevice();
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
D3D12_RESOURCE_DESC resourceDesc = {};
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
resourceDesc.Width = byteSize;
resourceDesc.Height = 1;
resourceDesc.DepthOrArraySize = 1;
resourceDesc.MipLevels = 1;
resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
resourceDesc.SampleDesc.Count = 1;
HRESULT hr = device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&resourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&outData.gpuBuffer)
);
if (FAILED(hr)) {
std::cerr << "Failed to create GPU buffer for NanoVDB" << std::endl;
free(bufferData);
return false;
}
D3D12_HEAP_PROPERTIES uploadHeapProps = {};
uploadHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
ID3D12Resource* uploadBuffer = nullptr;
hr = device->CreateCommittedResource(
&uploadHeapProps,
D3D12_HEAP_FLAG_NONE,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&uploadBuffer)
);
if (SUCCEEDED(hr)) {
void* mappedData = nullptr;
D3D12_RANGE readRange = {0, 0};
uploadBuffer->Map(0, &readRange, &mappedData);
memcpy(mappedData, bufferData, byteSize);
uploadBuffer->Unmap(0, nullptr);
cmdList->CopyBufferRegion(outData.gpuBuffer, 0, uploadBuffer, 0, byteSize);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = outData.gpuBuffer;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
cmdList->ResourceBarrier(1, &barrier);
EndCommandList();
WaitForCompletionOfCommandList();
uploadBuffer->Release();
GetCommandAllocator()->Reset();
cmdList->Reset(GetCommandAllocator(), nullptr);
}
std::cout << "[NanoVDB] Loaded: " << byteSize << " bytes, " << elementCount << " elements" << std::endl;
std::cout.flush();
return true;
}
catch (const std::exception& e) {
std::cerr << "[NanoVDB] Error: " << e.what() << std::endl;
std::cerr.flush();
return false;
}
}
void FreeNanoVDB(NanoVDBData& data) {
if (data.gpuBuffer) {
data.gpuBuffer->Release();
data.gpuBuffer = nullptr;
}
if (data.cpuData) {
free(data.cpuData);
data.cpuData = nullptr;
}
data.byteSize = 0;
data.elementCount = 0;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <d3d12.h>
#include <string>
#include <vector>
struct NanoVDBData {
ID3D12Resource* gpuBuffer;
void* cpuData;
uint64_t byteSize;
uint64_t elementCount;
double worldBBox[6];
};
bool LoadNanoVDB(const char* filePath, NanoVDBData& outData, ID3D12GraphicsCommandList* cmdList, ID3D12CommandAllocator* cmdAlloc = nullptr);
void FreeNanoVDB(NanoVDBData& data);

View File

@@ -0,0 +1,87 @@
# XCVolumeRenderer
基于 DirectX 12 的体积渲染器,使用 NanoVDB 格式实现云、烟雾等体积数据的实时渲染。
## 技术栈
- **渲染API**: DirectX 12
- **语言**: C++17
- **构建系统**: CMake
- **依赖库**: DirectX 12 SDK, stb_image, NanoVDB
## 项目结构
```
XCVolumeRenderer/
├── main.cpp # 主程序入口
├── BattleFireDirect.cpp/h # DirectX 12 核心渲染实现
├── NanoVDBLoader.cpp/h # NanoVDB 体积数据加载器
├── StaticMeshComponent.cpp/h # 静态网格组件
├── Utils.cpp/h # 工具函数
├── stbi/ # 图像加载库
├── Res/
│ ├── Shader/ # HLSL 着色器
│ │ ├── volume.hlsl # 体积渲染着色器
│ │ ├── gs.hlsl # 几何着色器
│ │ └── PNanoVDB.hlsl # NanoVDB GPU 解析
│ ├── Model/ # 模型文件
│ ├── Image/ # 纹理图片
│ └── NanoVDB/ # NanoVDB 体积数据
├── CMakeLists.txt # CMake 构建配置
└── build/ # 构建目录
```
## 构建方法
### 前置要求
- Windows 10/11
- Visual Studio 2019 或更高版本
- CMake 3.15+
### 构建步骤
```bash
# 创建并进入构建目录
mkdir build && cd build
# 配置项目
cmake ..
# 编译
cmake --build . --config Release
```
### 运行
编译完成后,运行 `XCVolumeRenderer.exe` 或使用 `run.bat`
## 功能特性
### 核心渲染
- DirectX 12 渲染管线
- 几何着色器GS支持
- 纹理映射
- 常量缓冲区CBV/SRV
### 体积渲染
- NanoVDB 格式支持
- 光线步进Ray Marching
- HDDA 空间跳跃加速
- 体积阴影Volumetric Shadow
- 指数步长抖动采样
- Gamma 校正
### 参数配置
- DensityScale - 密度缩放
- StepSize - 步进大小
- MaxSteps - 最大步数
- LightDir - 光照方向
- LightSamples - 阴影采样数
## 资源说明
- 着色器文件位于 `Res/Shader/` 目录
- 模型文件为 `.lhsm` 格式
- 纹理支持 PNG、JPG 格式
- 体积数据支持 NanoVDB `.nvdb` 格式

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
struct VertexData{
float4 position:POSITION;
float4 texcoord:TEXCOORD0;
float4 normal:NORMAL;
float4 tangent:TANGENT;
};
struct VSOut{
float4 position:SV_POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
static const float PI=3.141592;
cbuffer globalConstants:register(b0){
float4 misc;
};
Texture2D T_DiffuseTexture:register(t0);
SamplerState samplerState:register(s0);
struct MaterialData{
float r;
};
StructuredBuffer<MaterialData> materialData:register(t0,space1);
cbuffer DefaultVertexCB:register(b1){
float4x4 ProjectionMatrix;
float4x4 ViewMatrix;
float4x4 ModelMatrix;
float4x4 IT_ModelMatrix;
float4x4 ReservedMemory[1020];
};
VSOut MainVS(VertexData inVertexData){
VSOut vo;
vo.normal=mul(IT_ModelMatrix,inVertexData.normal);
float4 positionWS=mul(ModelMatrix,inVertexData.position);
float4 positionVS=mul(ViewMatrix,positionWS);
vo.position=mul(ProjectionMatrix,positionVS);
//vo.position=float4(positionWS.xyz+vo.normal.xyz*sin(misc.x)*0.2f,1.0f);
vo.texcoord=inVertexData.texcoord;
return vo;
}
[maxvertexcount(4)]
void MainGS(triangle VSOut inPoint[3],uint inPrimitiveID:SV_PrimitiveID,
inout TriangleStream<VSOut> outTriangleStream){
outTriangleStream.Append(inPoint[0]);
outTriangleStream.Append(inPoint[1]);
outTriangleStream.Append(inPoint[2]);
/*VSOut vo;
float3 positionWS=inPoint[0].position.xyz;
float3 N=normalize(inPoint[0].normal.xyz);
vo.normal=float4(N,0.0f);
float3 helperVec=abs(N.y)>0.999?float3(0.0f,0.0f,1.0f):float3(0.0f,1.0f,0.0f);
float3 tangent=normalize(cross(N,helperVec));//u
float3 bitangent=normalize(cross(tangent,N));//v
float scale=materialData[inPrimitiveID].r;
float3 p0WS=positionWS-(bitangent*0.5f-tangent*0.5f)*scale;//left bottom
float4 p0VS=mul(ViewMatrix,float4(p0WS,1.0f));
vo.position=mul(ProjectionMatrix,p0VS);
vo.texcoord=float4(0.0f,1.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p1WS=positionWS-(bitangent*0.5f+tangent*0.5f)*scale;//right bottom
float4 p1VS=mul(ViewMatrix,float4(p1WS,1.0f));
vo.position=mul(ProjectionMatrix,p1VS);
vo.texcoord=float4(1.0f,1.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p2WS=positionWS+(bitangent*0.5f+tangent*0.5f)*scale;//left top
float4 p2VS=mul(ViewMatrix,float4(p2WS,1.0f));
vo.position=mul(ProjectionMatrix,p2VS);
vo.texcoord=float4(0.0f,0.0f,0.0f,0.0f);
outTriangleStream.Append(vo);
float3 p3WS=positionWS+(bitangent*0.5f-tangent*0.5f)*scale;//right top
float4 p3VS=mul(ViewMatrix,float4(p3WS,1.0f));
vo.position=mul(ProjectionMatrix,p3VS);
vo.texcoord=float4(1.0f,0.0f,0.0f,0.0f);
outTriangleStream.Append(vo);*/
}
float4 MainPS(VSOut inPSInput):SV_TARGET{
float3 N=normalize(inPSInput.normal.xyz);
float3 bottomColor=float3(0.1f,0.4f,0.6f);
float3 topColor=float3(0.7f,0.7f,0.7f);
float theta=asin(N.y);//-PI/2 ~ PI/2
theta/=PI;//-0.5~0.5
theta+=0.5f;//0.0~1.0
float ambientColorIntensity=1.0;
float3 ambientColor=lerp(bottomColor,topColor,theta)*ambientColorIntensity;
float4 diffuseColor=T_DiffuseTexture.Sample(samplerState,inPSInput.texcoord.xy);
float3 surfaceColor=diffuseColor.rgb;
return float4(surfaceColor,1.0f);
}

View File

@@ -0,0 +1,65 @@
struct VertexData{
float4 position:POSITION;
float4 texcoord:TEXCOORD0;
float4 normal:NORMAL;
float4 tangent:TANGENT;
};
struct VSOut{
float4 position:SV_POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 positionWS:TEXCOORD1;
};
static const float PI=3.141592;
cbuffer globalConstants:register(b0){
float4 misc;
};
cbuffer DefaultVertexCB:register(b1){
float4x4 ProjectionMatrix;
float4x4 ViewMatrix;
float4x4 ModelMatrix;
float4x4 IT_ModelMatrix;
float4x4 ReservedMemory[1020];
};
VSOut MainVS(VertexData inVertexData){
VSOut vo;
vo.normal=mul(IT_ModelMatrix,inVertexData.normal);
float3 positionMS=inVertexData.position.xyz+vo.normal*sin(misc.x);
float4 positionWS=mul(ModelMatrix,float4(positionMS,1.0));
float4 positionVS=mul(ViewMatrix,positionWS);
vo.position=mul(ProjectionMatrix,positionVS);
vo.positionWS=positionWS;
vo.texcoord=inVertexData.texcoord;
return vo;
}
float4 MainPS(VSOut inPSInput):SV_TARGET{
float3 N=normalize(inPSInput.normal.xyz);
float3 bottomColor=float3(0.1f,0.4f,0.6f);
float3 topColor=float3(0.7f,0.7f,0.7f);
float theta=asin(N.y);//-PI/2 ~ PI/2
theta/=PI;//-0.5~0.5
theta+=0.5f;//0.0~1.0
float ambientColorIntensity=0.2;
float3 ambientColor=lerp(bottomColor,topColor,theta)*ambientColorIntensity;
float3 L=normalize(float3(1.0f,1.0f,-1.0f));
float diffuseIntensity=max(0.0f,dot(N,L));
float3 diffuseLightColor=float3(0.1f,0.4f,0.6f);
float3 diffuseColor=diffuseLightColor*diffuseIntensity;
float3 specularColor=float3(0.0f,0.0f,0.0f);
if(diffuseIntensity>0.0f){
float3 cameraPositionWS=float3(0.0f,0.0f,0.0f);
float3 V=normalize(cameraPositionWS.xyz-inPSInput.positionWS.xyz);
float3 R=normalize(reflect(-L,N));
float specularIntensity=pow(max(0.0f,dot(V,R)),128.0f);
specularColor=float3(1.0f,1.0f,1.0f)*specularIntensity;
}
float3 surfaceColor=ambientColor+diffuseColor+specularColor;
return float4(surfaceColor,1.0f);
}

View File

@@ -0,0 +1,250 @@
#define PNANOVDB_HLSL
#define PNANOVDB_ADDRESS_32
#include "PNanoVDB.hlsl"
cbuffer CB0 : register(b1)
{
float4x4 _InverseViewProjection; // 64 bytes
float4 _CameraPos_Density; // xyz = CameraPos, w = DensityScale
float4 _BBoxMin_Step; // xyz = BBoxMin, w = StepSize
float4 _BBoxMax_MaxSteps; // xyz = BBoxMax, w = MaxSteps
float4 _Rotation_Pad_LightSamples; // x = RotationY, yzw = pad, but we'll use differently
float4 _LightDir_Samples; // xyz = LightDir, w = LightSamples
};
StructuredBuffer<uint> buf : register(t1);
struct VSInput
{
float2 position : POSITION;
float2 texcoord : TEXCOORD0;
};
struct PSInput
{
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
struct NanoVolume
{
pnanovdb_grid_handle_t grid;
pnanovdb_grid_type_t grid_type;
pnanovdb_readaccessor_t acc;
};
void initVolume(inout NanoVolume volume)
{
pnanovdb_grid_handle_t grid;
grid.address.byte_offset = 0;
pnanovdb_grid_type_t grid_type = pnanovdb_buf_read_uint32(buf, PNANOVDB_GRID_OFF_GRID_TYPE);
pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buf, grid);
pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buf, tree);
pnanovdb_readaccessor_t acc;
pnanovdb_readaccessor_init(acc, root);
volume.grid = grid;
volume.grid_type = grid_type;
volume.acc = acc;
}
float get_value_coord(inout pnanovdb_readaccessor_t acc, float3 pos)
{
pnanovdb_vec3_t p = pos;
pnanovdb_coord_t ijk = pnanovdb_hdda_pos_to_ijk(p);
pnanovdb_address_t address = pnanovdb_readaccessor_get_value_address(PNANOVDB_GRID_TYPE_FLOAT, buf, acc, ijk);
return pnanovdb_read_float(buf, address);
}
uint get_dim_coord(inout pnanovdb_readaccessor_t acc, float3 pos)
{
pnanovdb_vec3_t p = pos;
pnanovdb_coord_t ijk = pnanovdb_hdda_pos_to_ijk(p);
return pnanovdb_readaccessor_get_dim(PNANOVDB_GRID_TYPE_FLOAT, buf, acc, ijk);
}
bool get_hdda_hit(inout pnanovdb_readaccessor_t acc, inout float tmin, float3 origin, float3 direction, float tmax, out float valueAtHit)
{
pnanovdb_vec3_t p_origin = origin;
pnanovdb_vec3_t p_direction = direction;
float thit;
bool hit = pnanovdb_hdda_tree_marcher(
PNANOVDB_GRID_TYPE_FLOAT,
buf,
acc,
p_origin, tmin,
p_direction, tmax,
thit,
valueAtHit
);
tmin = thit;
return hit;
}
float phase_function()
{
return 1.0;
}
uint rand_xorshift(uint seed)
{
seed ^= (seed << 13);
seed ^= (seed >> 17);
seed ^= (seed << 5);
return seed;
}
float random_float(float3 pos)
{
uint seed = asuint(pos.x + pos.y + pos.z);
float res = float(rand_xorshift(seed)) * (1.0 / 4294967296.0);
res = float(rand_xorshift(asuint(res))) * (1.0 / 4294967296.0);
return res;
}
float volumetric_shadow(float3 pos, float densityScale, inout pnanovdb_readaccessor_t acc)
{
float lightSamples = _LightDir_Samples.w;
if (lightSamples < 1) { return 0.0; }
float3 light_dir = _LightDir_Samples.xyz;
float shadow = 1.0;
float sigmaS = 0.0;
float sigmaE = 0.0;
float step_size = 1.0;
float jitter = 0;
int steps = 10;
for (int step = 0; step < steps; step++)
{
float3 sample_pos = pos + (jitter + step_size) * light_dir;
sigmaS = get_value_coord(acc, sample_pos) * densityScale;
sigmaE = max(0.000001, sigmaS);
sigmaE *= 0.3;
shadow *= exp(-sigmaE * step_size);
step_size *= 2.0;
}
return shadow;
}
PSInput MainVS(VSInput input)
{
PSInput output;
output.position = float4(input.position, 0.0, 1.0);
output.texcoord = input.texcoord;
float4 worldPosH = mul(_InverseViewProjection, float4(input.position, 0.5, 1.0));
output.worldPos = worldPosH.xyz / worldPosH.w;
return output;
}
bool intersectBox(float3 origin, float3 dir, float3 boxMin, float3 boxMax, out float tmin, out float tmax)
{
float3 invDir = 1.0 / dir;
float3 t1 = (boxMin - origin) * invDir;
float3 t2 = (boxMax - origin) * invDir;
tmin = max(max(min(t1.x, t2.x), min(t1.y, t2.y)), min(t1.z, t2.z));
tmax = min(min(max(t1.x, t2.x), max(t1.y, t2.y)), max(t1.z, t2.z));
return tmax >= tmin && tmax > 0;
}
float4 MainPS(PSInput input) : SV_TARGET
{
float3 rayDir = normalize(input.worldPos - _CameraPos_Density.xyz);
float tmin = 0.01;
float tmax = 5000.0;
NanoVolume volume;
initVolume(volume);
float3 color = float3(0, 0, 0);
float transmittance = 1.0;
float acc_density = 0.0;
float3 ambient_light = 0.005;
float _DensityScale = _CameraPos_Density.w;
float _StepSize = _BBoxMin_Step.w;
float _MaxSteps = _BBoxMax_MaxSteps.w;
float _RotationY = _Rotation_Pad_LightSamples.x;
float _LightSamples = _LightDir_Samples.w;
float cosR = cos(_RotationY);
float sinR = sin(_RotationY);
float3x3 invRotY = float3x3(
cosR, 0, sinR,
0, 1, 0,
-sinR, 0, cosR
);
float3 localCameraPos = mul(invRotY, _CameraPos_Density.xyz);
float3 localRayDir = mul(invRotY, rayDir);
float not_used;
bool hit = get_hdda_hit(volume.acc, tmin, localCameraPos, localRayDir, tmax, not_used);
if (!hit) { return float4(0, 0, 0, 0); }
float skip = 0;
for (int i = 0; i < (int)_MaxSteps; i++) {
if (tmin >= tmax) break;
float3 localPos = localCameraPos + localRayDir * tmin;
uint dim = get_dim_coord(volume.acc, localPos);
if (dim > 1) {
float skip_step = 15.0;
tmin += skip_step;
skip = skip_step;
continue;
}
float density = get_value_coord(volume.acc, localPos) * _DensityScale;
if (density < 0.01) {
float skip_step = 5.0;
tmin += skip_step;
skip = skip_step;
continue;
}
if (skip > 0) {
tmin -= skip * 0.8;
localPos = localCameraPos + localRayDir * tmin;
skip = 0;
}
float sigmaS = density;
float sigmaE = max(0.000001, sigmaS);
acc_density += sigmaS;
float shadow = volumetric_shadow(localPos, _DensityScale, volume.acc);
float3 S = sigmaS * phase_function() * shadow * float3(1, 1, 1);
float3 Sint = (S - S * exp(-sigmaE * _StepSize)) / sigmaE;
color += transmittance * Sint;
transmittance *= exp(-sigmaE * _StepSize);
if (acc_density > 1.0) break;
if (transmittance < 0.05)
{
transmittance = 0;
break;
}
tmin += _StepSize;
}
float3 final_color = (color + ambient_light) * acc_density;
final_color = pow(final_color, 1.0 / 2.2);
return float4(final_color, acc_density);
}

View File

@@ -0,0 +1,26 @@
cbuffer CB0 : register(b1)
{
float4x4 _ViewProjection;
};
struct VSInput
{
float3 position : POSITION;
};
struct PSInput
{
float4 position : SV_POSITION;
};
PSInput MainVS(VSInput input)
{
PSInput output;
output.position = mul(_ViewProjection, float4(input.position, 1.0));
return output;
}
float4 MainPS(PSInput input) : SV_TARGET
{
return float4(0.0, 1.0, 0.0, 1.0);
}

View File

@@ -0,0 +1,90 @@
#include "StaticMeshComponent.h"
#include "BattleFireDirect.h"
#include <stdio.h>
void StaticMeshComponent::SetVertexCount(int inVertexCount) {
mVertexCount = inVertexCount;
mVertexData = new StaticMeshComponentVertexData[inVertexCount];
memset(mVertexData, 0, sizeof(StaticMeshComponentVertexData)*inVertexCount);
}
void StaticMeshComponent::SetVertexPosition(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mPosition[0] = inX;
mVertexData[inIndex].mPosition[1] = inY;
mVertexData[inIndex].mPosition[2] = inZ;
mVertexData[inIndex].mPosition[3] = inW;
}
void StaticMeshComponent::SetVertexTexcoord(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mTexcoord[0] = inX;
mVertexData[inIndex].mTexcoord[1] = inY;
mVertexData[inIndex].mTexcoord[2] = inZ;
mVertexData[inIndex].mTexcoord[3] = inW;
}
void StaticMeshComponent::SetVertexNormal(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mNormal[0] = inX;
mVertexData[inIndex].mNormal[1] = inY;
mVertexData[inIndex].mNormal[2] = inZ;
mVertexData[inIndex].mNormal[3] = inW;
}
void StaticMeshComponent::SetVertexTangent(int inIndex, float inX, float inY, float inZ, float inW /* = 1.0f */) {
mVertexData[inIndex].mTangent[0] = inX;
mVertexData[inIndex].mTangent[1] = inY;
mVertexData[inIndex].mTangent[2] = inZ;
mVertexData[inIndex].mTangent[3] = inW;
}
void StaticMeshComponent::InitFromFile(ID3D12GraphicsCommandList* inCommandList, const char* inFilePath) {
FILE* pFile = nullptr;
errno_t err = fopen_s(&pFile, inFilePath, "rb");
if (err == 0) {
int temp = 0;
fread(&temp, 4, 1, pFile);
mVertexCount = temp;
mVertexData = new StaticMeshComponentVertexData[mVertexCount];
fread(mVertexData, 1, sizeof(StaticMeshComponentVertexData) * mVertexCount, pFile);
mVBO=CreateBufferObject(inCommandList,mVertexData,
sizeof(StaticMeshComponentVertexData) * mVertexCount,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
mVBOView.BufferLocation = mVBO->GetGPUVirtualAddress();
mVBOView.SizeInBytes = sizeof(StaticMeshComponentVertexData) * mVertexCount;
mVBOView.StrideInBytes = sizeof(StaticMeshComponentVertexData);
while (!feof(pFile)) {
fread(&temp, 4, 1, pFile);
if (feof(pFile)) {
break;
}
char name[256] = {0};
fread(name, 1, temp, pFile);
fread(&temp, 4, 1, pFile);
SubMesh* submesh = new SubMesh;
submesh->mIndexCount = temp;
unsigned int *indexes = new unsigned int[temp];
fread(indexes, 1, sizeof(unsigned int) * temp, pFile);
submesh->mIBO = CreateBufferObject(inCommandList, indexes,
sizeof(unsigned int) * temp,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
submesh->mIBView.BufferLocation = submesh->mIBO->GetGPUVirtualAddress();
submesh->mIBView.SizeInBytes = sizeof(unsigned int) * temp;
submesh->mIBView.Format = DXGI_FORMAT_R32_UINT;
mSubMeshes.insert(std::pair<std::string, SubMesh*>(name,submesh));
delete[]indexes;
}
fclose(pFile);
}
}
void StaticMeshComponent::Render(ID3D12GraphicsCommandList* inCommandList) {
D3D12_VERTEX_BUFFER_VIEW vbos[] = {
mVBOView
};
inCommandList->IASetVertexBuffers(0, 1, vbos);
if (mSubMeshes.empty()) {
inCommandList->DrawInstanced(mVertexCount, 1, 0, 0);
}
else {
for (auto iter = mSubMeshes.begin();
iter != mSubMeshes.end(); iter++) {
inCommandList->IASetIndexBuffer(&iter->second->mIBView);
inCommandList->DrawIndexedInstanced(iter->second->mIndexCount, 1, 0, 0, 0);
}
}
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <d3d12.h>
#include <unordered_map>
#include <string>
struct StaticMeshComponentVertexData {
float mPosition[4];
float mTexcoord[4];
float mNormal[4];
float mTangent[4];
};
struct SubMesh {
ID3D12Resource* mIBO;
D3D12_INDEX_BUFFER_VIEW mIBView;
int mIndexCount;
};
class StaticMeshComponent{
public:
ID3D12Resource* mVBO;
D3D12_VERTEX_BUFFER_VIEW mVBOView;
StaticMeshComponentVertexData* mVertexData;
int mVertexCount;
std::unordered_map<std::string, SubMesh*> mSubMeshes;
void SetVertexCount(int inVertexCount);
void SetVertexPosition(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexTexcoord(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexNormal(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void SetVertexTangent(int inIndex, float inX, float inY, float inZ, float inW = 1.0f);
void InitFromFile(ID3D12GraphicsCommandList*inCommandList,const char* inFilePath);
void Render(ID3D12GraphicsCommandList* inCommandList);
};

View File

@@ -0,0 +1,10 @@
#include "Utils.h"
#include <math.h>
#include <algorithm>
float srandom() {
float number = float(rand())/float(RAND_MAX);//0.0~1.0f
number *= 2.0f;//0.0~2.0
number -= 1.0f;//-1.0f~1.0f;
return number;
}

View File

@@ -0,0 +1,3 @@
#pragma once
float srandom();//-1.0f~1.0f

View File

@@ -0,0 +1,163 @@
#!/usr/bin/env python3
import os
import sys
import argparse
from pathlib import Path
if sys.platform == "win32":
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
def count_lines(file_path: Path) -> int:
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
return sum(1 for _ in f)
except Exception:
return 0
def should_include(file_path: Path, extensions: list[str]) -> bool:
if not extensions:
return True
return file_path.suffix in extensions
def walk_dir(root_path: Path, extensions: list[str], exclude_dirs: set[str]):
results = []
total_lines = 0
for root, dirs, files in os.walk(root_path):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
rel_root = Path(root).relative_to(root_path)
indent = len(rel_root.parts) if str(rel_root) != "." else 0
prefix = " " * indent
if indent > 0:
print(f"{prefix}└── {rel_root.name}/")
for i, file in enumerate(files):
file_path = Path(root) / file
if not should_include(file_path, extensions):
continue
lines = count_lines(file_path)
total_lines += lines
results.append((file_path, lines, indent + 1))
is_last = (i == len(files) - 1) and not any(
should_include(Path(root) / f, extensions) for f in dirs
)
connector = "└──" if is_last else "├──"
print(f"{' ' * (indent + 1)}{connector} {file} ({lines} lines)")
return total_lines
def main_all():
directories = {
".": [".cpp", ".h", ".hlsl"],
"docs": [".md"],
}
total_all = 0
results = []
for directory, extensions in directories.items():
root_path = Path(directory)
if not root_path.exists():
continue
exclude_dirs = {
".git",
"build",
"Release",
"bin",
"__pycache__",
".ruff_cache",
"stbi",
}
print(f"\n{'=' * 60}")
print(f"项目文件统计: {root_path}")
print(f"后缀过滤: {extensions}")
print(f"{'=' * 60}\n")
total = walk_dir(root_path, extensions, exclude_dirs)
results.append((directory, total))
total_all += total
# 单独统计 stbi
stbi_path = Path("stbi")
if stbi_path.exists():
exclude_dirs = {".git", "__pycache__"}
print(f"\n{'=' * 60}")
print(f"项目文件统计: stbi (第三方库)")
print(f"后缀过滤: ['.cpp', '.h']")
print(f"{'=' * 60}\n")
total = walk_dir(stbi_path, [".cpp", ".h"], exclude_dirs)
results.append(("stbi", total))
total_all += total
print(f"\n{'=' * 60}")
print("汇总统计")
print(f"{'=' * 60}")
for name, lines in results:
print(f"{name:15} {lines:>10,}")
print(f"{'=' * 60}")
print(f"{'总计':15} {total_all:>10,}")
print(f"{'=' * 60}\n")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="统计项目文件行数")
parser.add_argument(
"-e", "--extension", action="append", help="指定后缀名,如: .py .ts .js"
)
parser.add_argument("-d", "--directory", default=".", help="指定子文件夹路径")
parser.add_argument(
"-x", "--exclude", action="append", default=[], help="排除的文件夹"
)
parser.add_argument(
"--all",
action="store_true",
help="统计所有源码目录 (./docs/stbi)",
)
args = parser.parse_args()
if args.all:
main_all()
else:
root_path = Path(args.directory).resolve()
extensions = args.extension
exclude_dirs = {
".git",
"build",
"Release",
"bin",
"__pycache__",
}
exclude_dirs.update(args.exclude)
if not root_path.exists():
print(f"错误: 目录 {root_path} 不存在")
sys.exit(1)
print(f"\n{'=' * 60}")
print(f"项目文件统计: {root_path}")
if extensions:
print(f"后缀过滤: {extensions}")
print(f"{'=' * 60}\n")
total = walk_dir(root_path, extensions, exclude_dirs)
print(f"\n{'=' * 60}")
print(f"总行数: {total}")
print(f"{'=' * 60}\n")

View File

@@ -0,0 +1,24 @@
[Window][Scene View]
Pos=0,20
Size=880,480
[Window][Hierarchy]
Pos=0,20
Size=200,480
[Window][Inspector]
Pos=1080,20
Size=200,480
[Window][Project]
Pos=0,500
Size=640,150
[Window][Console]
Pos=640,500
Size=640,150
[Window][Debug##Default]
Pos=60,60
Size=400,400

308
MVS/VolumeRenderer/main.cpp Normal file
View File

@@ -0,0 +1,308 @@
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "BattleFireDirect.h"
#include "StaticMeshComponent.h"
#include "stbi/stb_image.h"
#include "Utils.h"
#include "NanoVDBLoader.h"
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"dxgi.lib")
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib,"winmm.lib")
LPCWSTR gWindowClassName = L"BattleFire";
LRESULT CALLBACK WindowProc(HWND inHWND, UINT inMSG, WPARAM inWParam, LPARAM inLParam) {
switch (inMSG) {
case WM_CLOSE:
PostQuitMessage(0);
break;
}
return DefWindowProc(inHWND, inMSG, inWParam, inLParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int inShowCmd) {
AttachConsole(ATTACH_PARENT_PROCESS);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
printf("XCVolumeRenderer started\n");
printf("Initializing D3D12...\n");
//register
WNDCLASSEX wndClassEx;
wndClassEx.cbSize = sizeof(WNDCLASSEX);
wndClassEx.style = CS_HREDRAW | CS_VREDRAW;
wndClassEx.cbClsExtra = NULL;//class
wndClassEx.cbWndExtra = NULL;//instance
wndClassEx.hInstance = hInstance;
wndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClassEx.hbrBackground = NULL;
wndClassEx.lpszMenuName = NULL;
wndClassEx.lpszClassName = gWindowClassName;
wndClassEx.lpfnWndProc = WindowProc;
if (!RegisterClassEx(&wndClassEx)) {
MessageBox(NULL, L"Register Class Failed!", L"Error", MB_OK | MB_ICONERROR);
return -1;
}
//create
int viewportWidth = 1280;
int viewportHeight = 720;
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = viewportWidth;
rect.bottom = viewportHeight;
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
HWND hwnd = CreateWindowEx(NULL,
gWindowClassName,
L"My Render Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
windowWidth, windowHeight,
NULL,
NULL,
hInstance,
NULL);
if (!hwnd) {
MessageBox(NULL, L"Create Window Failed!", L"Error", MB_OK | MB_ICONERROR);
return -1;
}
//show
InitD3D12(hwnd, 1280, 720);
printf("D3D12 initialized\n");
ID3D12GraphicsCommandList* commandList = GetCommandList();
ID3D12CommandAllocator* commandAllocator = GetCommandAllocator();
NanoVDBData nanoVDBData = {};
LoadNanoVDB("Res/NanoVDB/cloud.nvdb", nanoVDBData, commandList, commandAllocator);
printf("NanoVDB loaded: %llu bytes\n", (unsigned long long)nanoVDBData.byteSize);
printf("NanoVDB BBox: [%.2f, %.2f, %.2f] - [%.2f, %.2f, %.2f]\n",
nanoVDBData.worldBBox[0], nanoVDBData.worldBBox[1], nanoVDBData.worldBBox[2],
nanoVDBData.worldBBox[3], nanoVDBData.worldBBox[4], nanoVDBData.worldBBox[5]);
float bboxMin[3] = { (float)nanoVDBData.worldBBox[0], (float)nanoVDBData.worldBBox[1], (float)nanoVDBData.worldBBox[2] };
float bboxMax[3] = { (float)nanoVDBData.worldBBox[3], (float)nanoVDBData.worldBBox[4], (float)nanoVDBData.worldBBox[5] };
float bboxCenter[3] = { (bboxMin[0] + bboxMax[0]) / 2, (bboxMin[1] + bboxMax[1]) / 2, (bboxMin[2] + bboxMax[2]) / 2 };
float bboxSize[3] = { bboxMax[0] - bboxMin[0], bboxMax[1] - bboxMin[1], bboxMax[2] - bboxMin[2] };
printf("BBox center: [%.2f, %.2f, %.2f], size: [%.2f, %.2f, %.2f]\n",
bboxCenter[0], bboxCenter[1], bboxCenter[2],
bboxSize[0], bboxSize[1], bboxSize[2]);
float quadVertices[] = {
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
};
uint32_t quadIndices[] = { 0, 1, 2, 0, 2, 3 };
ID3D12Resource* quadVBO = CreateBufferObject(commandList, quadVertices, sizeof(quadVertices), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
ID3D12Resource* quadIBO = CreateBufferObject(commandList, quadIndices, sizeof(quadIndices), D3D12_RESOURCE_STATE_INDEX_BUFFER);
printf("Quad mesh created\n");
StaticMeshComponent staticMeshComponent;
staticMeshComponent.InitFromFile(commandList, "Res/Model/Sphere.lhsm");
printf("Mesh loaded\n");
ID3D12RootSignature* rootSignature = InitRootSignature();
printf("Root signature created\n");
D3D12_SHADER_BYTECODE vs,gs,ps;
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainVS", "vs_5_1", &vs);
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainGS", "gs_5_1", &gs);
CreateShaderFromFile(L"Res/Shader/gs.hlsl", "MainPS", "ps_5_1", &ps);
ID3D12PipelineState*pso=CreatePSO(rootSignature, vs, ps, gs);
printf("PSO created\n");
ID3D12RootSignature* volumeRootSignature = InitVolumeRootSignature();
printf("Volume root signature created\n");
D3D12_SHADER_BYTECODE volumeVS, volumePS;
memset(&volumeVS, 0, sizeof(volumeVS));
memset(&volumePS, 0, sizeof(volumePS));
CreateShaderFromFile(L"Res/Shader/volume.hlsl", "MainVS", "vs_5_1", &volumeVS);
CreateShaderFromFile(L"Res/Shader/volume.hlsl", "MainPS", "ps_5_1", &volumePS);
printf("Volume VS: ptr=%p, size=%zu\n", volumeVS.pShaderBytecode, volumeVS.BytecodeLength);
printf("Volume PS: ptr=%p, size=%zu\n", volumePS.pShaderBytecode, volumePS.BytecodeLength);
ID3D12PipelineState* quadPSO = CreateQuadPSO(volumeRootSignature, volumeVS, volumePS);
if (!quadPSO) {
printf("Quad PSO creation failed!\n");
} else {
printf("Quad PSO created: %p\n", quadPSO);
}
ID3D12Resource* volumeCB = CreateConstantBufferObject(65536);
ID3D12Resource* cb = CreateConstantBufferObject(65536);//1024x64(4x4)
DirectX::XMMATRIX projectionMatrix=DirectX::XMMatrixPerspectiveFovLH(
(45.0f*3.141592f)/180.0f,1280.0f/720.0f,0.1f,1000.0f);
DirectX::XMMATRIX viewMatrix = DirectX::XMMatrixLookAtLH(
DirectX::XMVectorSet(-10.0f, 300.0f, -1200.0f, 1.0f),
DirectX::XMVectorSet(-10.0f, 73.0f, 0.0f, 1.0f),
DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 1.0f));
DirectX::XMMATRIX modelMatrix = DirectX::XMMatrixTranslation(0.0f,0.0f,5.0f);
//modelMatrix *= DirectX::XMMatrixRotationZ(90.0f*3.141592f/180.0f);
DirectX::XMFLOAT4X4 tempMatrix;
float matrices[64];
DirectX::XMStoreFloat4x4(&tempMatrix, projectionMatrix);
memcpy(matrices, &tempMatrix, sizeof(float) * 16);
DirectX::XMStoreFloat4x4(&tempMatrix, viewMatrix);
memcpy(matrices+16, &tempMatrix, sizeof(float) * 16);
DirectX::XMStoreFloat4x4(&tempMatrix, modelMatrix);
memcpy(matrices + 32, &tempMatrix, sizeof(float) * 16);;
DirectX::XMVECTOR determinant;
DirectX::XMMATRIX inverseModelMatrix = DirectX::XMMatrixInverse(&determinant, modelMatrix);
if (DirectX::XMVectorGetX(determinant) != 0.0f) {
DirectX::XMMATRIX normalMatrix = DirectX::XMMatrixTranspose(inverseModelMatrix);
DirectX::XMStoreFloat4x4(&tempMatrix, modelMatrix);
memcpy(matrices + 48, &tempMatrix, sizeof(float) * 16);;
}
UpdateConstantBuffer(cb, matrices, sizeof(float) * 64);
ID3D12Resource* sb = CreateConstantBufferObject(65536);//1024x64(4x4)
struct MaterialData {
float r;
};
MaterialData* materialDatas = new MaterialData[3000];
for (int i=0;i<3000;i++){
materialDatas[i].r = srandom() * 0.1f + 0.1f;//0.0~1.0
}
UpdateConstantBuffer(sb, materialDatas, sizeof(MaterialData) * 3000);
int imageWidth, imageHeight,imageChannel;
stbi_uc* pixels = stbi_load("Res/Image/earth_d.jpg", &imageWidth, &imageHeight, &imageChannel, 4);
printf("Texture loaded: %dx%d\n", imageWidth, imageHeight);
ID3D12Resource* texture = CreateTexture2D(commandList, pixels,
imageWidth * imageHeight * imageChannel, imageWidth, imageHeight,DXGI_FORMAT_R8G8B8A8_UNORM);
delete[]pixels;
ID3D12Device* d3dDevice = GetD3DDevice();
ID3D12DescriptorHeap* srvHeap = nullptr;
D3D12_DESCRIPTOR_HEAP_DESC d3dDescriptorHeapDescSRV = {};
d3dDescriptorHeapDescSRV.NumDescriptors = 3;
d3dDescriptorHeapDescSRV.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
d3dDescriptorHeapDescSRV.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
d3dDevice->CreateDescriptorHeap(&d3dDescriptorHeapDescSRV, IID_PPV_ARGS(&srvHeap));
ID3D12DescriptorHeap* descriptorHeaps[] = {srvHeap};
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
D3D12_CPU_DESCRIPTOR_HANDLE srvHeapPtr = srvHeap->GetCPUDescriptorHandleForHeapStart();
d3dDevice->CreateShaderResourceView(texture, &srvDesc, srvHeapPtr);
srvHeapPtr.ptr += d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
EndCommandList();
WaitForCompletionOfCommandList();
printf("All resources uploaded to GPU\n");
ShowWindow(hwnd, inShowCmd);
UpdateWindow(hwnd);
printf("Window shown, entering render loop\n");
float color[] = {0.5f,0.5f,0.5f,1.0f};
MSG msg;
DWORD last_time = timeGetTime();
DWORD appStartTime = last_time;
int frameCount = 0;
while (true){
ZeroMemory(&msg, sizeof(MSG));
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
//rendering
WaitForCompletionOfCommandList();
DWORD current_time = timeGetTime();//ms
DWORD frameTime = current_time - last_time;
DWORD timeSinceAppStartInMS = current_time - appStartTime;
last_time = current_time;
float frameTimeInSecond = float(frameTime) / 1000.0f;//second
float timeSinceAppStartInSecond = float(timeSinceAppStartInMS) / 1000.0f;
color[0] = timeSinceAppStartInSecond;
commandAllocator->Reset();
commandList->Reset(commandAllocator, nullptr);
BeginRenderToSwapChain(commandList);
//draw
commandList->SetPipelineState(pso);
commandList->SetGraphicsRootSignature(rootSignature);
commandList->SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
commandList->SetGraphicsRootConstantBufferView(0, cb->GetGPUVirtualAddress());
commandList->SetGraphicsRoot32BitConstants(1, 4, color, 0);
commandList->SetGraphicsRootDescriptorTable(2, srvHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootShaderResourceView(3, sb->GetGPUVirtualAddress());
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
if (frameCount == 1) {
printf("Drawing mesh with %d vertices, VBO=%p, subMeshes=%zu\n",
staticMeshComponent.mVertexCount, staticMeshComponent.mVBO, staticMeshComponent.mSubMeshes.size());
}
staticMeshComponent.Render(commandList);
DirectX::XMMATRIX viewProj = viewMatrix * projectionMatrix;
DirectX::XMMATRIX invViewProj = DirectX::XMMatrixInverse(nullptr, viewProj);
DirectX::XMFLOAT4X4 invViewProjMat;
DirectX::XMStoreFloat4x4(&invViewProjMat, invViewProj);
float volumeCBData[36];
memcpy(volumeCBData, &invViewProjMat, sizeof(float) * 16);
volumeCBData[16] = -10.0f;
volumeCBData[17] = 300.0f;
volumeCBData[18] = -1200.0f;
volumeCBData[19] = 0.2f; // DensityScale
volumeCBData[20] = (float)nanoVDBData.worldBBox[0];
volumeCBData[21] = (float)nanoVDBData.worldBBox[1];
volumeCBData[22] = (float)nanoVDBData.worldBBox[2];
volumeCBData[23] = 1.0f; // StepSize
volumeCBData[24] = (float)nanoVDBData.worldBBox[3];
volumeCBData[25] = (float)nanoVDBData.worldBBox[4];
volumeCBData[26] = (float)nanoVDBData.worldBBox[5];
volumeCBData[27] = 2000.0f; // MaxSteps as float
volumeCBData[28] = timeSinceAppStartInSecond * 0.3f; // RotationY
volumeCBData[29] = 0.0f; // Pad0
volumeCBData[30] = 0.0f; // Pad1
volumeCBData[31] = 0.0f; // Pad2
volumeCBData[32] = 0.5f; // LightDir X
volumeCBData[33] = 0.8f; // LightDir Y
volumeCBData[34] = 0.3f; // LightDir Z
volumeCBData[35] = 8.0f; // LightSamples as float
UpdateConstantBuffer(volumeCB, volumeCBData, sizeof(float) * 36);
if (frameCount == 1) {
printf("Volume BBox: [%.2f, %.2f, %.2f] - [%.2f, %.2f, %.2f]\n",
volumeCBData[20], volumeCBData[21], volumeCBData[22],
volumeCBData[24], volumeCBData[25], volumeCBData[26]);
}
if (quadPSO) {
commandList->SetPipelineState(quadPSO);
commandList->SetGraphicsRootSignature(volumeRootSignature);
commandList->SetGraphicsRootConstantBufferView(0, volumeCB->GetGPUVirtualAddress());
commandList->SetGraphicsRootShaderResourceView(1, nanoVDBData.gpuBuffer->GetGPUVirtualAddress());
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &D3D12_VERTEX_BUFFER_VIEW{ quadVBO->GetGPUVirtualAddress(), sizeof(quadVertices), sizeof(float) * 4 });
commandList->IASetIndexBuffer(&D3D12_INDEX_BUFFER_VIEW{ quadIBO->GetGPUVirtualAddress(), sizeof(quadIndices), DXGI_FORMAT_R32_UINT });
commandList->DrawIndexedInstanced(6, 1, 0, 0, 0);
}
EndRenderToSwapChain(commandList);
EndCommandList();
SwapD3D12Buffers();
frameCount++;
if (frameCount <= 3) {
printf("Frame %d rendered\n", frameCount);
}
}
}
return 0;
}

View File

@@ -0,0 +1,4 @@
@echo off
cd /d "%~dp0"
Release\XCVolumeRenderer.exe
pause

View File

@@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

File diff suppressed because it is too large Load Diff

66
MVS/ui/CMakeLists.txt Normal file
View File

@@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 3.15)
project(XCVolumeRendererUI2 VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DUNICODE -D_UNICODE)
add_definitions(-DIMGUI_ENABLE_DOCKING)
include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://gitee.com/mirrors/imgui.git
GIT_TAG docking
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(imgui)
set(IMGUI_SOURCES
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_demo.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_win32.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_dx12.cpp
)
add_executable(${PROJECT_NAME} WIN32
src/main.cpp
src/Application.cpp
src/Theme.cpp
src/Managers/SceneManager.cpp
src/Managers/LogSystem.cpp
src/Managers/ProjectManager.cpp
src/panels/Panel.cpp
src/panels/MenuBar.cpp
src/panels/HierarchyPanel.cpp
src/panels/SceneViewPanel.cpp
src/panels/GameViewPanel.cpp
src/panels/InspectorPanel.cpp
src/panels/ConsolePanel.cpp
src/panels/ProjectPanel.cpp
${IMGUI_SOURCES}
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE)
target_compile_options(${PROJECT_NAME} PRIVATE /utf-8 /MT)
target_link_libraries(${PROJECT_NAME} PRIVATE
d3d12.lib
dxgi.lib
d3dcompiler.lib
)
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
)

183
MVS/ui/README.md Normal file
View File

@@ -0,0 +1,183 @@
# UI Editor
Unity 风格的编辑器 UI使用 ImGui 实现。
## 简介
XCVolumeRenderer UI 是一个仿 Unity 编辑器的桌面应用程序,提供场景管理、层级视图、属性检查器等功能。
## 技术栈
- **渲染 API**: DirectX 12
- **UI 框架**: ImGui
- **语言**: C++17
- **构建系统**: CMake
- **依赖库**: DirectX 12 SDK
## 项目结构
```
ui/
├── src/
│ ├── main.cpp # 程序入口
│ ├── Application.cpp/h # 应用主类
│ ├── Theme.cpp/h # 主题系统
│ ├── Core/
│ │ ├── GameObject.h # 游戏对象
│ │ └── LogEntry.h # 日志条目
│ ├── Managers/
│ │ ├── LogSystem.cpp/h # 日志系统
│ │ ├── ProjectManager.cpp/h # 项目管理
│ │ ├── SceneManager.cpp/h # 场景管理
│ │ └── SelectionManager.cpp/h # 选择管理
│ └── panels/
│ ├── Panel.cpp/h # 面板基类
│ ├── MenuBar.cpp/h # 菜单栏
│ ├── HierarchyPanel.cpp/h # 层级面板
│ ├── InspectorPanel.cpp/h # 检查器面板
│ ├── SceneViewPanel.cpp/h # 场景视图
│ ├── GameViewPanel.cpp/h # 游戏视图
│ ├── ProjectPanel.cpp/h # 项目面板
│ └── ConsolePanel.cpp/h # 控制台面板
├── bin/Release/ # 输出目录
│ ├── XCVolumeRendererUI2.exe # 可执行文件
│ ├── imgui.ini # ImGui 配置
│ └── Assets/
│ └── Models/
│ └── Character.fbx # 示例模型
├── build/ # 构建目录
└── CMakeLists.txt # CMake 配置
```
## 构建方法
### 前置要求
- Windows 10/11
- Visual Studio 2019 或更高版本
- CMake 3.15+
### 构建步骤
```bash
cd ui
mkdir build && cd build
cmake ..
cmake --build . --config Release
```
### 运行
```bash
# 运行编译好的可执行文件
.\bin\Release\XCVolumeRendererUI2.exe
```
## 功能特性
### 编辑器面板
#### 菜单栏MenuBar
- 文件菜单(新建、打开、保存等)
- 编辑菜单(撤销、重做等)
- 视图菜单(面板显示/隐藏)
- 帮助菜单
#### 层级面板Hierarchy Panel
- 显示场景中所有游戏对象
- 树形结构展示父子关系
- 支持对象选择
- 对象重命名
#### 检查器面板Inspector Panel
- 显示选中对象的属性
- 支持组件编辑
- 变换组件(位置、旋转、缩放)
- 材质组件
#### 场景视图Scene View
- 3D 场景预览
- 相机控制(平移、旋转、缩放)
- 对象选择
- 辅助工具(网格、轴心)
#### 游戏视图Game View
- 游戏运行时的画面预览
- 分辨率设置
- 宽高比选择
#### 项目面板Project Panel
- 项目文件浏览器
- 资源组织
- 搜索过滤
#### 控制台面板Console Panel
- 日志输出
- 警告和错误显示
- 日志级别过滤
- 清空日志
### 管理系统
#### 日志系统LogSystem
- 分级日志Info、Warning、Error
- 时间戳
- 日志持久化
#### 项目管理ProjectManager
- 项目创建/打开
- 资源路径管理
#### 场景管理SceneManager
- 场景加载/保存
- 对象生命周期管理
#### 选择管理SelectionManager
- 当前选中对象追踪
- 多选支持
### 主题系统
- 深色主题Dark Theme
- 可自定义配色方案
## 窗口布局
默认布局采用经典的 Unity 编辑器风格:
```
+----------------------------------------------------------+
| 菜单栏 |
+----------+------------------------+----------------------+
| | | |
| 项目 | 场景视图 | 检查器 |
| 面板 | | |
| | | |
+----------+------------------------+----------------------+
| 层级面板 | 游戏视图 |
| | |
+------------------------------------+----------------------+
| 控制台面板 |
+----------------------------------------------------------+
```
## 依赖说明
- ImGui - 跨平台 GUI 库
- DirectX 12 - 渲染 API
- Windows SDK - 窗口管理
## 扩展开发
### 添加新面板
1.`panels/` 目录下创建新的面板类
2. 继承 `Panel` 基类
3. 实现 `Render()` 方法
4.`Application` 中注册新面板
### 添加新组件
1. 定义组件类
2.`GameObject` 中注册组件类型
3.`InspectorPanel` 中添加属性编辑器

303
MVS/ui/src/Application.cpp Normal file
View File

@@ -0,0 +1,303 @@
#include "Application.h"
#include <imgui_impl_win32.h>
#include <imgui_impl_dx12.h>
#include <imgui_internal.h>
#include <stdio.h>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
namespace UI {
Application& Application::Get() {
static Application instance;
return instance;
}
bool Application::Initialize(HWND hwnd) {
m_hwnd = hwnd;
if (!CreateDevice()) {
MessageBoxW(hwnd, L"Failed to create D3D12 device", L"Error", MB_OK | MB_ICONERROR);
return false;
}
if (!CreateRenderTarget()) {
MessageBoxW(hwnd, L"Failed to create render target", L"Error", MB_OK | MB_ICONERROR);
return false;
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f);
io.Fonts->AddFontDefault();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
ApplyUnityDarkTheme();
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX12_Init(m_device, 3, DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap,
m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
m_srvHeap->GetGPUDescriptorHandleForHeapStart());
m_menuBar = std::make_unique<MenuBar>();
m_hierarchyPanel = std::make_unique<HierarchyPanel>();
m_sceneViewPanel = std::make_unique<SceneViewPanel>();
m_gameViewPanel = std::make_unique<GameViewPanel>();
m_inspectorPanel = std::make_unique<InspectorPanel>();
m_consolePanel = std::make_unique<ConsolePanel>();
m_projectPanel = std::make_unique<ProjectPanel>();
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
std::wstring exeDirW(exePath);
size_t pos = exeDirW.find_last_of(L"\\/");
if (pos != std::wstring::npos) {
exeDirW = exeDirW.substr(0, pos);
}
std::string exeDir;
int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len > 0) {
exeDir.resize(len - 1);
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
}
m_projectPanel->Initialize(exeDir);
return true;
}
void Application::Shutdown() {
ImGui_ImplDX12_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
CleanupRenderTarget();
if (m_fence) m_fence->Release();
if (m_commandList) m_commandList->Release();
if (m_commandAllocator) m_commandAllocator->Release();
if (m_commandQueue) m_commandQueue->Release();
if (m_rtvHeap) m_rtvHeap->Release();
if (m_srvHeap) m_srvHeap->Release();
if (m_swapChain) m_swapChain->Release();
if (m_device) m_device->Release();
}
void Application::Render() {
ImGui_ImplDX12_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
SetupDockspace();
RenderUI();
ImGui::Render();
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator, nullptr);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = m_renderTargets[m_frameIndex];
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_commandList->ResourceBarrier(1, &barrier);
float clearColor[4] = { 0.12f, 0.12f, 0.12f, 1.0f };
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
m_commandList->SetDescriptorHeaps(1, heaps);
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_commandList);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
m_commandList->ResourceBarrier(1, &barrier);
m_commandList->Close();
ID3D12CommandList* cmdLists[] = { m_commandList };
m_commandQueue->ExecuteCommandLists(1, cmdLists);
m_swapChain->Present(1, 0);
m_fenceValue++;
m_commandQueue->Signal(m_fence, m_fenceValue);
if (m_fence->GetCompletedValue() < m_fenceValue) {
m_fence->SetEventOnCompletion(m_fenceValue, nullptr);
}
}
void Application::OnResize(int width, int height) {
if (width <= 0 || height <= 0) return;
m_width = width;
m_height = height;
CleanupRenderTarget();
if (m_swapChain) {
DXGI_SWAP_CHAIN_DESC desc;
m_swapChain->GetDesc(&desc);
m_swapChain->ResizeBuffers(3, width, height, desc.BufferDesc.Format, desc.Flags);
}
CreateRenderTarget();
}
bool Application::CreateDevice() {
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
if (FAILED(hr)) {
return false;
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Priority = 0;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.NodeMask = 0;
hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
if (FAILED(hr)) return false;
m_commandList->Close();
hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
if (FAILED(hr)) return false;
IDXGIFactory4* factory = nullptr;
hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr)) return false;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = 3;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
IDXGISwapChain1* swapChain1 = nullptr;
hr = factory->CreateSwapChainForHwnd(m_commandQueue, m_hwnd, &swapChainDesc, nullptr, nullptr, &swapChain1);
factory->Release();
if (FAILED(hr)) return false;
hr = swapChain1->QueryInterface(IID_PPV_ARGS(&m_swapChain));
swapChain1->Release();
if (FAILED(hr)) return false;
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = 3;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = m_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap));
if (FAILED(hr)) return false;
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvDesc.NumDescriptors = 1;
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
if (FAILED(hr)) return false;
return true;
}
bool Application::CreateRenderTarget() {
if (!m_swapChain || !m_device || !m_rtvHeap) return false;
for (UINT i = 0; i < 3; i++) {
HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i]));
if (FAILED(hr)) return false;
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += i * m_rtvDescriptorSize;
m_device->CreateRenderTargetView(m_renderTargets[i], nullptr, rtvHandle);
}
return true;
}
void Application::CleanupRenderTarget() {
for (UINT i = 0; i < 3; i++) {
if (m_renderTargets[i]) {
m_renderTargets[i]->Release();
m_renderTargets[i] = nullptr;
}
}
}
void Application::SetupDockspace() {
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("MainDockspace", nullptr, windowFlags);
ImGui::PopStyleVar();
ImGui::PopStyleVar(2);
ImGuiID dockspaceId = ImGui::GetID("MyDockspace");
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
static bool firstTime = true;
if (firstTime) {
firstTime = false;
ImGui::DockBuilderRemoveNode(dockspaceId);
ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
ImGuiID dockMain = dockspaceId;
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
ImGui::DockBuilderDockWindow("Scene", dockMain);
ImGui::DockBuilderDockWindow("Game", dockMain);
ImGui::DockBuilderDockWindow("Inspector", dockRight);
ImGui::DockBuilderDockWindow("Console", dockBottom);
ImGui::DockBuilderDockWindow("Project", dockBottom);
ImGui::DockBuilderFinish(dockspaceId);
}
ImGui::End();
}
void Application::RenderUI() {
m_menuBar->Render();
m_hierarchyPanel->Render();
m_sceneViewPanel->Render();
m_gameViewPanel->Render();
m_inspectorPanel->Render();
m_consolePanel->Render();
m_projectPanel->Render();
}
}

65
MVS/ui/src/Application.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include <memory>
#include <imgui.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include "Theme.h"
#include "panels/Panel.h"
#include "panels/MenuBar.h"
#include "panels/HierarchyPanel.h"
#include "panels/SceneViewPanel.h"
#include "panels/GameViewPanel.h"
#include "panels/InspectorPanel.h"
#include "panels/ConsolePanel.h"
#include "panels/ProjectPanel.h"
namespace UI {
class Application {
public:
static Application& Get();
bool Initialize(HWND hwnd);
void Shutdown();
void Render();
void OnResize(int width, int height);
private:
Application() = default;
~Application() = default;
bool CreateDevice();
bool CreateRenderTarget();
void CleanupRenderTarget();
void SetupDockspace();
void RenderUI();
HWND m_hwnd = nullptr;
int m_width = 1280;
int m_height = 720;
ID3D12Device* m_device = nullptr;
ID3D12CommandQueue* m_commandQueue = nullptr;
ID3D12CommandAllocator* m_commandAllocator = nullptr;
ID3D12GraphicsCommandList* m_commandList = nullptr;
IDXGISwapChain3* m_swapChain = nullptr;
ID3D12DescriptorHeap* m_rtvHeap = nullptr;
ID3D12DescriptorHeap* m_srvHeap = nullptr;
ID3D12Resource* m_renderTargets[3] = {};
ID3D12Fence* m_fence = nullptr;
UINT64 m_fenceValue = 0;
UINT m_rtvDescriptorSize = 0;
UINT m_frameIndex = 0;
std::unique_ptr<MenuBar> m_menuBar;
std::unique_ptr<HierarchyPanel> m_hierarchyPanel;
std::unique_ptr<SceneViewPanel> m_sceneViewPanel;
std::unique_ptr<GameViewPanel> m_gameViewPanel;
std::unique_ptr<InspectorPanel> m_inspectorPanel;
std::unique_ptr<ConsolePanel> m_consolePanel;
std::unique_ptr<ProjectPanel> m_projectPanel;
};
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
namespace UI {
struct AssetItem {
std::string name;
std::string type;
bool isFolder;
std::string fullPath;
std::vector<std::shared_ptr<AssetItem>> children;
};
using AssetItemPtr = std::shared_ptr<AssetItem>;
}

48
MVS/ui/src/Core/Event.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <functional>
#include <vector>
#include <memory>
namespace UI {
template<typename... Args>
class Event {
public:
using HandlerID = size_t;
using Handler = std::function<void(Args...)>;
HandlerID Subscribe(Handler handler) {
HandlerID id = m_nextId++;
m_handlers.emplace_back(id, std::move(handler));
return id;
}
void Unsubscribe(HandlerID id) {
m_handlers.erase(
std::remove_if(m_handlers.begin(), m_handlers.end(),
[id](const auto& pair) { return pair.first == id; }),
m_handlers.end()
);
}
void Invoke(Args... args) {
for (const auto& pair : m_handlers) {
pair.second(args...);
}
}
void operator()(Args... args) {
Invoke(args...);
}
void Clear() {
m_handlers.clear();
}
private:
HandlerID m_nextId = 0;
std::vector<std::pair<HandlerID, Handler>> m_handlers;
};
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <functional>
#include <cstdint>
namespace UI {
using EntityID = uint64_t;
constexpr EntityID INVALID_ENTITY = 0;
class Component {
public:
virtual ~Component() = default;
virtual std::string GetName() const = 0;
};
class TransformComponent : public Component {
public:
float position[3] = {0.0f, 0.0f, 0.0f};
float rotation[3] = {0.0f, 0.0f, 0.0f};
float scale[3] = {1.0f, 1.0f, 1.0f};
std::string GetName() const override { return "Transform"; }
};
class MeshRendererComponent : public Component {
public:
std::string materialName = "Default-Material";
std::string meshName = "";
std::string GetName() const override { return "Mesh Renderer"; }
};
struct Entity {
EntityID id = INVALID_ENTITY;
std::string name;
EntityID parent = INVALID_ENTITY;
std::vector<EntityID> children;
std::vector<std::unique_ptr<Component>> components;
bool selected = false;
template<typename T, typename... Args>
T* AddComponent(Args&&... args) {
auto comp = std::make_unique<T>(std::forward<Args>(args)...);
T* ptr = comp.get();
components.push_back(std::move(comp));
return ptr;
}
template<typename T>
T* GetComponent() {
for (auto& comp : components) {
if (auto casted = dynamic_cast<T*>(comp.get())) {
return casted;
}
}
return nullptr;
}
};
using ComponentInspectorFn = std::function<void(Component*)>;
struct ComponentInspectorInfo {
std::string name;
ComponentInspectorFn renderFn;
};
class ComponentRegistry {
public:
static ComponentRegistry& Get() {
static ComponentRegistry instance;
return instance;
}
template<typename T>
void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) {
m_inspectors[name] = {name, inspectorFn};
m_factories[name] = []() -> std::unique_ptr<Component> {
return std::make_unique<T>();
};
}
ComponentInspectorInfo* GetInspector(const std::string& name) {
auto it = m_inspectors.find(name);
if (it != m_inspectors.end()) {
return &it->second;
}
return nullptr;
}
private:
ComponentRegistry() = default;
std::unordered_map<std::string, ComponentInspectorInfo> m_inspectors;
std::unordered_map<std::string, std::function<std::unique_ptr<Component>()>> m_factories;
};
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace UI {
struct LogEntry {
enum class Level { Info, Warning, Error };
Level level;
std::string message;
};
}

View File

@@ -0,0 +1,19 @@
#include "LogSystem.h"
namespace UI {
LogSystem& LogSystem::Get() {
static LogSystem instance;
return instance;
}
void LogSystem::AddLog(LogEntry::Level level, const std::string& message) {
m_logs.push_back({level, message});
if (m_callback) m_callback();
}
void LogSystem::Clear() {
m_logs.clear();
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "Core/LogEntry.h"
#include <vector>
#include <functional>
namespace UI {
class LogSystem {
public:
static LogSystem& Get();
void AddLog(LogEntry::Level level, const std::string& message);
void Clear();
const std::vector<LogEntry>& GetLogs() const { return m_logs; }
void SetCallback(std::function<void()> callback) { m_callback = callback; }
private:
LogSystem() = default;
std::vector<LogEntry> m_logs;
std::function<void()> m_callback;
};
}

View File

@@ -0,0 +1,246 @@
#include "ProjectManager.h"
#include <filesystem>
#include <algorithm>
#include <fstream>
#include <windows.h>
namespace fs = std::filesystem;
namespace UI {
ProjectManager& ProjectManager::Get() {
static ProjectManager instance;
return instance;
}
std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() {
if (m_path.empty()) {
static std::vector<AssetItemPtr> empty;
return empty;
}
return m_path.back()->children;
}
void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) {
m_path.push_back(folder);
m_selectedIndex = -1;
}
void ProjectManager::NavigateBack() {
if (m_path.size() > 1) {
m_path.pop_back();
m_selectedIndex = -1;
}
}
void ProjectManager::NavigateToIndex(size_t index) {
if (index >= m_path.size()) return;
while (m_path.size() > index + 1) {
m_path.pop_back();
}
m_selectedIndex = -1;
}
std::string ProjectManager::GetCurrentPath() const {
if (m_path.empty()) return "Assets";
std::string result = "Assets";
for (size_t i = 1; i < m_path.size(); i++) {
result += "/";
result += m_path[i]->name;
}
return result;
}
std::string ProjectManager::GetPathName(size_t index) const {
if (index >= m_path.size()) return "";
return m_path[index]->name;
}
static std::wstring Utf8ToWstring(const std::string& str) {
if (str.empty()) return L"";
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
if (len <= 0) return L"";
std::wstring result(len - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], len);
return result;
}
static std::string WstringToUtf8(const std::wstring& wstr) {
if (wstr.empty()) return "";
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len <= 0) return "";
std::string result(len - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
return result;
}
void ProjectManager::Initialize(const std::string& projectPath) {
m_projectPath = projectPath;
std::wstring projectPathW = Utf8ToWstring(projectPath);
fs::path assetsPath = fs::path(projectPathW) / L"Assets";
try {
if (!fs::exists(assetsPath)) {
fs::create_directories(assetsPath);
fs::create_directories(assetsPath / L"Textures");
fs::create_directories(assetsPath / L"Models");
fs::create_directories(assetsPath / L"Scripts");
fs::create_directories(assetsPath / L"Materials");
fs::create_directories(assetsPath / L"Scenes");
std::ofstream((assetsPath / L"Textures" / L"Grass.png").wstring());
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
std::ofstream((assetsPath / L"Scenes" / L"Main.unity").wstring());
}
m_rootFolder = ScanDirectory(assetsPath.wstring());
m_rootFolder->name = "Assets";
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
m_path.clear();
m_path.push_back(m_rootFolder);
m_selectedIndex = -1;
} catch (const std::exception& e) {
m_rootFolder = std::make_shared<AssetItem>();
m_rootFolder->name = "Assets";
m_rootFolder->isFolder = true;
m_rootFolder->type = "Folder";
m_path.push_back(m_rootFolder);
}
}
std::wstring ProjectManager::GetCurrentFullPathW() const {
if (m_path.empty()) return Utf8ToWstring(m_projectPath);
std::wstring fullPath = Utf8ToWstring(m_projectPath);
for (size_t i = 0; i < m_path.size(); i++) {
fullPath += L"/" + Utf8ToWstring(m_path[i]->name);
}
return fullPath;
}
void ProjectManager::RefreshCurrentFolder() {
if (m_path.empty()) return;
try {
auto newFolder = ScanDirectory(GetCurrentFullPathW());
m_path.back()->children = newFolder->children;
} catch (...) {
}
}
void ProjectManager::CreateFolder(const std::string& name) {
try {
std::wstring fullPath = GetCurrentFullPathW();
fs::path newFolderPath = fs::path(fullPath) / Utf8ToWstring(name);
fs::create_directory(newFolderPath);
RefreshCurrentFolder();
} catch (...) {
}
}
void ProjectManager::DeleteItem(int index) {
if (m_path.empty()) return;
auto& items = m_path.back()->children;
if (index < 0 || index >= (int)items.size()) return;
try {
std::wstring fullPath = GetCurrentFullPathW();
fs::path itemPath = fs::path(fullPath) / Utf8ToWstring(items[index]->name);
fs::remove_all(itemPath);
m_selectedIndex = -1;
RefreshCurrentFolder();
} catch (...) {
}
}
bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) {
try {
fs::path sourcePath = Utf8ToWstring(sourceFullPath);
fs::path destPath = fs::path(Utf8ToWstring(destFolderFullPath)) / sourcePath.filename();
if (!fs::exists(sourcePath)) {
return false;
}
if (fs::exists(destPath)) {
return false;
}
fs::rename(sourcePath, destPath);
RefreshCurrentFolder();
return true;
} catch (...) {
return false;
}
}
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
auto folder = std::make_shared<AssetItem>();
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
folder->isFolder = true;
folder->type = "Folder";
if (!fs::exists(path)) return folder;
std::vector<AssetItemPtr> items;
try {
for (const auto& entry : fs::directory_iterator(path)) {
std::wstring nameW = entry.path().filename().wstring();
bool isFolder = entry.is_directory();
items.push_back(CreateAssetItem(entry.path().wstring(), nameW, isFolder));
}
} catch (...) {
}
std::sort(items.begin(), items.end(), [](const AssetItemPtr& a, const AssetItemPtr& b) {
if (a->isFolder != b->isFolder) return a->isFolder;
return a->name < b->name;
});
folder->children = items;
return folder;
}
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
auto item = std::make_shared<AssetItem>();
item->name = WstringToUtf8(nameW);
item->isFolder = isFolder;
item->fullPath = WstringToUtf8(path);
if (isFolder) {
item->type = "Folder";
try {
auto subFolder = ScanDirectory(path);
item->children = subFolder->children;
} catch (...) {
}
} else {
std::wstring ext = fs::path(path).extension().wstring();
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
if (ext == L".png" || ext == L".jpg" || ext == L".tga" || ext == L".bmp") {
item->type = "Texture";
} else if (ext == L".fbx" || ext == L".obj" || ext == L".gltf" || ext == L".glb") {
item->type = "Model";
} else if (ext == L".cs" || ext == L".cpp" || ext == L".h") {
item->type = "Script";
} else if (ext == L".mat") {
item->type = "Material";
} else if (ext == L".unity" || ext == L".scene") {
item->type = "Scene";
} else if (ext == L".prefab") {
item->type = "Prefab";
} else {
item->type = "File";
}
}
return item;
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include "Core/AssetItem.h"
#include <vector>
#include <string>
#include <memory>
namespace UI {
class ProjectManager {
public:
static ProjectManager& Get();
std::vector<AssetItemPtr>& GetCurrentItems();
int GetSelectedIndex() const { return m_selectedIndex; }
void SetSelectedIndex(int index) { m_selectedIndex = index; }
void NavigateToFolder(const AssetItemPtr& folder);
void NavigateBack();
void NavigateToIndex(size_t index);
bool CanNavigateBack() const { return m_path.size() > 1; }
std::string GetCurrentPath() const;
size_t GetPathDepth() const { return m_path.size(); }
std::string GetPathName(size_t index) const;
void Initialize(const std::string& projectPath);
void RefreshCurrentFolder();
void CreateFolder(const std::string& name);
void DeleteItem(int index);
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath);
const std::string& GetProjectPath() const { return m_projectPath; }
private:
ProjectManager() = default;
AssetItemPtr ScanDirectory(const std::wstring& path);
AssetItemPtr CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder);
std::wstring GetCurrentFullPathW() const;
AssetItemPtr m_rootFolder;
std::vector<AssetItemPtr> m_path;
int m_selectedIndex = -1;
std::string m_projectPath;
};
}

View File

@@ -0,0 +1,185 @@
#include "SceneManager.h"
#include <algorithm>
namespace UI {
EntityID SceneManager::CreateEntity(const std::string& name, EntityID parent) {
EntityID id = m_nextEntityId++;
Entity entity;
entity.id = id;
entity.name = name;
entity.parent = parent;
m_entities[id] = std::move(entity);
if (parent != INVALID_ENTITY) {
m_entities[parent].children.push_back(id);
} else {
m_rootEntities.push_back(id);
}
OnEntityCreated.Invoke(id);
return id;
}
void SceneManager::DeleteEntity(EntityID id) {
auto it = m_entities.find(id);
if (it == m_entities.end()) return;
Entity& entity = it->second;
std::vector<EntityID> childrenToDelete = entity.children;
for (EntityID childId : childrenToDelete) {
DeleteEntity(childId);
}
if (entity.parent != INVALID_ENTITY) {
auto* parent = GetEntity(entity.parent);
if (parent) {
auto& siblings = parent->children;
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
}
} else {
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
}
if (SelectionManager::Get().GetSelectedEntity() == id) {
SelectionManager::Get().ClearSelection();
}
m_entities.erase(it);
OnEntityDeleted.Invoke(id);
}
ClipboardData SceneManager::CopyEntityRecursive(const Entity* entity) {
ClipboardData data;
data.name = entity->name;
for (const auto& comp : entity->components) {
if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) {
auto newComp = std::make_unique<TransformComponent>();
memcpy(newComp->position, transform->position, sizeof(transform->position));
memcpy(newComp->rotation, transform->rotation, sizeof(transform->rotation));
memcpy(newComp->scale, transform->scale, sizeof(transform->scale));
data.components.push_back(std::move(newComp));
}
else if (auto* meshRenderer = dynamic_cast<const MeshRendererComponent*>(comp.get())) {
auto newComp = std::make_unique<MeshRendererComponent>();
newComp->materialName = meshRenderer->materialName;
newComp->meshName = meshRenderer->meshName;
data.components.push_back(std::move(newComp));
}
}
for (EntityID childId : entity->children) {
const Entity* child = GetEntity(childId);
if (child) {
data.children.push_back(CopyEntityRecursive(child));
}
}
return data;
}
void SceneManager::CopyEntity(EntityID id) {
const Entity* entity = GetEntity(id);
if (!entity) return;
m_clipboard = CopyEntityRecursive(entity);
}
EntityID SceneManager::PasteEntityRecursive(const ClipboardData& data, EntityID parent) {
EntityID newId = CreateEntity(data.name, parent);
Entity* newEntity = GetEntity(newId);
if (newEntity) {
newEntity->components.clear();
for (const auto& comp : data.components) {
if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) {
auto newComp = std::make_unique<TransformComponent>();
memcpy(newComp->position, transform->position, sizeof(transform->position));
memcpy(newComp->rotation, transform->rotation, sizeof(transform->rotation));
memcpy(newComp->scale, transform->scale, sizeof(transform->scale));
newEntity->components.push_back(std::move(newComp));
}
else if (auto* meshRenderer = dynamic_cast<const MeshRendererComponent*>(comp.get())) {
auto newComp = std::make_unique<MeshRendererComponent>();
newComp->materialName = meshRenderer->materialName;
newComp->meshName = meshRenderer->meshName;
newEntity->components.push_back(std::move(newComp));
}
}
}
for (const auto& childData : data.children) {
PasteEntityRecursive(childData, newId);
}
return newId;
}
EntityID SceneManager::PasteEntity(EntityID parent) {
if (!m_clipboard) return INVALID_ENTITY;
return PasteEntityRecursive(*m_clipboard, parent);
}
EntityID SceneManager::DuplicateEntity(EntityID id) {
CopyEntity(id);
const Entity* entity = GetEntity(id);
if (!entity) return INVALID_ENTITY;
return PasteEntity(entity->parent);
}
void SceneManager::MoveEntity(EntityID id, EntityID newParent) {
Entity* entity = GetEntity(id);
if (!entity || id == newParent) return;
if (entity->parent != INVALID_ENTITY) {
Entity* oldParent = GetEntity(entity->parent);
if (oldParent) {
auto& siblings = oldParent->children;
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
}
} else {
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
}
entity->parent = newParent;
if (newParent != INVALID_ENTITY) {
Entity* newParentEntity = GetEntity(newParent);
if (newParentEntity) {
newParentEntity->children.push_back(id);
}
} else {
m_rootEntities.push_back(id);
}
OnEntityChanged.Invoke(id);
}
void SceneManager::CreateDemoScene() {
m_entities.clear();
m_rootEntities.clear();
m_nextEntityId = 1;
m_clipboard.reset();
EntityID camera = CreateEntity("Main Camera");
GetEntity(camera)->AddComponent<TransformComponent>();
EntityID light = CreateEntity("Directional Light");
EntityID cube = CreateEntity("Cube");
GetEntity(cube)->AddComponent<TransformComponent>();
GetEntity(cube)->AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh";
EntityID sphere = CreateEntity("Sphere");
GetEntity(sphere)->AddComponent<TransformComponent>();
GetEntity(sphere)->AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh";
EntityID player = CreateEntity("Player");
EntityID weapon = CreateEntity("Weapon", player);
OnSceneChanged.Invoke();
}
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include "Core/GameObject.h"
#include "SelectionManager.h"
#include <unordered_map>
#include <vector>
#include <memory>
#include <optional>
namespace UI {
struct ClipboardData {
std::string name;
std::vector<std::unique_ptr<Component>> components;
std::vector<ClipboardData> children;
};
class SceneManager {
public:
static SceneManager& Get() {
static SceneManager instance;
return instance;
}
EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY);
Entity* GetEntity(EntityID id) {
auto it = m_entities.find(id);
if (it != m_entities.end()) {
return &it->second;
}
return nullptr;
}
const Entity* GetEntity(EntityID id) const {
auto it = m_entities.find(id);
if (it != m_entities.end()) {
return &it->second;
}
return nullptr;
}
const std::vector<EntityID>& GetRootEntities() const {
return m_rootEntities;
}
void DeleteEntity(EntityID id);
void RenameEntity(EntityID id, const std::string& newName) {
auto* entity = GetEntity(id);
if (entity) {
entity->name = newName;
OnEntityChanged.Invoke(id);
}
}
void CopyEntity(EntityID id);
EntityID PasteEntity(EntityID parent = INVALID_ENTITY);
EntityID DuplicateEntity(EntityID id);
void MoveEntity(EntityID id, EntityID newParent);
void CreateDemoScene();
bool HasClipboardData() const { return m_clipboard.has_value(); }
Event<EntityID> OnEntityCreated;
Event<EntityID> OnEntityDeleted;
Event<EntityID> OnEntityChanged;
Event<> OnSceneChanged;
private:
SceneManager() = default;
ClipboardData CopyEntityRecursive(const Entity* entity);
EntityID PasteEntityRecursive(const ClipboardData& data, EntityID parent);
EntityID m_nextEntityId = 1;
std::unordered_map<EntityID, Entity> m_entities;
std::vector<EntityID> m_rootEntities;
std::optional<ClipboardData> m_clipboard;
};
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "Core/GameObject.h"
#include "Core/Event.h"
#include <unordered_set>
namespace UI {
class SelectionManager {
public:
static SelectionManager& Get() {
static SelectionManager instance;
return instance;
}
EntityID GetSelectedEntity() const { return m_selectedEntity; }
void SetSelectedEntity(EntityID id) {
m_selectedEntity = id;
OnSelectionChanged.Invoke(id);
}
void ClearSelection() {
SetSelectedEntity(INVALID_ENTITY);
}
bool IsSelected(EntityID id) const {
return m_selectedEntity == id;
}
Event<EntityID> OnSelectionChanged;
private:
SelectionManager() = default;
EntityID m_selectedEntity = INVALID_ENTITY;
};
}

82
MVS/ui/src/Theme.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include "Theme.h"
#include <imgui.h>
namespace UI {
void ApplyUnityDarkTheme() {
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.18f, 0.18f, 0.18f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.12f, 0.12f, 0.12f, 0.75f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.18f, 0.18f, 0.18f, 0.86f);
colors[ImGuiCol_TabHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.15f, 0.15f, 0.97f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.40f, 0.40f, 0.40f, 0.70f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.40f, 0.40f, 0.40f, 0.50f);
colors[ImGuiCol_DragDropTarget] = ImVec4(0.60f, 0.60f, 0.60f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
style.WindowRounding = 4.0f;
style.ChildRounding = 4.0f;
style.FrameRounding = 4.0f;
style.GrabRounding = 4.0f;
style.PopupRounding = 4.0f;
style.ScrollbarRounding = 4.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.ChildBorderSize = 1.0f;
style.FrameBorderSize = 0.0f;
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 4.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
}
}

7
MVS/ui/src/Theme.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
namespace UI {
void ApplyUnityDarkTheme();
}

96
MVS/ui/src/main.cpp Normal file
View File

@@ -0,0 +1,96 @@
#include "Application.h"
#include <imgui.h>
#include <windows.h>
#include <stdio.h>
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
AllocConsole();
freopen("CONOUT$", "w", stdout);
printf("Starting UI application...\n");
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"XCVolumeRendererUI2";
if (!RegisterClassExW(&wc)) {
printf("Failed to register window class, error: %lu\n", GetLastError());
return 1;
}
printf("Window class registered.\n");
HWND hwnd = CreateWindowExW(
0, wc.lpszClassName, L"XCVolumeRenderer - Unity Style Editor",
WS_OVERLAPPEDWINDOW, 100, 100, 1280, 720,
nullptr, nullptr, wc.hInstance, nullptr
);
if (!hwnd) {
printf("Failed to create window, error: %lu\n", GetLastError());
return 1;
}
printf("Window created.\n");
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
printf("Window shown.\n");
printf("Initializing application...\n");
if (!UI::Application::Get().Initialize(hwnd)) {
printf("Failed to initialize application!\n");
UnregisterClassW(wc.lpszClassName, wc.hInstance);
system("pause");
return 1;
}
printf("Application initialized successfully.\n");
MSG msg = {};
int frameCount = 0;
while (msg.message != WM_QUIT) {
if (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
} else {
UI::Application::Get().Render();
frameCount++;
if (frameCount % 100 == 0) {
printf("Frame %d\n", frameCount);
}
}
}
printf("Shutting down...\n");
UI::Application::Get().Shutdown();
UnregisterClassW(wc.lpszClassName, wc.hInstance);
printf("Press any key to exit...\n");
system("pause");
return 0;
}
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return true;
switch (msg) {
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
UI::Application::Get().OnResize((int)LOWORD(lParam), (int)HIWORD(lParam));
}
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU)
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}

View File

@@ -0,0 +1,70 @@
#include "ConsolePanel.h"
#include "Managers/LogSystem.h"
#include "Core/LogEntry.h"
#include <imgui.h>
namespace UI {
ConsolePanel::ConsolePanel() : Panel("Console") {
LogSystem::Get().AddLog(LogEntry::Level::Info, "Engine initialized successfully");
LogSystem::Get().AddLog(LogEntry::Level::Info, "Loading default scene...");
LogSystem::Get().AddLog(LogEntry::Level::Warning, "Missing material on object 'Cube'");
LogSystem::Get().AddLog(LogEntry::Level::Error, "Failed to load texture: 'Assets/Textures/missing.png'");
LogSystem::Get().AddLog(LogEntry::Level::Info, "Scene loaded successfully");
}
void ConsolePanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
if (ImGui::Button("Clear")) {
LogSystem::Get().Clear();
}
ImGui::SameLine();
if (ImGui::Button("Info")) {
LogSystem::Get().AddLog(LogEntry::Level::Info, "Test info message");
}
ImGui::SameLine();
if (ImGui::Button("Warn")) {
LogSystem::Get().AddLog(LogEntry::Level::Warning, "Test warning message");
}
ImGui::SameLine();
if (ImGui::Button("Error")) {
LogSystem::Get().AddLog(LogEntry::Level::Error, "Test error message");
}
ImGui::Separator();
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
for (const auto& log : LogSystem::Get().GetLogs()) {
ImVec4 color;
const char* prefix;
switch (log.level) {
case LogEntry::Level::Info:
color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
prefix = "[Info] ";
break;
case LogEntry::Level::Warning:
color = ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
prefix = "[Warn] ";
break;
case LogEntry::Level::Error:
color = ImVec4(1.0f, 0.3f, 0.3f, 1.0f);
prefix = "[Error]";
break;
}
ImGui::TextColored(color, "%s%s", prefix, log.message.c_str());
}
if (m_scrollToBottom) {
ImGui::SetScrollHereY(1.0f);
m_scrollToBottom = false;
}
ImGui::EndChild();
ImGui::End();
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Panel.h"
namespace UI {
class ConsolePanel : public Panel {
public:
ConsolePanel();
void Render() override;
private:
bool m_scrollToBottom = false;
};
}

View File

@@ -0,0 +1,31 @@
#include "GameViewPanel.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace UI {
GameViewPanel::GameViewPanel() : Panel("Game") {}
void GameViewPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
RenderGameView();
ImGui::End();
}
void GameViewPanel::RenderGameView() {
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
ImU32 bgColor = IM_COL32(20, 20, 25, 255);
drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), bgColor);
const char* text = "Game View (Press Play)";
ImVec2 textSize = ImGui::CalcTextSize(text);
ImVec2 textPos(canvasPos.x + (canvasSize.x - textSize.x) * 0.5f, canvasPos.y + (canvasSize.y - textSize.y) * 0.5f);
drawList->AddText(textPos, IM_COL32(128, 128, 128, 255), text);
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Panel.h"
namespace UI {
class GameViewPanel : public Panel {
public:
GameViewPanel();
void Render() override;
private:
void RenderGameView();
};
}

View File

@@ -0,0 +1,345 @@
#include "HierarchyPanel.h"
#include "Managers/SceneManager.h"
#include "Managers/SelectionManager.h"
#include <imgui.h>
#include <cstring>
namespace UI {
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
SceneManager::Get().CreateDemoScene();
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
});
}
HierarchyPanel::~HierarchyPanel() {
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
}
void HierarchyPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
RenderSearchBar();
ImGui::Separator();
HandleKeyboardShortcuts();
std::string filter = m_searchBuffer;
ImGui::BeginChild("EntityList");
for (EntityID id : SceneManager::Get().GetRootEntities()) {
RenderEntity(id, filter);
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) {
if (!m_renaming) {
SelectionManager::Get().ClearSelection();
}
}
if (ImGui::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
RenderCreateMenu(INVALID_ENTITY);
ImGui::EndPopup();
}
ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1));
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
EntityID sourceId = *(const EntityID*)payload->Data;
if (sourceId != INVALID_ENTITY) {
const Entity* sourceEntity = SceneManager::Get().GetEntity(sourceId);
if (sourceEntity && sourceEntity->parent != INVALID_ENTITY) {
SceneManager::Get().MoveEntity(sourceId, INVALID_ENTITY);
}
}
}
ImGui::EndDragDropTarget();
}
ImGui::EndChild();
ImGui::End();
}
void HierarchyPanel::RenderSearchBar() {
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
}
void HierarchyPanel::RenderEntity(EntityID id, const std::string& filter) {
auto& sceneManager = SceneManager::Get();
Entity* entity = sceneManager.GetEntity(id);
if (!entity) return;
if (!filter.empty() && !PassesFilter(id, filter)) {
return;
}
ImGui::PushID(static_cast<int>(id));
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
if (entity->children.empty()) {
flags |= ImGuiTreeNodeFlags_Leaf;
}
if (SelectionManager::Get().IsSelected(id)) {
flags |= ImGuiTreeNodeFlags_Selected;
}
if (m_renaming && m_renamingEntity == id) {
if (m_renameJustStarted) {
ImGui::SetKeyboardFocusHere();
m_renameJustStarted = false;
}
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
if (strlen(m_renameBuffer) > 0) {
sceneManager.RenameEntity(id, m_renameBuffer);
}
m_renaming = false;
m_renamingEntity = INVALID_ENTITY;
}
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
if (strlen(m_renameBuffer) > 0) {
sceneManager.RenameEntity(id, m_renameBuffer);
}
m_renaming = false;
m_renamingEntity = INVALID_ENTITY;
}
} else {
bool isOpen = ImGui::TreeNodeEx(entity->name.c_str(), flags);
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
SelectionManager::Get().SetSelectedEntity(id);
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_renaming = true;
m_renamingEntity = id;
strcpy_s(m_renameBuffer, entity->name.c_str());
m_renameJustStarted = true;
}
HandleDragDrop(id);
if (ImGui::BeginPopupContextItem("EntityContextMenu")) {
RenderContextMenu(id);
ImGui::EndPopup();
}
if (isOpen) {
for (EntityID childId : entity->children) {
RenderEntity(childId, filter);
}
ImGui::TreePop();
}
}
ImGui::PopID();
}
void HierarchyPanel::RenderContextMenu(EntityID id) {
auto& sceneManager = SceneManager::Get();
auto& selectionManager = SelectionManager::Get();
if (ImGui::BeginMenu("Create")) {
RenderCreateMenu(id);
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::MenuItem("Rename", "F2")) {
const Entity* entity = sceneManager.GetEntity(id);
if (entity) {
m_renaming = true;
m_renamingEntity = id;
strcpy_s(m_renameBuffer, entity->name.c_str());
m_renameJustStarted = true;
}
}
if (ImGui::MenuItem("Delete", "Delete")) {
sceneManager.DeleteEntity(id);
}
ImGui::Separator();
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
sceneManager.CopyEntity(id);
}
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
sceneManager.PasteEntity(id);
}
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
EntityID newId = sceneManager.DuplicateEntity(id);
if (newId != INVALID_ENTITY) {
selectionManager.SetSelectedEntity(newId);
}
}
}
void HierarchyPanel::RenderCreateMenu(EntityID parent) {
auto& sceneManager = SceneManager::Get();
auto& selectionManager = SelectionManager::Get();
if (ImGui::MenuItem("Empty Object")) {
EntityID newId = sceneManager.CreateEntity("GameObject", parent);
selectionManager.SetSelectedEntity(newId);
}
ImGui::Separator();
if (ImGui::MenuItem("Camera")) {
EntityID newId = sceneManager.CreateEntity("Camera", parent);
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
selectionManager.SetSelectedEntity(newId);
}
if (ImGui::MenuItem("Light")) {
EntityID newId = sceneManager.CreateEntity("Light", parent);
selectionManager.SetSelectedEntity(newId);
}
ImGui::Separator();
if (ImGui::MenuItem("Cube")) {
EntityID newId = sceneManager.CreateEntity("Cube", parent);
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Cube";
selectionManager.SetSelectedEntity(newId);
}
if (ImGui::MenuItem("Sphere")) {
EntityID newId = sceneManager.CreateEntity("Sphere", parent);
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Sphere";
selectionManager.SetSelectedEntity(newId);
}
if (ImGui::MenuItem("Plane")) {
EntityID newId = sceneManager.CreateEntity("Plane", parent);
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Plane";
selectionManager.SetSelectedEntity(newId);
}
}
void HierarchyPanel::HandleDragDrop(EntityID id) {
auto& sceneManager = SceneManager::Get();
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
m_dragSource = id;
ImGui::SetDragDropPayload("ENTITY_ID", &id, sizeof(EntityID));
const Entity* entity = sceneManager.GetEntity(id);
if (entity) {
ImGui::Text("%s", entity->name.c_str());
}
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
EntityID sourceId = *(const EntityID*)payload->Data;
if (sourceId != id && sourceId != INVALID_ENTITY) {
const Entity* targetEntity = sceneManager.GetEntity(id);
const Entity* sourceEntity = sceneManager.GetEntity(sourceId);
bool isValidMove = true;
EntityID checkParent = targetEntity ? targetEntity->parent : INVALID_ENTITY;
while (checkParent != INVALID_ENTITY) {
if (checkParent == sourceId) {
isValidMove = false;
break;
}
const Entity* parentEntity = sceneManager.GetEntity(checkParent);
checkParent = parentEntity ? parentEntity->parent : INVALID_ENTITY;
}
if (isValidMove && sourceEntity && sourceEntity->parent != id) {
sceneManager.MoveEntity(sourceId, id);
}
}
}
ImGui::EndDragDropTarget();
}
}
void HierarchyPanel::HandleKeyboardShortcuts() {
auto& sceneManager = SceneManager::Get();
auto& selectionManager = SelectionManager::Get();
EntityID selectedId = selectionManager.GetSelectedEntity();
if (ImGui::IsWindowFocused()) {
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
if (selectedId != INVALID_ENTITY) {
sceneManager.DeleteEntity(selectedId);
}
}
if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
if (selectedId != INVALID_ENTITY) {
const Entity* entity = sceneManager.GetEntity(selectedId);
if (entity) {
m_renaming = true;
m_renamingEntity = selectedId;
strcpy_s(m_renameBuffer, entity->name.c_str());
m_renameJustStarted = true;
}
}
}
ImGuiIO& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
if (selectedId != INVALID_ENTITY) {
sceneManager.CopyEntity(selectedId);
}
}
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
if (sceneManager.HasClipboardData()) {
sceneManager.PasteEntity(selectedId);
}
}
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
if (selectedId != INVALID_ENTITY) {
EntityID newId = sceneManager.DuplicateEntity(selectedId);
if (newId != INVALID_ENTITY) {
selectionManager.SetSelectedEntity(newId);
}
}
}
}
}
}
bool HierarchyPanel::PassesFilter(EntityID id, const std::string& filter) {
auto& sceneManager = SceneManager::Get();
const Entity* entity = sceneManager.GetEntity(id);
if (!entity) return false;
if (entity->name.find(filter) != std::string::npos) {
return true;
}
for (EntityID childId : entity->children) {
if (PassesFilter(childId, filter)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include "Panel.h"
#include "Core/Event.h"
#include "Core/GameObject.h"
namespace UI {
class HierarchyPanel : public Panel {
public:
HierarchyPanel();
~HierarchyPanel();
void Render() override;
private:
void RenderSearchBar();
void RenderEntity(EntityID id, const std::string& filter);
void RenderContextMenu(EntityID id);
void RenderCreateMenu(EntityID parent);
void HandleDragDrop(EntityID id);
void HandleKeyboardShortcuts();
bool PassesFilter(EntityID id, const std::string& filter);
Event<EntityID>::HandlerID m_selectionHandlerId = 0;
char m_searchBuffer[256] = "";
bool m_renaming = false;
EntityID m_renamingEntity = INVALID_ENTITY;
char m_renameBuffer[256] = "";
bool m_renameJustStarted = false;
EntityID m_dragSource = INVALID_ENTITY;
};
}

View File

@@ -0,0 +1,94 @@
#include "InspectorPanel.h"
#include "Managers/SceneManager.h"
#include "Managers/SelectionManager.h"
#include <imgui.h>
#include <string>
namespace UI {
InspectorPanel::InspectorPanel() : Panel("Inspector") {
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
});
}
InspectorPanel::~InspectorPanel() {
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
}
void InspectorPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
EntityID selectedId = SelectionManager::Get().GetSelectedEntity();
Entity* entity = SceneManager::Get().GetEntity(selectedId);
if (entity) {
RenderEntity(entity);
} else {
ImGui::Text("No object selected");
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy");
}
ImGui::End();
}
void InspectorPanel::RenderEntity(Entity* entity) {
ImGui::Text("%s", entity->name.c_str());
ImGui::Separator();
for (auto& component : entity->components) {
RenderComponent(component.get());
ImGui::Separator();
}
}
void InspectorPanel::RenderComponent(Component* component) {
if (!component) return;
const char* name = component->GetName().c_str();
std::string headerId = name + std::string("##") + std::to_string(reinterpret_cast<uintptr_t>(component));
if (ImGui::CollapsingHeader(headerId.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
if (auto* transform = dynamic_cast<TransformComponent*>(component)) {
ImGui::Text("Position");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Position", transform->position, 0.1f);
ImGui::Text("Rotation");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f);
ImGui::Text("Scale");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Scale", transform->scale, 0.1f);
}
else if (auto* meshRenderer = dynamic_cast<MeshRendererComponent*>(component)) {
char materialBuffer[256] = {};
strncpy_s(materialBuffer, meshRenderer->materialName.c_str(), sizeof(materialBuffer) - 1);
ImGui::Text("Material");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
if (ImGui::InputText("##Material", materialBuffer, sizeof(materialBuffer))) {
meshRenderer->materialName = materialBuffer;
}
char meshBuffer[256] = {};
strncpy_s(meshBuffer, meshRenderer->meshName.c_str(), sizeof(meshBuffer) - 1);
ImGui::Text("Mesh");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
if (ImGui::InputText("##Mesh", meshBuffer, sizeof(meshBuffer))) {
meshRenderer->meshName = meshBuffer;
}
}
ImGui::Unindent(10.0f);
}
}
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "Panel.h"
#include "Core/Event.h"
#include "Core/GameObject.h"
namespace UI {
class InspectorPanel : public Panel {
public:
InspectorPanel();
~InspectorPanel();
void Render() override;
private:
void RenderEntity(Entity* entity);
void RenderComponent(Component* component);
Event<EntityID>::HandlerID m_selectionHandlerId = 0;
};
}

View File

@@ -0,0 +1,55 @@
#include "MenuBar.h"
#include <imgui.h>
namespace UI {
MenuBar::MenuBar() : Panel("MenuBar") {}
void MenuBar::Render() {
if (ImGui::BeginMainMenuBar()) {
ShowFileMenu();
ShowEditMenu();
ShowViewMenu();
ShowHelpMenu();
ImGui::EndMainMenuBar();
}
}
void MenuBar::ShowFileMenu() {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {}
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {}
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {}
ImGui::Separator();
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
ImGui::EndMenu();
}
}
void MenuBar::ShowEditMenu() {
if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {}
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {}
ImGui::Separator();
if (ImGui::MenuItem("Cut", "Ctrl+X")) {}
if (ImGui::MenuItem("Copy", "Ctrl+C")) {}
if (ImGui::MenuItem("Paste", "Ctrl+V")) {}
ImGui::EndMenu();
}
}
void MenuBar::ShowViewMenu() {
if (ImGui::BeginMenu("View")) {
if (ImGui::MenuItem("Reset Layout")) {}
ImGui::EndMenu();
}
}
void MenuBar::ShowHelpMenu() {
if (ImGui::BeginMenu("Help")) {
if (ImGui::MenuItem("About")) {}
ImGui::EndMenu();
}
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "Panel.h"
namespace UI {
class MenuBar : public Panel {
public:
MenuBar();
void Render() override;
private:
void ShowFileMenu();
void ShowEditMenu();
void ShowViewMenu();
void ShowHelpMenu();
};
}

View File

@@ -0,0 +1,4 @@
#include "Panel.h"
namespace UI {
}

25
MVS/ui/src/panels/Panel.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <imgui.h>
namespace UI {
class Panel {
public:
Panel(const std::string& name) : m_name(name), m_isOpen(true) {}
virtual ~Panel() = default;
virtual void Render() = 0;
const std::string& GetName() const { return m_name; }
bool IsOpen() const { return m_isOpen; }
void SetOpen(bool open) { m_isOpen = open; }
void Toggle() { m_isOpen = !m_isOpen; }
protected:
std::string m_name;
bool m_isOpen;
};
}

View File

@@ -0,0 +1,297 @@
#include "ProjectPanel.h"
#include "Managers/ProjectManager.h"
#include "Core/AssetItem.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace UI {
const char* DRAG_DROP_TYPE = "ASSET_ITEM";
ProjectPanel::ProjectPanel() : Panel("Project") {
}
void ProjectPanel::Initialize(const std::string& projectPath) {
ProjectManager::Get().Initialize(projectPath);
}
void ProjectPanel::Render() {
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
if (payload && payload->IsDataType(DRAG_DROP_TYPE)) {
m_draggingPath = (const char*)payload->Data;
} else if (!ImGui::IsMouseDown(0)) {
m_draggingPath.clear();
}
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
auto& manager = ProjectManager::Get();
bool canGoBack = manager.CanNavigateBack();
ImGui::BeginDisabled(!canGoBack);
if (ImGui::Button("<")) {
if (canGoBack) {
manager.NavigateBack();
}
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0));
size_t pathDepth = manager.GetPathDepth();
for (size_t i = 0; i < pathDepth; i++) {
if (i > 0) {
ImGui::SameLine();
ImGui::Text("/");
ImGui::SameLine();
}
std::string name = manager.GetPathName(i);
if (i < pathDepth - 1) {
if (ImGui::Button(name.c_str())) {
manager.NavigateToIndex(i);
}
} else {
ImGui::Text("%s", name.c_str());
}
}
ImGui::PopStyleColor(2);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f);
if (ImGui::Button("Refresh")) {
manager.RefreshCurrentFolder();
}
ImGui::Separator();
ImGui::PushItemWidth(-1);
ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
ImGui::PopItemWidth();
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 10));
float buttonWidth = 80.0f;
float padding = 10.0f;
float panelWidth = ImGui::GetContentRegionAvail().x;
int columns = (int)(panelWidth / (buttonWidth + padding));
if (columns < 1) columns = 1;
auto& items = manager.GetCurrentItems();
std::string searchStr = m_searchBuffer;
int itemIndex = 0;
for (int i = 0; i < (int)items.size(); i++) {
if (!searchStr.empty()) {
if (items[i]->name.find(searchStr) == std::string::npos) {
continue;
}
}
if (itemIndex > 0 && itemIndex % columns != 0) {
ImGui::SameLine();
}
RenderAssetItem(items[i], itemIndex);
itemIndex++;
}
ImGui::PopStyleVar();
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
manager.SetSelectedIndex(-1);
}
if (ImGui::BeginPopup("ItemContextMenu")) {
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
auto& item = items[m_contextMenuIndex];
if (item->isFolder) {
if (ImGui::MenuItem("Open")) {
manager.NavigateToFolder(item);
}
ImGui::Separator();
}
if (ImGui::MenuItem("Delete")) {
manager.DeleteItem(m_contextMenuIndex);
m_contextMenuIndex = -1;
}
}
ImGui::EndPopup();
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) {
ImGui::OpenPopup("EmptyContextMenu");
}
if (ImGui::BeginPopup("EmptyContextMenu")) {
if (ImGui::MenuItem("Create Folder")) {
m_showCreateFolderPopup = true;
strcpy_s(m_newFolderName, "NewFolder");
}
ImGui::Separator();
if (ImGui::MenuItem("Refresh")) {
manager.RefreshCurrentFolder();
}
ImGui::EndPopup();
}
ImGui::End();
if (m_showCreateFolderPopup) {
ImGui::OpenPopup("Create Folder");
m_showCreateFolderPopup = false;
}
if (ImGui::BeginPopupModal("Create Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::InputText("Name", m_newFolderName, sizeof(m_newFolderName));
ImGui::Separator();
if (ImGui::Button("Create", ImVec2(80, 0))) {
CreateNewFolder(m_newFolderName);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(80, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
auto& manager = ProjectManager::Get();
bool isSelected = (manager.GetSelectedIndex() == index);
ImGui::PushID(index);
if (isSelected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.40f, 0.40f, 0.40f, 0.50f));
} else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.30f, 0.30f, 0.30f, 0.40f));
}
ImVec2 buttonSize(80.0f, 90.0f);
if (ImGui::Button("##AssetBtn", buttonSize)) {
manager.SetSelectedIndex(index);
}
bool doubleClicked = false;
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
doubleClicked = true;
}
bool openContextMenu = false;
if (ImGui::IsItemClicked(1)) {
manager.SetSelectedIndex(index);
m_contextMenuIndex = index;
openContextMenu = true;
}
if (isSelected) {
ImGui::PopStyleColor();
} else {
ImGui::PopStyleColor(2);
}
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 max = ImVec2(min.x + buttonSize.x, min.y + buttonSize.y);
ImDrawList* drawList = ImGui::GetWindowDrawList();
if (!m_draggingPath.empty() && item->fullPath == m_draggingPath) {
drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 60), 0.0f);
}
ImU32 iconColor;
if (item->isFolder) {
iconColor = IM_COL32(200, 180, 100, 255);
} else if (item->type == "Texture") {
iconColor = IM_COL32(150, 200, 150, 255);
} else if (item->type == "Model") {
iconColor = IM_COL32(150, 150, 200, 255);
} else if (item->type == "Script") {
iconColor = IM_COL32(200, 150, 150, 255);
} else if (item->type == "Scene") {
iconColor = IM_COL32(200, 200, 150, 255);
} else {
iconColor = IM_COL32(100, 150, 200, 255);
}
float iconSize = 40.0f;
ImVec2 iconMin(min.x + (80.0f - iconSize) * 0.5f, min.y + 10.0f);
ImVec2 iconMax(iconMin.x + iconSize, iconMin.y + iconSize);
drawList->AddRectFilled(iconMin, iconMax, iconColor, 4.0f);
ImVec4 textColor = isSelected ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
ImVec2 textSize = ImGui::CalcTextSize(item->name.c_str());
float textOffset = std::max(0.0f, (80.0f - textSize.x) * 0.5f);
ImGui::PushClipRect(min, ImVec2(min.x + 80.0f, min.y + 90.0f), true);
drawList->AddText(ImVec2(min.x + textOffset, min.y + 60.0f), ImGui::GetColorU32(textColor), item->name.c_str());
ImGui::PopClipRect();
if (item->isFolder) {
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) {
const char* draggedPath = (const char*)payload->Data;
std::string sourcePath(draggedPath);
manager.MoveItem(sourcePath, item->fullPath);
}
ImGui::EndDragDropTarget();
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
ImDrawList* hoverDrawList = ImGui::GetWindowDrawList();
hoverDrawList->AddRect(min, ImVec2(min.x + buttonSize.x, min.y + buttonSize.y), IM_COL32(255, 255, 255, 80), 4.0f);
}
}
if (!item->fullPath.empty()) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1);
ImU32 iconColor;
if (item->isFolder) {
iconColor = IM_COL32(200, 180, 100, 100);
} else if (item->type == "Texture") {
iconColor = IM_COL32(150, 200, 150, 100);
} else if (item->type == "Model") {
iconColor = IM_COL32(150, 150, 200, 100);
} else if (item->type == "Script") {
iconColor = IM_COL32(200, 150, 150, 100);
} else if (item->type == "Scene") {
iconColor = IM_COL32(200, 200, 150, 100);
} else {
iconColor = IM_COL32(100, 150, 200, 100);
}
ImVec2 previewMin = ImGui::GetMousePos();
ImVec2 previewMax = ImVec2(previewMin.x + 40, previewMin.y + 40);
ImGui::GetForegroundDrawList()->AddRectFilled(previewMin, previewMax, iconColor, 4.0f);
ImGui::EndDragDropSource();
}
}
if (doubleClicked && item->isFolder) {
manager.NavigateToFolder(item);
}
ImGui::PopID();
if (openContextMenu) {
ImGui::OpenPopup("ItemContextMenu");
}
}
void ProjectPanel::CreateNewFolder(const std::string& name) {
auto& manager = ProjectManager::Get();
manager.CreateFolder(name);
}
bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) {
return false;
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "Panel.h"
#include "Core/AssetItem.h"
namespace UI {
class ProjectPanel : public Panel {
public:
ProjectPanel();
void Render() override;
void Initialize(const std::string& projectPath);
private:
void RenderAssetItem(const AssetItemPtr& item, int index);
void CreateNewFolder(const std::string& name);
bool HandleDrop(const AssetItemPtr& targetFolder);
char m_searchBuffer[256] = "";
bool m_showCreateFolderPopup = false;
char m_newFolderName[256] = "NewFolder";
int m_contextMenuIndex = -1;
std::string m_draggingPath;
};
}

View File

@@ -0,0 +1,54 @@
#include "SceneViewPanel.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace UI {
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
void SceneViewPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
ImU32 bgColor = IM_COL32(30, 30, 30, 255);
drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), bgColor);
RenderGrid();
ImGui::End();
}
void SceneViewPanel::RenderGrid() {
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
float gridSize = 50.0f;
ImU32 gridColor = IM_COL32(50, 50, 50, 255);
for (float x = fmodf(0, gridSize); x < canvasSize.x; x += gridSize) {
drawList->AddLine(
ImVec2(canvasPos.x + x, canvasPos.y),
ImVec2(canvasPos.x + x, canvasPos.y + canvasSize.y),
gridColor
);
}
for (float y = fmodf(0, gridSize); y < canvasSize.y; y += gridSize) {
drawList->AddLine(
ImVec2(canvasPos.x, canvasPos.y + y),
ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + y),
gridColor
);
}
const char* label = "Scene View";
ImVec2 labelSize = ImGui::CalcTextSize(label);
ImVec2 labelPos(canvasPos.x + 10, canvasPos.y + 10);
drawList->AddText(labelPos, IM_COL32(100, 100, 100, 255), label);
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Panel.h"
namespace UI {
class SceneViewPanel : public Panel {
public:
SceneViewPanel();
void Render() override;
private:
void RenderGrid();
};
}

149
README.md
View File

@@ -1,37 +1,77 @@
# XCVolumeRenderer
# XCEngine
基于 DirectX 12 的体积渲染器,使用 NanoVDB 格式实现云、烟雾等体积数据的实时渲染
基于 DirectX 12 的渲染引擎项目,包含体积渲染器、基础渲染示例和 Unity 风格编辑器 UI
## 项目概述
XCEngine 是一个正在开发中的图形渲染引擎,使用 DirectX 12 作为底层渲染 API。项目采用模块化设计包含多个独立演示项目MVS - Multiple Version Samples
## 技术栈
- **渲染API**: DirectX 12
- **渲染 API**: DirectX 12
- **语言**: C++17
- **构建系统**: CMake
- **依赖库**: DirectX 12 SDK, stb_image, NanoVDB
- **UI 框架**: ImGui
## 项目结构
```
XCVolumeRenderer/
├── main.cpp # 主程序入口
├── BattleFireDirect.cpp/h # DirectX 12 核心渲染实现
├── NanoVDBLoader.cpp/h # NanoVDB 体积数据加载器
├── StaticMeshComponent.cpp/h # 静态网格组件
├── Utils.cpp/h # 工具函数
├── stbi/ # 图像加载库
├── Res/
├── Shader/ # HLSL 着色器
│ │ ├── volume.hlsl # 体积渲染着色器
│ │ ├── gs.hlsl # 几何着色器
│ │ └── PNanoVDB.hlsl # NanoVDB GPU 解析
├── Model/ # 模型文件
│ ├── Image/ # 纹理图片
└── NanoVDB/ # NanoVDB 体积数据
├── CMakeLists.txt # CMake 构建配置
└── build/ # 构建目录
XCEngine/
├── MVS/ # 多个示例版本
│ ├── HelloEarth/ # 基础渲染示例
│ │ ├── main.cpp # 程序入口
│ │ ├── BattleFireDirect.cpp/h # DirectX 12 核心
│ │ ├── StaticMeshComponent.cpp/h # 静态网格组件
├── Utils.cpp/h # 工具函数
│ │ ├── stbi/ # 图像加载库
│ └── Res/ # 资源文件
│ │ ├── Shader/ # HLSL 着色器
│ │ ├── Model/ # 模型文件
│ │ └── Image/ # 纹理图片
│ ├── VolumeRenderer/ # 体积渲染器
│ ├── main.cpp # 主程序入口
│ │ ├── BattleFireDirect.cpp/h # DirectX 12 核心渲染
├── NanoVDBLoader.cpp/h # NanoVDB 体积数据加载
│ │ ├── StaticMeshComponent.cpp/h # 静态网格组件
│ │ ├── Utils.cpp/h # 工具函数
│ │ ├── stbi/ # 图像加载库
│ │ ├── CMakeLists.txt # CMake 构建配置
│ │ ├── Res/ # 资源文件
│ │ │ ├── Shader/ # HLSL 着色器
│ │ │ ├── Model/ # 模型文件
│ │ │ ├── Image/ # 纹理图片
│ │ │ └── NanoVDB/ # NanoVDB 体积数据
│ │ └── README.md # 详细文档
│ │
│ └── ui/ # Unity 风格编辑器 UI
│ ├── src/
│ │ ├── main.cpp # 程序入口
│ │ ├── Application.cpp/h # 应用主类
│ │ ├── Theme.cpp/h # 主题系统
│ │ ├── Core/ # 核心类
│ │ ├── Managers/ # 管理系统
│ │ │ ├── LogSystem # 日志系统
│ │ │ ├── ProjectManager # 项目管理
│ │ │ └── SceneManager # 场景管理
│ │ └── panels/ # UI 面板
│ │ ├── HierarchyPanel # 层级面板
│ │ ├── InspectorPanel # 检查器面板
│ │ ├── SceneViewPanel # 场景视图
│ │ ├── GameViewPanel # 游戏视图
│ │ ├── ProjectPanel # 项目面板
│ │ ├── ConsolePanel # 控制台面板
│ │ └── MenuBar # 菜单栏
│ ├── bin/Release/ # 输出目录
│ └── CMakeLists.txt
└── docs/ # 设计文档
├── XCVolumeRenderer渲染引擎架构设计.md
├── XCGameEngine架构设计.md
└── ...
```
## 构建方法
## 快速开始
### 前置要求
@@ -39,49 +79,52 @@ XCVolumeRenderer/
- Visual Studio 2019 或更高版本
- CMake 3.15+
### 构建步骤
### 构建项目
每个子项目都可以独立构建,以 HelloEarth 为例:
```bash
# 创建并进入构建目录
cd MVS/HelloEarth
mkdir build && cd build
# 配置项目
cmake ..
# 编译
cmake --build . --config Release
```
### 运行
## 各模块说明
编译完成后,运行 `XCVolumeRenderer.exe` 或使用 `run.bat`
### HelloEarth
## 功能特性
### 核心渲染
- DirectX 12 渲染管线
- 几何着色器GS支持
基础的 DirectX 12 渲染示例,展示如何:
- 初始化 DirectX 12 渲染环境
- 加载和渲染静态网格
- 使用几何着色器
- 纹理映射
- 常量缓冲区CBV/SRV
- 常量缓冲区更新
### 体积渲染
- NanoVDB 格式支持
- 光线步进Ray Marching
### VolumeRenderer
基于 NanoVDB 的体积渲染器,支持:
- NanoVDB 格式体积数据加载
- 光线步进Ray Marching渲染
- HDDA 空间跳跃加速
- 体积阴影Volumetric Shadow
- 指数步长抖动采样
- Gamma 校正
- 体积阴影
- 多种渲染参数配置
### 参数配置
- DensityScale - 密度缩放
- StepSize - 步进大小
- MaxSteps - 最大步数
- LightDir - 光照方向
- LightSamples - 阴影采样数
### ui
## 资源说明
Unity 风格的编辑器 UI包含
- 层级面板Hierarchy Panel
- 检查器面板Inspector Panel
- 场景视图Scene View
- 游戏视图Game View
- 项目面板Project Panel
- 控制台面板Console Panel
- 日志系统
- 着色器文件位于 `Res/Shader/` 目录
- 模型文件为 `.lhsm` 格式
- 纹理支持 PNG、JPG 格式
- 体积数据支持 NanoVDB `.nvdb` 格式
## 文档
更多详细设计文档请参考 `docs/` 目录。
## 许可证
MIT License