文章目录
前言
渲染从 glTF 2.0 文件加载的完整场景(sponza宫殿)。该示例基于 glTF 模型加载示例,并添加了使用 Crytek 的 Sponza 模型以及每个材质管道和法线贴图渲染更复杂场景所需的数据结构、函数和着色器。
一、gltfscenerendering.h文件
包含在 Vulkan 中渲染基本 glTF 场景所需的一切,此类经过了大量简化(与 glTF 的功能集相比),但保留了基本的 glTF 结构。
1.VulkanglTFScene类:
class VulkanglTFScene
{
public:
// 该类需要一些 Vulkan 对象,以便它可以创建自己的资源
vks::VulkanDevice* vulkanDevice;
VkQueue copyQueue;
// 样本模型的顶点布局
struct Vertex {
glm::vec3 pos;
glm::vec3 normal;
glm::vec2 uv;
glm::vec3 color;
glm::vec4 tangent;
};
// 所有基元的单个顶点缓冲区
struct {
VkBuffer buffer;
VkDeviceMemory memory;
} vertices;
// 所有基元的单个索引缓冲区
struct {
int count;
VkBuffer buffer;
VkDeviceMemory memory;
} indices;
//以下结构大致表示glTF场景结构
//为简单起见,它们仅包含此示例所需的那些属性
struct Node;
//基元包含单个绘制调用的数据
struct Primitive {
uint32_t firstIndex;
uint32_t indexCount;
int32_t materialIndex;
};
// 包含节点的(可选)几何图形,并且可以由任意数量的基元组成
struct Mesh {
std::vector<Primitive> primitives;
};
// 节点表示 glTF 场景图中的对象
struct Node {
Node* parent;
std::vector<Node> children;
Mesh mesh;
glm::mat4 matrix;
std::string name;
bool visible = true;
};
//glTF材料将信息存储在例如附着在其上的纹理和颜色中
struct Material {
glm::vec4 baseColorFactor = glm::vec4(1.0f);
uint32_t baseColorTextureIndex;
uint32_t normalTextureIndex;
std::string alphaMode = "OPAQUE";
float alphaCutOff;
bool doubleSided = false;
VkDescriptorSet descriptorSet;
VkPipeline pipeline;
};
//包含单个 glTF 图像的纹理
//图像可以被纹理对象重用,并且被这样分开
struct Image {
vks::Texture2D texture;
};
//glTF 纹理存储对图像的引用和采样器
//在此示例中,我们只对图像感兴趣
struct Texture {
int32_t imageIndex;
};
/*
Model data
*/
std::vector<Image> images;
std::vector<Texture> textures;
std::vector<Material> materials;
std::vector<Node> nodes;
std::string path;
~VulkanglTFScene();
VkDescriptorImageInfo getTextureDescriptor(const size_t index);
void loadImages(tinygltf::Model& input);
void loadTextures(tinygltf::Model& input);
void loadMaterials(tinygltf::Model& input);
void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFScene::Vertex>& vertexBuffer);
void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node);
void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout);
};
2.VulkanExample 类:
class VulkanExample : public VulkanExampleBase
{
public:
VulkanglTFScene glTFScene;
struct ShaderData {
vks::Buffer buffer;
struct Values {
glm::mat4 projection;
glm::mat4 view;
glm::vec4 lightPos = glm::vec4(0.0f, 2.5f, 0.0f, 1.0f);
glm::vec4 viewPos;
} values;
} shaderData;
VkPipelineLayout pipelineLayout;
VkDescriptorSet descriptorSet;
struct DescriptorSetLayouts {
VkDescriptorSetLayout matrices;
VkDescriptorSetLayout textures;
} descriptorSetLayouts;
VulkanExample();
~VulkanExample();
virtual void getEnabledFeatures();
void buildCommandBuffers();
void loadglTFFile(std::string filename);
void loadAssets();
void setupDescriptors();
void preparePipelines();
void prepareUniformBuffers();
void updateUniformBuffers();
void prepare();
virtual void render();
virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay);
};
二、CPP文件
1.VulkanglTFScene函数
代码如下:
/*
glTF 加载函数
以下函数采用通过 tinyglTF 加载的 glTF 输入模型,并将所有必需的数据转换为我们自己的结构
*/
void VulkanglTFScene::loadImages(tinygltf::Model& input);
void VulkanglTFScene::loadTextures(tinygltf::Model& input);
void VulkanglTFScene::loadMaterials(tinygltf::Model& input);
void VulkanglTFScene::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFScene::Vertex>& vertexBuffer);
VkDescriptorImageInfo VulkanglTFScene::getTextureDescriptor(const size_t index);
2.glTF rendering函数
绘制包含子节点的单个节点(如果存在):
void VulkanglTFScene::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node)
{
if (!node.visible) {
return;
}
if (node.mesh.primitives.size() > 0) {
// Pass the node's matrix via push constants
// Traverse the node hierarchy to the top-most parent to get the final matrix of the current node
glm::mat4 nodeMatrix = node.matrix;
VulkanglTFScene::Node* currentParent = node.parent;
while (currentParent) {
nodeMatrix = currentParent->matrix * nodeMatrix;
currentParent = currentParent->parent;
}
// Pass the final matrix to the vertex shader using push constants
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
for (VulkanglTFScene::Primitive& primitive : node.mesh.primitives) {
if (primitive.indexCount > 0) {
VulkanglTFScene::Material& material = materials[primitive.materialIndex];
// POI: Bind the pipeline for the node's material
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.pipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &material.descriptorSet, 0, nullptr);
vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
}
}
}
for (auto& child : node.children) {
drawNode(commandBuffer, pipelineLayout, child);
}
}
从顶级节点开始绘制 glTF 场景
void VulkanglTFScene::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout)
{
// All vertices and indices are stored in single buffers, so we only need to bind once
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
// Render all nodes at top-level
for (auto& node : nodes) {
drawNode(commandBuffer, pipelineLayout, node);
}
}
3.Vulkan Example类函数
构造函数
VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION);
析构函数
VulkanExample::~VulkanExample();
重建命令缓冲
void VulkanExample::buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 1.0f } };;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.offset.x = 0;
renderPassBeginInfo.renderArea.offset.y = 0;
renderPassBeginInfo.renderArea.extent.width = width;
renderPassBeginInfo.renderArea.extent.height = height;
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues;
const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
{
renderPassBeginInfo.framebuffer = frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
// Bind scene matrices descriptor to set 0
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
// POI: Draw the glTF scene
glTFScene.draw(drawCmdBuffers[i], pipelineLayout);
drawUI(drawCmdBuffers[i]);
vkCmdEndRenderPass(drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
加载gltf文件
void VulkanExample::loadglTFFile(std::string filename)
更新uniformbuffer
void VulkanExample::updateUniformBuffers()
{
shaderData.values.projection = camera.matrices.perspective;
shaderData.values.view = camera.matrices.view;
shaderData.values.viewPos = camera.viewPos;
memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
}
三、shader
1.创建shader
在VulkanExample::preparePipelines() 准备渲染管线阶段创建shaer 并且传递定线信息。
...
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI);
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFScene::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
};
const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, pos)),
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, normal)),
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, uv)),
vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, color)),
vks::initializers::vertexInputAttributeDescription(0, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, tangent)),
};
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(vertexInputBindings, vertexInputAttributes);
VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
pipelineCI.pVertexInputState = &vertexInputStateCI;
pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
pipelineCI.pRasterizationState = &rasterizationStateCI;
pipelineCI.pColorBlendState = &colorBlendStateCI;
pipelineCI.pMultisampleState = &multisampleStateCI;
pipelineCI.pViewportState = &viewportStateCI;
pipelineCI.pDepthStencilState = &depthStencilStateCI;
pipelineCI.pDynamicState = &dynamicStateCI;
pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCI.pStages = shaderStages.data();
shaderStages[0] = loadShader(getShadersPath() + "gltfscenerendering/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "gltfscenerendering/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
...
2.scene.vert
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inColor;
layout (location = 4) in vec4 inTangent;
layout (set = 0, binding = 0) uniform UBOScene
{
mat4 projection;
mat4 view;
vec4 lightPos;
vec4 viewPos;
} uboScene;
layout(push_constant) uniform PushConsts {
mat4 model;
} primitive;
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec2 outUV;
layout (location = 3) out vec3 outViewVec;
layout (location = 4) out vec3 outLightVec;
layout (location = 5) out vec4 outTangent;
void main()
{
outNormal = inNormal;
outColor = inColor;
outUV = inUV;
outTangent = inTangent;
gl_Position = uboScene.projection * uboScene.view * primitive.model * vec4(inPos.xyz, 1.0);
outNormal = mat3(primitive.model) * inNormal;
vec4 pos = primitive.model * vec4(inPos, 1.0);
outLightVec = uboScene.lightPos.xyz - pos.xyz;
outViewVec = uboScene.viewPos.xyz - pos.xyz;
}
3.scene.frag
#version 450
layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
layout (set = 1, binding = 1) uniform sampler2D samplerNormalMap;
layout (location = 0) in vec3 inNormal;
layout (location = 1) in vec3 inColor;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inViewVec;
layout (location = 4) in vec3 inLightVec;
layout (location = 5) in vec4 inTangent;
layout (location = 0) out vec4 outFragColor;
layout (constant_id = 0) const bool ALPHA_MASK = false;
layout (constant_id = 1) const float ALPHA_MASK_CUTOFF = 0.0f;
void main()
{
vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
if (ALPHA_MASK) {
if (color.a < ALPHA_MASK_CUTOFF) {
discard;
}
}
vec3 N = normalize(inNormal);
vec3 T = normalize(inTangent.xyz);
vec3 B = cross(inNormal, inTangent.xyz) * inTangent.w;
mat3 TBN = mat3(T, B, N);
N = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
const float ambient = 0.1;
vec3 L = normalize(inLightVec);
vec3 V = normalize(inViewVec);
vec3 R = reflect(-L, N);
vec3 diffuse = max(dot(N, L), ambient).rrr;
float specular = pow(max(dot(R, V), 0.0), 32.0);
outFragColor = vec4(diffuse * color.rgb + specular, color.a);
}