mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Character renderer is fully Vulkan.
This commit is contained in:
parent
83b576e8d9
commit
dea52744a4
3 changed files with 282 additions and 7 deletions
|
|
@ -58,7 +58,8 @@ public:
|
|||
void update(float deltaTime, const glm::vec3& cameraPos = glm::vec3(0.0f));
|
||||
|
||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
|
||||
void renderShadow(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
|
||||
bool initializeShadow(VkRenderPass shadowRenderPass);
|
||||
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix);
|
||||
|
||||
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
|
||||
void setInstanceRotation(uint32_t instanceId, const glm::vec3& rotation);
|
||||
|
|
@ -261,6 +262,15 @@ private:
|
|||
|
||||
// Maximum bones supported
|
||||
static constexpr int MAX_BONES = 240;
|
||||
|
||||
// Shadow pipeline resources
|
||||
VkPipeline shadowPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout shadowPipelineLayout_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout shadowParamsLayout_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPool shadowParamsPool_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSet shadowParamsSet_ = VK_NULL_HANDLE;
|
||||
VkBuffer shadowParamsUBO_ = VK_NULL_HANDLE;
|
||||
VmaAllocation shadowParamsAlloc_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
|
|
|
|||
|
|
@ -299,6 +299,13 @@ void CharacterRenderer::shutdown() {
|
|||
if (materialSetLayout_) { vkDestroyDescriptorSetLayout(device, materialSetLayout_, nullptr); materialSetLayout_ = VK_NULL_HANDLE; }
|
||||
if (boneSetLayout_) { vkDestroyDescriptorSetLayout(device, boneSetLayout_, nullptr); boneSetLayout_ = VK_NULL_HANDLE; }
|
||||
|
||||
// Shadow resources
|
||||
if (shadowPipeline_) { vkDestroyPipeline(device, shadowPipeline_, nullptr); shadowPipeline_ = VK_NULL_HANDLE; }
|
||||
if (shadowPipelineLayout_) { vkDestroyPipelineLayout(device, shadowPipelineLayout_, nullptr); shadowPipelineLayout_ = VK_NULL_HANDLE; }
|
||||
if (shadowParamsPool_) { vkDestroyDescriptorPool(device, shadowParamsPool_, nullptr); shadowParamsPool_ = VK_NULL_HANDLE; }
|
||||
if (shadowParamsLayout_) { vkDestroyDescriptorSetLayout(device, shadowParamsLayout_, nullptr); shadowParamsLayout_ = VK_NULL_HANDLE; }
|
||||
if (shadowParamsUBO_) { vmaDestroyBuffer(alloc, shadowParamsUBO_, shadowParamsAlloc_); shadowParamsUBO_ = VK_NULL_HANDLE; shadowParamsAlloc_ = VK_NULL_HANDLE; }
|
||||
|
||||
vkCtx_ = nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -1791,10 +1798,262 @@ void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
|||
}
|
||||
}
|
||||
|
||||
void CharacterRenderer::renderShadow(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
||||
// Phase 6 stub -- shadow rendering will be implemented with shadow pipeline
|
||||
(void)cmd;
|
||||
(void)perFrameSet;
|
||||
bool CharacterRenderer::initializeShadow(VkRenderPass shadowRenderPass) {
|
||||
if (!vkCtx_ || shadowRenderPass == VK_NULL_HANDLE) return false;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
// ShadowCharParams UBO (matches character_shadow.frag.glsl set=1 binding=1)
|
||||
struct ShadowCharParams {
|
||||
int32_t alphaTest = 0;
|
||||
int32_t colorKeyBlack = 0;
|
||||
};
|
||||
|
||||
// Create ShadowCharParams UBO
|
||||
VkBufferCreateInfo bufCI{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bufCI.size = sizeof(ShadowCharParams);
|
||||
bufCI.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
VmaAllocationCreateInfo allocCI{};
|
||||
allocCI.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
allocCI.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
VmaAllocationInfo allocInfo{};
|
||||
if (vmaCreateBuffer(vkCtx_->getAllocator(), &bufCI, &allocCI,
|
||||
&shadowParamsUBO_, &shadowParamsAlloc_, &allocInfo) != VK_SUCCESS) {
|
||||
LOG_ERROR("CharacterRenderer: failed to create shadow params UBO");
|
||||
return false;
|
||||
}
|
||||
ShadowCharParams defaultParams{};
|
||||
std::memcpy(allocInfo.pMappedData, &defaultParams, sizeof(defaultParams));
|
||||
|
||||
// Descriptor set layout for set 1: binding 0 = sampler2D, binding 1 = ShadowCharParams UBO
|
||||
VkDescriptorSetLayoutBinding layoutBindings[2]{};
|
||||
layoutBindings[0].binding = 0;
|
||||
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
layoutBindings[0].descriptorCount = 1;
|
||||
layoutBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
layoutBindings[1].binding = 1;
|
||||
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
layoutBindings[1].descriptorCount = 1;
|
||||
layoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
VkDescriptorSetLayoutCreateInfo layoutCI{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
layoutCI.bindingCount = 2;
|
||||
layoutCI.pBindings = layoutBindings;
|
||||
if (vkCreateDescriptorSetLayout(device, &layoutCI, nullptr, &shadowParamsLayout_) != VK_SUCCESS) {
|
||||
LOG_ERROR("CharacterRenderer: failed to create shadow params layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Descriptor pool (1 set)
|
||||
VkDescriptorPoolSize poolSizes[2]{};
|
||||
poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
poolSizes[0].descriptorCount = 1;
|
||||
poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
poolSizes[1].descriptorCount = 1;
|
||||
VkDescriptorPoolCreateInfo poolCI{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
poolCI.maxSets = 1;
|
||||
poolCI.poolSizeCount = 2;
|
||||
poolCI.pPoolSizes = poolSizes;
|
||||
if (vkCreateDescriptorPool(device, &poolCI, nullptr, &shadowParamsPool_) != VK_SUCCESS) {
|
||||
LOG_ERROR("CharacterRenderer: failed to create shadow params pool");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate descriptor set
|
||||
VkDescriptorSetAllocateInfo setAlloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
setAlloc.descriptorPool = shadowParamsPool_;
|
||||
setAlloc.descriptorSetCount = 1;
|
||||
setAlloc.pSetLayouts = &shadowParamsLayout_;
|
||||
if (vkAllocateDescriptorSets(device, &setAlloc, &shadowParamsSet_) != VK_SUCCESS) {
|
||||
LOG_ERROR("CharacterRenderer: failed to allocate shadow params set");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write descriptors (white dummy texture + ShadowCharParams UBO)
|
||||
VkDescriptorImageInfo imgInfo{};
|
||||
imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
imgInfo.imageView = whiteTexture_->getImageView();
|
||||
imgInfo.sampler = whiteTexture_->getSampler();
|
||||
VkDescriptorBufferInfo bufInfo{};
|
||||
bufInfo.buffer = shadowParamsUBO_;
|
||||
bufInfo.offset = 0;
|
||||
bufInfo.range = sizeof(ShadowCharParams);
|
||||
VkWriteDescriptorSet writes[2]{};
|
||||
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[0].dstSet = shadowParamsSet_;
|
||||
writes[0].dstBinding = 0;
|
||||
writes[0].descriptorCount = 1;
|
||||
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
writes[0].pImageInfo = &imgInfo;
|
||||
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[1].dstSet = shadowParamsSet_;
|
||||
writes[1].dstBinding = 1;
|
||||
writes[1].descriptorCount = 1;
|
||||
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
writes[1].pBufferInfo = &bufInfo;
|
||||
vkUpdateDescriptorSets(device, 2, writes, 0, nullptr);
|
||||
|
||||
// Pipeline layout: set 0 = perFrameLayout_ (dummy), set 1 = shadowParamsLayout_, set 2 = boneSetLayout_
|
||||
// Push constant: 128 bytes (lightSpaceMatrix + model), VERTEX stage
|
||||
VkDescriptorSetLayout setLayouts[] = {perFrameLayout_, shadowParamsLayout_, boneSetLayout_};
|
||||
VkPushConstantRange pc{};
|
||||
pc.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
pc.offset = 0;
|
||||
pc.size = 128;
|
||||
VkPipelineLayoutCreateInfo plCI{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
plCI.setLayoutCount = 3;
|
||||
plCI.pSetLayouts = setLayouts;
|
||||
plCI.pushConstantRangeCount = 1;
|
||||
plCI.pPushConstantRanges = &pc;
|
||||
if (vkCreatePipelineLayout(device, &plCI, nullptr, &shadowPipelineLayout_) != VK_SUCCESS) {
|
||||
LOG_ERROR("CharacterRenderer: failed to create shadow pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load character shadow shaders
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/character_shadow.vert.spv")) {
|
||||
LOG_ERROR("CharacterRenderer: failed to load character_shadow.vert.spv");
|
||||
return false;
|
||||
}
|
||||
if (!fragShader.loadFromFile(device, "assets/shaders/character_shadow.frag.spv")) {
|
||||
LOG_ERROR("CharacterRenderer: failed to load character_shadow.frag.spv");
|
||||
vertShader.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Character vertex format (M2Vertex): stride = 48 bytes
|
||||
// loc 0: vec3 aPos (R32G32B32_SFLOAT, offset 0)
|
||||
// loc 1: vec4 aBoneWeights (R8G8B8A8_UNORM, offset 12)
|
||||
// loc 2: ivec4 aBoneIndices (R8G8B8A8_UINT, offset 16)
|
||||
// loc 3: vec2 aTexCoord (R32G32_SFLOAT, offset 32)
|
||||
VkVertexInputBindingDescription vertBind{};
|
||||
vertBind.binding = 0;
|
||||
vertBind.stride = static_cast<uint32_t>(sizeof(pipeline::M2Vertex));
|
||||
vertBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
std::vector<VkVertexInputAttributeDescription> vertAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, position))},
|
||||
{1, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneWeights))},
|
||||
{2, 0, VK_FORMAT_R8G8B8A8_UINT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneIndices))},
|
||||
{3, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, texCoords))},
|
||||
};
|
||||
|
||||
shadowPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({vertBind}, vertAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setDepthBias(2.0f, 4.0f)
|
||||
.setNoColorAttachment()
|
||||
.setLayout(shadowPipelineLayout_)
|
||||
.setRenderPass(shadowRenderPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
|
||||
if (!shadowPipeline_) {
|
||||
LOG_ERROR("CharacterRenderer: failed to create shadow pipeline");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("CharacterRenderer shadow pipeline initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterRenderer::renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix) {
|
||||
if (!shadowPipeline_ || !shadowParamsSet_) return;
|
||||
if (instances.empty() || models.empty()) return;
|
||||
|
||||
uint32_t frameIndex = vkCtx_->getCurrentFrame();
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipeline_);
|
||||
// Bind shadow params set at set 1
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipelineLayout_,
|
||||
1, 1, &shadowParamsSet_, 0, nullptr);
|
||||
|
||||
struct ShadowPush { glm::mat4 lightSpaceMatrix; glm::mat4 model; };
|
||||
|
||||
for (auto& pair : instances) {
|
||||
auto& inst = pair.second;
|
||||
if (!inst.visible) continue;
|
||||
|
||||
auto modelIt = models.find(inst.modelId);
|
||||
if (modelIt == models.end()) continue;
|
||||
const M2ModelGPU& gpuModel = modelIt->second;
|
||||
if (!gpuModel.vertexBuffer) continue;
|
||||
|
||||
glm::mat4 modelMat = inst.hasOverrideModelMatrix
|
||||
? inst.overrideModelMatrix
|
||||
: getModelMatrix(inst);
|
||||
|
||||
// Ensure bone SSBO is allocated and upload bone matrices
|
||||
int numBones = std::min(static_cast<int>(inst.boneMatrices.size()), MAX_BONES);
|
||||
if (numBones > 0) {
|
||||
if (!inst.boneBuffer[frameIndex]) {
|
||||
VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bci.size = MAX_BONES * sizeof(glm::mat4);
|
||||
bci.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
VmaAllocationCreateInfo aci{};
|
||||
aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
VmaAllocationInfo ai{};
|
||||
vmaCreateBuffer(vkCtx_->getAllocator(), &bci, &aci,
|
||||
&inst.boneBuffer[frameIndex], &inst.boneAlloc[frameIndex], &ai);
|
||||
inst.boneMapped[frameIndex] = ai.pMappedData;
|
||||
|
||||
VkDescriptorSetAllocateInfo dsAI{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
dsAI.descriptorPool = boneDescPool_;
|
||||
dsAI.descriptorSetCount = 1;
|
||||
dsAI.pSetLayouts = &boneSetLayout_;
|
||||
vkAllocateDescriptorSets(device, &dsAI, &inst.boneSet[frameIndex]);
|
||||
|
||||
if (inst.boneSet[frameIndex]) {
|
||||
VkDescriptorBufferInfo bInfo{};
|
||||
bInfo.buffer = inst.boneBuffer[frameIndex];
|
||||
bInfo.offset = 0;
|
||||
bInfo.range = bci.size;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
w.dstSet = inst.boneSet[frameIndex];
|
||||
w.dstBinding = 0;
|
||||
w.descriptorCount = 1;
|
||||
w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
w.pBufferInfo = &bInfo;
|
||||
vkUpdateDescriptorSets(device, 1, &w, 0, nullptr);
|
||||
}
|
||||
}
|
||||
if (inst.boneMapped[frameIndex]) {
|
||||
memcpy(inst.boneMapped[frameIndex], inst.boneMatrices.data(),
|
||||
numBones * sizeof(glm::mat4));
|
||||
}
|
||||
}
|
||||
|
||||
if (!inst.boneSet[frameIndex]) continue;
|
||||
|
||||
// Bind bone SSBO at set 2
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipelineLayout_,
|
||||
2, 1, &inst.boneSet[frameIndex], 0, nullptr);
|
||||
|
||||
ShadowPush push{lightSpaceMatrix, modelMat};
|
||||
vkCmdPushConstants(cmd, shadowPipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, 128, &push);
|
||||
|
||||
VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &gpuModel.vertexBuffer, &offset);
|
||||
vkCmdBindIndexBuffer(cmd, gpuModel.indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
|
||||
bool applyGeosetFilter = !inst.activeGeosets.empty();
|
||||
for (const auto& batch : gpuModel.data.batches) {
|
||||
uint16_t blendMode = 0;
|
||||
if (batch.materialIndex < gpuModel.data.materials.size()) {
|
||||
blendMode = gpuModel.data.materials[batch.materialIndex].blendMode;
|
||||
}
|
||||
if (blendMode >= 2) continue; // skip transparent
|
||||
if (applyGeosetFilter &&
|
||||
inst.activeGeosets.find(batch.submeshId) == inst.activeGeosets.end()) continue;
|
||||
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 CharacterRenderer::getModelMatrix(const CharacterInstance& instance) const {
|
||||
|
|
|
|||
|
|
@ -3321,7 +3321,7 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
|
|||
wmoRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
||||
}
|
||||
|
||||
// Initialize shadow pipelines (Phase 7)
|
||||
// Initialize shadow pipelines (Phase 7/8)
|
||||
if (wmoRenderer && shadowRenderPass != VK_NULL_HANDLE) {
|
||||
wmoRenderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
|
|
@ -3332,6 +3332,9 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
|
|||
characterRenderer = std::make_unique<CharacterRenderer>();
|
||||
characterRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
||||
}
|
||||
if (characterRenderer && shadowRenderPass != VK_NULL_HANDLE) {
|
||||
characterRenderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
|
||||
// Create and initialize terrain manager
|
||||
if (!terrainManager) {
|
||||
|
|
@ -3774,13 +3777,16 @@ void Renderer::renderShadowPass() {
|
|||
VkRect2D sc{{0, 0}, {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE}};
|
||||
vkCmdSetScissor(currentCmd, 0, 1, &sc);
|
||||
|
||||
// Phase 7: render shadow casters
|
||||
// Phase 7/8: render shadow casters
|
||||
if (wmoRenderer) {
|
||||
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
||||
}
|
||||
if (m2Renderer) {
|
||||
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix);
|
||||
}
|
||||
if (characterRenderer) {
|
||||
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
||||
}
|
||||
|
||||
vkCmdEndRenderPass(currentCmd);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue