Xây Mã Nguồn HLSL Xử Lý Đồ Họa
Bước đầu tiên trong việc định hình pipeline đồ họa là viết các đoạn mã tiền tố bằng ngôn ngữ HLSL để xử lý thông tin đỉnh và màu sắc điểm ảnh.
// Hàm xử lý luồng đỉnh
OutputStruct MainVS(InputStruct vInput)
{
OutputStruct vResult;
// Mở rộng tọa độ thành dạng vector 4 chiều để tính toán phép chiếu
vResult.positionHS = float4(vInput.positionHS, 1.0f);
vResult.colorValue = vInput.colorRGB;
return vResult;
}
// Hàm xử lý luồng pixel tại mỗi điểm ảnh
float4 MainPS(OutputStruct pInput) : SV_TARGET
{
// Trả về giá trị màu đã được truyền từ bước trước
return pInput.colorValue;
}
Trong cấu hình dự án, cần chú ý tới các đặc tả (semantics) đi kèm sau tên biến để ánh xạ dữ liệu đúng cách:
- POSITION: Chỉ định biến chứa tọa độ không gian.
- SV_POSITION: Đánh dấu vị trí đỉnh cuối cùng dùng cho công đoạn rasterization, giá trị này bất biến sau khi qua vertex shader.
- COLOR: Định nghĩa dữ liệu màu sắc truyền giữa các stage.
- SV_Target: Xác nhận đầu ra sẽ được ghi trực tiếp vào bộ đệm khung hình (back buffer).
Cấu Hình Layout Dữ Liệu Đầu Vào
Trên môi trường C++, ta cần xác định cấu trúc dữ liệu khớp với HLSL và khởi tạo đối tượng ID3D11InputLayout. Đối tượng này đóng vai trò cầu nối giữa định dạng dữ liệu bộ nhớ hệ thống và yêu cầu của driver.
struct RenderVertexData
{
DirectX::XMFLOAT3 pos; // Tọa độ XYZ
DirectX::XMFLOAT4 color; // Màu RGBA
static const D3D11_INPUT_ELEMENT_DESC layoutDef[2];
};
Sau đó định nghĩa mô tả chi tiết cho từng thành phần dữ liệu:
const D3D11_INPUT_ELEMENT_DESC RenderVertexData::layoutDef[2] = {
// Mô tả thành phần POSITION
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// Mô tả thành phần COLOR (bắt đầu sau 12 bytes do 3 số thực)
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
Để hiểu rõ hơn về cấu trúc D3D11_INPUT_ELEMENT_DESC, xem xét các tham số chính:
| Tên tham số | Mô tả kỹ thuật |
|---|---|
| SemanticName | Chuỗi văn bản chỉ định loại dữ liệu (ví dụ: POSITION) |
| SemanticIndex | Công cụ phân biệt các biến có cùng tên |
| Format | Định dạng kiểu dữ liệu pixel (float3, float4) |
| AlignedByteOffset | Vị trí bắt đầu của dữ liệu tính theo byte trong cấu trúc |
| InputSlotClass | Hướng dẫn driver đọc dữ liệu (đến từng đỉnh hay từng instance) |
Giai Đoạn Nạp Shader Và Cấu Hình Layout
Quá trình nạp file nhị phân (.cso) và khởi tạo giao diện thực thi diễn ra trong hàm cấu hình ban đầu.
Định nghĩa các thành viên lưu trữ trỏ đến đối tượng shader và layout:
private:
bool SetupGraphicsPipeline(); // Khởi tạo tài nguyên đồ họa
ComPtr<ID3D11VertexShader> vp_ShaderVert; // Tham chiếu Shader đỉnh
ComPtr<ID3D11InputLayout> vp_InputDesc; // Tham chiếu Layout
ComPtr<ID3D11PixelShader> vp_ShaderPix; // Tham chiếu Shader điểm
Quy trình thực hiện nạp dữ liệu dưới dạng blob:
ComPtr<ID3DBlob> shaderBuffer;
// Đọc file shader đỉnh từ đường dẫn tương đối
HR(D3DReadFileToBlob(L"HLSL Files\\Octagon_VS.cso", shaderBuffer.GetAddressOf()));
Tạo đối tượng Vertex Shader từ bytecode:
HR(m_pd3dDevice->CreateVertexShader(
shaderBuffer->GetBufferPointer(),
shaderBuffer->GetBufferSize(),
nullptr,
vp_ShaderVert.GetAddressOf()
));
Sử dụng thông tin byte code để thiết lập Input Layout khớp với định nghĩa cấu trúc C++:
HR(m_pd3dDevice->CreateInputLayout(
RenderVertexData::layoutDef,
ARRAYSIZE(RenderVertexData::layoutDef),
shaderBuffer->GetBufferPointer(),
shaderBuffer->GetBufferSize(),
vp_InputDesc.GetAddressOf()
));
Thao tác tương tự được áp dụng để tạo Pixel Shader từ file Octagon_PS.cso.
Xử Lý Bộ Đệm (Buffers) Và Topology
Tài nguyên hình học như đỉnh và chỉ số cần được lưu trữ trong GPU. Các hàm khởi tạo nằm ở giai đoạn chuẩn bị tài nguyên tổng thể.
Khẳng định các thành viên bộ đệm:
ComPtr<ID3D11Buffer> vp_VertexMem; // Bộ nhớ đỉnh
ComPtr<ID3D11Buffer> vp_IndexMem; // Bộ nhớ chỉ số
Định nghĩa tập hợp các điểm đỉnh cho hình đa giác (lục giác hoặc bát giác tùy mục đích):
RenderVertexData meshVertices[] = {
{XMFLOAT3(-0.5f,1.0f,0.0f),XMFLOAT4(1.0f,0.0f,0.0f,1.0f)},
{XMFLOAT3(0.5f,1.0f,0.0f),XMFLOAT4(0.0f,1.0f,0.0f,1.0f)},
{XMFLOAT3(1.0f,0.5f,0.0f),XMFLOAT4(0.0f,0.0f,1.0f,1.0f)},
{XMFLOAT3(1.0f,-0.5f,0.0f),XMFLOAT4(1.0f,1.0f,0.0f,1.0f)},
{XMFLOAT3(0.5f,-1.0f,0.0f),XMFLOAT4(0.0f,1.0f,1.0f,1.0f)},
{XMFLOAT3(-0.5f,-1.0f,0.0f),XMFLOAT4(1.0f,0.0f,1.0f,1.0f)},
{XMFLOAT3(-1.0f,-0.5f,0.0f),XMFLOAT4(1.0f,1.0f,1.0f,1.0f)},
{XMFLOAT3(-1.0f,0.5f,0.0f),XMFLOAT4(0.0f,0.0f,0.0f,1.0f)},
};
Thiết lập thuộc tính cho bộ đệm đỉnh:
D3D11_BUFFER_DESC bufDesc;
ZeroMemory(&bufDesc, sizeof(bufDesc));
bufDesc.ByteWidth = sizeof(meshVertices); // Kích thước bộ nhớ tổng
bufDesc.Usage = D3D11_USAGE_IMMUTABLE; // Chỉ GPU đọc, không thay đổi sau
bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // Mục đích sử dụng
bufDesc.CPUAccessFlags = 0;
Lập cấu dữ liệu để sao chép bộ nhớ hệ thống vào GPU:
D3D11_SUBRESOURCE_DATA initVertexInfo;
initVertexInfo.pSysMem = meshVertices;
initVertexInfo.SysMemPitch = 0;
initVertexInfo.SysMemSlicePitch = 0;
HR(m_pd3dDevice->CreateBuffer(&bufDesc, &initVertexInfo, vp_VertexMem.GetAddressOf()));
Tương tự cho bộ đệm chỉ số (Index Buffer) xác định thứ tự vẽ tam giác:
DWORD drawIndices[] = {
0,1,2, 0,2,3,
0,3,4, 0,4,5,
0,5,6, 0,6,7,
};
D3D11_BUFFER_DESC ibDesc;
ZeroMemory(&ibDesc, sizeof(ibDesc));
ibDesc.ByteWidth = sizeof(drawIndices);
ibDesc.Usage = D3D11_USAGE_IMMUTABLE;
ibDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibDesc.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA initIndexInfo;
initIndexInfo.pSysMem = drawIndices;
HR(m_pd3dDevice->CreateBuffer(&ibDesc, &initIndexInfo, vp_IndexMem.GetAddressOf()));
Liên Kết Với Render Context
Trước khi vẽ, các tài nguyên phải được liên kết chặt chẽ với trạng thái hiện hành của thiết bị.
Đặt bộ đệm đỉnh tại slot đầu tiên:
UINT strideSize = sizeof(RenderVertexData);
UINT offsetBytes = 0;
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, vp_VertexMem.GetAddressOf(), &strideSize, &offsetBytes);
Áp dụng mô tả Layout đã tạo lên luồng nhập:
m_pd3dImmediateContext->IASetInputLayout(vp_InputDesc.Get());
Thiết lập bộ đệm chỉ số:
m_pd3dImmediateContext->IASetIndexBuffer(vp_IndexMem.Get(), DXGI_FORMAT_R32_UINT, 0);
Cấu hình các stage Shader và định dạng hình học cơ sở:
// Gắn shader hiện hành
m_pd3dImmediateContext->VSSetShader(vp_ShaderVert.Get(), nullptr, 0);
m_pd3dImmediateContext->PSSetShader(vp_ShaderPix.Get(), nullptr, 0);
// Chọn kiểu tô pô vẽ (ở đây là danh sách tam giác)
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);