Merge pull request #694 from SaschaWillems/gltf
Replace model loading sample using ASSIMP with glTF scene loading sample
This commit is contained in:
commit
84a458cae5
23 changed files with 15819 additions and 6182 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -226,4 +226,11 @@ data/models/cerberus/*.*
|
|||
*.vcxproj.user
|
||||
|
||||
# Ignore macOS .DS_Store
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Assets that are part of the asset pack and should not be stored in the repo
|
||||
data/models/**/*.gltf
|
||||
data/models/**/*.bin
|
||||
data/models/**/*.glb
|
||||
data/models/**/*.png
|
||||
data/models/**/*.jpg
|
||||
|
|
@ -103,9 +103,9 @@ 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 - Model rendering](examples/mesh/)
|
||||
#### [11 - glTF scene loading and rendering](examples/gltfscene/)
|
||||
|
||||
Loads a 3D model and texture maps from a common file format (using [assimp](https://github.com/assimp/assimp)), uploads the vertex and index buffer data to video memory, sets up a matching vertex layout and renders the 3D model.
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
|
||||
|
||||
set(NAME mesh)
|
||||
set(NAME gltfscene)
|
||||
|
||||
set(SRC_DIR ../../../examples/${NAME})
|
||||
set(BASE_DIR ../../../base)
|
||||
|
|
@ -24,6 +24,7 @@ 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(
|
||||
|
|
@ -4,7 +4,7 @@ apply from: '../gradle/outputfilename.gradle'
|
|||
android {
|
||||
compileSdkVersion 26
|
||||
defaultConfig {
|
||||
applicationId "de.saschawillems.vulkanMesh"
|
||||
applicationId "de.saschawillems.vulkanglTFScene"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
|
|
@ -49,14 +49,14 @@ task copyTask {
|
|||
}
|
||||
|
||||
copy {
|
||||
from '../../../data/shaders/mesh'
|
||||
into 'assets/shaders/mesh'
|
||||
from '../../../data/shaders/gltfscene'
|
||||
into 'assets/shaders/gltfscene'
|
||||
include '*.*'
|
||||
}
|
||||
|
||||
copy {
|
||||
from '../../../data/models/voyager'
|
||||
into 'assets/models/voyager'
|
||||
from '../../../data/models/FlightHelmet/glTF'
|
||||
into 'assets/models/FlightHelmet/glTF'
|
||||
include '*.*'
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.saschawillems.vulkanMesh">
|
||||
package="de.saschawillems.vulkanglTFScene">
|
||||
|
||||
<application
|
||||
android:label="Vulkan model rendering"
|
||||
android:label="Vulkan glTF scene rendering"
|
||||
android:icon="@drawable/icon"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
<activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
|
||||
|
|
@ -23,11 +23,15 @@ private:
|
|||
glm::mat4 rotM = glm::mat4(1.0f);
|
||||
glm::mat4 transM;
|
||||
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.x * (flipY ? -1.0f : 1.0f)), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
transM = glm::translate(glm::mat4(1.0f), position);
|
||||
glm::vec3 translation = position;
|
||||
if (flipY) {
|
||||
translation.y *= -1.0f;
|
||||
}
|
||||
transM = glm::translate(glm::mat4(1.0f), translation);
|
||||
|
||||
if (type == CameraType::firstperson)
|
||||
{
|
||||
|
|
@ -51,6 +55,7 @@ public:
|
|||
float movementSpeed = 1.0f;
|
||||
|
||||
bool updated = false;
|
||||
bool flipY = false;
|
||||
|
||||
struct
|
||||
{
|
||||
|
|
@ -85,11 +90,17 @@ public:
|
|||
this->znear = znear;
|
||||
this->zfar = zfar;
|
||||
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
|
||||
if (flipY) {
|
||||
matrices.perspective[1, 1] *= -1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
void updateAspectRatio(float aspect)
|
||||
{
|
||||
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
|
||||
if (flipY) {
|
||||
matrices.perspective[1, 1] *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void setPosition(glm::vec3 position)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,15 @@ VkResult VulkanExampleBase::createInstance(bool enableValidation)
|
|||
return vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
|
||||
}
|
||||
|
||||
void VulkanExampleBase::renderFrame()
|
||||
{
|
||||
VulkanExampleBase::prepareFrame();
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||
VulkanExampleBase::submitFrame();
|
||||
}
|
||||
|
||||
std::string VulkanExampleBase::getWindowTitle()
|
||||
{
|
||||
std::string device(deviceProperties.deviceName);
|
||||
|
|
@ -227,7 +236,7 @@ VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileNa
|
|||
return shaderStage;
|
||||
}
|
||||
|
||||
void VulkanExampleBase::renderFrame()
|
||||
void VulkanExampleBase::nextFrame()
|
||||
{
|
||||
auto tStart = std::chrono::high_resolution_clock::now();
|
||||
if (viewUpdated)
|
||||
|
|
@ -298,8 +307,8 @@ void VulkanExampleBase::renderLoop()
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!IsIconic(window)) {
|
||||
renderFrame();
|
||||
if (prepared && !IsIconic(window)) {
|
||||
nextFrame();
|
||||
}
|
||||
}
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@
|
|||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <array>
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
|
|
@ -57,17 +57,20 @@
|
|||
class VulkanExampleBase
|
||||
{
|
||||
private:
|
||||
// Get window title with example name, device, et.
|
||||
std::string getWindowTitle();
|
||||
/** brief Indicates that the view (position, rotation) has changed and buffers containing camera matrices need to be updated */
|
||||
bool viewUpdated = false;
|
||||
// Destination dimensions for resizing the window
|
||||
uint32_t destWidth;
|
||||
uint32_t destHeight;
|
||||
bool resizing = false;
|
||||
// Called if the window is resized and some resources have to be recreatesd
|
||||
void windowResize();
|
||||
void handleMouseMove(int32_t x, int32_t y);
|
||||
void nextFrame();
|
||||
void updateOverlay();
|
||||
void createPipelineCache();
|
||||
void createCommandPool();
|
||||
void createSynchronizationPrimitives();
|
||||
void initSwapchain();
|
||||
void setupSwapChain();
|
||||
protected:
|
||||
// Frame counter to display fps
|
||||
uint32_t frameCounter = 0;
|
||||
|
|
@ -241,13 +244,9 @@ public:
|
|||
xcb_intern_atom_reply_t *atom_wm_delete_window;
|
||||
#endif
|
||||
|
||||
// Default ctor
|
||||
VulkanExampleBase(bool enableValidation = false);
|
||||
|
||||
// dtor
|
||||
virtual ~VulkanExampleBase();
|
||||
|
||||
// Setup the vulkan instance, enable required extensions and connect to the physical device (GPU)
|
||||
/** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */
|
||||
bool initVulkan();
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -310,92 +309,59 @@ public:
|
|||
void initxcbConnection();
|
||||
void handleEvent(const xcb_generic_event_t *event);
|
||||
#endif
|
||||
/**
|
||||
* Create the application wide Vulkan instance
|
||||
*
|
||||
* @note Virtual, can be overriden by derived example class for custom instance creation
|
||||
*/
|
||||
/** @brief (Virtual) Creates the application wide Vulkan instance */
|
||||
virtual VkResult createInstance(bool enableValidation);
|
||||
|
||||
// Pure virtual render function (override in derived class)
|
||||
/** @brief (Pure virtual) Render function to be implemented by the sample application */
|
||||
virtual void render() = 0;
|
||||
// Called when view change occurs
|
||||
// Can be overriden in derived class to e.g. update uniform buffers
|
||||
// Containing view dependant matrices
|
||||
/** @brief (Virtual) Called when the camera view has changed */
|
||||
virtual void viewChanged();
|
||||
/** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */
|
||||
virtual void keyPressed(uint32_t);
|
||||
/** @brief (Virtual) Called after th mouse cursor moved and before internal events (like camera rotation) is handled */
|
||||
/** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */
|
||||
virtual void mouseMoved(double x, double y, bool &handled);
|
||||
// Called when the window has been resized
|
||||
// Can be overriden in derived class to recreate or rebuild resources attached to the frame buffer / swapchain
|
||||
/** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */
|
||||
virtual void windowResized();
|
||||
// Pure virtual function to be overriden by the dervice class
|
||||
// Called in case of an event where e.g. the framebuffer has to be rebuild and thus
|
||||
// all command buffers that may reference this
|
||||
/** @brief (Virtual) Called when resources have been recreated that require a rebuild of the command buffers (e.g. frame buffer), to be implemente by the sample application */
|
||||
virtual void buildCommandBuffers();
|
||||
|
||||
void createSynchronizationPrimitives();
|
||||
|
||||
// Creates a new (graphics) command pool object storing command buffers
|
||||
void createCommandPool();
|
||||
// Setup default depth and stencil views
|
||||
/** @brief (Virtual) Setup default depth and stencil views */
|
||||
virtual void setupDepthStencil();
|
||||
// Create framebuffers for all requested swap chain images
|
||||
// Can be overriden in derived class to setup a custom framebuffer (e.g. for MSAA)
|
||||
/** @brief (Virtual) Setup default framebuffers for all requested swapchain images */
|
||||
virtual void setupFrameBuffer();
|
||||
// Setup a default render pass
|
||||
// Can be overriden in derived class to setup a custom render pass (e.g. for MSAA)
|
||||
/** @brief (Virtual) Setup a default renderpass */
|
||||
virtual void setupRenderPass();
|
||||
|
||||
/** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */
|
||||
virtual void getEnabledFeatures();
|
||||
|
||||
// Connect and prepare the swap chain
|
||||
void initSwapchain();
|
||||
// Create swap chain images
|
||||
void setupSwapChain();
|
||||
|
||||
// Check if command buffers are valid (!= VK_NULL_HANDLE)
|
||||
/** @brief Checks if command buffers are valid (!= VK_NULL_HANDLE) */
|
||||
bool checkCommandBuffers();
|
||||
// Create command buffers for drawing commands
|
||||
/** @brief Creates the per-frame command buffers */
|
||||
void createCommandBuffers();
|
||||
// Destroy all command buffers and set their handles to VK_NULL_HANDLE
|
||||
// May be necessary during runtime if options are toggled
|
||||
/** @brief Destroy all command buffers and set their handles to VK_NULL_HANDLE */
|
||||
void destroyCommandBuffers();
|
||||
|
||||
// Command buffer creation
|
||||
// Creates and returns a new command buffer
|
||||
/** @brief Creates and returns a new command buffer */
|
||||
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin);
|
||||
// End the command buffer, submit it to the queue and free (if requested)
|
||||
// Note : Waits for the queue to become idle
|
||||
/** @brief End the command buffer, submit it to the queue and free (if requested) */
|
||||
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free);
|
||||
|
||||
// Create a cache pool for rendering pipelines
|
||||
void createPipelineCache();
|
||||
|
||||
// Prepare commonly used Vulkan functions
|
||||
/** @brief Prepares all Vulkan resources and functions required to run the sample */
|
||||
virtual void prepare();
|
||||
|
||||
// Load a SPIR-V shader
|
||||
/** @brief Loads a SPIR-V shader file for the given shader stage */
|
||||
VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
|
||||
|
||||
// Start the main render loop
|
||||
/** @brief Entry point for the main render loop */
|
||||
void renderLoop();
|
||||
|
||||
// Render one frame of a render loop on platforms that sync rendering
|
||||
void renderFrame();
|
||||
|
||||
void updateOverlay();
|
||||
/** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */
|
||||
void drawUI(const VkCommandBuffer commandBuffer);
|
||||
|
||||
// Prepare the frame for workload submission
|
||||
// - Acquires the next image from the swap chain
|
||||
// - Sets the default wait and signal semaphores
|
||||
/** Prepare the next frame for workload sumbission by acquiring the next swap chain image */
|
||||
void prepareFrame();
|
||||
|
||||
// Submit the frames' workload
|
||||
/** @brief Presents the current image to the swap chain */
|
||||
void submitFrame();
|
||||
/** @brief (Virtual) Default image acquire + submission and command buffer submission function */
|
||||
virtual void renderFrame();
|
||||
|
||||
/** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */
|
||||
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#version 450
|
||||
|
||||
layout (binding = 1) uniform sampler2D samplerColorMap;
|
||||
layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
|
||||
|
||||
layout (location = 0) in vec3 inNormal;
|
||||
layout (location = 1) in vec3 inColor;
|
||||
|
|
@ -18,7 +18,7 @@ void main()
|
|||
vec3 L = normalize(inLightVec);
|
||||
vec3 V = normalize(inViewVec);
|
||||
vec3 R = reflect(-L, N);
|
||||
vec3 diffuse = max(dot(N, L), 0.0) * inColor;
|
||||
vec3 diffuse = max(dot(N, L), 0.15) * inColor;
|
||||
vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
|
||||
outFragColor = vec4(diffuse * color.rgb + specular, 1.0);
|
||||
}
|
||||
BIN
data/shaders/gltfscene/mesh.frag.spv
Normal file
BIN
data/shaders/gltfscene/mesh.frag.spv
Normal file
Binary file not shown.
|
|
@ -5,12 +5,16 @@ layout (location = 1) in vec3 inNormal;
|
|||
layout (location = 2) in vec2 inUV;
|
||||
layout (location = 3) in vec3 inColor;
|
||||
|
||||
layout (binding = 0) uniform UBO
|
||||
layout (set = 0, binding = 0) uniform UBOScene
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
vec4 lightPos;
|
||||
} ubo;
|
||||
} uboScene;
|
||||
|
||||
layout(push_constant) uniform PushConsts {
|
||||
mat4 model;
|
||||
} primitive;
|
||||
|
||||
layout (location = 0) out vec3 outNormal;
|
||||
layout (location = 1) out vec3 outColor;
|
||||
|
|
@ -18,21 +22,16 @@ 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;
|
||||
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
|
||||
gl_Position = uboScene.projection * uboScene.view * primitive.model * vec4(inPos.xyz, 1.0);
|
||||
|
||||
vec4 pos = ubo.model * vec4(inPos, 1.0);
|
||||
outNormal = mat3(ubo.model) * inNormal;
|
||||
vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
|
||||
vec4 pos = uboScene.view * vec4(inPos, 1.0);
|
||||
outNormal = mat3(uboScene.view) * inNormal;
|
||||
vec3 lPos = mat3(uboScene.view) * uboScene.lightPos.xyz;
|
||||
outLightVec = lPos - pos.xyz;
|
||||
outViewVec = -pos.xyz;
|
||||
}
|
||||
BIN
data/shaders/gltfscene/mesh.vert.spv
Normal file
BIN
data/shaders/gltfscene/mesh.vert.spv
Normal file
Binary file not shown.
|
|
@ -1,2 +0,0 @@
|
|||
glslangvalidator -V mesh.vert -o mesh.vert.spv
|
||||
glslangvalidator -V mesh.frag -o mesh.frag.spv
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -64,13 +64,13 @@ set(EXAMPLES
|
|||
dynamicuniformbuffer
|
||||
gears
|
||||
geometryshader
|
||||
gltfscene
|
||||
hdr
|
||||
imgui
|
||||
indirectdraw
|
||||
inlineuniformblocks
|
||||
inputattachments
|
||||
instancing
|
||||
mesh
|
||||
multisampling
|
||||
multithreading
|
||||
multiview
|
||||
|
|
|
|||
759
examples/gltfscene/gltfscene.cpp
Normal file
759
examples/gltfscene/gltfscene.cpp
Normal file
|
|
@ -0,0 +1,759 @@
|
|||
/*
|
||||
* Vulkan Example - glTF scene loading and rendering
|
||||
*
|
||||
* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Shows how to load and display a simple scene from a glTF file
|
||||
* Note that this isn't a complete glTF loader and only basic functions are shown here
|
||||
* This means no complex materials, no animations, no skins, etc.
|
||||
* For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
|
||||
*
|
||||
* Other samples will load models using a dedicated model loader with more features (see base/VulkanglTFModel.hpp)
|
||||
*
|
||||
* If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define TINYGLTF_NO_STB_IMAGE_WRITE
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
|
||||
#endif
|
||||
#include "tiny_gltf.h"
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "vulkanexamplebase.h"
|
||||
#include "VulkanTexture.hpp"
|
||||
|
||||
#define ENABLE_VALIDATION false
|
||||
|
||||
// Contains everything required to render a glTF model in Vulkan
|
||||
// This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure
|
||||
class VulkanglTFModel
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
// 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<Primitive> primitives;
|
||||
};
|
||||
|
||||
// A node represents an object in the glTF scene graph
|
||||
struct Node {
|
||||
Node* parent;
|
||||
std::vector<Node> children;
|
||||
Mesh mesh;
|
||||
glm::mat4 matrix;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// 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;
|
||||
// We also store (and create) a descriptor set that's used to access this texture from the fragment shader
|
||||
VkDescriptorSet descriptorSet;
|
||||
};
|
||||
|
||||
// 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<Image> images;
|
||||
std::vector<Texture> textures;
|
||||
std::vector<Material> materials;
|
||||
std::vector<Node> nodes;
|
||||
|
||||
~VulkanglTFModel()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
glTF loading functions
|
||||
|
||||
The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure
|
||||
*/
|
||||
|
||||
void loadImages(tinygltf::Model& input)
|
||||
{
|
||||
// Images can be stored inside the glTF (which is the case for the sample model), so instead of directly
|
||||
// loading them from disk, we fetch them from the glTF loader and upload the buffers
|
||||
images.resize(input.images.size());
|
||||
for (size_t i = 0; i < input.images.size(); i++) {
|
||||
tinygltf::Image& glTFImage = input.images[i];
|
||||
// Get the image data from the glTF loader
|
||||
unsigned char* buffer = nullptr;
|
||||
VkDeviceSize bufferSize = 0;
|
||||
bool deleteBuffer = false;
|
||||
// We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan
|
||||
if (glTFImage.component == 3) {
|
||||
bufferSize = glTFImage.width * glTFImage.height * 4;
|
||||
buffer = new unsigned char[bufferSize];
|
||||
unsigned char* rgba = buffer;
|
||||
unsigned char* rgb = &glTFImage.image[0];
|
||||
for (size_t i = 0; i < glTFImage.width * glTFImage.height; ++i) {
|
||||
for (int32_t j = 0; j < 3; ++j) {
|
||||
rgba[j] = rgb[j];
|
||||
}
|
||||
rgba += 4;
|
||||
rgb += 3;
|
||||
}
|
||||
deleteBuffer = true;
|
||||
}
|
||||
else {
|
||||
buffer = &glTFImage.image[0];
|
||||
bufferSize = glTFImage.image.size();
|
||||
}
|
||||
// Load texture from image buffer
|
||||
images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue);
|
||||
}
|
||||
}
|
||||
|
||||
void 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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFModel::Vertex>& vertexBuffer)
|
||||
{
|
||||
VulkanglTFModel::Node node{};
|
||||
node.matrix = glm::mat4(1.0f);
|
||||
|
||||
// Get the local node matrix
|
||||
// It's either made up from translation, rotation, scale or a 4x4 matrix
|
||||
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<uint32_t>(indexBuffer.size());
|
||||
uint32_t vertexStart = static_cast<uint32_t>(vertexBuffer.size());
|
||||
uint32_t indexCount = 0;
|
||||
// Vertices
|
||||
{
|
||||
const float* positionBuffer = nullptr;
|
||||
const float* normalsBuffer = nullptr;
|
||||
const float* texCoordsBuffer = 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<const float*>(&(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<const float*>(&(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<const float*>(&(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);
|
||||
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<uint32_t>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
glTF rendering functions
|
||||
*/
|
||||
|
||||
// Draw a single node including child nodes (if present)
|
||||
void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node)
|
||||
{
|
||||
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;
|
||||
VulkanglTFModel::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 (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) {
|
||||
if (primitive.indexCount > 0) {
|
||||
// Get the texture index for this primitive
|
||||
VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex];
|
||||
// Bind the descriptor for the current primitive's texture
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].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 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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class VulkanExample : public VulkanExampleBase
|
||||
{
|
||||
public:
|
||||
bool wireframe = false;
|
||||
|
||||
VulkanglTFModel glTFModel;
|
||||
|
||||
struct ShaderData {
|
||||
vks::Buffer buffer;
|
||||
struct Values {
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, -5.0f, 1.0f);
|
||||
} values;
|
||||
} shaderData;
|
||||
|
||||
struct Pipelines {
|
||||
VkPipeline solid;
|
||||
VkPipeline wireframe = VK_NULL_HANDLE;
|
||||
} pipelines;
|
||||
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkDescriptorSet descriptorSet;
|
||||
|
||||
struct DescriptorSetLayouts {
|
||||
VkDescriptorSetLayout matrices;
|
||||
VkDescriptorSetLayout textures;
|
||||
} descriptorSetLayouts;
|
||||
|
||||
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
|
||||
{
|
||||
title = "glTF model rendering";
|
||||
camera.type = Camera::CameraType::lookat;
|
||||
camera.flipY = true;
|
||||
camera.setPosition(glm::vec3(0.0f, -0.1f, -1.0f));
|
||||
camera.setRotation(glm::vec3(0.0f, -135.0f, 0.0f));
|
||||
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
|
||||
settings.overlay = true;
|
||||
}
|
||||
|
||||
~VulkanExample()
|
||||
{
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
vkDestroyPipeline(device, pipelines.solid, nullptr);
|
||||
if (pipelines.wireframe != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipelines.wireframe, nullptr);
|
||||
}
|
||||
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr);
|
||||
|
||||
shaderData.buffer.destroy();
|
||||
}
|
||||
|
||||
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[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);
|
||||
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid);
|
||||
glTFModel.draw(drawCmdBuffers[i], pipelineLayout);
|
||||
drawUI(drawCmdBuffers[i]);
|
||||
vkCmdEndRenderPass(drawCmdBuffers[i]);
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void loadglTFFile(std::string filename)
|
||||
{
|
||||
tinygltf::Model glTFInput;
|
||||
tinygltf::TinyGLTF gltfContext;
|
||||
std::string error, warning;
|
||||
|
||||
this->device = device;
|
||||
|
||||
#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
|
||||
glTFModel.vulkanDevice = vulkanDevice;
|
||||
glTFModel.copyQueue = queue;
|
||||
|
||||
std::vector<uint32_t> indexBuffer;
|
||||
std::vector<VulkanglTFModel::Vertex> vertexBuffer;
|
||||
|
||||
if (fileLoaded) {
|
||||
glTFModel.loadImages(glTFInput);
|
||||
glTFModel.loadMaterials(glTFInput);
|
||||
glTFModel.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]];
|
||||
glTFModel.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(VulkanglTFModel::Vertex);
|
||||
size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
|
||||
glTFModel.indices.count = static_cast<uint32_t>(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,
|
||||
&glTFModel.vertices.buffer,
|
||||
&glTFModel.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,
|
||||
&glTFModel.indices.buffer,
|
||||
&glTFModel.indices.memory));
|
||||
|
||||
// Copy data from staging buffers (host) do device local buffer (gpu)
|
||||
VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
VkBufferCopy copyRegion = {};
|
||||
|
||||
copyRegion.size = vertexBufferSize;
|
||||
vkCmdCopyBuffer(
|
||||
copyCmd,
|
||||
vertexStaging.buffer,
|
||||
glTFModel.vertices.buffer,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
copyRegion.size = indexBufferSize;
|
||||
vkCmdCopyBuffer(
|
||||
copyCmd,
|
||||
indexStaging.buffer,
|
||||
glTFModel.indices.buffer,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
VulkanExampleBase::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 loadAssets()
|
||||
{
|
||||
loadglTFFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf");
|
||||
}
|
||||
|
||||
void setupDescriptors()
|
||||
{
|
||||
/*
|
||||
This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures)
|
||||
*/
|
||||
|
||||
std::vector<VkDescriptorPoolSize> poolSizes = {
|
||||
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
// One combined image sampler per model image/texture
|
||||
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(glTFModel.images.size())),
|
||||
};
|
||||
// One set for matrices and one per model image/texture
|
||||
const uint32_t maxSetCount = static_cast<uint32_t>(glTFModel.images.size()) + 1;
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount);
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
|
||||
|
||||
// Descriptor set layout for passing matrices
|
||||
VkDescriptorSetLayoutBinding setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(&setLayoutBinding, 1);
|
||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices));
|
||||
// Descriptor set layout for passing material textures
|
||||
setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures));
|
||||
// Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material)
|
||||
std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures };
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCI= vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(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& image : glTFModel.images) {
|
||||
const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1);
|
||||
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet));
|
||||
VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor);
|
||||
vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void 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<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
||||
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
|
||||
// Vertex input bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
|
||||
vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
|
||||
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, pos)), // Location 0: Position
|
||||
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, normal)),// Location 1: Normal
|
||||
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)), // Location 2: Texture coordinates
|
||||
vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)), // Location 3: Color
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo();
|
||||
vertexInputStateCI.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
|
||||
vertexInputStateCI.pVertexBindingDescriptions = vertexInputBindings.data();
|
||||
vertexInputStateCI.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
|
||||
vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data();
|
||||
|
||||
const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
|
||||
loadShader(getAssetPath() + "shaders/gltfscene/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
||||
loadShader(getAssetPath() + "shaders/gltfscene/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
|
||||
};
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
|
||||
pipelineCI.pVertexInputState = &vertexInputStateCI;
|
||||
pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
|
||||
pipelineCI.pRasterizationState = &rasterizationStateCI;
|
||||
pipelineCI.pColorBlendState = &colorBlendStateCI;
|
||||
pipelineCI.pMultisampleState = &multisampleStateCI;
|
||||
pipelineCI.pViewportState = &viewportStateCI;
|
||||
pipelineCI.pDepthStencilState = &depthStencilStateCI;
|
||||
pipelineCI.pDynamicState = &dynamicStateCI;
|
||||
pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
|
||||
pipelineCI.pStages = shaderStages.data();
|
||||
|
||||
// Solid rendering pipeline
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
|
||||
|
||||
// Wire frame rendering pipeline
|
||||
if (deviceFeatures.fillModeNonSolid) {
|
||||
rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
|
||||
rasterizationStateCI.lineWidth = 1.0f;
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void prepareUniformBuffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
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)));
|
||||
|
||||
// Map persistent
|
||||
VK_CHECK_RESULT(shaderData.buffer.map());
|
||||
|
||||
updateUniformBuffers();
|
||||
}
|
||||
|
||||
void updateUniformBuffers()
|
||||
{
|
||||
shaderData.values.projection = camera.matrices.perspective;
|
||||
shaderData.values.model = camera.matrices.view;
|
||||
memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
|
||||
}
|
||||
|
||||
void prepare()
|
||||
{
|
||||
VulkanExampleBase::prepare();
|
||||
loadAssets();
|
||||
prepareUniformBuffers();
|
||||
setupDescriptors();
|
||||
preparePipelines();
|
||||
buildCommandBuffers();
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
virtual void render()
|
||||
{
|
||||
renderFrame();
|
||||
if (camera.updated) {
|
||||
updateUniformBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
|
||||
{
|
||||
if (overlay->header("Settings")) {
|
||||
if (overlay->checkBox("Wireframe", &wireframe)) {
|
||||
buildCommandBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VULKAN_EXAMPLE_MAIN()
|
||||
|
|
@ -1,633 +0,0 @@
|
|||
/*
|
||||
* Vulkan Example - Model loading and rendering
|
||||
*
|
||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/cimport.h>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "vulkanexamplebase.h"
|
||||
#include "VulkanTexture.hpp"
|
||||
|
||||
#define VERTEX_BUFFER_BIND_ID 0
|
||||
#define ENABLE_VALIDATION false
|
||||
|
||||
class VulkanExample : public VulkanExampleBase
|
||||
{
|
||||
public:
|
||||
bool wireframe = false;
|
||||
|
||||
struct {
|
||||
vks::Texture2D colorMap;
|
||||
} textures;
|
||||
|
||||
struct {
|
||||
VkPipelineVertexInputStateCreateInfo inputState;
|
||||
std::vector<VkVertexInputBindingDescription> bindingDescriptions;
|
||||
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
|
||||
} vertices;
|
||||
|
||||
// Vertex layout used in this example
|
||||
// This must fit input locations of the vertex shader used to render the model
|
||||
struct Vertex {
|
||||
glm::vec3 pos;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
// Contains all Vulkan resources required to represent vertex and index buffers for a model
|
||||
// This is for demonstration and learning purposes, the other examples use a model loader class for easy access
|
||||
struct Model {
|
||||
struct {
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
} vertices;
|
||||
struct {
|
||||
int count;
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
} indices;
|
||||
// Destroys all Vulkan resources created for this model
|
||||
void destroy(VkDevice device)
|
||||
{
|
||||
vkDestroyBuffer(device, vertices.buffer, nullptr);
|
||||
vkFreeMemory(device, vertices.memory, nullptr);
|
||||
vkDestroyBuffer(device, indices.buffer, nullptr);
|
||||
vkFreeMemory(device, indices.memory, nullptr);
|
||||
};
|
||||
} model;
|
||||
|
||||
struct {
|
||||
vks::Buffer scene;
|
||||
} uniformBuffers;
|
||||
|
||||
struct {
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 lightPos = glm::vec4(25.0f, 5.0f, 5.0f, 1.0f);
|
||||
} uboVS;
|
||||
|
||||
struct Pipelines {
|
||||
VkPipeline solid;
|
||||
VkPipeline wireframe = VK_NULL_HANDLE;
|
||||
} pipelines;
|
||||
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkDescriptorSet descriptorSet;
|
||||
VkDescriptorSetLayout descriptorSetLayout;
|
||||
|
||||
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
|
||||
{
|
||||
zoom = -5.5f;
|
||||
zoomSpeed = 2.5f;
|
||||
rotationSpeed = 0.5f;
|
||||
rotation = { -0.5f, -112.75f, 0.0f };
|
||||
cameraPos = { 0.1f, 1.1f, 0.0f };
|
||||
title = "Model rendering";
|
||||
settings.overlay = true;
|
||||
}
|
||||
|
||||
~VulkanExample()
|
||||
{
|
||||
// Clean up used Vulkan resources
|
||||
// Note : Inherited destructor cleans up resources stored in base class
|
||||
vkDestroyPipeline(device, pipelines.solid, nullptr);
|
||||
if (pipelines.wireframe != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipelines.wireframe, nullptr);
|
||||
}
|
||||
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
||||
|
||||
model.destroy(device);
|
||||
|
||||
textures.colorMap.destroy();
|
||||
uniformBuffers.scene.destroy();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Set target frame buffer
|
||||
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);
|
||||
|
||||
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
||||
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid);
|
||||
|
||||
VkDeviceSize offsets[1] = { 0 };
|
||||
// Bind mesh vertex buffer
|
||||
vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &model.vertices.buffer, offsets);
|
||||
// Bind mesh index buffer
|
||||
vkCmdBindIndexBuffer(drawCmdBuffers[i], model.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
// Render mesh vertex buffer using its indices
|
||||
vkCmdDrawIndexed(drawCmdBuffers[i], model.indices.count, 1, 0, 0, 0);
|
||||
|
||||
drawUI(drawCmdBuffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(drawCmdBuffers[i]);
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Load a model from file using the ASSIMP model loader and generate all resources required to render the model
|
||||
void loadModel(std::string filename)
|
||||
{
|
||||
// Load the model from file using ASSIMP
|
||||
|
||||
const aiScene* scene;
|
||||
Assimp::Importer Importer;
|
||||
|
||||
// Flags for loading the mesh
|
||||
static const int assimpFlags = aiProcess_FlipWindingOrder | aiProcess_Triangulate | aiProcess_PreTransformVertices;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Meshes are stored inside the apk on Android (compressed)
|
||||
// So they need to be loaded via the asset manager
|
||||
|
||||
AAsset* asset = AAssetManager_open(androidApp->activity->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);
|
||||
|
||||
scene = Importer.ReadFileFromMemory(meshData, size, assimpFlags);
|
||||
|
||||
free(meshData);
|
||||
#else
|
||||
scene = Importer.ReadFile(filename.c_str(), assimpFlags);
|
||||
#endif
|
||||
|
||||
// Generate vertex buffer from ASSIMP scene data
|
||||
float scale = 1.0f;
|
||||
std::vector<Vertex> vertexBuffer;
|
||||
|
||||
// Iterate through all meshes in the file and extract the vertex components
|
||||
for (uint32_t m = 0; m < scene->mNumMeshes; m++)
|
||||
{
|
||||
for (uint32_t v = 0; v < scene->mMeshes[m]->mNumVertices; v++)
|
||||
{
|
||||
Vertex vertex;
|
||||
|
||||
// Use glm make_* functions to convert ASSIMP vectors to glm vectors
|
||||
vertex.pos = glm::make_vec3(&scene->mMeshes[m]->mVertices[v].x) * scale;
|
||||
vertex.normal = glm::make_vec3(&scene->mMeshes[m]->mNormals[v].x);
|
||||
// Texture coordinates and colors may have multiple channels, we only use the first [0] one
|
||||
vertex.uv = glm::make_vec2(&scene->mMeshes[m]->mTextureCoords[0][v].x);
|
||||
// Mesh may not have vertex colors
|
||||
vertex.color = (scene->mMeshes[m]->HasVertexColors(0)) ? glm::make_vec3(&scene->mMeshes[m]->mColors[0][v].r) : glm::vec3(1.0f);
|
||||
|
||||
// Vulkan uses a right-handed NDC (contrary to OpenGL), so simply flip Y-Axis
|
||||
vertex.pos.y *= -1.0f;
|
||||
|
||||
vertexBuffer.push_back(vertex);
|
||||
}
|
||||
}
|
||||
size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex);
|
||||
|
||||
// Generate index buffer from ASSIMP scene data
|
||||
std::vector<uint32_t> indexBuffer;
|
||||
for (uint32_t m = 0; m < scene->mNumMeshes; m++)
|
||||
{
|
||||
uint32_t indexBase = static_cast<uint32_t>(indexBuffer.size());
|
||||
for (uint32_t f = 0; f < scene->mMeshes[m]->mNumFaces; f++)
|
||||
{
|
||||
// We assume that all faces are triangulated
|
||||
for (uint32_t i = 0; i < 3; i++)
|
||||
{
|
||||
indexBuffer.push_back(scene->mMeshes[m]->mFaces[f].mIndices[i] + indexBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
|
||||
model.indices.count = static_cast<uint32_t>(indexBuffer.size());
|
||||
|
||||
// Static mesh should always be device local
|
||||
|
||||
bool useStaging = true;
|
||||
|
||||
if (useStaging)
|
||||
{
|
||||
struct {
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
} vertexStaging, indexStaging;
|
||||
|
||||
// Create staging buffers
|
||||
// Vertex data
|
||||
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
|
||||
// Vertex buffer
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
vertexBufferSize,
|
||||
&model.vertices.buffer,
|
||||
&model.vertices.memory));
|
||||
// Index buffer
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
indexBufferSize,
|
||||
&model.indices.buffer,
|
||||
&model.indices.memory));
|
||||
|
||||
// Copy from staging buffers
|
||||
VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
VkBufferCopy copyRegion = {};
|
||||
|
||||
copyRegion.size = vertexBufferSize;
|
||||
vkCmdCopyBuffer(
|
||||
copyCmd,
|
||||
vertexStaging.buffer,
|
||||
model.vertices.buffer,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
copyRegion.size = indexBufferSize;
|
||||
vkCmdCopyBuffer(
|
||||
copyCmd,
|
||||
indexStaging.buffer,
|
||||
model.indices.buffer,
|
||||
1,
|
||||
©Region);
|
||||
|
||||
VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true);
|
||||
|
||||
vkDestroyBuffer(device, vertexStaging.buffer, nullptr);
|
||||
vkFreeMemory(device, vertexStaging.memory, nullptr);
|
||||
vkDestroyBuffer(device, indexStaging.buffer, nullptr);
|
||||
vkFreeMemory(device, indexStaging.memory, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertex buffer
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
vertexBufferSize,
|
||||
&model.vertices.buffer,
|
||||
&model.vertices.memory,
|
||||
vertexBuffer.data()));
|
||||
// Index buffer
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
indexBufferSize,
|
||||
&model.indices.buffer,
|
||||
&model.indices.memory,
|
||||
indexBuffer.data()));
|
||||
}
|
||||
}
|
||||
|
||||
void loadAssets()
|
||||
{
|
||||
loadModel(getAssetPath() + "models/voyager/voyager.dae");
|
||||
textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_rgba_unorm.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
|
||||
}
|
||||
|
||||
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,
|
||||
offsetof(Vertex, pos));
|
||||
// Location 1 : Normal
|
||||
vertices.attributeDescriptions[1] =
|
||||
vks::initializers::vertexInputAttributeDescription(
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
1,
|
||||
VK_FORMAT_R32G32B32_SFLOAT,
|
||||
offsetof(Vertex, normal));
|
||||
// Location 2 : Texture coordinates
|
||||
vertices.attributeDescriptions[2] =
|
||||
vks::initializers::vertexInputAttributeDescription(
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
2,
|
||||
VK_FORMAT_R32G32_SFLOAT,
|
||||
offsetof(Vertex, uv));
|
||||
// Location 3 : Color
|
||||
vertices.attributeDescriptions[3] =
|
||||
vks::initializers::vertexInputAttributeDescription(
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
3,
|
||||
VK_FORMAT_R32G32B32_SFLOAT,
|
||||
offsetof(Vertex, color));
|
||||
|
||||
vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
||||
vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
|
||||
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
|
||||
vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
|
||||
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
|
||||
}
|
||||
|
||||
void setupDescriptorPool()
|
||||
{
|
||||
// Example uses one ubo and one combined image sampler
|
||||
std::vector<VkDescriptorPoolSize> poolSizes =
|
||||
{
|
||||
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1),
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo =
|
||||
vks::initializers::descriptorPoolCreateInfo(
|
||||
static_cast<uint32_t>(poolSizes.size()),
|
||||
poolSizes.data(),
|
||||
1);
|
||||
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
|
||||
}
|
||||
|
||||
void setupDescriptorSetLayout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vks::initializers::descriptorSetLayoutBinding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0),
|
||||
// Binding 1 : Fragment shader combined sampler
|
||||
vks::initializers::descriptorSetLayoutBinding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptorLayout =
|
||||
vks::initializers::descriptorSetLayoutCreateInfo(
|
||||
setLayoutBindings.data(),
|
||||
static_cast<uint32_t>(setLayoutBindings.size()));
|
||||
|
||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
|
||||
vks::initializers::pipelineLayoutCreateInfo(
|
||||
&descriptorSetLayout,
|
||||
1);
|
||||
|
||||
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
|
||||
}
|
||||
|
||||
void setupDescriptorSet()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
|
||||
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
|
||||
|
||||
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
|
||||
// Binding 1 : Color map
|
||||
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.colorMap.descriptor)
|
||||
};
|
||||
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
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_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<VkDynamicState> dynamicStateEnables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
VkPipelineDynamicStateCreateInfo dynamicState =
|
||||
vks::initializers::pipelineDynamicStateCreateInfo(
|
||||
dynamicStateEnables.data(),
|
||||
static_cast<uint32_t>(dynamicStateEnables.size()),
|
||||
0);
|
||||
|
||||
// Solid rendering pipeline
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
|
||||
|
||||
shaderStages[0] = loadShader(getAssetPath() + "shaders/mesh/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shaderStages[1] = loadShader(getAssetPath() + "shaders/mesh/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
|
||||
vks::initializers::pipelineCreateInfo(
|
||||
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<uint32_t>(shaderStages.size());
|
||||
pipelineCreateInfo.pStages = shaderStages.data();
|
||||
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid));
|
||||
|
||||
// Wire frame rendering pipeline
|
||||
if (deviceFeatures.fillModeNonSolid) {
|
||||
rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
|
||||
rasterizationState.lineWidth = 1.0f;
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.wireframe));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void prepareUniformBuffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
&uniformBuffers.scene,
|
||||
sizeof(uboVS)));
|
||||
|
||||
// Map persistent
|
||||
VK_CHECK_RESULT(uniformBuffers.scene.map());
|
||||
|
||||
updateUniformBuffers();
|
||||
}
|
||||
|
||||
void updateUniformBuffers()
|
||||
{
|
||||
uboVS.projection = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f);
|
||||
glm::mat4 viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
uboVS.model = viewMatrix * glm::translate(glm::mat4(1.0f), cameraPos);
|
||||
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
memcpy(uniformBuffers.scene.mapped, &uboVS, sizeof(uboVS));
|
||||
}
|
||||
|
||||
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 prepare()
|
||||
{
|
||||
VulkanExampleBase::prepare();
|
||||
loadAssets();
|
||||
setupVertexDescriptions();
|
||||
prepareUniformBuffers();
|
||||
setupDescriptorSetLayout();
|
||||
preparePipelines();
|
||||
setupDescriptorPool();
|
||||
setupDescriptorSet();
|
||||
buildCommandBuffers();
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
virtual void render()
|
||||
{
|
||||
if (!prepared)
|
||||
return;
|
||||
draw();
|
||||
if (camera.updated)
|
||||
updateUniformBuffers();
|
||||
}
|
||||
|
||||
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
|
||||
{
|
||||
if (overlay->header("Settings")) {
|
||||
if (overlay->checkBox("Wireframe", &wireframe)) {
|
||||
buildCommandBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VULKAN_EXAMPLE_MAIN()
|
||||
105
external/tinygltf/README.md
vendored
105
external/tinygltf/README.md
vendored
|
|
@ -2,12 +2,16 @@
|
|||
|
||||
`TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
|
||||
|
||||
`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler.
|
||||
If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
||||
|
||||
## Status
|
||||
|
||||
Work in process(`devel` branch). Very near to release, but need more tests and examples.
|
||||
|
||||
`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler.
|
||||
If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
||||
- v2.4.0 Experimental RapidJSON support. Experimental C++14 support(C++14 may give better performance)
|
||||
- v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class)
|
||||
- v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
|
||||
- v2.1.0 release(Draco support)
|
||||
- v2.0.0 release(22 Aug, 2018)!
|
||||
|
||||
## Builds
|
||||
|
||||
|
|
@ -24,45 +28,75 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
|||
* [x] Windows + MinGW
|
||||
* [x] Windows + Visual Studio 2015 Update 3 or later.
|
||||
* Visual Studio 2013 is not supported since they have limited C++11 support and failed to compile `json.hpp`.
|
||||
* [x] Android NDK
|
||||
* [x] Android + CrystaX(NDK drop-in replacement) GCC
|
||||
* [x] Web using Emscripten(LLVM)
|
||||
* Moderate parsing time and memory consumption.
|
||||
* glTF specification v2.0.0
|
||||
* [x] ASCII glTF
|
||||
* [x] Load
|
||||
* [x] Save
|
||||
* [x] Binary glTF(GLB)
|
||||
* [x] PBR material description
|
||||
* [x] Load
|
||||
* [x] Save(.bin embedded .glb)
|
||||
* Buffers
|
||||
* [x] Parse BASE64 encoded embedded buffer fata(DataURI).
|
||||
* [x] Parse BASE64 encoded embedded buffer data(DataURI).
|
||||
* [x] Load `.bin` file.
|
||||
* Image(Using stb_image)
|
||||
* [x] Parse BASE64 encoded embedded image fata(DataURI).
|
||||
* [x] Parse BASE64 encoded embedded image data(DataURI).
|
||||
* [x] Load external image file.
|
||||
* [x] PNG(8bit only)
|
||||
* [x] JPEG(8bit only)
|
||||
* [x] BMP
|
||||
* [x] GIF
|
||||
* [x] Load PNG(8bit and 16bit)
|
||||
* [x] Load JPEG(8bit only)
|
||||
* [x] Load BMP
|
||||
* [x] Load GIF
|
||||
* [x] Custom Image decoder callback(e.g. for decoding OpenEXR image)
|
||||
* Morph traget
|
||||
* [x] Sparse accessor
|
||||
* Load glTF from memory
|
||||
* Custom callback handler
|
||||
* [x] Image load
|
||||
* [x] Image save
|
||||
* Extensions
|
||||
* [x] Draco mesh decoding
|
||||
* [ ] Draco mesh encoding
|
||||
|
||||
## Note on extension property
|
||||
|
||||
In extension(`ExtensionMap`), JSON number value is parsed as int or float(number) and stored as `tinygltf::Value` object. If you want a floating point value from `tinygltf::Value`, use `GetNumberAsDouble()` method.
|
||||
|
||||
`IsNumber()` returns true if the underlying value is an int value or a floating point value.
|
||||
|
||||
## Examples
|
||||
|
||||
* [glview](examples/glview) : Simple glTF geometry viewer.
|
||||
* [validator](examples/validator) : Simple glTF validator with JSON schema.
|
||||
* [basic](examples/basic) : Basic glTF viewer with texturing support.
|
||||
|
||||
## Projects using TinyGLTF
|
||||
|
||||
* px_render Single header C++ Libraries for Thread Scheduling, Rendering, and so on... https://github.com/pplux/px
|
||||
* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
|
||||
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
|
||||
* [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization.
|
||||
* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework
|
||||
* Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2
|
||||
* [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf.
|
||||
* [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux
|
||||
* [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications.
|
||||
* Your projects here! (Please send PR)
|
||||
|
||||
## TODOs
|
||||
|
||||
* [ ] Write C++ code generator from jSON schema for robust parsing.
|
||||
* [x] Serialization
|
||||
* [ ] Compression/decompression(Open3DGC, etc)
|
||||
* [ ] Support `extensions` and `extras` property
|
||||
* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing.
|
||||
* [ ] Mesh Compression/decompression(Open3DGC, etc)
|
||||
* [x] Load Draco compressed mesh
|
||||
* [ ] Save Draco compressed mesh
|
||||
* [ ] Open3DGC?
|
||||
* [x] Support `extensions` and `extras` property
|
||||
* [ ] HDR image?
|
||||
* [ ] OpenEXR extension through TinyEXR.
|
||||
* [ ] Write tests for `animation` and `skin`
|
||||
* [ ] 16bit PNG support in Serialization
|
||||
* [ ] Write example and tests for `animation` and `skin`
|
||||
|
||||
## Licenses
|
||||
|
||||
|
|
@ -92,12 +126,18 @@ Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your pr
|
|||
|
||||
using namespace tinygltf;
|
||||
|
||||
Model model;
|
||||
Model model;
|
||||
TinyGLTF loader;
|
||||
std::string err;
|
||||
|
||||
bool ret = loader.LoadASCIIFromFile(&model, &err, argv[1]);
|
||||
//bool ret = loader.LoadBinaryFromFile(&model, &err, argv[1]); // for binary glTF(.glb)
|
||||
std::string warn;
|
||||
|
||||
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
|
||||
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)
|
||||
|
||||
if (!warn.empty()) {
|
||||
printf("Warn: %s\n", warn.c_str());
|
||||
}
|
||||
|
||||
if (!err.empty()) {
|
||||
printf("Err: %s\n", err.c_str());
|
||||
}
|
||||
|
|
@ -113,16 +153,28 @@ if (!ret) {
|
|||
* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF.
|
||||
* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
|
||||
* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
|
||||
* `TINYGLTF_NO_EXTERNAL_IMAGE` : Do not try to load external image file. This option would be helpful if you do not want to load image files during glTF parsing.
|
||||
* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand.
|
||||
* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file.
|
||||
* `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
|
||||
* `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
|
||||
* `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
|
||||
* `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this featrure.
|
||||
* `TINYGLTF_USE_CPP14` : Use C++14 feature(requires C++14 compiler). This may give better performance than C++11.
|
||||
|
||||
|
||||
### Saving gltTF 2.0 model
|
||||
* [ ] Buffers.
|
||||
|
||||
* Buffers.
|
||||
* [x] To file
|
||||
* [x] Embedded
|
||||
* [ ] Draco compressed?
|
||||
* [x] Images
|
||||
* [x] To file
|
||||
* [x] Embedded
|
||||
* [ ] Binary(.glb)
|
||||
* Binary(.glb)
|
||||
* [x] .bin embedded single .glb
|
||||
* [ ] External .bin
|
||||
|
||||
## Running tests.
|
||||
|
||||
|
|
@ -150,8 +202,17 @@ $ ./tester
|
|||
$ ./tester_noexcept
|
||||
```
|
||||
|
||||
### Fuzzing tests
|
||||
|
||||
See `tests/fuzzer` for details.
|
||||
|
||||
After running fuzzer on Ryzen9 3950X a week, at least `LoadASCIIFromString` looks safe except for out-of-memory error in Fuzzer.
|
||||
We may be better to introduce bounded memory size checking when parsing glTF data.
|
||||
|
||||
## Third party licenses
|
||||
|
||||
* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
|
||||
* stb_image : Public domain.
|
||||
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
|
||||
* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
|
||||
* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html
|
||||
|
|
|
|||
12482
external/tinygltf/json.hpp
vendored
12482
external/tinygltf/json.hpp
vendored
File diff suppressed because it is too large
Load diff
2753
external/tinygltf/stb_image.h
vendored
2753
external/tinygltf/stb_image.h
vendored
File diff suppressed because it is too large
Load diff
5082
external/tinygltf/tiny_gltf.h
vendored
5082
external/tinygltf/tiny_gltf.h
vendored
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue