如何用Direct3D 12和Visual C++动态修改顶点颜色?
解决方案:Direct3D 12每帧更新顶点颜色绘制三向渐变三角形
针对你的需求,核心是利用Direct3D 12的上传堆资源实现高频顶点颜色更新,同时依托硬件原生支持的三角形插值完成渐变渲染。以下是具体实现步骤:
1. 定义顶点结构体
顶点包含固定位置和需要每帧更新的颜色,结构体需符合D3D12内存对齐要求:
struct Vertex { XMFLOAT2 position; // 固定屏幕位置(正交投影下可直接用像素坐标或归一化坐标) XMFLOAT4 color; // 每帧更新的顶点颜色 }; // 输入布局描述,对应着色器输入 const D3D12_INPUT_ELEMENT_DESC inputElements[] = { { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, sizeof(XMFLOAT2), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } };
2. 创建可频繁更新的顶点缓冲区
使用**上传堆(Upload Heap)**创建顶点缓冲区,这类资源允许CPU直接写入、GPU高效读取,适配100Hz高频更新场景:
// 假设你有N个三角形,共3*N个顶点 UINT vertexCount = 3 * triangleCount; UINT vertexBufferSize = vertexCount * sizeof(Vertex); // 初始化顶点数据(位置固定,颜色设初始值) std::vector<Vertex> vertices(vertexCount); for (int i = 0; i < triangleCount; ++i) { // 设置第i个三角形的三个顶点固定位置 vertices[3*i + 0].position = XMFLOAT2(x1, y1); vertices[3*i + 1].position = XMFLOAT2(x2, y2); vertices[3*i + 2].position = XMFLOAT2(x3, y3); // 初始颜色赋值 vertices[3*i + 0].color = XMFLOAT4(r1, g1, b1, 1.0f); vertices[3*i + 1].color = XMFLOAT4(r2, g2, b2, 1.0f); vertices[3*i + 2].color = XMFLOAT4(r3, g3, b3, 1.0f); } // 创建上传堆顶点缓冲区 D3D12_HEAP_PROPERTIES uploadHeapProps = { D3D12_HEAP_TYPE_UPLOAD }; D3D12_RESOURCE_DESC vbDesc = {}; vbDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; vbDesc.Width = vertexBufferSize; vbDesc.Height = 1; vbDesc.DepthOrArraySize = 1; vbDesc.MipLevels = 1; vbDesc.Format = DXGI_FORMAT_UNKNOWN; vbDesc.SampleDesc.Count = 1; vbDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; ID3D12Resource* pVertexBuffer; device->CreateCommittedResource( &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &vbDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pVertexBuffer) ); // 映射顶点缓冲区到CPU地址,上传堆可保持永久映射无需频繁Unmap Vertex* pMappedVertices = nullptr; pVertexBuffer->Map(0, nullptr, reinterpret_cast<void**>(&pMappedVertices)); // 写入初始顶点数据 memcpy(pMappedVertices, vertices.data(), vertexBufferSize);
3. 每帧更新顶点颜色并渲染
因为顶点位置固定,每帧仅需修改color字段,再提交绘制命令:
// 每帧更新与渲染逻辑(多媒体定时器回调或渲染循环内) void UpdateAndRender() { // 1. 根据MIDI同步逻辑更新每个顶点的颜色 for (int i = 0; i < vertexCount; ++i) { // 替换为你的颜色计算逻辑(如随音乐节奏动态调整) pMappedVertices[i].color = XMFLOAT4(newR, newG, newB, 1.0f); } // 2. 记录渲染命令 ID3D12GraphicsCommandList* pCmdList = ...; // 获取当前帧的命令列表 pCmdList->Reset(pCmdAllocator, pPipelineState); pCmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // 设置顶点缓冲区视图 D3D12_VERTEX_BUFFER_VIEW vbView = {}; vbView.BufferLocation = pVertexBuffer->GetGPUVirtualAddress(); vbView.StrideInBytes = sizeof(Vertex); vbView.SizeInBytes = vertexBufferSize; pCmdList->IASetVertexBuffers(0, 1, &vbView); // 绘制所有三角形 pCmdList->DrawInstanced(vertexCount, 1, 0, 0); pCmdList->Close(); // 3. 提交命令列表并同步CPU/GPU ID3D12CommandQueue* pCmdQueue = ...; pCmdQueue->ExecuteCommandLists(1, reinterpret_cast<ID3D12CommandList**>(&pCmdList)); // 用Fence同步,避免CPU更新过快覆盖GPU正在读取的数据 UINT64 currentFenceValue = ++fenceValue; pCmdQueue->Signal(pFence, currentFenceValue); if (pFence->GetCompletedValue() < currentFenceValue) { HANDLE eventHandle = CreateEvent(nullptr, FALSE, FALSE, nullptr); pFence->SetEventOnCompletion(currentFenceValue, eventHandle); WaitForSingleObject(eventHandle, INFINITE); CloseHandle(eventHandle); } }
4. 配套着色器实现
顶点着色器直接传递位置和颜色,像素着色器输出插值后的颜色,硬件自动完成三向渐变:
// 顶点着色器 struct VSInput { float2 position : POSITION; float4 color : COLOR; }; struct PSInput { float4 position : SV_POSITION; float4 color : COLOR; }; PSInput VS(VSInput input) { PSInput output; output.position = float4(input.position, 0.0f, 1.0f); // 正交投影下直接输出位置 output.color = input.color; return output; } // 像素着色器 float4 PS(PSInput input) : SV_TARGET { return input.color; }
关键注意事项
- 上传堆映射:上传堆资源可保持永久映射状态,无需每帧调用
Map/Unmap,减少开销。 - 同步策略:100Hz帧率下必须用Fence同步CPU和GPU,防止颜色数据被GPU读取时覆盖,避免画面错误。
- 带宽优化:仅更新顶点颜色字段,固定位置无需修改,节省内存带宽。
- 正交投影:保留已设置的正交矩阵,确保顶点位置直接对应屏幕坐标,无需额外3D变换。
内容的提问来源于stack exchange,提问作者Chris Korda
相关产品推荐
相关产品推荐

