Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 119-graphicspipeline-refactoring
  • 129-projekte-und-assets-auslagern
  • 132-denoising-module
  • 143-ar-vr-support-via-openxr
  • 43-multi-threading
  • 91-compute-first-network
  • 95-arm64-raspberry-pi-4-support
  • develop
  • master
  • optimizations
  • 0.1.0
  • 0.2.0
12 results

Target

Select target project
  • vulkan2021/vkcv-framework
1 result
Select Git revision
  • 119-graphicspipeline-refactoring
  • 129-projekte-und-assets-auslagern
  • 132-denoising-module
  • 143-ar-vr-support-via-openxr
  • 43-multi-threading
  • 91-compute-first-network
  • 95-arm64-raspberry-pi-4-support
  • develop
  • master
  • optimizations
  • 0.1.0
  • 0.2.0
12 results
Show changes
Commits on Source (154)
Showing
with 733 additions and 335 deletions
<<<<<<< HEAD
=======
>>>>>>> develop
# IDE specific files
.project
.cproject
......@@ -19,3 +15,6 @@ cmake-build-release/
*.exe
*.ilk
*.pdb
# GUI configuration files
imgui.ini
......@@ -14,7 +14,7 @@ build_ubuntu_gcc:
- $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
stage: build
tags:
- ubuntu-gcc
- ubuntu-gcc-cached
variables:
GIT_SUBMODULE_STRATEGY: recursive
timeout: 10m
......@@ -37,11 +37,11 @@ build_win10_msvc:
- $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i
stage: build
tags:
- win10-msvc
- win10-msvc-cached
variables:
GIT_SUBMODULE_STRATEGY: recursive
timeout: 10m
retry: 1
retry: 0
script:
- cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\'
- .\Launch-VsDevShell.ps1
......
......@@ -15,4 +15,10 @@
url = https://github.com/nothings/stb.git
[submodule "modules/camera/lib/glm"]
path = modules/camera/lib/glm
url = https://github.com/g-truc/glm.git
\ No newline at end of file
url = https://github.com/g-truc/glm.git
[submodule "modules/shader_compiler/lib/glslang"]
path = modules/shader_compiler/lib/glslang
url = https://github.com/KhronosGroup/glslang.git
[submodule "modules/gui/lib/imgui"]
path = modules/gui/lib/imgui
url = https://github.com/ocornut/imgui.git
......@@ -41,15 +41,15 @@ if (vkcv_build_debug)
endif()
endif()
# configure everything to use the required dependencies
include(${vkcv_config}/Libraries.cmake)
# add modules as targets
add_subdirectory(modules)
# add source files for compilation
include(${vkcv_config}/Sources.cmake)
# configure everything to use the required dependencies
include(${vkcv_config}/Libraries.cmake)
message("-- Libraries: [ ${vkcv_libraries} ]")
message("-- Flags: [ ${vkcv_flags} ]")
......
......@@ -29,15 +29,18 @@ set(vkcv_sources
${vkcv_source}/vkcv/ImageManager.hpp
${vkcv_source}/vkcv/ImageManager.cpp
${vkcv_include}/vkcv/Logger.hpp
${vkcv_include}/vkcv/SwapChain.hpp
${vkcv_source}/vkcv/SwapChain.cpp
${vkcv_include}/vkcv/Swapchain.hpp
${vkcv_source}/vkcv/Swapchain.cpp
${vkcv_include}/vkcv/ShaderStage.hpp
${vkcv_include}/vkcv/ShaderProgram.hpp
${vkcv_source}/vkcv/ShaderProgram.cpp
${vkcv_include}/vkcv/PipelineConfig.hpp
${vkcv_source}/vkcv/PipelineConfig.cpp
${vkcv_source}/vkcv/PipelineManager.hpp
${vkcv_source}/vkcv/PipelineManager.cpp
......
......@@ -6,9 +6,20 @@ if (spirv-cross_FOUND)
message(${vkcv_config_msg} " SPIRV Cross - " ${SPIRV_CROSS_VERSION})
else()
if (EXISTS "${vkcv_lib_path}/SPIRV-Cross")
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF CACHE INTERNAL "")
set(SPIRV_CROSS_SHARED OFF CACHE INTERNAL "")
set(SPIRV_CROSS_STATIC ON CACHE INTERNAL "")
set(SPIRV_CROSS_CLI OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_GLSL ON CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_HLSL OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_MSL OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_CPP ON CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_REFLECT OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_C_API OFF CACHE INTERNAL "")
set(SPIRV_CROSS_ENABLE_UTIL OFF CACHE INTERNAL "")
set(SPIRV_CROSS_SKIP_INSTALL ON CACHE INTERNAL "")
add_subdirectory(${vkcv_lib}/SPIRV-Cross)
......
......@@ -8,7 +8,7 @@
#include <vulkan/vulkan.hpp>
#include "vkcv/Context.hpp"
#include "vkcv/SwapChain.hpp"
#include "vkcv/Swapchain.hpp"
#include "vkcv/Window.hpp"
#include "vkcv/PassConfig.hpp"
#include "vkcv/Handles.hpp"
......@@ -41,6 +41,7 @@ namespace vkcv
QueueType queueType;
std::vector<vk::Semaphore> waitSemaphores;
std::vector<vk::Semaphore> signalSemaphores;
vk::Fence fence;
};
class Core final
......@@ -52,7 +53,7 @@ namespace vkcv
*
* @param context encapsulates various Vulkan objects
*/
Core(Context &&context, Window &window, const SwapChain& swapChain, std::vector<vk::ImageView> imageViews,
Core(Context &&context, Window &window, const Swapchain& swapChain, std::vector<vk::ImageView> imageViews,
const CommandResources& commandResources, const SyncResources& syncResources) noexcept;
// explicit destruction of default constructor
Core() = delete;
......@@ -61,11 +62,11 @@ namespace vkcv
Context m_Context;
SwapChain m_swapchain;
Swapchain m_swapchain;
std::vector<vk::ImageView> m_swapchainImageViews;
std::vector<vk::Image> m_swapchainImages;
std::vector<vk::ImageLayout> m_swapchainImageLayouts;
const Window& m_window;
Window& m_window;
std::unique_ptr<PassManager> m_PassManager;
std::unique_ptr<PipelineManager> m_PipelineManager;
......@@ -78,10 +79,10 @@ namespace vkcv
CommandResources m_CommandResources;
SyncResources m_SyncResources;
uint32_t m_currentSwapchainImageIndex;
event_handle<int,int> e_resizeHandle;
std::function<void(int, int)> e_resizeHandle;
static std::vector<vk::ImageView> createImageViews( Context &context, SwapChain& swapChain);
static std::vector<vk::ImageView> createImageViews( Context &context, Swapchain& swapChain);
void recordSwapchainImageLayoutTransition(vk::CommandBuffer cmdBuffer, vk::ImageLayout newLayout);
......@@ -123,6 +124,9 @@ namespace vkcv
[[nodiscard]]
const Context &getContext() const;
[[nodiscard]]
const Swapchain& getSwapchain() const;
/**
* Creates a #Core with given @p applicationName and @p applicationVersion for your application.
......@@ -157,6 +161,19 @@ namespace vkcv
[[nodiscard]]
PipelineHandle createGraphicsPipeline(const PipelineConfig &config);
/**
* Creates a basic vulkan compute pipeline using @p shader program and returns it using the @p handle.
* Fixed Functions for pipeline are set with standard values.
*
* @param shader program that hold the compiles compute shader
* @param handle a handle to return the created vulkan handle
* @return True if pipeline creation was successful, False if not
*/
[[nodiscard]]
PipelineHandle createComputePipeline(
const ShaderProgram &config,
const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);
/**
* Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle.
* Fixed Functions for pipeline are set with standard values.
......@@ -211,7 +228,7 @@ namespace vkcv
*/
[[nodiscard]]
DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &bindings);
void writeResourceDescription(DescriptorSetHandle handle, size_t setIndex, const DescriptorWrites& writes);
void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites& writes);
DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
......@@ -228,13 +245,18 @@ namespace vkcv
const std::vector<DrawcallInfo> &drawcalls,
const std::vector<ImageHandle> &renderTargets);
void recordComputeDispatchToCmdStream(
CommandStreamHandle cmdStream,
PipelineHandle computePipeline,
const uint32_t dispatchCount[3],
const std::vector<DescriptorSetUsage> &descriptorSetUsages,
const PushConstantData& pushConstantData);
/**
* @brief end recording and present image
*/
void endFrame();
vk::Format getSwapchainImageFormat();
/**
* Submit a command buffer to any queue of selected type. The recording can be customized by a
* custom record-command-function. If the command submission has finished, an optional finish-function
......@@ -259,5 +281,8 @@ namespace vkcv
void submitCommandStream(const CommandStreamHandle handle);
void prepareSwapchainImageForPresent(const CommandStreamHandle handle);
void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image);
const vk::ImageView& getSwapchainImageView() const;
};
}
#pragma once
#include <vkcv/ShaderProgram.hpp>
#include <vkcv/Handles.hpp>
#include <vulkan/vulkan.hpp>
#include "vkcv/Handles.hpp"
#include "vkcv/ShaderStage.hpp"
namespace vkcv
{
struct DescriptorSet
......@@ -33,11 +35,13 @@ namespace vkcv
struct DescriptorBinding
{
DescriptorBinding(
uint32_t bindingID,
DescriptorType descriptorType,
uint32_t descriptorCount,
ShaderStage shaderStage
) noexcept;
uint32_t bindingID;
DescriptorType descriptorType;
uint32_t descriptorCount;
ShaderStage shaderStage;
......
......@@ -3,10 +3,18 @@
#include <functional>
namespace vkcv {
template<typename... T>
struct event_handle {
uint32_t id;
};
template<typename... T>
struct event_function {
typedef std::function<void(T...)> type;
event_handle<T...> handle;
type callback;
};
/**
......@@ -16,7 +24,8 @@ namespace vkcv {
template<typename... T>
struct event {
private:
std::vector<typename event_function<T...>::type> m_handles;
std::vector< event_function<T...> > m_functions;
uint32_t m_id_counter;
public:
......@@ -25,28 +34,34 @@ namespace vkcv {
* @param arguments of the given function
*/
void operator()(T... arguments) {
for (auto &handle : this->m_handles) {
handle(arguments...);
for (auto &function : this->m_functions) {
function.callback(arguments...);
}
}
/**
* adds a function handle to the event to be called
* @param handle of the function
* @param callback of the function
* @return handle of the function
*/
typename event_function<T...>::type add(typename event_function<T...>::type handle) {
this->m_handles.push_back(handle);
return handle;
event_handle<T...> add(typename event_function<T...>::type callback) {
event_function<T...> function;
function.handle = { m_id_counter++ };
function.callback = callback;
this->m_functions.push_back(function);
return function.handle;
}
/**
* removes a function handle of the event
* @param handle of the function
*/
void remove(typename event_function<T...>::type handle) {
this->m_handles.erase(
remove(this->m_handles.begin(), this->m_handles.end(), handle),
this->m_handles.end()
void remove(event_handle<T...> handle) {
this->m_functions.erase(
std::remove_if(this->m_functions.begin(), this->m_functions.end(), [&handle](auto function){
return (handle.id == function.handle.id);
}),
this->m_functions.end()
);
}
......
......@@ -29,9 +29,6 @@ namespace vkcv {
[[nodiscard]]
uint32_t getDepth() const;
[[nodiscard]]
vk::ImageLayout getLayout() const;
[[nodiscard]]
vkcv::ImageHandle getHandle() const;
......
#pragma once
#include <iostream>
namespace vkcv {
enum class LogLevel {
INFO,
WARNING,
ERROR
};
constexpr auto getLogOutput(LogLevel level) {
switch (level) {
case LogLevel::INFO:
return stdout;
default:
return stderr;
}
}
constexpr const char* getLogName(LogLevel level) {
switch (level) {
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
default:
return "UNKNOWN";
}
}
#ifndef NDEBUG
#ifndef VKCV_DEBUG_MESSAGE_LEN
#define VKCV_DEBUG_MESSAGE_LEN 1024
#endif
#ifdef _MSC_VER
#define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
#define vkcv_log(level, ...) { \
char output_message [ \
VKCV_DEBUG_MESSAGE_LEN \
]; \
std::snprintf( \
output_message, \
VKCV_DEBUG_MESSAGE_LEN, \
__VA_ARGS__ \
); \
std::fprintf( \
getLogOutput(level), \
"[%s]: %s [%s, line %d: %s]\n", \
vkcv::getLogName(level), \
output_message, \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__ \
); \
}
#else
#define vkcv_log(level, ...) {}
#endif
}
......@@ -7,38 +7,20 @@
#include <vector>
#include <cstdint>
#include "vkcv/Handles.hpp"
#include "Handles.hpp"
#include "ShaderProgram.hpp"
#include <vkcv/VertexLayout.hpp>
#include "VertexLayout.hpp"
namespace vkcv {
struct PipelineConfig {
/**
* Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the
* dimensions of the application window @p width and @p height. A handle for the Render Pass is also needed, @p passHandle.
*
* @param shaderProgram shaders of the pipeline
* @param height height of the application window
* @param width width of the application window
* @param passHandle handle for Render Pass
*/
PipelineConfig(
const ShaderProgram& shaderProgram,
uint32_t width,
uint32_t height,
const PassHandle &passHandle,
const std::vector<VertexAttribute> &vertexAttributes,
const std::vector<vk::DescriptorSetLayout> &descriptorLayouts,
bool useDynamicViewport);
ShaderProgram m_ShaderProgram;
uint32_t m_Height;
uint32_t m_Width;
PassHandle m_PassHandle;
std::vector<VertexAttribute> m_VertexAttributes;
std::vector<vk::DescriptorSetLayout> m_DescriptorLayouts;
bool m_UseDynamicViewport;
ShaderProgram m_ShaderProgram;
uint32_t m_Width;
uint32_t m_Height;
PassHandle m_PassHandle;
VertexLayout m_VertexLayout;
std::vector<vk::DescriptorSetLayout> m_DescriptorLayouts;
bool m_UseDynamicViewport;
};
......
......@@ -8,23 +8,16 @@
#include <unordered_map>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <filesystem>
#include <vulkan/vulkan.hpp>
#include <spirv_cross.hpp>
#include "vkcv/VertexLayout.hpp"
#include "VertexLayout.hpp"
#include "ShaderStage.hpp"
#include "DescriptorConfig.hpp"
namespace vkcv {
enum class ShaderStage
{
VERTEX,
TESS_CONTROL,
TESS_EVAL,
GEOMETRY,
FRAGMENT,
COMPUTE
};
struct Shader
{
std::vector<char> shaderCode;
......@@ -55,15 +48,24 @@ namespace vkcv {
bool existsShader(ShaderStage shaderStage) const;
void reflectShader(ShaderStage shaderStage);
const VertexLayout &getVertexLayout() const;
const std::vector<VertexAttachment> &getVertexAttachments() const;
size_t getPushConstantSize() const;
const std::vector<std::vector<DescriptorBinding>>& getReflectedDescriptors() const;
private:
/**
* Called after successfully adding a shader to the program.
* Fills vertex input attachments and descriptor sets (if present).
* @param shaderStage the stage to reflect data from
*/
void reflectShader(ShaderStage shaderStage);
std::unordered_map<ShaderStage, Shader> m_Shaders;
VertexLayout m_VertexLayout;
// contains all vertex input attachments used in the vertex buffer
std::vector<VertexAttachment> m_VertexAttachments;
std::vector<std::vector<DescriptorBinding>> m_DescriptorSets;
size_t m_pushConstantSize = 0;
};
}
#pragma once
namespace vkcv {
enum class ShaderStage
{
VERTEX,
TESS_CONTROL,
TESS_EVAL,
GEOMETRY,
FRAGMENT,
COMPUTE
};
}
......@@ -7,8 +7,9 @@
namespace vkcv
{
class SwapChain final {
class Swapchain final {
private:
friend class Core;
struct Surface
{
......@@ -21,10 +22,10 @@ namespace vkcv
Surface m_Surface;
vk::SwapchainKHR m_Swapchain;
vk::Format m_SwapchainFormat;
vk::ColorSpaceKHR m_SwapchainColorSpace;
vk::PresentModeKHR m_SwapchainPresentMode;
uint32_t m_SwapchainImageCount;
vk::Format m_Format;
vk::ColorSpaceKHR m_ColorSpace;
vk::PresentModeKHR m_PresentMode;
uint32_t m_ImageCount;
vk::Extent2D m_Extent;
......@@ -39,16 +40,36 @@ namespace vkcv
* @param format
*/
// TODO:
SwapChain(const Surface &surface,
Swapchain(const Surface &surface,
vk::SwapchainKHR swapchain,
vk::Format format,
vk::ColorSpaceKHR colorSpace,
vk::PresentModeKHR presentMode,
uint32_t imageCount,
vk::Extent2D extent) noexcept;
/**
* TODO
*
* @return
*/
bool shouldUpdateSwapchain() const;
/**
* TODO
*
* context
* window
*/
void updateSwapchain(const Context &context, const Window &window);
/**
*
*/
void signalSwapchainRecreation();
public:
SwapChain(const SwapChain& other);
Swapchain(const Swapchain& other);
/**
* @return The swapchain linked with the #SwapChain class
......@@ -69,7 +90,7 @@ namespace vkcv
* @return gets the chosen swapchain format
*/
[[nodiscard]]
vk::Format getSwapchainFormat() const;
vk::Format getFormat() const;
/**
* creates a swap chain object out of the given window and the given context
......@@ -77,37 +98,17 @@ namespace vkcv
* @param context of the application
* @return returns an object of swapChain
*/
static SwapChain create(const Window &window, const Context &context);
static Swapchain create(const Window &window, const Context &context);
/**
* Destructor of SwapChain
*/
virtual ~SwapChain();
virtual ~Swapchain();
/**
* @return number of images in swapchain
*/
uint32_t getImageCount();
/**
* TODO
*
* @return
*/
bool shouldUpdateSwapchain() const;
/**
* TODO
*
* context
* window
*/
void updateSwapchain(const Context &context, const Window &window);
/**
*
*/
void signalSwapchainRecreation();
uint32_t getImageCount() const;
/**
* TODO
......
#pragma once
#include <unordered_map>
#include <vector>
#include <iostream>
#include <string>
namespace vkcv{
/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
enum class PrimitiveType : uint32_t {
UNDEFINED = 0,
POSITION = 1,
NORMAL = 2,
TEXCOORD_0 = 3
};
/* This struct describes one vertex attribute of a vertex buffer. */
typedef struct {
PrimitiveType type; // POSITION, NORMAL, ...
uint32_t offset; // offset in bytes
uint32_t length; // length of ... in bytes
uint32_t stride; // stride in bytes
uint16_t componentType; // eg. 5126 for float
uint8_t componentCount; // eg. 3 for vec3
} VertexAttribute;
enum class VertexFormat{
enum class VertexAttachmentFormat{
FLOAT,
FLOAT2,
FLOAT3,
......@@ -34,22 +16,51 @@ namespace vkcv{
INT4
};
uint32_t getFormatSize(VertexFormat format);
uint32_t getFormatSize(VertexAttachmentFormat format);
struct VertexInputAttachment{
VertexInputAttachment() = delete;
VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept;
struct VertexAttachment{
friend struct VertexBinding;
/**
* Describes an individual vertex input attribute/attachment.
* @param inputLocation its location in the vertex shader.
* @param name the name referred to in the shader.
* @param format the format (and therefore, the size) this attachment is in.
* The offset is calculated when a collection of attachments forms a binding, hence the friend declaration.
*/
VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept;
VertexAttachment() = delete;
uint32_t location;
uint32_t binding;
VertexFormat format;
uint32_t offset;
uint32_t inputLocation;
std::string name;
VertexAttachmentFormat format;
uint32_t offset;
};
struct VertexBinding{
/**
* Describes all vertex input attachments _one_ buffer contains to create a vertex buffer binding.
* NOTE: multiple vertex layouts may contain various (mutually exclusive) vertex input attachments
* to form one complete vertex buffer binding!
* @param bindingLocation its entry in the buffers that make up the whole vertex buffer.
* @param attachments the vertex input attachments this specific buffer layout contains.
*/
VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept;
VertexBinding() = delete;
uint32_t bindingLocation;
uint32_t stride;
std::vector<VertexAttachment> vertexAttachments;
};
struct VertexLayout{
/**
* Describes the complete layout of one vertex, e.g. all of the vertex input attachments used,
* and all of the buffer bindings that refer to the attachments (for when multiple buffers are used).
* @param bindings bindings the complete vertex buffer is comprised of.
*/
VertexLayout() noexcept;
VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept;
std::unordered_map<uint32_t, VertexInputAttachment> attachmentMap;
uint32_t stride;
VertexLayout(const std::vector<VertexBinding> &bindings) noexcept;
std::vector<VertexBinding> vertexBindings;
};
}
\ No newline at end of file
}
......@@ -13,16 +13,19 @@ struct GLFWwindow;
namespace vkcv {
class Window final {
private:
GLFWwindow *m_window;
/**
class Window {
protected:
GLFWwindow *m_window;
/**
*
* @param GLFWwindow of the class
*/
explicit Window(GLFWwindow *window);
explicit Window(GLFWwindow *window);
static GLFWwindow* createGLFWWindow(const char *windowTitle, int width, int height, bool resizable);
private:
/**
* mouse callback for moving the mouse on the screen
* @param[in] window The window that received the event.
......@@ -58,6 +61,13 @@ namespace vkcv {
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
*/
static void onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods);
/**
* char callback for any typed character
* @param[in] window The window that received the event
* @param[in] c The character that got typed
*/
static void onCharEvent(GLFWwindow *callbackWindow, unsigned int c);
public:
/**
......@@ -95,6 +105,7 @@ namespace vkcv {
event< double, double > e_mouseScroll;
event< int, int > e_resize;
event< int, int, int, int > e_key;
event< unsigned int > e_char;
/**
* returns the current window
......
......@@ -2,4 +2,6 @@
# Add new modules here:
add_subdirectory(asset_loader)
add_subdirectory(camera)
add_subdirectory(gui)
add_subdirectory(shader_compiler)
add_subdirectory(testing)
#pragma once
/**
* @authors Trevor Hollmann
* @authors Trevor Hollmann, Mara Vogt, Susanne Dötsch
* @file include/vkcv/asset/asset_loader.h
* @brief Interface of the asset loader module for the vkcv framework.
*/
#include <string>
#include <vector>
#include <array>
#include <cstdint>
#include <vkcv/VertexLayout.hpp>
/* These macros define limits of the following structs. Implementations can
/** These macros define limits of the following structs. Implementations can
* test against these limits when performing sanity checks. The main constraint
* expressed is that of the data type: Material indices are identified by a
* uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
......@@ -20,17 +20,18 @@
#define MAX_MATERIALS_PER_MESH UINT8_MAX
#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
/* LOADING MESHES
/** LOADING MESHES
* The description of meshes is a hierarchy of structures with the Mesh at the
* top.
*
* Each Mesh has an array of one or more vertex groups (called "primitives" in
* glTF parlance) and an array of zero or more Materials.
* glTF parlance). Specifically, it has an array of indices into an array of
* vertex groups defined by the Scene struct.
*
* Each vertex group describes a part of the meshes vertices by defining how
* they should be rendered (as points, lines, triangles), how many indices and
* vertices there are, how the content of the vertex buffer is to be
* interpreted and which material from the Meshes materials array should be
* interpreted and which material from the Scenes materials array should be
* used for the surface of the vertices.
* As a bonus there is also the axis aligned bounding box of the vertices.
*
......@@ -44,20 +45,99 @@
namespace vkcv::asset {
/* This enum matches modes in fx-gltf, the library returns a standard mode
/** This enum matches modes in fx-gltf, the library returns a standard mode
* (TRIANGLES) if no mode is given in the file. */
enum PrimitiveMode {
enum class PrimitiveMode : uint8_t {
POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
TRIANGLEFAN
};
/* The indices in the index buffer can be of different bit width. */
enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
/** The indices in the index buffer can be of different bit width. */
enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
typedef struct {
// TODO define struct for samplers (low priority)
// NOTE: glTF defines samplers based on OpenGL, which can not be
// directly translated to Vulkan. Specifically, OpenGL (and glTF)
// define a different set of Min/Mag-filters than Vulkan.
} Sampler;
/** struct for defining the loaded texture */
typedef struct {
// TODO not yet needed for the first (unlit) triangle
int sampler; // index into the sampler array of the Scene
uint8_t channels; // number of channels
uint16_t w, h; // width and height of the texture
std::vector<uint8_t> data; // binary data of the decoded texture
} Texture;
/** The asset loader module only supports the PBR-MetallicRoughness model for
* materials.*/
typedef struct {
uint16_t textureMask; // bit mask with active texture targets
// Indices into the Array.textures array
int baseColor, metalRough, normal, occlusion, emissive;
// Scaling factors for each texture target
struct { float r, g, b, a; } baseColorFactor;
float metallicFactor, roughnessFactor;
float normalScale;
float occlusionStrength;
struct { float r, g, b; } emissiveFactor;
} Material;
/* This struct represents one (possibly the only) part of a mesh. There is
/** Flags for the bit-mask in the Material struct. To check if a material has a
* certain texture target, you can use the hasTexture() function below, passing
* the material struct and the enum. */
enum class PBRTextureTarget {
baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
};
/** This macro translates the index of an enum in the defined order to an
* integer with a single bit set in the corresponding place. It is used for
* working with the bitmask of texture targets ("textureMask") in the Material
* struct:
* Material mat = ...;
* if (mat.textureMask & bitflag(PBRTextureTarget::baseColor)) {...}
* However, this logic is also encapsulated in the convenience-function
* materialHasTexture() so users of the asset loader module can avoid direct
* contact with bit-level operations. */
#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))
/** To signal that a certain texture target is active in a Material struct, its
* bit is set in the textureMask. You can use this function to check that:
* Material mat = ...;
* if (materialHasTexture(&mat, baseColor)) {...} */
bool materialHasTexture(const Material *const m, const PBRTextureTarget t);
/** With these enums, 0 is reserved to signal uninitialized or invalid data. */
enum class PrimitiveType : uint32_t {
UNDEFINED = 0,
POSITION = 1,
NORMAL = 2,
TEXCOORD_0 = 3,
TEXCOORD_1 = 4
};
/** These integer values are used the same way in OpenGL, Vulkan and glTF. This
* enum is not needed for translation, it's only for the programmers
* convenience (easier to read in if/switch statements etc). While this enum
* exists in (almost) the same definition in the fx-gltf library, we want to
* avoid exposing that dependency, thus it is re-defined here. */
enum class ComponentType : uint16_t {
NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123,
UINT32 = 5125, FLOAT32 = 5126
};
/** This struct describes one vertex attribute of a vertex buffer. */
typedef struct {
PrimitiveType type; // POSITION, NORMAL, ...
uint32_t offset; // offset in bytes
uint32_t length; // length of ... in bytes
uint32_t stride; // stride in bytes
ComponentType componentType; // eg. 5126 for float
uint8_t componentCount; // eg. 3 for vec3
} VertexAttribute;
/** This struct represents one (possibly the only) part of a mesh. There is
* always one vertexBuffer and zero or one indexBuffer (indexed rendering is
* common but not always used). If there is no index buffer, this is indicated
* by indexBuffer.data being empty. Each vertex buffer can have one or more
......@@ -71,38 +151,43 @@ typedef struct {
} indexBuffer;
struct {
std::vector<uint8_t> data; // binary data of the vertex buffer
std::vector<VertexAttribute> attributes;
std::vector<VertexAttribute> attributes; // description of one
} vertexBuffer;
struct { float x, y, z; } min; // bounding box lower left
struct { float x, y, z; } max; // bounding box upper right
uint8_t materialIndex; // index to one of the meshes materials
int materialIndex; // index to one of the materials
} VertexGroup;
/* This struct represents a single mesh loaded from a glTF file. It consists of
* at least one VertexVroup and any number of Materials. */
/** This struct represents a single mesh as it was loaded from a glTF file. It
* consists of at least one VertexGroup, which then references other resources
* such as Materials. */
typedef struct {
std::string name;
std::vector<VertexGroup> vertexGroups;
std::vector<Material> materials;
// FIXME Dirty hack to get one(!) texture for our cube demo
// hardcoded to always have RGBA channel layout
struct {
int w, h, ch; // width, height and channels of image
uint8_t *img; // raw bytes, free after use (deal with it)
} texture_hack;
std::array<float, 16> modelMatrix;
std::vector<int> vertexGroups;
} Mesh;
/** The scene struct is simply a collection of objects in the scene as well as
* the resources used by those objects.
* For now the only type of object are the meshes and they are represented in a
* flat array.
* Note that parent-child relations are not yet possible. */
typedef struct {
std::vector<Mesh> meshes;
std::vector<VertexGroup> vertexGroups;
std::vector<Material> materials;
std::vector<Texture> textures;
std::vector<Sampler> samplers;
} Scene;
/**
* In its first iteration the asset loader module will only allow loading
* single meshes, one per glTF file.
* It will later be extended to allow loading entire scenes from glTF files.
* Load every mesh from the glTF file, as well as materials and textures.
*
* @param path must be the path to a glTF file containing a single mesh.
* @param mesh is a reference to a Mesh struct that will be filled with the
* @param path must be the path to a glTF or glb file.
* @param scene is a reference to a Scene struct that will be filled with the
* content of the glTF file being loaded.
* */
int loadMesh(const std::string &path, Mesh &mesh);
int loadScene(const std::string &path, Scene &scene);
}
......@@ -5,7 +5,10 @@
#include <fx/gltf.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_JPEG
#define STBI_ONLY_PNG
#include <stb_image.h>
#include <vkcv/Logger.hpp>
#include <algorithm>
namespace vkcv::asset {
......@@ -39,170 +42,328 @@ uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) {
* @param path path to file that is responsible for error
*/
void print_what (const std::exception& e, const std::string &path) {
fprintf(stderr, "ERROR loading file %s: %s\n", path.c_str(), e.what());
vkcv_log(LogLevel::ERROR, "Loading file %s: %s",
path.c_str(), e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
std::cerr << "nested: ";
print_what(nested, path);
}
}
int loadMesh(const std::string &path, Mesh &mesh) {
fx::gltf::Document object;
try {
if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
object = fx::gltf::LoadFromBinary(path);
} else {
object = fx::gltf::LoadFromText(path);
}
} catch (const std::system_error &err) {
print_what(err, path);
return 0;
} catch (const std::exception &e) {
print_what(e, path);
return 0;
}
// TODO Temporary restriction: Only one mesh per glTF file allowed
// currently. Later, we want to support whole scenes with more than
// just meshes.
if (object.meshes.size() != 1) return 0;
fx::gltf::Mesh const &objectMesh = object.meshes[0];
// TODO We want to support more than one vertex group per mesh
// eventually... right now this is hard-coded to use only the first one
// because we only care about the example triangle and cube
fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[0];
fx::gltf::Accessor posAccessor;
std::vector<VertexAttribute> vertexAttributes;
vertexAttributes.reserve(objectPrimitive.attributes.size());
for (auto const & attrib : objectPrimitive.attributes) {
fx::gltf::Accessor accessor = object.accessors[attrib.second];
VertexAttribute attribute;
if (attrib.first == "POSITION") {
attribute.type = PrimitiveType::POSITION;
posAccessor = accessor;
} else if (attrib.first == "NORMAL") {
attribute.type = PrimitiveType::NORMAL;
} else if (attrib.first == "TEXCOORD_0") {
attribute.type = PrimitiveType::TEXCOORD_0;
} else {
return 0;
}
attribute.offset = object.bufferViews[accessor.bufferView].byteOffset;
attribute.length = object.bufferViews[accessor.bufferView].byteLength;
attribute.stride = object.bufferViews[accessor.bufferView].byteStride;
attribute.componentType = static_cast<uint16_t>(accessor.componentType);
if (convertTypeToInt(accessor.type) != 10) {
attribute.componentCount = convertTypeToInt(accessor.type);
} else {
return 0;
}
vertexAttributes.push_back(attribute);
}
// TODO consider the case where there is no index buffer (not all
// meshes have to use indexed rendering)
const fx::gltf::Accessor &indexAccessor = object.accessors[objectPrimitive.indices];
const fx::gltf::BufferView &indexBufferView = object.bufferViews[indexAccessor.bufferView];
const fx::gltf::Buffer &indexBuffer = object.buffers[indexBufferView.buffer];
std::vector<uint8_t> indexBufferData;
indexBufferData.resize(indexBufferView.byteLength);
{
const size_t off = indexBufferView.byteOffset;
const void *const ptr = ((char*)indexBuffer.data.data()) + off;
if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
std::cerr << "ERROR copying index buffer data.\n";
return 0;
}
}
const fx::gltf::BufferView& vertexBufferView = object.bufferViews[posAccessor.bufferView];
const fx::gltf::Buffer& vertexBuffer = object.buffers[vertexBufferView.buffer];
// FIXME: This only works when all vertex attributes are in one buffer
std::vector<uint8_t> vertexBufferData;
vertexBufferData.resize(vertexBuffer.byteLength);
{
const size_t off = 0;
const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
if (!memcpy(vertexBufferData.data(), ptr, vertexBuffer.byteLength)) {
std::cerr << "ERROR copying vertex buffer data.\n";
return 0;
}
}
IndexType indexType;
switch(indexAccessor.componentType) {
/** Translate the component type used in the index accessor of fx-gltf to our
* enum for index type. The reason we have defined an incompatible enum that
* needs translation is that only a subset of component types is valid for
* indices and we want to catch these incompatibilities here. */
enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t)
{
switch (t) {
case fx::gltf::Accessor::ComponentType::UnsignedByte:
indexType = UINT8; break;
return IndexType::UINT8;
case fx::gltf::Accessor::ComponentType::UnsignedShort:
indexType = UINT16; break;
return IndexType::UINT16;
case fx::gltf::Accessor::ComponentType::UnsignedInt:
indexType = UINT32; break;
return IndexType::UINT32;
default:
std::cerr << "ERROR: Index type not supported: " <<
static_cast<uint16_t>(indexAccessor.componentType) <<
std::endl;
return 0;
std::cerr << "ERROR: Index type not supported: " <<
static_cast<uint16_t>(t) << std::endl;
return IndexType::UNDEFINED;
}
}
const size_t numVertexGroups = objectMesh.primitives.size();
std::vector<VertexGroup> vertexGroups;
vertexGroups.reserve(numVertexGroups);
vertexGroups.push_back({
static_cast<PrimitiveMode>(objectPrimitive.mode),
object.accessors[objectPrimitive.indices].count,
posAccessor.count,
{indexType, indexBufferData},
{vertexBufferData, vertexAttributes},
{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
static_cast<uint8_t>(objectPrimitive.material)
});
std::vector<Material> materials;
mesh = {
object.meshes[0].name,
vertexGroups,
materials,
0, 0, 0, NULL
};
// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
// fail quietly if there is no texture
if (object.textures.size()) {
const std::string mime_type("image/jpeg");
const fx::gltf::Texture &tex = object.textures[0];
const fx::gltf::Image &img = object.images[tex.source];
#ifndef NDEBUG
printf("texture name=%s sampler=%u source=%u\n",
tex.name.c_str(), tex.sampler, tex.source);
printf("image name=%s uri=%s mime=%s\n", img.name.c_str(),
img.uri.c_str(), img.mimeType.c_str());
#endif
size_t pos = path.find_last_of("/");
auto dir = path.substr(0, pos);
mesh.texture_hack.img = stbi_load((dir + "/" + img.uri).c_str(),
&mesh.texture_hack.w, &mesh.texture_hack.h,
&mesh.texture_hack.ch, 4);
}
// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
return 1;
/**
* This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
* @param translation possible translation vector (default 0,0,0)
* @param scale possible scale vector (default 1,1,1)
* @param rotation possible rotation, given in quaternion (default 0,0,0,1)
* @param matrix possible modelmatrix (default identity)
* @return model Matrix as an array of floats
*/
std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
std::array<float, 16> modelMatrix = {1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1};
if (matrix != modelMatrix){
return matrix;
} else {
// translation
modelMatrix[3] = translation[0];
modelMatrix[7] = translation[1];
modelMatrix[11] = translation[2];
// rotation and scale
auto a = rotation[0];
auto q1 = rotation[1];
auto q2 = rotation[2];
auto q3 = rotation[3];
modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
// flip y, because GLTF uses y up, but vulkan -y up
modelMatrix[5] *= -1;
return modelMatrix;
}
}
bool materialHasTexture(const Material *const m, const PBRTextureTarget t)
{
return m->textureMask & bitflag(t);
}
int loadScene(const std::string &path, Scene &scene){
fx::gltf::Document sceneObjects;
try {
if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
sceneObjects = fx::gltf::LoadFromBinary(path);
} else {
sceneObjects = fx::gltf::LoadFromText(path);
}
} catch (const std::system_error &err) {
print_what(err, path);
return 0;
} catch (const std::exception &e) {
print_what(e, path);
return 0;
}
size_t pos = path.find_last_of("/");
auto dir = path.substr(0, pos);
// file has to contain at least one mesh
if (sceneObjects.meshes.size() == 0) return 0;
fx::gltf::Accessor posAccessor;
std::vector<VertexAttribute> vertexAttributes;
std::vector<Material> materials;
std::vector<Texture> textures;
std::vector<Sampler> samplers;
std::vector<Mesh> meshes;
std::vector<VertexGroup> vertexGroups;
int groupCount = 0;
Mesh mesh = {};
for(int i = 0; i < sceneObjects.meshes.size(); i++){
std::vector<int> vertexGroupsIndices;
fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
for(int j = 0; j < objectMesh.primitives.size(); j++){
fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j];
vertexAttributes.clear();
vertexAttributes.reserve(objectPrimitive.attributes.size());
for (auto const & attrib : objectPrimitive.attributes) {
fx::gltf::Accessor accessor = sceneObjects.accessors[attrib.second];
VertexAttribute attribute;
if (attrib.first == "POSITION") {
attribute.type = PrimitiveType::POSITION;
posAccessor = accessor;
} else if (attrib.first == "NORMAL") {
attribute.type = PrimitiveType::NORMAL;
} else if (attrib.first == "TEXCOORD_0") {
attribute.type = PrimitiveType::TEXCOORD_0;
} else if (attrib.first == "TEXCOORD_1") {
attribute.type = PrimitiveType::TEXCOORD_1;
} else {
return 0;
}
attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset;
attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength;
attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride;
attribute.componentType = static_cast<ComponentType>(accessor.componentType);
if (convertTypeToInt(accessor.type) != 10) {
attribute.componentCount = convertTypeToInt(accessor.type);
} else {
return 0;
}
vertexAttributes.push_back(attribute);
}
IndexType indexType;
std::vector<uint8_t> indexBufferData = {};
if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf
const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
indexBufferData.resize(indexBufferView.byteLength);
{
const size_t off = indexBufferView.byteOffset;
const void *const ptr = ((char*)indexBuffer.data.data()) + off;
if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
vkcv_log(LogLevel::ERROR, "Copying index buffer data");
return 0;
}
}
indexType = getIndexType(indexAccessor.componentType);
if (indexType == IndexType::UNDEFINED){
vkcv_log(LogLevel::ERROR, "Index Type undefined.");
return 0;
}
}
const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
// only copy relevant part of vertex data
uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
uint32_t relevantBufferEnd = 0;
for (const auto &attribute : vertexAttributes) {
relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
const uint32_t attributeEnd = attribute.offset + attribute.length;
relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd); // TODO: need to incorporate stride?
}
const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
// FIXME: This only works when all vertex attributes are in one buffer
std::vector<uint8_t> vertexBufferData;
vertexBufferData.resize(relevantBufferSize);
{
const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
return 0;
}
}
// make vertex attributes relative to copied section
for (auto &attribute : vertexAttributes) {
attribute.offset -= relevantBufferOffset;
}
const size_t numVertexGroups = objectMesh.primitives.size();
vertexGroups.reserve(numVertexGroups);
vertexGroups.push_back({
static_cast<PrimitiveMode>(objectPrimitive.mode),
sceneObjects.accessors[objectPrimitive.indices].count,
posAccessor.count,
{indexType, indexBufferData},
{vertexBufferData, vertexAttributes},
{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
static_cast<uint8_t>(objectPrimitive.material)
});
vertexGroupsIndices.push_back(groupCount);
groupCount++;
}
mesh.name = sceneObjects.meshes[i].name;
mesh.vertexGroups = vertexGroupsIndices;
meshes.push_back(mesh);
}
for(int m = 0; m < sceneObjects.nodes.size(); m++) {
meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation,
sceneObjects.nodes[m].scale,
sceneObjects.nodes[m].rotation,
sceneObjects.nodes[m].matrix);
}
if (sceneObjects.textures.size() > 0){
textures.reserve(sceneObjects.textures.size());
for(int k = 0; k < sceneObjects.textures.size(); k++){
const fx::gltf::Texture &tex = sceneObjects.textures[k];
const fx::gltf::Image &img = sceneObjects.images[tex.source];
std::string img_uri = dir + "/" + img.uri;
int w, h, c;
uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
c = 4; // FIXME hardcoded to always have RGBA channel layout
if (!data) {
vkcv_log(LogLevel::ERROR, "Loading texture image data.")
return 0;
}
const size_t byteLen = w * h * c;
std::vector<uint8_t> imgdata;
imgdata.resize(byteLen);
if (!memcpy(imgdata.data(), data, byteLen)) {
vkcv_log(LogLevel::ERROR, "Copying texture image data")
free(data);
return 0;
}
free(data);
textures.push_back({
0,
static_cast<uint8_t>(c),
static_cast<uint16_t>(w),
static_cast<uint16_t>(h),
imgdata
});
}
}
if (sceneObjects.materials.size() > 0){
materials.reserve(sceneObjects.materials.size());
for (int l = 0; l < sceneObjects.materials.size(); l++){
fx::gltf::Material material = sceneObjects.materials[l];
// TODO I think we shouldn't set the index for a texture target if
// it isn't defined. So we need to test first if there is a normal
// texture before assigning material.normalTexture.index.
// About the bitmask: If a normal texture is there, modify the
// materials textureMask like this:
// mat.textureMask |= bitflag(asset::normal);
materials.push_back({
0,
material.pbrMetallicRoughness.baseColorTexture.index,
material.pbrMetallicRoughness.metallicRoughnessTexture.index,
material.normalTexture.index,
material.occlusionTexture.index,
material.emissiveTexture.index,
{
material.pbrMetallicRoughness.baseColorFactor[0],
material.pbrMetallicRoughness.baseColorFactor[1],
material.pbrMetallicRoughness.baseColorFactor[2],
material.pbrMetallicRoughness.baseColorFactor[3]
},
material.pbrMetallicRoughness.metallicFactor,
material.pbrMetallicRoughness.roughnessFactor,
material.normalTexture.scale,
material.occlusionTexture.strength,
{
material.emissiveFactor[0],
material.emissiveFactor[1],
material.emissiveFactor[2]
}
});
}
}
scene = {
meshes,
vertexGroups,
materials,
textures,
samplers
};
return 1;
}
}