diff --git a/README.md b/README.md index 27045fe1..0805482e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A comprehensive collection of open source C++ examples for [VulkanĀ®](https://ww + [Shaders](#Shaders) + [Examples](#Examples) + [Basics](#Basics) + + [glTF](#glTF) + [Advanced](#Advanced) + [Performance](#Performance) + [Physically Based Rendering](#PBR) @@ -108,70 +109,69 @@ Loads a 2D texture array containing multiple 2D texture slices (each with its ow Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions. -#### [11 - glTF scene loading and rendering](examples/gltfscene/) - -Shows how to load the scene from a [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The structure of the glTF 2.0 scene is converted into data structures required to render the scene with Vulkan. - -#### [12 - Input attachments](examples/inputattachments) +#### [11 - Input attachments](examples/inputattachments) Uses input attachments to read framebuffer contents from a previous sub pass at the same pixel position within a single render pass. This can be used for basic post processing or image composition ([blog entry](https://www.saschawillems.de/tutorials/vulkan/input_attachments_subpasses)). -#### [13 - Sub passes](examples/subpasses/) +#### [12 - Sub passes](examples/subpasses/) Advanced example that uses sub passes and input attachments to write and read back data from framebuffer attachments (same location only) in single render pass. This is used to implement deferred render composition with added forward transparency in a single pass. -#### [14 - Offscreen rendering](examples/offscreen/) +#### [13 - Offscreen rendering](examples/offscreen/) Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface. -#### [15 - CPU particle system](examples/particlefire/) +#### [14 - CPU particle system](examples/particlefire/) Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha. -#### [16 - Stencil buffer](examples/stencilbuffer/) +#### [15 - Stencil buffer](examples/stencilbuffer/) Uses the stencil buffer and its compare functionality for rendering a 3D model with dynamic outlines. +### glTF + +These samples show how implement different features of the [glTF 2.0 3D format](https://www.khronos.org/gltf/) 3D transmission file format in detail. + +#### [01 - glTF model loading and rendering](examples/gltfloading/) + +Shows how to load a complete scene from a [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The structure of the glTF 2.0 scene is converted into the data structures required to render the scene with Vulkan. + +#### [02 - glTF vertex skinning](examples/gltfskinning/) + +Demonstrates how to do GPU vertex skinning from animation data stored in a [glTF 2.0](https://github.com/KhronosGroup/glTF) model. Along with reading all the data structures required for doing vertex skinning, the sample also shows how to upload animation data to the GPU and how to render it using shaders. + +#### [03 - glTF scene rendering](examples/gltfscenerendering/) + +Renders a complete scene loaded from an [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The sample is based on the glTF model loading sample, and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model with per-material pipelines and normal mapping. + ### Advanced -#### [01 - Scene rendering](examples/scenerendering/) - -Combines multiple techniques to render a complex scene consisting of multiple meshes, textures and materials. Meshes are stored and rendered from a single buffer using vertex offsets. Material parameters are passed via push constants, and separate per-model and scene descriptor sets are used to pass data to the shaders. - -#### [02 - Multi sampling](examples/multisampling/) +#### [01 - Multi sampling](examples/multisampling/) Implements multisample anti-aliasing (MSAA) using a renderpass with multisampled attachments and resolve attachments that get resolved into the visible frame buffer. -#### [03 - High dynamic range](examples/hdr/) +#### [02 - High dynamic range](examples/hdr/) Implements a high dynamic range rendering pipeline using 16/32 bit floating point precision for all internal formats, textures and calculations, including a bloom pass, manual exposure and tone mapping. -#### [04 - Shadow mapping](examples/shadowmapping/) +#### [03 - Shadow mapping](examples/shadowmapping/) Rendering shadows for a directional light source. First pass stores depth values from the light's pov, second pass compares against these to check if a fragment is shadowed. Uses depth bias to avoid shadow artifacts and applies a PCF filter to smooth shadow edges. -#### [05 - Cascaded shadow mapping](examples/shadowmappingcascade/) +#### [04 - Cascaded shadow mapping](examples/shadowmappingcascade/) Uses multiple shadow maps (stored as a layered texture) to increase shadow resolution for larger scenes. The camera frustum is split up into multiple cascades with corresponding layers in the shadow map. Layer selection for shadowing depth compare is then done by comparing fragment depth with the cascades' depths ranges. -#### [06 - Omnidirectional shadow mapping](examples/shadowmappingomni/) +#### [05 - Omnidirectional shadow mapping](examples/shadowmappingomni/) Uses a dynamic floating point cube map to implement shadowing for a point light source that casts shadows in all directions. The cube map is updated every frame and stores distance to the light source for each fragment used to determine if a fragment is shadowed. -#### [07 - Run-time mip-map generation](examples/texturemipmapgen/) +#### [06 - Run-time mip-map generation](examples/texturemipmapgen/) Generating a complete mip-chain at runtime instead of loading it from a file, by blitting from one mip level, starting with the actual texture image, down to the next smaller size until the lower 1x1 pixel end of the mip chain. -#### [11 - glTF scene loading and rendering](examples/gltfscene/) - -Shows how to load the scene from a [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The structure of the glTF 2.0 scene is converted into data structures required to render the scene with Vulkan. - - -#### [08 - glTF vertex skinning](examples/gltfskinning/) - -Rendering an animated [glTF 2.0](https://github.com/KhronosGroup/glTF) scene using GPU vertex skinning. - -#### [09 - Capturing screenshots](examples/screenshot/) +#### [07 - Capturing screenshots](examples/screenshot/) Capturing and saving an image after a scene has been rendered using blits to copy the last swapchain image from optimal device to host local linear memory, so that it can be stored into a ppm image. diff --git a/android/examples/gltfscene/CMakeLists.txt b/android/examples/gltfloading/CMakeLists.txt similarity index 97% rename from android/examples/gltfscene/CMakeLists.txt rename to android/examples/gltfloading/CMakeLists.txt index 6a77bb64..142b4404 100644 --- a/android/examples/gltfscene/CMakeLists.txt +++ b/android/examples/gltfloading/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) -set(NAME gltfscene) +set(NAME gltfloading) set(SRC_DIR ../../../examples/${NAME}) set(BASE_DIR ../../../base) diff --git a/android/examples/gltfscene/build.gradle b/android/examples/gltfloading/build.gradle similarity index 93% rename from android/examples/gltfscene/build.gradle rename to android/examples/gltfloading/build.gradle index 6af39be5..971b6640 100644 --- a/android/examples/gltfscene/build.gradle +++ b/android/examples/gltfloading/build.gradle @@ -49,8 +49,8 @@ task copyTask { } copy { - from '../../../data/shaders/glsl/gltfscene' - into 'assets/shaders/glsl/gltfscene' + from '../../../data/shaders/glsl/gltfloading' + into 'assets/shaders/glsl/gltfloading' include '*.*' } diff --git a/android/examples/gltfscene/src/main/AndroidManifest.xml b/android/examples/gltfloading/src/main/AndroidManifest.xml similarity index 100% rename from android/examples/gltfscene/src/main/AndroidManifest.xml rename to android/examples/gltfloading/src/main/AndroidManifest.xml diff --git a/android/examples/gltfscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java similarity index 100% rename from android/examples/gltfscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java rename to android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java diff --git a/android/examples/scenerendering/CMakeLists.txt b/android/examples/gltfscenerendering/CMakeLists.txt similarity index 92% rename from android/examples/scenerendering/CMakeLists.txt rename to android/examples/gltfscenerendering/CMakeLists.txt index 343a518c..f7e6c398 100644 --- a/android/examples/scenerendering/CMakeLists.txt +++ b/android/examples/gltfscenerendering/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) -set(NAME scenerendering) +set(NAME gltfscenerendering) set(SRC_DIR ../../../examples/${NAME}) set(BASE_DIR ../../../base) @@ -23,7 +23,7 @@ include_directories(${EXTERNAL_DIR}) include_directories(${EXTERNAL_DIR}/glm) include_directories(${EXTERNAL_DIR}/gli) include_directories(${EXTERNAL_DIR}/imgui) -include_directories(${EXTERNAL_DIR}/assimp) +include_directories(${EXTERNAL_DIR}/tinygltf) include_directories(${ANDROID_NDK}/sources/android/native_app_glue) target_link_libraries( diff --git a/android/examples/scenerendering/build.gradle b/android/examples/gltfscenerendering/build.gradle similarity index 87% rename from android/examples/scenerendering/build.gradle rename to android/examples/gltfscenerendering/build.gradle index 4de41b21..69ddcf96 100644 --- a/android/examples/scenerendering/build.gradle +++ b/android/examples/gltfscenerendering/build.gradle @@ -49,14 +49,14 @@ task copyTask { } copy { - from '../../../data/shaders/glsl/scenerendering' - into 'assets/shaders/glsl/scenerendering' + from '../../../data/shaders/glsl/gltfscenerendering' + into 'assets/shaders/glsl/gltfscenerendering' include '*.*' } copy { - from '../../../data/models/sibenik' - into 'assets/models/sibenik' + from '../../../data/models/sponza' + into 'assets/models/sponza' include '*.*' } diff --git a/android/examples/scenerendering/src/main/AndroidManifest.xml b/android/examples/gltfscenerendering/src/main/AndroidManifest.xml similarity index 100% rename from android/examples/scenerendering/src/main/AndroidManifest.xml rename to android/examples/gltfscenerendering/src/main/AndroidManifest.xml diff --git a/android/examples/scenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java similarity index 100% rename from android/examples/scenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java rename to android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java diff --git a/base/VulkanInitializers.hpp b/base/VulkanInitializers.hpp index fd627de3..6ae53302 100644 --- a/base/VulkanInitializers.hpp +++ b/base/VulkanInitializers.hpp @@ -390,6 +390,20 @@ namespace vks return pipelineVertexInputStateCreateInfo; } + inline VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo( + const std::vector &vertexBindingDescriptions, + const std::vector &vertexAttributeDescriptions + ) + { + VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{}; + pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = static_cast(vertexBindingDescriptions.size()); + pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = vertexBindingDescriptions.data(); + pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast(vertexAttributeDescriptions.size()); + pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data(); + return pipelineVertexInputStateCreateInfo; + } + inline VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo( VkPrimitiveTopology topology, VkPipelineInputAssemblyStateCreateFlags flags, @@ -586,5 +600,17 @@ namespace vks specializationInfo.pData = data; return specializationInfo; } + + /** @brief Initialize a specialization constant info structure to pass to a shader stage */ + inline VkSpecializationInfo specializationInfo(const std::vector &mapEntries, size_t dataSize, const void* data) + { + VkSpecializationInfo specializationInfo{}; + specializationInfo.mapEntryCount = static_cast(mapEntries.size()); + specializationInfo.pMapEntries = mapEntries.data(); + specializationInfo.dataSize = dataSize; + specializationInfo.pData = data; + return specializationInfo; + } + } } \ No newline at end of file diff --git a/data/shaders/glsl/gltfscene/mesh.frag b/data/shaders/glsl/gltfloading/mesh.frag similarity index 100% rename from data/shaders/glsl/gltfscene/mesh.frag rename to data/shaders/glsl/gltfloading/mesh.frag diff --git a/data/shaders/glsl/gltfscene/mesh.frag.spv b/data/shaders/glsl/gltfloading/mesh.frag.spv similarity index 100% rename from data/shaders/glsl/gltfscene/mesh.frag.spv rename to data/shaders/glsl/gltfloading/mesh.frag.spv diff --git a/data/shaders/glsl/gltfscene/mesh.vert b/data/shaders/glsl/gltfloading/mesh.vert similarity index 100% rename from data/shaders/glsl/gltfscene/mesh.vert rename to data/shaders/glsl/gltfloading/mesh.vert diff --git a/data/shaders/glsl/gltfscene/mesh.vert.spv b/data/shaders/glsl/gltfloading/mesh.vert.spv similarity index 100% rename from data/shaders/glsl/gltfscene/mesh.vert.spv rename to data/shaders/glsl/gltfloading/mesh.vert.spv diff --git a/data/shaders/glsl/gltfscenerendering/scene.frag b/data/shaders/glsl/gltfscenerendering/scene.frag new file mode 100644 index 00000000..54e0ef8d --- /dev/null +++ b/data/shaders/glsl/gltfscenerendering/scene.frag @@ -0,0 +1,41 @@ +#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); +} \ No newline at end of file diff --git a/data/shaders/glsl/gltfscenerendering/scene.frag.spv b/data/shaders/glsl/gltfscenerendering/scene.frag.spv new file mode 100644 index 00000000..4f043667 Binary files /dev/null and b/data/shaders/glsl/gltfscenerendering/scene.frag.spv differ diff --git a/data/shaders/glsl/gltfscenerendering/scene.vert b/data/shaders/glsl/gltfscenerendering/scene.vert new file mode 100644 index 00000000..2c4655e1 --- /dev/null +++ b/data/shaders/glsl/gltfscenerendering/scene.vert @@ -0,0 +1,40 @@ +#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; +} \ No newline at end of file diff --git a/data/shaders/glsl/gltfscenerendering/scene.vert.spv b/data/shaders/glsl/gltfscenerendering/scene.vert.spv new file mode 100644 index 00000000..c3ff307c Binary files /dev/null and b/data/shaders/glsl/gltfscenerendering/scene.vert.spv differ diff --git a/data/shaders/glsl/scenerendering/generate-spirv.bat b/data/shaders/glsl/scenerendering/generate-spirv.bat deleted file mode 100644 index d1d31371..00000000 --- a/data/shaders/glsl/scenerendering/generate-spirv.bat +++ /dev/null @@ -1,2 +0,0 @@ -glslangvalidator -V scene.vert -o scene.vert.spv -glslangvalidator -V scene.frag -o scene.frag.spv diff --git a/data/shaders/glsl/scenerendering/scene.frag b/data/shaders/glsl/scenerendering/scene.frag deleted file mode 100644 index f4c01181..00000000 --- a/data/shaders/glsl/scenerendering/scene.frag +++ /dev/null @@ -1,31 +0,0 @@ -#version 450 - -layout (set = 1, binding = 0) uniform sampler2D samplerColorMap; - -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(push_constant) uniform Material -{ - vec4 ambient; - vec4 diffuse; - vec4 specular; - float opacity; -} material; - -layout (location = 0) out vec4 outFragColor; - -void main() -{ - vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0); - vec3 N = normalize(inNormal); - vec3 L = normalize(inLightVec); - vec3 V = normalize(inViewVec); - vec3 R = reflect(-L, N); - vec3 diffuse = max(dot(N, L), 0.0) * material.diffuse.rgb; - vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * material.specular.rgb; - outFragColor = vec4((material.ambient.rgb + diffuse) * color.rgb + specular, 1.0-material.opacity); -} \ No newline at end of file diff --git a/data/shaders/glsl/scenerendering/scene.frag.spv b/data/shaders/glsl/scenerendering/scene.frag.spv deleted file mode 100644 index 68d36d01..00000000 Binary files a/data/shaders/glsl/scenerendering/scene.frag.spv and /dev/null differ diff --git a/data/shaders/glsl/scenerendering/scene.vert b/data/shaders/glsl/scenerendering/scene.vert deleted file mode 100644 index 913c11de..00000000 --- a/data/shaders/glsl/scenerendering/scene.vert +++ /dev/null @@ -1,42 +0,0 @@ -#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 (set = 0, binding = 0) uniform UBO -{ - mat4 projection; - mat4 view; - mat4 model; - vec4 lightPos; -} ubo; - -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; - -out gl_PerVertex -{ - vec4 gl_Position; -}; - -void main() -{ - outNormal = inNormal; - outColor = inColor; - outUV = inUV; - - mat4 modelView = ubo.view * ubo.model; - - gl_Position = ubo.projection * modelView * vec4(inPos.xyz, 1.0); - - vec4 pos = modelView * vec4(inPos, 0.0); - outNormal = mat3(ubo.model) * inNormal; - vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz; - outLightVec = lPos - (ubo.model * vec4(inPos, 1.0)).xyz; - outViewVec = -(ubo.model * vec4(inPos, 1.0)).xyz; -} \ No newline at end of file diff --git a/data/shaders/glsl/scenerendering/scene.vert.spv b/data/shaders/glsl/scenerendering/scene.vert.spv deleted file mode 100644 index 64d21748..00000000 Binary files a/data/shaders/glsl/scenerendering/scene.vert.spv and /dev/null differ diff --git a/data/shaders/hlsl/gltfscene/mesh.frag b/data/shaders/hlsl/gltfloading/mesh.frag similarity index 90% rename from data/shaders/hlsl/gltfscene/mesh.frag rename to data/shaders/hlsl/gltfloading/mesh.frag index 15d11a51..914421a0 100644 --- a/data/shaders/hlsl/gltfscene/mesh.frag +++ b/data/shaders/hlsl/gltfloading/mesh.frag @@ -3,6 +3,11 @@ Texture2D textureColorMap : register(t0, space1); SamplerState samplerColorMap : register(s0, space1); +struct PushConsts { + float4x4 model; +}; +[[vk::push_constant]] PushConsts primitive; + struct VSOutput { [[vk::location(0)]] float3 Normal : NORMAL0; diff --git a/data/shaders/hlsl/gltfloading/mesh.frag.spv b/data/shaders/hlsl/gltfloading/mesh.frag.spv new file mode 100644 index 00000000..a801c5f2 Binary files /dev/null and b/data/shaders/hlsl/gltfloading/mesh.frag.spv differ diff --git a/data/shaders/hlsl/gltfscene/mesh.vert b/data/shaders/hlsl/gltfloading/mesh.vert similarity index 100% rename from data/shaders/hlsl/gltfscene/mesh.vert rename to data/shaders/hlsl/gltfloading/mesh.vert diff --git a/data/shaders/hlsl/gltfloading/mesh.vert.spv b/data/shaders/hlsl/gltfloading/mesh.vert.spv new file mode 100644 index 00000000..3061fec4 Binary files /dev/null and b/data/shaders/hlsl/gltfloading/mesh.vert.spv differ diff --git a/data/shaders/hlsl/gltfscene/mesh.frag.spv b/data/shaders/hlsl/gltfscene/mesh.frag.spv deleted file mode 100644 index 9801be96..00000000 Binary files a/data/shaders/hlsl/gltfscene/mesh.frag.spv and /dev/null differ diff --git a/data/shaders/hlsl/gltfscene/mesh.vert.spv b/data/shaders/hlsl/gltfscene/mesh.vert.spv deleted file mode 100644 index 963f9b04..00000000 Binary files a/data/shaders/hlsl/gltfscene/mesh.vert.spv and /dev/null differ diff --git a/data/shaders/hlsl/gltfscenerendering/scene.frag b/data/shaders/hlsl/gltfscenerendering/scene.frag new file mode 100644 index 00000000..62aad5d9 --- /dev/null +++ b/data/shaders/hlsl/gltfscenerendering/scene.frag @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC + +Texture2D textureColorMap : register(t0, space1); +SamplerState samplerColorMap : register(s0, space1); +Texture2D textureNormalMap : register(t1, space1); +SamplerState samplerNormalMap : register(s1, space1); + +[[vk::constant_id(0)]] const bool ALPHA_MASK = false; +[[vk::constant_id(1)]] const float ALPHA_MASK_CUTOFF = 0.0; + +struct VSOutput +{ +[[vk::location(0)]] float3 Normal : NORMAL0; +[[vk::location(1)]] float3 Color : COLOR0; +[[vk::location(2)]] float2 UV : TEXCOORD0; +[[vk::location(3)]] float3 ViewVec : TEXCOORD1; +[[vk::location(4)]] float3 LightVec : TEXCOORD2; +[[vk::location(5)]] float4 Tangent : TEXCOORD3; +}; + +float4 main(VSOutput input) : SV_TARGET +{ + float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0); + + if (ALPHA_MASK) { + if (color.a < ALPHA_MASK_CUTOFF) { + discard; + } + } + + float3 N = normalize(input.Normal); + float3 T = normalize(input.Tangent.xyz); + float3 B = cross(input.Normal, input.Tangent.xyz) * input.Tangent.w; + float3x3 TBN = float3x3(T, B, N); + N = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN); + + const float ambient = 0.1; + float3 L = normalize(input.LightVec); + float3 V = normalize(input.ViewVec); + float3 R = reflect(-L, N); + float3 diffuse = max(dot(N, L), ambient).rrr; + float3 specular = pow(max(dot(R, V), 0.0), 32.0); + return float4(diffuse * color.rgb + specular, color.a); +} \ No newline at end of file diff --git a/data/shaders/hlsl/gltfscenerendering/scene.frag.spv b/data/shaders/hlsl/gltfscenerendering/scene.frag.spv new file mode 100644 index 00000000..4774d074 Binary files /dev/null and b/data/shaders/hlsl/gltfscenerendering/scene.frag.spv differ diff --git a/data/shaders/hlsl/scenerendering/scene.vert b/data/shaders/hlsl/gltfscenerendering/scene.vert similarity index 64% rename from data/shaders/hlsl/scenerendering/scene.vert rename to data/shaders/hlsl/gltfscenerendering/scene.vert index 7fa6d216..41edd8be 100644 --- a/data/shaders/hlsl/scenerendering/scene.vert +++ b/data/shaders/hlsl/gltfscenerendering/scene.vert @@ -6,17 +6,23 @@ struct VSInput [[vk::location(1)]] float3 Normal : NORMAL0; [[vk::location(2)]] float2 UV : TEXCOORD0; [[vk::location(3)]] float3 Color : COLOR0; +[[vk::location(4)]] float4 Tangent : TEXCOORD1; }; struct UBO { float4x4 projection; float4x4 view; - float4x4 model; float4 lightPos; + float4 viewPos; }; cbuffer ubo : register(b0) { UBO ubo; }; +struct PushConsts { + float4x4 model; +}; +[[vk::push_constant]] PushConsts primitive; + struct VSOutput { float4 Pos : SV_POSITION; @@ -25,6 +31,7 @@ struct VSOutput [[vk::location(2)]] float2 UV : TEXCOORD0; [[vk::location(3)]] float3 ViewVec : TEXCOORD1; [[vk::location(4)]] float3 LightVec : TEXCOORD2; +[[vk::location(5)]] float4 Tangent : TEXCOORD3; }; VSOutput main(VSInput input) @@ -33,15 +40,15 @@ VSOutput main(VSInput input) output.Normal = input.Normal; output.Color = input.Color; output.UV = input.UV; + output.Tangent = input.Tangent; - float4x4 modelView = mul(ubo.view, ubo.model); + float4x4 modelView = mul(ubo.view, primitive.model); output.Pos = mul(ubo.projection, mul(modelView, float4(input.Pos.xyz, 1.0))); - float4 pos = mul(modelView, float4(input.Pos, 0.0)); - output.Normal = mul((float3x3)ubo.model, input.Normal); - float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz); - output.LightVec = lPos - mul(ubo.model, float4(input.Pos, 1.0)).xyz; - output.ViewVec = -mul(ubo.model, float4(input.Pos, 1.0)).xyz; + output.Normal = mul((float3x3)primitive.model, input.Normal); + float4 pos = mul(primitive.model, float4(input.Pos, 1.0)); + output.LightVec = ubo.lightPos.xyz - pos.xyz; + output.ViewVec = ubo.viewPos.xyz - pos.xyz; return output; } \ No newline at end of file diff --git a/data/shaders/hlsl/gltfscenerendering/scene.vert.spv b/data/shaders/hlsl/gltfscenerendering/scene.vert.spv new file mode 100644 index 00000000..35000d17 Binary files /dev/null and b/data/shaders/hlsl/gltfscenerendering/scene.vert.spv differ diff --git a/data/shaders/hlsl/scenerendering/scene.frag b/data/shaders/hlsl/scenerendering/scene.frag deleted file mode 100644 index 5cf6f627..00000000 --- a/data/shaders/hlsl/scenerendering/scene.frag +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 Google LLC - -Texture2D textureColorMap : register(t0, space1); -SamplerState samplerColorMap : register(s0, space1); - -struct VSOutput -{ -[[vk::location(0)]] float3 Normal : NORMAL0; -[[vk::location(1)]] float3 Color : COLOR0; -[[vk::location(2)]] float2 UV : TEXCOORD0; -[[vk::location(3)]] float3 ViewVec : TEXCOORD1; -[[vk::location(4)]] float3 LightVec : TEXCOORD2; -}; - -struct Material -{ - float4 ambient; - float4 diffuse; - float4 specular; - float opacity; -}; -[[vk::push_constant]] Material material; - -float4 main(VSOutput input) : SV_TARGET -{ - float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0); - float3 N = normalize(input.Normal); - float3 L = normalize(input.LightVec); - float3 V = normalize(input.ViewVec); - float3 R = reflect(-L, N); - float3 diffuse = max(dot(N, L), 0.0) * material.diffuse.rgb; - float3 specular = pow(max(dot(R, V), 0.0), 16.0) * material.specular.rgb; - return float4((material.ambient.rgb + diffuse) * color.rgb + specular, 1.0-material.opacity); -} \ No newline at end of file diff --git a/data/shaders/hlsl/scenerendering/scene.frag.spv b/data/shaders/hlsl/scenerendering/scene.frag.spv deleted file mode 100644 index c40dbaa5..00000000 Binary files a/data/shaders/hlsl/scenerendering/scene.frag.spv and /dev/null differ diff --git a/data/shaders/hlsl/scenerendering/scene.vert.spv b/data/shaders/hlsl/scenerendering/scene.vert.spv deleted file mode 100644 index a4e02ff4..00000000 Binary files a/data/shaders/hlsl/scenerendering/scene.vert.spv and /dev/null differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1ca68dad..8d40c6b2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -27,11 +27,13 @@ function(buildExample EXAMPLE_NAME) file(GLOB SHADERS_HLSL "${SHADER_DIR_HLSL}/*.vert" "${SHADER_DIR_HLSL}/*.frag" "${SHADER_DIR_HLSL}/*.comp" "${SHADER_DIR_HLSL}/*.geom" "${SHADER_DIR_HLSL}/*.tesc" "${SHADER_DIR_HLSL}/*.tese" "${SHADER_DIR_HLSL}/*.mesh" "${SHADER_DIR_HLSL}/*.task" "${SHADER_DIR_HLSL}/*.rgen" "${SHADER_DIR_HLSL}/*.rchit" "${SHADER_DIR_HLSL}/*.rmiss") source_group("Shaders\\GLSL" FILES ${SHADERS_GLSL}) source_group("Shaders\\HLSL" FILES ${SHADERS_HLSL}) + # Add optional readme / tutorial + file(GLOB README_FILES "${EXAMPLE_FOLDER}/*.md") if(WIN32) - add_executable(${EXAMPLE_NAME} WIN32 ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL}) + add_executable(${EXAMPLE_NAME} WIN32 ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES}) target_link_libraries(${EXAMPLE_NAME} base ${Vulkan_LIBRARY} ${ASSIMP_LIBRARIES} ${WINLIBS}) else(WIN32) - add_executable(${EXAMPLE_NAME} ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL}) + add_executable(${EXAMPLE_NAME} ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES}) target_link_libraries(${EXAMPLE_NAME} base ) endif(WIN32) @@ -70,7 +72,8 @@ set(EXAMPLES dynamicuniformbuffer gears geometryshader - gltfscene + gltfloading + gltfscenerendering gltfskinning hdr imgui @@ -98,7 +101,6 @@ set(EXAMPLES pushdescriptors radialblur renderheadless - scenerendering screenshot shadowmapping shadowmappingomni diff --git a/examples/gltfscene/gltfscene.cpp b/examples/gltfloading/gltfloading.cpp similarity index 99% rename from examples/gltfscene/gltfscene.cpp rename to examples/gltfloading/gltfloading.cpp index aa3fccd3..c1b868dc 100644 --- a/examples/gltfscene/gltfscene.cpp +++ b/examples/gltfloading/gltfloading.cpp @@ -678,8 +678,8 @@ public: vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); const std::array shaderStages = { - loadShader(getShadersPath() + "gltfscene/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "gltfscene/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) + loadShader(getShadersPath() + "gltfloading/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getShadersPath() + "gltfloading/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) }; VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); diff --git a/examples/gltfscenerendering/README.md b/examples/gltfscenerendering/README.md new file mode 100644 index 00000000..f2d4b409 --- /dev/null +++ b/examples/gltfscenerendering/README.md @@ -0,0 +1,323 @@ +# glTF scene rendering + + + +## Synopsis + +Render a complete scene loaded from an glTF file. The sample is based on the [glTF scene](../gltfscene) sample, and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model. + +## Description + +This example demonstrates how to render a more complex scene loaded from a glTF model. + +It builds on the basic glTF scene sample but instead of using global pipelines, it adds per-material pipelines that are dynamically created from the material definitions of the glTF model. + +Those pipelines pass per-material parameters to the shader so different materials for e.g. displaying opaque and transparent objects can be built from a single shader. + +It also adds data structures, loading functions and shaders to do normal mapping and an easy way of toggling visibility for the scene nodes. + +Note that this is not a full glTF implementation as this would be beyond the scope of a simple example. For a complete glTF Vulkan implementation see [my Vulkan glTF PBR renderer](https://github.com/SaschaWillems/Vulkan-glTF-PBR/). + +For details on glTF refer to the [official glTF 2.0 specification](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). + +## Points of interest + +**Note:** Points of interest are marked with a **POI** in the code comments: + +```cpp +// POI: This sample uses normal mapping, so we also need to load the tangents from the glTF file +``` + +For this sample, those points of interest mark additions and changes compared to the basic glTF sample. + +### Loading external images + +Unlike the other samples, the glTF scene used for this example doesn't embed the images but uses external ktx images instead. This makes loading a lot faster as the ktx image format natively maps to the GPU and no longer requires us to convert RGB to RGBA, but ktx also allows us to store the mip-chain in the image file itself. + +So instead of creating the textures from a buffer that has been converted from the embedded RGB images, we just load the ktx files from disk: + +```cpp +void VulkanglTFScene::loadImages(tinygltf::Model& input) +{ + images.resize(input.images.size()); + for (size_t i = 0; i < input.images.size(); i++) { + tinygltf::Image& glTFImage = input.images[i]; + images[i].texture.loadFromFile(path + "/" + glTFImage.uri, VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, copyQueue); + } +} +``` + +### Materials + +#### New Material poperties + +```cpp +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; +}; +``` + +Several new properties have been added to the material class for this example that are taken from the glTF source. + +Along with the base color we now also get the index of the normal map for that material in ```normalTextureIndex```, and store several material properties required to render the different materials in this scene: + +- ```alphaMode```
+The alpha mode defines how the alpha value for this material is determined. For opaque materials it's ignored, for masked materials the shader will discard fragments based on the alpha cutoff. +- ```alphaCutOff```
+For masked materials, this value specifies the threshold between fully opaque and fully transparent. This is used to discard fragments in the fragment shader. +- ```doubleSided```
+This property is used to select the appropriate culling mode for this material. For double-sided materials, culling will be disabled. + +Retrieving these additional values is done here: + +```cpp +void VulkanglTFScene::loadMaterials(tinygltf::Model& input) +{ + materials.resize(input.materials.size()); + for (size_t i = 0; i < input.materials.size(); i++) { + tinygltf::Material glTFMaterial = input.materials[i]; + ... + materials[i].alphaMode = glTFMaterial.alphaMode; + materials[i].alphaCutOff = (float)glTFMaterial.alphaCutoff; + materials[i].doubleSided = glTFMaterial.doubleSided; + } +} +``` +**Note:** We only read the glTF material properties we use in this sample. There are many more, details on those can be found [here](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials). + +#### Per-Material pipelines + +Unlike most of the other samples that use a few pre-defined pipelines, this sample will dynamically generate per-material pipelines based on material properties in the ```VulkanExample::preparePipelines()``` function + +We first setup pipeline state that's common for all materials: + +```cpp +// Setup common pipeline state properties... +VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = ... +VkPipelineRasterizationStateCreateInfo rasterizationStateCI = ... +VkPipelineColorBlendAttachmentState blendAttachmentStateCI = ... +... + +for (auto &material : glTFScene.materials) +{ + ... +``` + +For each material we then set constant properties for the fragment shader using specialization constants: + +```cpp + struct MaterialSpecializationData { + bool alphaMask; + float alphaMaskCutoff; + } materialSpecializationData; + + materialSpecializationData.alphaMask = material.alphaMode == "MASK"; + materialSpecializationData.alphaMaskCutoff = material.alphaCutOff; + + std::vector specializationMapEntries = { + vks::initializers::specializationMapEntry(0, offsetof(MaterialSpecializationData, alphaMask), sizeof(MaterialSpecializationData::alphaMask)), + vks::initializers::specializationMapEntry(1, offsetof(MaterialSpecializationData, alphaMaskCutoff), sizeof(MaterialSpecializationData::alphaMaskCutoff)), + }; + VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(specializationMapEntries, sizeof(materialSpecializationData), &materialSpecializationData); + shaderStages[1].pSpecializationInfo = &specializationInfo; + ... +``` + +We also set the culling mode depending on whether this material is double-sided: + +```cpp + // For double sided materials, culling will be disabled + rasterizationStateCI.cullMode = material.doubleSided ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT; +``` + +With those setup we create a pipeline for the current material and store it as a property of the material class: + +```cpp + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &material.pipeline)); +} +``` + +The material now also get's it's own ```pipeline```. + +The alpha mask properties are used in the fragment shader to distinguish between opaque and transparent materials (```scene.frag```). + +Specialization constant declaration in the shaders's header: + +```glsl +layout (constant_id = 0) const bool ALPHA_MASK = false; +layout (constant_id = 1) const float ALPHA_MASK_CUTOFF = 0.0; +``` +*Note:* The default values provided in the shader are overwritten by the values passed at pipeline creation time. + +For alpha masked materials, fragments below the cutoff threshold are discarded: + +```glsl + vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0); + + if (ALPHA_MASK) { + if (color.a < ALPHA_MASK_CUTOFF) { + discard; + } + } +``` + +### Normal mapping + +This sample also adds tangent space normal mapping to the rendering equation to add additional detail to the scene, which requires loading additional data. + +#### Normal maps + +Along with the color maps, we now also load all normal maps. From the glTF POV those are just images like all other texture maps, and are stored in the image vector. So as for loading normal maps no code changes are required. The normal map images are then referenced by the index of the normal map of the material, which is now read in addition to the other material properties: + +```cpp +void VulkanglTFScene::loadMaterials(tinygltf::Model& input) +{ + materials.resize(input.materials.size()); + for (size_t i = 0; i < input.materials.size(); i++) { + tinygltf::Material glTFMaterial = input.materials[i]; + ... + // Get the normal map texture index + if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) { + materials[i].normalTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex(); + } + ... + } +} +``` +**Note:* Unlike the color map index, the normal map index is stored in the ```additionalValues``` of the material. + +The normal maps are then bound to binding 1 via the material's descriptor set in ```VulkanExample::setupDescriptors```: + +```cpp +for (auto& material : glTFScene.materials) { + ... + VkDescriptorImageInfo colorMap = glTFScene.getTextureDescriptor(material.baseColorTextureIndex); + VkDescriptorImageInfo normalMap = glTFScene.getTextureDescriptor(material.normalTextureIndex); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorMap), + vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &normalMap), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); +} +``` + +The descriptor set itself is then bound to set 1 at draw time in ```VulkanglTFScene::drawNode```: + +```cpp +if (node.mesh.primitives.size() > 0) { + ... + for (VulkanglTFScene::Primitive& primitive : node.mesh.primitives) { + if (primitive.indexCount > 0) { + VulkanglTFScene::Material& material = materials[primitive.materialIndex]; + ... + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &material.descriptorSet, 0, nullptr); + ... + } + } +} +``` + +Fragment shader interface in ```scene.frag```: + +```glsl +layout (set = 1, binding = 0) uniform sampler2D samplerColorMap; +layout (set = 1, binding = 1) uniform sampler2D samplerNormalMap; +``` + +#### Per-Vertex tangents + +Along with the normals we also need per-vertex tangents and bitangents for normal mapping. As the bitangent can easily be calculated using the normal and tangent, glTF only stores those two. + +So just like with other vertex data already loaded we need to check if there are tangents for a node and load them from the appropriate buffer using a glTF accessor: + +```cpp +void VulkanglTFScene::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector& indexBuffer, std::vector& vertexBuffer) +{ + VulkanglTFScene::Node node{}; + ... + + if (inputNode.mesh > -1) { + const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; + // Vertices + { + ... + const float* tangentsBuffer = nullptr; + + if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + tangentsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + ... + vert.tangent = tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f); + vertexBuffer.push_back(vert); + } + } + ... +``` + +**Note:** The tangent is a four-component vector, with the w-component storing the handedness of the tangent basis. This will be used later on in the shader. + +#### Shaders + +Normal mapping is applied in the ```scene.frag``` fragment shader and boils down to calculating a new world-space normal from the already provided per-vertex normal and the per-fragment tangent space normals provided via the materials' normal map. + +With the per-vertex normal and tangent values passed to the fragment shader, we simply change the way the per-fragment normal is calculated: + +```glsl +vec3 normal = normalize(inNormal); +vec3 tangent = normalize(inTangent.xyz); +vec3 bitangent = cross(inNormal, inTangent.xyz) * inTangent.w; +mat3 TBN = mat3(tangent, bitangent, normal); +vec3 localNormal = texture(samplerNormalMap, inUV).xyz * 2.0 - 1.0; +normal = normalize(TBN * localNormal); +``` + +As noted earlier, glTF does not store bitangents, but we can easily calculate them using the cross product of the normal and tangent. We also multiply this with the tangent's w-component which stores the handedness of the tangent. This is important, as this may differ between nodes in a glTF file. + +After that we calculate the tangent to world-space transformation matrix that is then applied to the per-fragment normal read from the normal map. + +This is then our new normal that is used for the lighting calculations to follow. + +### Rendering the scene + +Just like in the basic glTF sample, the scene hierarchy is added to the command buffer in ```VulkanglTFModel::draw```. Since glTF has a hierarchical node structure this function recursively calls ```VulkanglTFModel::drawNode``` for rendering a give node with it's children. + +The only real change in this sample is binding the per-material pipeline for a node's mesh: + +```cpp +void VulkanglTFScene::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node) +{ + if (!node.visible) { + return; + } + if (node.mesh.primitives.size() > 0) { + ... + 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]; + 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); + } +} +``` \ No newline at end of file diff --git a/examples/gltfscenerendering/gltfscenerendering.cpp b/examples/gltfscenerendering/gltfscenerendering.cpp new file mode 100644 index 00000000..0febf383 --- /dev/null +++ b/examples/gltfscenerendering/gltfscenerendering.cpp @@ -0,0 +1,669 @@ +/* +* Vulkan Example - Scene rendering +* +* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +* +* Summary: +* Render a complete scene loaded from an glTF file. The sample is based on the glTF model loading sample, +* and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model. +* +* This sample comes with a tutorial, see the README.md in this folder +*/ + +#include "gltfscenerendering.h" + +/* + Vulkan glTF scene class +*/ + +VulkanglTFScene::~VulkanglTFScene() +{ + // Release all Vulkan resources allocated for the model + vkDestroyBuffer(vulkanDevice->logicalDevice, vertices.buffer, nullptr); + vkFreeMemory(vulkanDevice->logicalDevice, vertices.memory, nullptr); + vkDestroyBuffer(vulkanDevice->logicalDevice, indices.buffer, nullptr); + vkFreeMemory(vulkanDevice->logicalDevice, indices.memory, nullptr); + for (Image image : images) { + vkDestroyImageView(vulkanDevice->logicalDevice, image.texture.view, nullptr); + vkDestroyImage(vulkanDevice->logicalDevice, image.texture.image, nullptr); + vkDestroySampler(vulkanDevice->logicalDevice, image.texture.sampler, nullptr); + vkFreeMemory(vulkanDevice->logicalDevice, image.texture.deviceMemory, nullptr); + } + for (Material material : materials) { + vkDestroyPipeline(vulkanDevice->logicalDevice, material.pipeline, nullptr); + } +} + +/* + glTF loading functions + + The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure +*/ + +void VulkanglTFScene::loadImages(tinygltf::Model& input) +{ + // POI: The textures for the glTF file used in this sample are stored as external ktx files, so we can directly load them from disk without the need for conversion + images.resize(input.images.size()); + for (size_t i = 0; i < input.images.size(); i++) { + tinygltf::Image& glTFImage = input.images[i]; + images[i].texture.loadFromFile(path + "/" + glTFImage.uri, VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, copyQueue); + } +} + +void VulkanglTFScene::loadTextures(tinygltf::Model& input) +{ + textures.resize(input.textures.size()); + for (size_t i = 0; i < input.textures.size(); i++) { + textures[i].imageIndex = input.textures[i].source; + } +} + +void VulkanglTFScene::loadMaterials(tinygltf::Model& input) +{ + materials.resize(input.materials.size()); + for (size_t i = 0; i < input.materials.size(); i++) { + // We only read the most basic properties required for our sample + tinygltf::Material glTFMaterial = input.materials[i]; + // Get the base color factor + if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { + materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); + } + // Get base color texture index + if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { + materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); + } + // Get the normal map texture index + if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) { + materials[i].normalTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex(); + } + // Get some additonal material parameters that are used in this sample + materials[i].alphaMode = glTFMaterial.alphaMode; + materials[i].alphaCutOff = (float)glTFMaterial.alphaCutoff; + materials[i].doubleSided = glTFMaterial.doubleSided; + } +} + +void VulkanglTFScene::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector& indexBuffer, std::vector& vertexBuffer) +{ + VulkanglTFScene::Node node{}; + node.name = inputNode.name; + + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + node.matrix = glm::mat4(1.0f); + if (inputNode.translation.size() == 3) { + node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); + } + if (inputNode.rotation.size() == 4) { + glm::quat q = glm::make_quat(inputNode.rotation.data()); + node.matrix *= glm::mat4(q); + } + if (inputNode.scale.size() == 3) { + node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.scale.data()))); + } + if (inputNode.matrix.size() == 16) { + node.matrix = glm::make_mat4x4(inputNode.matrix.data()); + }; + + // Load node's children + if (inputNode.children.size() > 0) { + for (size_t i = 0; i < inputNode.children.size(); i++) { + loadNode(input.nodes[inputNode.children[i]], input, &node, indexBuffer, vertexBuffer); + } + } + + // If the node contains mesh data, we load vertices and indices from the the buffers + // In glTF this is done via accessors and buffer views + if (inputNode.mesh > -1) { + const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; + uint32_t firstIndex = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; + // Vertices + { + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + const float* tangentsBuffer = nullptr; + size_t vertexCount = 0; + + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + positionBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + vertexCount = accessor.count; + } + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + normalsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + // POI: This sample uses normal mapping, so we also need to load the tangents from the glTF file + if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + tangentsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); + vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + vert.color = glm::vec3(1.0f); + vert.tangent = tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f); + vertexBuffer.push_back(vert); + } + } + // Indices + { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices]; + const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer]; + + indexCount += static_cast(accessor.count); + + // glTF supports different component types of indices + switch (accessor.componentType) { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { + uint32_t* buf = new uint32_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + uint16_t* buf = new uint16_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { + uint8_t* buf = new uint8_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + default: + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; + return; + } + } + Primitive primitive{}; + primitive.firstIndex = firstIndex; + primitive.indexCount = indexCount; + primitive.materialIndex = glTFPrimitive.material; + node.mesh.primitives.push_back(primitive); + } + } + + if (parent) { + parent->children.push_back(node); + } + else { + nodes.push_back(node); + } +} + +VkDescriptorImageInfo VulkanglTFScene::getTextureDescriptor(const size_t index) +{ + return images[index].texture.descriptor; +} + +/* + glTF rendering functions +*/ + +// Draw a single node including child nodes (if present) +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 constanst + // 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); + } +} + +// Draw the glTF scene starting at the top-level-nodes +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); + } +} + +/* + Vulkan Example class +*/ + +VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) +{ + title = "glTF scene rendering"; + camera.type = Camera::CameraType::firstperson; + camera.flipY = true; + camera.setPosition(glm::vec3(0.0f, 1.0f, 0.0f)); + camera.setRotation(glm::vec3(0.0f, -90.0f, 0.0f)); + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); + settings.overlay = true; +} + +VulkanExample::~VulkanExample() +{ + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); + shaderData.buffer.destroy(); +} + +void VulkanExample::getEnabledFeatures() +{ + enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy; +} + +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])); + } +} + +void VulkanExample::loadglTFFile(std::string filename) +{ + tinygltf::Model glTFInput; + tinygltf::TinyGLTF gltfContext; + std::string error, warning; + + this->device = device; + + // @todo: comment + //gltfContext.SetImageLoader(glTFScene.loadImageCallback, nullptr); + +#if defined(__ANDROID__) + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager + // We let tinygltf handle this, by passing the asset manager of our app + tinygltf::asset_manager = androidApp->activity->assetManager; +#endif + bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename); + + // Pass some Vulkan resources required for setup and rendering to the glTF model loading class + glTFScene.vulkanDevice = vulkanDevice; + glTFScene.copyQueue = queue; + + size_t pos = filename.find_last_of('/'); + glTFScene.path = filename.substr(0, pos); + + std::vector indexBuffer; + std::vector vertexBuffer; + + if (fileLoaded) { + glTFScene.loadImages(glTFInput); + glTFScene.loadMaterials(glTFInput); + glTFScene.loadTextures(glTFInput); + const tinygltf::Scene& scene = glTFInput.scenes[0]; + for (size_t i = 0; i < scene.nodes.size(); i++) { + const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]]; + glTFScene.loadNode(node, glTFInput, nullptr, indexBuffer, vertexBuffer); + } + } + else { + vks::tools::exitFatal("Could not open the glTF file.\n\nThe file is part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1); + return; + } + + // Create and upload vertex and index buffer + // We will be using one single vertex buffer and one single index buffer for the whole glTF scene + // Primitives (of the glTF model) will then index into these using index offsets + + size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFScene::Vertex); + size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); + glTFScene.indices.count = static_cast(indexBuffer.size()); + + struct StagingBuffer { + VkBuffer buffer; + VkDeviceMemory memory; + } vertexStaging, indexStaging; + + // Create host visible staging buffers (source) + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vertexBufferSize, + &vertexStaging.buffer, + &vertexStaging.memory, + vertexBuffer.data())); + // Index data + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + indexBufferSize, + &indexStaging.buffer, + &indexStaging.memory, + indexBuffer.data())); + + // Create device local buffers (targat) + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + vertexBufferSize, + &glTFScene.vertices.buffer, + &glTFScene.vertices.memory)); + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + indexBufferSize, + &glTFScene.indices.buffer, + &glTFScene.indices.memory)); + + // Copy data from staging buffers (host) do device local buffer (gpu) + VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBufferCopy copyRegion = {}; + + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer( + copyCmd, + vertexStaging.buffer, + glTFScene.vertices.buffer, + 1, + ©Region); + + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer( + copyCmd, + indexStaging.buffer, + glTFScene.indices.buffer, + 1, + ©Region); + + vulkanDevice->flushCommandBuffer(copyCmd, queue, true); + + // Free staging resources + vkDestroyBuffer(device, vertexStaging.buffer, nullptr); + vkFreeMemory(device, vertexStaging.memory, nullptr); + vkDestroyBuffer(device, indexStaging.buffer, nullptr); + vkFreeMemory(device, indexStaging.memory, nullptr); +} + +void VulkanExample::loadAssets() +{ + loadglTFFile(getAssetPath() + "models/sponza/sponza.gltf"); +} + +void VulkanExample::setupDescriptors() +{ + /* + This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures) + */ + + // One ubo to pass dynamic data to the shader + // Two combined image samplers per material as each material uses color and normal maps + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(glTFScene.materials.size()) * 2), + }; + // One set for matrices and one per model image/texture + const uint32_t maxSetCount = static_cast(glTFScene.images.size()) + 1; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + + // Descriptor set layout for passing matrices + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0) + }; + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices)); + + // Descriptor set layout for passing material textures + setLayoutBindings = { + // Color map + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), + // Normal map + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), + }; + descriptorSetLayoutCI.pBindings = setLayoutBindings.data(); + descriptorSetLayoutCI.bindingCount = 2; + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures)); + + // Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material) + std::array setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); + // We will use push constants to push the local matrices of a primitive to the vertex shader + VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0); + // Push constant ranges are part of the pipeline layout + pipelineLayoutCI.pushConstantRangeCount = 1; + pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); + + // Descriptor set for scene matrices + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + + // Descriptor sets for materials + for (auto& material : glTFScene.materials) { + const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &material.descriptorSet)); + VkDescriptorImageInfo colorMap = glTFScene.getTextureDescriptor(material.baseColorTextureIndex); + VkDescriptorImageInfo normalMap = glTFScene.getTextureDescriptor(material.normalTextureIndex); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorMap), + vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &normalMap), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + } +} + +void VulkanExample::preparePipelines() +{ + 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); + std::array shaderStages; + + const std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFScene::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), + }; + const std::vector 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(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); + + // POI: Instead if using a few fixed pipelines, we create one pipeline for each material using the properties of that material + for (auto &material : glTFScene.materials) { + + struct MaterialSpecializationData { + bool alphaMask; + float alphaMaskCutoff; + } materialSpecializationData; + + materialSpecializationData.alphaMask = material.alphaMode == "MASK"; + materialSpecializationData.alphaMaskCutoff = material.alphaCutOff; + + // POI: Constant fragment shader material parameters will be set using specialization constants + std::vector specializationMapEntries = { + vks::initializers::specializationMapEntry(0, offsetof(MaterialSpecializationData, alphaMask), sizeof(MaterialSpecializationData::alphaMask)), + vks::initializers::specializationMapEntry(1, offsetof(MaterialSpecializationData, alphaMaskCutoff), sizeof(MaterialSpecializationData::alphaMaskCutoff)), + }; + VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(specializationMapEntries, sizeof(materialSpecializationData), &materialSpecializationData); + shaderStages[1].pSpecializationInfo = &specializationInfo; + + // For double sided materials, culling will be disabled + rasterizationStateCI.cullMode = material.doubleSided ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT; + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &material.pipeline)); + } +} + +void VulkanExample::prepareUniformBuffers() +{ + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &shaderData.buffer, + sizeof(shaderData.values))); + VK_CHECK_RESULT(shaderData.buffer.map()); + updateUniformBuffers(); +} + +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)); +} + +void VulkanExample::prepare() +{ + VulkanExampleBase::prepare(); + loadAssets(); + prepareUniformBuffers(); + setupDescriptors(); + preparePipelines(); + buildCommandBuffers(); + prepared = true; +} + +void VulkanExample::render() +{ + renderFrame(); + if (camera.updated) { + updateUniformBuffers(); + } +} + +void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay) +{ + if (overlay->header("Visibility")) { + + if (overlay->button("All")) { + std::for_each(glTFScene.nodes.begin(), glTFScene.nodes.end(), [](VulkanglTFScene::Node &node) { node.visible = true; }); + buildCommandBuffers(); + } + ImGui::SameLine(); + if (overlay->button("None")) { + std::for_each(glTFScene.nodes.begin(), glTFScene.nodes.end(), [](VulkanglTFScene::Node &node) { node.visible = false; }); + buildCommandBuffers(); + } + ImGui::NewLine(); + + // POI: Create a list of glTF nodes for visibility toggle + ImGui::BeginChild("#nodelist", ImVec2(200.0f, 340.0f), false); + for (auto &node : glTFScene.nodes) + { + if (overlay->checkBox(node.name.c_str(), &node.visible)) + { + buildCommandBuffers(); + } + } + ImGui::EndChild(); + } +} + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/examples/gltfscenerendering/gltfscenerendering.h b/examples/gltfscenerendering/gltfscenerendering.h new file mode 100644 index 00000000..6d5ba2f5 --- /dev/null +++ b/examples/gltfscenerendering/gltfscenerendering.h @@ -0,0 +1,181 @@ +/* +* Vulkan Example - Scene rendering +* +* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +* +* Summary: +* Render a complete scene loaded from an glTF file. The sample is based on the glTF model loading sample, +* and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model. +* +* This sample comes with a tutorial, see the README.md in this folder +*/ + +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include + +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define TINYGLTF_NO_STB_IMAGE_WRITE +#define TINYGLTF_NO_STB_IMAGE +#define TINYGLTF_NO_EXTERNAL_IMAGE +#ifdef VK_USE_PLATFORM_ANDROID_KHR +#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS +#endif +#include "tiny_gltf.h" + +#include +#include "vulkanexamplebase.h" +#include "VulkanTexture.hpp" + + +#define ENABLE_VALIDATION false + + // Contains everything required to render a basic glTF scene in Vulkan + // This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure +class VulkanglTFScene +{ +public: + // The class requires some Vulkan objects so it can create it's own resources + vks::VulkanDevice* vulkanDevice; + VkQueue copyQueue; + + // The vertex layout for the samples' model + struct Vertex { + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv; + glm::vec3 color; + glm::vec4 tangent; + }; + + // Single vertex buffer for all primitives + struct { + VkBuffer buffer; + VkDeviceMemory memory; + } vertices; + + // Single index buffer for all primitives + struct { + int count; + VkBuffer buffer; + VkDeviceMemory memory; + } indices; + + // The following structures roughly represent the glTF scene structure + // To keep things simple, they only contain those properties that are required for this sample + struct Node; + + // A primitive contains the data for a single draw call + struct Primitive { + uint32_t firstIndex; + uint32_t indexCount; + int32_t materialIndex; + }; + + // Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives + struct Mesh { + std::vector primitives; + }; + + // A node represents an object in the glTF scene graph + struct Node { + Node* parent; + std::vector children; + Mesh mesh; + glm::mat4 matrix; + std::string name; + bool visible = true; + }; + + // A glTF material stores information in e.g. the exture that is attached to it and colors + 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; + }; + + // Contains the texture for a single glTF image + // Images may be reused by texture objects and are as such separted + struct Image { + vks::Texture2D texture; + }; + + // A glTF texture stores a reference to the image and a sampler + // In this sample, we are only interested in the image + struct Texture { + int32_t imageIndex; + }; + + /* + Model data + */ + std::vector images; + std::vector textures; + std::vector materials; + std::vector 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& indexBuffer, std::vector& vertexBuffer); + void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node); + void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout); +}; + +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); +}; \ No newline at end of file diff --git a/examples/scenerendering/scenerendering.cpp b/examples/scenerendering/scenerendering.cpp deleted file mode 100644 index ebc1c2ce..00000000 --- a/examples/scenerendering/scenerendering.cpp +++ /dev/null @@ -1,884 +0,0 @@ -/* -* Vulkan Example - Scene rendering -* -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -* -* Summary: -* Renders a scene made of multiple parts with different materials and textures. -* -* The example loads a scene made up of multiple parts into one vertex and index buffer to only -* have one (big) memory allocation. In Vulkan it's advised to keep number of memory allocations -* down and try to allocate large blocks of memory at once instead of having many small allocations. -* -* Every part has a separate material and multiple descriptor sets (set = x layout qualifier in GLSL) -* are used to bind a uniform buffer with global matrices and the part's material's sampler at once. -* -* To demonstrate another way of passing data the example also uses push constants for passing -* material properties. -* -* Note that this example is just one way of rendering a scene made up of multiple parts in Vulkan. -*/ - -#include -#include -#include -#include -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include -#include - -#include -#include -#include -#include - -#include -#include "vulkanexamplebase.h" -#include "VulkanTexture.hpp" -#include "VulkanDevice.hpp" -#include "VulkanBuffer.hpp" - -#define VERTEX_BUFFER_BIND_ID 0 -#define ENABLE_VALIDATION false - -// Vertex layout used in this example -struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv; - glm::vec3 color; -}; - -// Scene related structs - -// Shader properites for a material -// Will be passed to the shaders using push constant -struct SceneMaterialProperties -{ - glm::vec4 ambient; - glm::vec4 diffuse; - glm::vec4 specular; - float opacity; -}; - -// Stores info on the materials used in the scene -struct SceneMaterial -{ - std::string name; - // Material properties - SceneMaterialProperties properties; - // The example only uses a diffuse channel - vks::Texture2D diffuse; - // The material's descriptor contains the material descriptors - VkDescriptorSet descriptorSet; - // Pointer to the pipeline used by this material - VkPipeline *pipeline; -}; - -// Stores per-mesh Vulkan resources -struct ScenePart -{ - // Index of first index in the scene buffer - uint32_t indexBase; - uint32_t indexCount; - - // Pointer to the material used by this mesh - SceneMaterial *material; -}; - -// Class for loading the scene and generating all Vulkan resources -class Scene -{ -private: - vks::VulkanDevice *vulkanDevice; - VkQueue queue; - - VkDescriptorPool descriptorPool; - - // We will be using separate descriptor sets (and bindings) - // for material and scene related uniforms - struct - { - VkDescriptorSetLayout material; - VkDescriptorSetLayout scene; - } descriptorSetLayouts; - - // We will be using one single index and vertex buffer - // containing vertices and indices for all meshes in the scene - // This allows us to keep memory allocations down - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - - VkDescriptorSet descriptorSetScene; - - const aiScene* aScene; - - // Get materials from the assimp scene and map to our scene structures - void loadMaterials() - { - materials.resize(aScene->mNumMaterials); - - for (size_t i = 0; i < materials.size(); i++) - { - materials[i] = {}; - - aiString name; - aScene->mMaterials[i]->Get(AI_MATKEY_NAME, name); - - // Properties - aiColor4D color; - aScene->mMaterials[i]->Get(AI_MATKEY_COLOR_AMBIENT, color); - materials[i].properties.ambient = glm::make_vec4(&color.r) + glm::vec4(0.1f); - aScene->mMaterials[i]->Get(AI_MATKEY_COLOR_DIFFUSE, color); - materials[i].properties.diffuse = glm::make_vec4(&color.r); - aScene->mMaterials[i]->Get(AI_MATKEY_COLOR_SPECULAR, color); - materials[i].properties.specular = glm::make_vec4(&color.r); - aScene->mMaterials[i]->Get(AI_MATKEY_OPACITY, materials[i].properties.opacity); - - if ((materials[i].properties.opacity) > 0.0f) - materials[i].properties.specular = glm::vec4(0.0f); - - materials[i].name = name.C_Str(); - std::cout << "Material \"" << materials[i].name << "\"" << std::endl; - - // Textures - std::string texFormatSuffix; - VkFormat texFormat; - // Get supported compressed texture format - if (vulkanDevice->features.textureCompressionBC) { - texFormatSuffix = "_bc3_unorm"; - texFormat = VK_FORMAT_BC3_UNORM_BLOCK; - } - else if (vulkanDevice->features.textureCompressionASTC_LDR) { - texFormatSuffix = "_astc_8x8_unorm"; - texFormat = VK_FORMAT_ASTC_8x8_UNORM_BLOCK; - } - else if (vulkanDevice->features.textureCompressionETC2) { - texFormatSuffix = "_etc2_unorm"; - texFormat = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; - } - else { - vks::tools::exitFatal("Device does not support any compressed texture format!", VK_ERROR_FEATURE_NOT_PRESENT); - } - - aiString texturefile; - // Diffuse - aScene->mMaterials[i]->GetTexture(aiTextureType_DIFFUSE, 0, &texturefile); - if (aScene->mMaterials[i]->GetTextureCount(aiTextureType_DIFFUSE) > 0) - { - std::cout << " Diffuse: \"" << texturefile.C_Str() << "\"" << std::endl; - std::string fileName = std::string(texturefile.C_Str()); - std::replace(fileName.begin(), fileName.end(), '\\', '/'); - fileName.insert(fileName.find(".ktx"), texFormatSuffix); - materials[i].diffuse.loadFromFile(assetPath + fileName, texFormat, vulkanDevice, queue); - } - else - { - std::cout << " Material has no diffuse, using dummy texture!" << std::endl; - // todo : separate pipeline and layout - materials[i].diffuse.loadFromFile(assetPath + "dummy_rgba_unorm.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - - // For scenes with multiple textures per material we would need to check for additional texture types, e.g.: - // aiTextureType_HEIGHT, aiTextureType_OPACITY, aiTextureType_SPECULAR, etc. - - // Assign pipeline - materials[i].pipeline = (materials[i].properties.opacity == 0.0f) ? &pipelines.solid : &pipelines.blending; - } - - // Generate descriptor sets for the materials - - // Descriptor pool - std::vector poolSizes; - poolSizes.push_back(vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, static_cast(materials.size()))); - poolSizes.push_back(vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(materials.size()))); - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - static_cast(poolSizes.size()), - poolSizes.data(), - static_cast(materials.size()) + 1); - - VK_CHECK_RESULT(vkCreateDescriptorPool(vulkanDevice->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor set and pipeline layouts - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo descriptorLayout; - - // Set 0: Scene matrices - setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_VERTEX_BIT, - 0)); - descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - static_cast(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayouts.scene)); - - // Set 1: Material data - setLayoutBindings.clear(); - setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 0)); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayouts.material)); - - // Setup pipeline layout - std::array setLayouts = { descriptorSetLayouts.scene, descriptorSetLayouts.material }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); - - // We will be using a push constant block to pass material properties to the fragment shaders - VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange( - VK_SHADER_STAGE_FRAGMENT_BIT, - sizeof(SceneMaterialProperties), - 0); - pipelineLayoutCreateInfo.pushConstantRangeCount = 1; - pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; - - VK_CHECK_RESULT(vkCreatePipelineLayout(vulkanDevice->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Material descriptor sets - for (size_t i = 0; i < materials.size(); i++) - { - // Descriptor set - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &descriptorSetLayouts.material, - 1); - - VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &allocInfo, &materials[i].descriptorSet)); - - std::vector writeDescriptorSets; - - // todo : only use image sampler descriptor set and use one scene ubo for matrices - - // Binding 0: Diffuse texture - writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet( - materials[i].descriptorSet, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 0, - &materials[i].diffuse.descriptor)); - - vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - // Scene descriptor set - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &descriptorSetLayouts.scene, - 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &allocInfo, &descriptorSetScene)); - - std::vector writeDescriptorSets; - // Binding 0 : Vertex shader uniform buffer - writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet( - descriptorSetScene, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 0, - &uniformBuffer.descriptor)); - - vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - // Load all meshes from the scene and generate the buffers for rendering them - void loadMeshes(VkCommandBuffer copyCmd) - { - std::vector vertices; - std::vector indices; - uint32_t indexBase = 0; - - meshes.resize(aScene->mNumMeshes); - for (uint32_t i = 0; i < meshes.size(); i++) - { - aiMesh *aMesh = aScene->mMeshes[i]; - - std::cout << "Mesh \"" << aMesh->mName.C_Str() << "\"" << std::endl; - std::cout << " Material: \"" << materials[aMesh->mMaterialIndex].name << "\"" << std::endl; - std::cout << " Faces: " << aMesh->mNumFaces << std::endl; - - meshes[i].material = &materials[aMesh->mMaterialIndex]; - meshes[i].indexBase = indexBase; - meshes[i].indexCount = aMesh->mNumFaces * 3; - - // Vertices - bool hasUV = aMesh->HasTextureCoords(0); - bool hasColor = aMesh->HasVertexColors(0); - bool hasNormals = aMesh->HasNormals(); - - const uint32_t vertexOffset = static_cast(vertices.size()); - - for (uint32_t v = 0; v < aMesh->mNumVertices; v++) - { - Vertex vertex; - vertex.pos = glm::make_vec3(&aMesh->mVertices[v].x); - vertex.pos.y = -vertex.pos.y; - vertex.uv = hasUV ? glm::make_vec2(&aMesh->mTextureCoords[0][v].x) : glm::vec2(0.0f); - vertex.normal = hasNormals ? glm::make_vec3(&aMesh->mNormals[v].x) : glm::vec3(0.0f); - vertex.normal.y = -vertex.normal.y; - vertex.color = hasColor ? glm::make_vec3(&aMesh->mColors[0][v].r) : glm::vec3(1.0f); - vertices.push_back(vertex); - } - - // Indices - for (uint32_t f = 0; f < aMesh->mNumFaces; f++) - { - for (uint32_t j = 0; j < 3; j++) - { - indices.push_back(vertexOffset + aMesh->mFaces[f].mIndices[j]); - } - } - - indexBase += aMesh->mNumFaces * 3; - } - - // Create buffers - // For better performance we only create one index and vertex buffer to keep number of memory allocations down - size_t vertexDataSize = vertices.size() * sizeof(Vertex); - size_t indexDataSize = indices.size() * sizeof(uint32_t); - - vks::Buffer vertexStaging, indexStaging; - - // Vertex buffer - // Staging buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &vertexStaging, - static_cast(vertexDataSize), - vertices.data())); - // Target - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &vertexBuffer, - static_cast(vertexDataSize))); - - // Index buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &indexStaging, - static_cast(indexDataSize), - indices.data())); - // Target - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &indexBuffer, - static_cast(indexDataSize))); - - // Copy - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); - - VkBufferCopy copyRegion = {}; - - copyRegion.size = vertexDataSize; - vkCmdCopyBuffer( - copyCmd, - vertexStaging.buffer, - vertexBuffer.buffer, - 1, - ©Region); - - copyRegion.size = indexDataSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - indexBuffer.buffer, - 1, - ©Region); - - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ©Cmd; - - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - VK_CHECK_RESULT(vkQueueWaitIdle(queue)); - - //todo: fence - vertexStaging.destroy(); - indexStaging.destroy(); - } - -public: -#if defined(__ANDROID__) - AAssetManager* assetManager = nullptr; -#endif - - std::string assetPath = ""; - - std::vector materials; - std::vector meshes; - - // Shared ubo containing matrices used by all - // materials and meshes - vks::Buffer uniformBuffer; - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(1.25f, 8.35f, 0.0f, 0.0f); - } uniformData; - - // Scene uses multiple pipelines - struct { - VkPipeline solid; - VkPipeline blending; - VkPipeline wireframe; - } pipelines; - - // Shared pipeline layout - VkPipelineLayout pipelineLayout; - - // For displaying only a single part of the scene - bool renderSingleScenePart = false; - int32_t scenePartIndex = 0; - - // Default constructor - Scene(vks::VulkanDevice *vulkanDevice, VkQueue queue) - { - this->vulkanDevice = vulkanDevice; - this->queue = queue; - - // Prepare uniform buffer for global matrices - VkMemoryRequirements memReqs; - VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); - VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(uniformData)); - VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &uniformBuffer.buffer)); - vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, uniformBuffer.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAlloc, nullptr, &uniformBuffer.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, uniformBuffer.buffer, uniformBuffer.memory, 0)); - VK_CHECK_RESULT(vkMapMemory(vulkanDevice->logicalDevice, uniformBuffer.memory, 0, sizeof(uniformData), 0, (void **)&uniformBuffer.mapped)); - uniformBuffer.descriptor.offset = 0; - uniformBuffer.descriptor.buffer = uniformBuffer.buffer; - uniformBuffer.descriptor.range = sizeof(uniformData); - uniformBuffer.device = vulkanDevice->logicalDevice; - } - - // Default destructor - ~Scene() - { - vertexBuffer.destroy(); - indexBuffer.destroy(); - for (auto material : materials) - { - material.diffuse.destroy(); - } - vkDestroyPipelineLayout(vulkanDevice->logicalDevice, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(vulkanDevice->logicalDevice, descriptorSetLayouts.material, nullptr); - vkDestroyDescriptorSetLayout(vulkanDevice->logicalDevice, descriptorSetLayouts.scene, nullptr); - vkDestroyDescriptorPool(vulkanDevice->logicalDevice, descriptorPool, nullptr); - vkDestroyPipeline(vulkanDevice->logicalDevice, pipelines.solid, nullptr); - vkDestroyPipeline(vulkanDevice->logicalDevice, pipelines.blending, nullptr); - vkDestroyPipeline(vulkanDevice->logicalDevice, pipelines.wireframe, nullptr); - uniformBuffer.destroy(); - } - - void load(std::string filename, VkCommandBuffer copyCmd) - { - Assimp::Importer Importer; - - int flags = aiProcess_PreTransformVertices | aiProcess_Triangulate | aiProcess_GenNormals; - -#if defined(__ANDROID__) - AAsset* asset = AAssetManager_open(assetManager, filename.c_str(), AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - void *meshData = malloc(size); - AAsset_read(asset, meshData, size); - AAsset_close(asset); - aScene = Importer.ReadFileFromMemory(meshData, size, flags); - free(meshData); -#else - aScene = Importer.ReadFile(filename.c_str(), flags); -#endif - if (aScene) - { - loadMaterials(); - loadMeshes(copyCmd); - } - else - { - printf("Error parsing '%s': '%s'\n", filename.c_str(), Importer.GetErrorString()); -#if defined(__ANDROID__) - LOGE("Error parsing '%s': '%s'", filename.c_str(), Importer.GetErrorString()); -#endif - } - - } - - // Renders the scene into an active command buffer - // In a real world application we would do some visibility culling in here - void render(VkCommandBuffer cmdBuffer, bool wireframe) - { - VkDeviceSize offsets[1] = { 0 }; - - // Bind scene vertex and index buffers - vkCmdBindVertexBuffers(cmdBuffer, 0, 1, &vertexBuffer.buffer, offsets); - vkCmdBindIndexBuffer(cmdBuffer, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); - - for (size_t i = 0; i < meshes.size(); i++) - { - if ((renderSingleScenePart) && (i != scenePartIndex)) - continue; - - // todo : per material pipelines - // vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *mesh.material->pipeline); - - // We will be using multiple descriptor sets for rendering - // In GLSL the selection is done via the set and binding keywords - // VS: layout (set = 0, binding = 0) uniform UBO; - // FS: layout (set = 1, binding = 0) uniform sampler2D samplerColorMap; - - std::array descriptorSets; - // Set 0: Scene descriptor set containing global matrices - descriptorSets[0] = descriptorSetScene; - // Set 1: Per-Material descriptor set containing bound images - descriptorSets[1] = meshes[i].material->descriptorSet; - - vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : *meshes[i].material->pipeline); - vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast(descriptorSets.size()), descriptorSets.data(), 0, NULL); - - // Pass material properies via push constants - vkCmdPushConstants( - cmdBuffer, - pipelineLayout, - VK_SHADER_STAGE_FRAGMENT_BIT, - 0, - sizeof(SceneMaterialProperties), - &meshes[i].material->properties); - - // Render from the global scene vertex buffer using the mesh index offset - vkCmdDrawIndexed(cmdBuffer, meshes[i].indexCount, 1, 0, meshes[i].indexBase, 0); - } - } -}; - -class VulkanExample : public VulkanExampleBase -{ -public: - bool wireframe = false; - bool attachLight = false; - - Scene *scene = nullptr; - - struct { - VkPipelineVertexInputStateCreateInfo inputState; - std::vector bindingDescriptions; - std::vector attributeDescriptions; - } vertices; - - VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) - { - title = "Multi-part scene rendering"; - camera.type = Camera::CameraType::firstperson; - camera.movementSpeed = 7.5f; - camera.position = { 15.0f, -13.5f, 0.0f }; - camera.setRotation(glm::vec3(5.0f, 90.0f, 0.0f)); - camera.setRotationSpeed(0.5f); - camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); - settings.overlay = true; - } - - ~VulkanExample() - { - delete(scene); - } - - // Enable physical device features required for this example - virtual void getEnabledFeatures() - { - // Fill mode non solid is required for wireframe display - if (deviceFeatures.fillModeNonSolid) { - enabledFeatures.fillModeNonSolid = VK_TRUE; - }; - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - VkClearValue clearValues[2]; - clearValues[0].color = defaultClearColor; - 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; - - 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); - - VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); - vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - - VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); - vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - - scene->render(drawCmdBuffers[i], wireframe); - - drawUI(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - void setupVertexDescriptions() - { - // Binding description - vertices.bindingDescriptions.resize(1); - vertices.bindingDescriptions[0] = - vks::initializers::vertexInputBindingDescription( - VERTEX_BUFFER_BIND_ID, - sizeof(Vertex), - VK_VERTEX_INPUT_RATE_VERTEX); - - // Attribute descriptions - // Describes memory layout and shader positions - vertices.attributeDescriptions.resize(4); - // Location 0 : Position - vertices.attributeDescriptions[0] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 0, - VK_FORMAT_R32G32B32_SFLOAT, - 0); - // Location 1 : Normal - vertices.attributeDescriptions[1] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 1, - VK_FORMAT_R32G32B32_SFLOAT, - sizeof(float) * 3); - // Location 2 : Texture coordinates - vertices.attributeDescriptions[2] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 2, - VK_FORMAT_R32G32_SFLOAT, - sizeof(float) * 6); - // Location 3 : Color - vertices.attributeDescriptions[3] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 3, - VK_FORMAT_R32G32B32_SFLOAT, - sizeof(float) * 8); - - vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertices.inputState.vertexBindingDescriptionCount = static_cast(vertices.bindingDescriptions.size()); - vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); - vertices.inputState.vertexAttributeDescriptionCount = static_cast(vertices.attributeDescriptions.size()); - vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); - } - - void preparePipelines() - { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vks::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_BACK_BIT, - VK_FRONT_FACE_COUNTER_CLOCKWISE, - 0); - - VkPipelineColorBlendAttachmentState blendAttachmentState = - vks::initializers::pipelineColorBlendAttachmentState( - 0xf, - VK_FALSE); - - VkPipelineColorBlendStateCreateInfo colorBlendState = - vks::initializers::pipelineColorBlendStateCreateInfo( - 1, - &blendAttachmentState); - - VkPipelineDepthStencilStateCreateInfo depthStencilState = - vks::initializers::pipelineDepthStencilStateCreateInfo( - VK_TRUE, - VK_TRUE, - VK_COMPARE_OP_LESS_OR_EQUAL); - - VkPipelineViewportStateCreateInfo viewportState = - vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); - - VkPipelineMultisampleStateCreateInfo multisampleState = - vks::initializers::pipelineMultisampleStateCreateInfo( - VK_SAMPLE_COUNT_1_BIT, - 0); - - std::vector dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo( - dynamicStateEnables.data(), - static_cast(dynamicStateEnables.size()), - 0); - - std::array shaderStages; - - // Solid rendering pipeline - shaderStages[0] = loadShader(getShadersPath() + "scenerendering/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "scenerendering/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo( - scene->pipelineLayout, - renderPass, - 0); - - pipelineCreateInfo.pVertexInputState = &vertices.inputState; - pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; - pipelineCreateInfo.pRasterizationState = &rasterizationState; - pipelineCreateInfo.pColorBlendState = &colorBlendState; - pipelineCreateInfo.pMultisampleState = &multisampleState; - pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.pDynamicState = &dynamicState; - pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &scene->pipelines.solid)); - - // Alpha blended pipeline - rasterizationState.cullMode = VK_CULL_MODE_NONE; - blendAttachmentState.blendEnable = VK_TRUE; - blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; - blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR; - blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &scene->pipelines.blending)); - - // Wire frame rendering pipeline - if (deviceFeatures.fillModeNonSolid) { - rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; - blendAttachmentState.blendEnable = VK_FALSE; - rasterizationState.polygonMode = VK_POLYGON_MODE_LINE; - rasterizationState.lineWidth = 1.0f; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &scene->pipelines.wireframe)); - } - } - - void updateUniformBuffers() - { - if (attachLight) - { - scene->uniformData.lightPos = glm::vec4(-camera.position, 1.0f); - } - - scene->uniformData.projection = camera.matrices.perspective; - scene->uniformData.view = camera.matrices.view; - scene->uniformData.model = glm::mat4(1.0f); - - memcpy(scene->uniformBuffer.mapped, &scene->uniformData, sizeof(scene->uniformData)); - } - - void draw() - { - VulkanExampleBase::prepareFrame(); - - // Command buffer to be sumitted to the queue - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - - // Submit to queue - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - VulkanExampleBase::submitFrame(); - } - - void loadScene() - { - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false); - scene = new Scene(vulkanDevice, queue); - -#if defined(__ANDROID__) - scene->assetManager = androidApp->activity->assetManager; -#endif - scene->assetPath = getAssetPath() + "models/sibenik/"; - scene->load(getAssetPath() + "models/sibenik/sibenik.dae", copyCmd); - vkFreeCommandBuffers(device, cmdPool, 1, ©Cmd); - updateUniformBuffers(); - } - - void prepare() - { - VulkanExampleBase::prepare(); - setupVertexDescriptions(); - loadScene(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - draw(); - } - - virtual void viewChanged() - { - updateUniformBuffers(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (deviceFeatures.fillModeNonSolid) { - if (overlay->checkBox("Wireframe", &wireframe)) { - buildCommandBuffers(); - } - } - if (scene) { - if (overlay->checkBox("Attach light to camera", &attachLight)) { - updateUniformBuffers(); - } - if (overlay->checkBox("Render single part", &scene->renderSingleScenePart)) { - buildCommandBuffers(); - } - if (scene->renderSingleScenePart) { - if (overlay->sliderInt("Part to render", &scene->scenePartIndex, 0, static_cast(scene->meshes.size()))) { - buildCommandBuffers(); - } - } - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/screenshots/gltf_scene.jpg b/screenshots/gltf_scene.jpg new file mode 100644 index 00000000..68ed640c Binary files /dev/null and b/screenshots/gltf_scene.jpg differ diff --git a/screenshots/scenerendering.jpg b/screenshots/scenerendering.jpg deleted file mode 100644 index dfd18c0d..00000000 Binary files a/screenshots/scenerendering.jpg and /dev/null differ