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

Target

Select target project
  • vulkan2021/vkcv-framework
1 result
Select Git revision
Show changes
Showing
with 1220 additions and 619 deletions
#pragma once
#include <filesystem>
namespace vkcv {
std::filesystem::path generateTemporaryFilePath();
std::filesystem::path generateTemporaryDirectoryPath();
}
...@@ -31,21 +31,21 @@ namespace vkcv { ...@@ -31,21 +31,21 @@ namespace vkcv {
uint32_t getDepth() const; uint32_t getDepth() const;
[[nodiscard]] [[nodiscard]]
vkcv::ImageHandle getHandle() const; const vkcv::ImageHandle& getHandle() const;
[[nodiscard]] [[nodiscard]]
uint32_t getMipCount() const; uint32_t getMipCount() const;
void switchLayout(vk::ImageLayout newLayout); void switchLayout(vk::ImageLayout newLayout);
void fill(void* data, size_t size = SIZE_MAX); void fill(const void* data, size_t size = SIZE_MAX);
void generateMipChainImmediate(); void generateMipChainImmediate();
void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream); void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream);
private: private:
// TODO: const qualifier removed, very hacky!!! // TODO: const qualifier removed, very hacky!!!
// Else you cannot recreate an image. Pls fix. // Else you cannot recreate an image. Pls fix.
ImageManager* m_manager; ImageManager* m_manager;
ImageHandle m_handle; ImageHandle m_handle;
Image(ImageManager* manager, const ImageHandle& handle); Image(ImageManager* manager, const ImageHandle& handle);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
namespace vkcv { namespace vkcv {
enum class LogLevel { enum class LogLevel {
RAW_INFO,
INFO, INFO,
WARNING, WARNING,
ERROR ERROR
...@@ -12,6 +13,7 @@ namespace vkcv { ...@@ -12,6 +13,7 @@ namespace vkcv {
constexpr auto getLogOutput(LogLevel level) { constexpr auto getLogOutput(LogLevel level) {
switch (level) { switch (level) {
case LogLevel::RAW_INFO:
case LogLevel::INFO: case LogLevel::INFO:
return stdout; return stdout;
default: default:
...@@ -21,6 +23,7 @@ namespace vkcv { ...@@ -21,6 +23,7 @@ namespace vkcv {
constexpr const char* getLogName(LogLevel level) { constexpr const char* getLogName(LogLevel level) {
switch (level) { switch (level) {
case LogLevel::RAW_INFO:
case LogLevel::INFO: case LogLevel::INFO:
return "INFO"; return "INFO";
case LogLevel::WARNING: case LogLevel::WARNING:
...@@ -41,24 +44,35 @@ namespace vkcv { ...@@ -41,24 +44,35 @@ namespace vkcv {
#define __PRETTY_FUNCTION__ __FUNCSIG__ #define __PRETTY_FUNCTION__ __FUNCSIG__
#endif #endif
#define vkcv_log(level, ...) { \ #define vkcv_log(level, ...) { \
char output_message [ \ char output_message [ \
VKCV_DEBUG_MESSAGE_LEN \ VKCV_DEBUG_MESSAGE_LEN \
]; \ ]; \
snprintf( \ snprintf( \
output_message, \ output_message, \
VKCV_DEBUG_MESSAGE_LEN, \ VKCV_DEBUG_MESSAGE_LEN, \
__VA_ARGS__ \ __VA_ARGS__ \
); \ ); \
fprintf( \ auto output = getLogOutput(level); \
getLogOutput(level), \ if (level != vkcv::LogLevel::RAW_INFO) { \
"[%s]: %s [%s, line %d: %s]\n", \ fprintf( \
vkcv::getLogName(level), \ output, \
output_message, \ "[%s]: %s [%s, line %d: %s]\n", \
__FILE__, \ vkcv::getLogName(level), \
__LINE__, \ output_message, \
__PRETTY_FUNCTION__ \ __FILE__, \
); \ __LINE__, \
__PRETTY_FUNCTION__ \
); \
} else { \
fprintf( \
output, \
"[%s]: %s\n", \
vkcv::getLogName(level), \
output_message \
); \
} \
fflush(output); \
} }
#else #else
......
#pragma once
#include <vector>
#include <vulkan/vulkan.hpp>
#include "Logger.hpp"
namespace vkcv {
class PushConstants {
private:
std::vector<uint8_t> m_data;
size_t m_sizePerDrawcall;
public:
template<typename T>
PushConstants() : PushConstants(sizeof(T)) {}
explicit PushConstants(size_t sizePerDrawcall) :
m_data(),
m_sizePerDrawcall(sizePerDrawcall) {}
PushConstants(const PushConstants& other) = default;
PushConstants(PushConstants&& other) = default;
~PushConstants() = default;
PushConstants& operator=(const PushConstants& other) = default;
PushConstants& operator=(PushConstants&& other) = default;
[[nodiscard]]
size_t getSizePerDrawcall() const {
return m_sizePerDrawcall;
}
[[nodiscard]]
size_t getFullSize() const {
return m_data.size();
}
[[nodiscard]]
size_t getDrawcallCount() const {
return (m_data.size() / m_sizePerDrawcall);
}
void clear() {
m_data.clear();
}
template<typename T = uint8_t>
bool appendDrawcall(const T& value) {
if (sizeof(T) != m_sizePerDrawcall) {
vkcv_log(LogLevel::WARNING, "Size (%lu) of value does not match the specified size per drawcall (%lu)",
sizeof(value), m_sizePerDrawcall);
return false;
}
const size_t offset = m_data.size();
m_data.resize(offset + sizeof(value));
std::memcpy(m_data.data() + offset, &value, sizeof(value));
return true;
}
template<typename T = uint8_t>
T& getDrawcall(size_t index) {
const size_t offset = (index * m_sizePerDrawcall);
return *reinterpret_cast<T*>(m_data.data() + offset);
}
template<typename T = uint8_t>
const T& getDrawcall(size_t index) const {
const size_t offset = (index * m_sizePerDrawcall);
return *reinterpret_cast<const T*>(m_data.data() + offset);
}
[[nodiscard]]
const void* getDrawcallData(size_t index) const {
const size_t offset = (index * m_sizePerDrawcall);
return reinterpret_cast<const void*>(m_data.data() + offset);
}
[[nodiscard]]
const void* getData() const {
if (m_data.empty()) {
return nullptr;
} else {
return m_data.data();
}
}
};
}
...@@ -32,8 +32,8 @@ namespace vkcv { ...@@ -32,8 +32,8 @@ namespace vkcv {
const std::vector<Queue> &getTransferQueues() const; const std::vector<Queue> &getTransferQueues() const;
static void queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice, static void queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice,
std::vector<float> &queuePriorities, const std::vector<float> &queuePriorities,
std::vector<vk::QueueFlagBits> &queueFlags, const std::vector<vk::QueueFlagBits> &queueFlags,
std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos, std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos,
std::vector<std::pair<int, int>> &queuePairsGraphics, std::vector<std::pair<int, int>> &queuePairsGraphics,
std::vector<std::pair<int, int>> &queuePairsCompute, std::vector<std::pair<int, int>> &queuePairsCompute,
......
...@@ -9,7 +9,11 @@ namespace vkcv { ...@@ -9,7 +9,11 @@ namespace vkcv {
TESS_EVAL, TESS_EVAL,
GEOMETRY, GEOMETRY,
FRAGMENT, FRAGMENT,
COMPUTE COMPUTE,
TASK,
MESH
}; };
} }
Subproject commit 3a61240a5354ce56c222969a69825aabb6ba0a21
# Add new modules here: # Add new modules here:
add_subdirectory(asset_loader) add_subdirectory(asset_loader)
add_subdirectory(material)
add_subdirectory(camera) add_subdirectory(camera)
add_subdirectory(gui) add_subdirectory(gui)
add_subdirectory(material)
add_subdirectory(meshlet)
add_subdirectory(scene)
add_subdirectory(shader_compiler) add_subdirectory(shader_compiler)
add_subdirectory(testing) add_subdirectory(testing)
add_subdirectory(upscaling)
...@@ -31,10 +31,10 @@ include(config/FX_GLTF.cmake) ...@@ -31,10 +31,10 @@ include(config/FX_GLTF.cmake)
include(config/STB.cmake) include(config/STB.cmake)
# link the required libraries to the module # link the required libraries to the module
target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv) target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv ${vkcv_libraries})
# including headers of dependencies and the VkCV framework # including headers of dependencies and the VkCV framework
target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes}) target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes} ${vkcv_includes})
# add the own include directory for public headers # add the own include directory for public headers
target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include}) target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include})
......
...@@ -11,17 +11,7 @@ ...@@ -11,17 +11,7 @@
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
/** These macros define limits of the following structs. Implementations can /* LOADING MESHES
* 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
* materials in the mesh. Should these limits be too narrow, the data type has
* to be changed, but the current ones should be generous enough for most use
* cases. */
#define MAX_MATERIALS_PER_MESH UINT8_MAX
#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
/** LOADING MESHES
* The description of meshes is a hierarchy of structures with the Mesh at the * The description of meshes is a hierarchy of structures with the Mesh at the
* top. * top.
* *
...@@ -46,53 +36,89 @@ ...@@ -46,53 +36,89 @@
namespace vkcv::asset { namespace vkcv::asset {
/** This enum matches modes in fx-gltf, the library returns a standard mode /**
* (TRIANGLES) if no mode is given in the file. */ * These return codes are limited to the asset loader module. If unified return
* codes are defined for the vkcv framework, these will be used instead.
*/
#define ASSET_ERROR 0
#define ASSET_SUCCESS 1
/**
* This enum matches modes in fx-gltf, the library returns a standard mode
* (TRIANGLES) if no mode is given in the file.
*/
enum class PrimitiveMode : uint8_t { enum class PrimitiveMode : uint8_t {
POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP, POINTS = 0,
TRIANGLEFAN LINES = 1,
LINELOOP = 2,
LINESTRIP = 3,
TRIANGLES = 4,
TRIANGLESTRIP = 5,
TRIANGLEFAN = 6
}; };
/** 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 }; * The indices in the index buffer can be of different bit width.
*/
typedef struct { enum class IndexType : uint8_t {
// TODO define struct for samplers (low priority) UNDEFINED=0,
// NOTE: glTF defines samplers based on OpenGL, which can not be UINT8=1,
// directly translated to Vulkan. Specifically, OpenGL (and glTF) UINT16=2,
// define a different set of Min/Mag-filters than Vulkan. UINT32=3
} Sampler; };
/** struct for defining the loaded texture */
typedef struct {
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.*/ * This struct defines a sampler for a texture object. All values here can
typedef struct { * directly be passed to VkSamplerCreateInfo.
uint16_t textureMask; // bit mask with active texture targets * NOTE that glTF defines samplers based on OpenGL, which can not be directly
// Indices into the Array.textures array * translated to Vulkan. The vkcv::asset::Sampler struct defined here adheres
int baseColor, metalRough, normal, occlusion, emissive; * to the Vulkan spec, having alerady translated the flags from glTF to Vulkan.
// Scaling factors for each texture target * Since glTF does not specify border sampling for more than two dimensions,
struct { float r, g, b, a; } baseColorFactor; * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT.
float metallicFactor, roughnessFactor; */
float normalScale; struct Sampler {
float occlusionStrength; int minFilter, magFilter;
struct { float r, g, b; } emissiveFactor; int mipmapMode;
} Material; float minLOD, maxLOD;
int addressModeU, addressModeV, addressModeW;
};
/** Flags for the bit-mask in the Material struct. To check if a material has a /**
* This struct describes a (partially) loaded texture.
* The data member is not populated after calling probeScene() but only when
* calling loadMesh(), loadScene() or loadTexture(). Note that textures are
* currently always loaded with 4 channels as RGBA, even if the image has just
* RGB or is grayscale. In the case where the glTF-file does not provide a URI
* but references a buffer view for the raw data, the path member will be empty
* even though the rest is initialized properly.
* NOTE: Loading textures without URI is untested.
*/
struct Texture {
std::filesystem::path path; // URI to the encoded texture data
int sampler; // index into the sampler array of the Scene
union { int width; int w; };
union { int height; int h; };
int channels;
std::vector<uint8_t> data; // binary data of the decoded texture
};
/**
* 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 * certain texture target, you can use the hasTexture() function below, passing
* the material struct and the enum. */ * the material struct and the enum.
*/
enum class PBRTextureTarget { enum class PBRTextureTarget {
baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16 baseColor=1,
metalRough=2,
normal=4,
occlusion=8,
emissive=16
}; };
/** This macro translates the index of an enum in the defined order to an /**
* 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 * 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 * working with the bitmask of texture targets ("textureMask") in the Material
* struct: * struct:
...@@ -103,100 +129,196 @@ enum class PBRTextureTarget { ...@@ -103,100 +129,196 @@ enum class PBRTextureTarget {
* contact with bit-level operations. */ * contact with bit-level operations. */
#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM))) #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: * The asset loader module only supports the PBR-MetallicRoughness model for
* Material mat = ...; * materials.
* if (materialHasTexture(&mat, baseColor)) {...} */ */
bool materialHasTexture(const Material *const m, const PBRTextureTarget t); struct Material {
uint16_t textureMask; // bit mask with active texture targets
// Indices into the Scene.textures vector
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;
/**
* To signal that a certain texture target is active in this Material
* struct, its bit is set in the textureMask. You can use this function
* to check that:
* if (myMaterial.hasTexture(baseColor)) {...}
*
* @param t The target to query for
* @return Boolean to signal whether the texture target is active in
* the material.
*/
bool hasTexture(PBRTextureTarget target) const;
};
/** With these enums, 0 is reserved to signal uninitialized or invalid data. */ /* With these enums, 0 is reserved to signal uninitialized or invalid data. */
enum class PrimitiveType : uint32_t { enum class PrimitiveType : uint32_t {
UNDEFINED = 0, UNDEFINED = 0,
POSITION = 1, POSITION = 1,
NORMAL = 2, NORMAL = 2,
TEXCOORD_0 = 3, TEXCOORD_0 = 3,
TEXCOORD_1 = 4, TEXCOORD_1 = 4,
TANGENT = 5 TANGENT = 5,
COLOR_0 = 6,
COLOR_1 = 7,
JOINTS_0 = 8,
WEIGHTS_0 = 9
}; };
/** These integer values are used the same way in OpenGL, Vulkan and glTF. This /**
* 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 * enum is not needed for translation, it's only for the programmers
* convenience (easier to read in if/switch statements etc). While this enum * 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 * exists in (almost) the same definition in the fx-gltf library, we want to
* avoid exposing that dependency, thus it is re-defined here. */ * avoid exposing that dependency, thus it is re-defined here.
*/
enum class ComponentType : uint16_t { enum class ComponentType : uint16_t {
NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123, NONE = 0,
UINT32 = 5125, FLOAT32 = 5126 INT8 = 5120,
UINT8 = 5121,
INT16 = 5122,
UINT16 = 5123,
UINT32 = 5125,
FLOAT32 = 5126
}; };
/** This struct describes one vertex attribute of a vertex buffer. */ /**
typedef struct { * This struct describes one vertex attribute of a vertex buffer.
*/
struct VertexAttribute {
PrimitiveType type; // POSITION, NORMAL, ... PrimitiveType type; // POSITION, NORMAL, ...
uint32_t offset; // offset in bytes uint32_t offset; // offset in bytes
uint32_t length; // length of ... in bytes uint32_t length; // length of ... in bytes
uint32_t stride; // stride in bytes uint32_t stride; // stride in bytes
ComponentType componentType; // eg. 5126 for float
uint8_t componentCount; // eg. 3 for vec3 ComponentType componentType; // eg. 5126 for float
} VertexAttribute; uint8_t componentCount; // eg. 3 for vec3
};
/** This struct represents one (possibly the only) part of a mesh. There is /**
* This struct represents one (possibly the only) part of a mesh. There is
* always one vertexBuffer and zero or one indexBuffer (indexed rendering 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 * 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 * by indexBuffer.data being empty. Each vertex buffer can have one or more
* vertex attributes. */ * vertex attributes.
typedef struct { */
struct VertexGroup {
enum PrimitiveMode mode; // draw as points, lines or triangle? enum PrimitiveMode mode; // draw as points, lines or triangle?
size_t numIndices, numVertices; size_t numIndices;
size_t numVertices;
struct { struct {
enum IndexType type; // data type of the indices enum IndexType type; // data type of the indices
std::vector<uint8_t> data; // binary data of the index buffer std::vector<uint8_t> data; // binary data of the index buffer
} indexBuffer; } indexBuffer;
struct { struct {
std::vector<uint8_t> data; // binary data of the vertex buffer std::vector<uint8_t> data; // binary data of the vertex buffer
std::vector<VertexAttribute> attributes; // description of one std::vector<VertexAttribute> attributes; // description of one
} vertexBuffer; } vertexBuffer;
struct { float x, y, z; } min; // bounding box lower left struct { float x, y, z; } min; // bounding box lower left
struct { float x, y, z; } max; // bounding box upper right struct { float x, y, z; } max; // bounding box upper right
int materialIndex; // index to one of the materials int materialIndex; // index to one of the materials
} VertexGroup; };
/** This struct represents a single mesh as it was loaded from a glTF file. It /**
* 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 * consists of at least one VertexGroup, which then references other resources
* such as Materials. */ * such as Materials.
typedef struct { */
struct Mesh {
std::string name; std::string name;
std::array<float, 16> modelMatrix; std::array<float, 16> modelMatrix;
std::vector<int> vertexGroups; std::vector<int> vertexGroups;
} Mesh; };
/** The scene struct is simply a collection of objects in the scene as well as /**
* The scene struct is simply a collection of objects in the scene as well as
* the resources used by those objects. * the resources used by those objects.
* For now the only type of object are the meshes and they are represented in a * Note that parent-child relations are not yet possible.
* flat array. */
* Note that parent-child relations are not yet possible. */ struct Scene {
typedef struct {
std::vector<Mesh> meshes; std::vector<Mesh> meshes;
std::vector<VertexGroup> vertexGroups; std::vector<VertexGroup> vertexGroups;
std::vector<Material> materials; std::vector<Material> materials;
std::vector<Texture> textures; std::vector<Texture> textures;
std::vector<Sampler> samplers; std::vector<Sampler> samplers;
} Scene; std::vector<std::string> uris;
};
/** /**
* Load every mesh from the glTF file, as well as materials and textures. * Parse the given glTF file and create a shallow description of the content.
* Only the meta-data of the objects in the scene is loaded, not the binary
* content. The rationale is to provide a means of probing the content of a
* glTF file without the costly process of loading and decoding large amounts
* of data. The returned Scene struct can be used to search for specific meshes
* in the scene, that can then be loaded on their own using the loadMesh()
* function. Note that the Scene struct received as output argument will be
* overwritten by this function.
* After this function completes, the returned Scene struct is completely
* initialized and all information is final, except for the missing binary
* data. This means that indices to vectors will remain valid even when the
* shallow scene struct is filled with data by loadMesh().
* Note that for URIs only (local) filesystem paths are supported, no
* URLs using network protocols etc.
* *
* @param path must be the path to a glTF or glb file. * @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
* meta-data of all objects described in the glTF file.
* @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
*/
int probeScene(const std::filesystem::path &path, Scene &scene);
/**
* This function loads a single mesh from the given file and adds it to the
* given scene. The scene must already be initialized (via probeScene()).
* The mesh_index refers to the Scenes meshes array and identifies the mesh to
* load. To find the mesh you want, iterate over the probed scene and check the
* meshes details (eg. name).
* Besides the mesh, this function will also add any associated data to the
* Scene struct such as Materials and Textures required by the Mesh.
*
* @param path must be the path to a glTF- or glb-file.
* @param scene is the scene struct to which the results will be written.
* @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
*/
int loadMesh(Scene &scene, int mesh_index);
/**
* Load every mesh from the glTF file, as well as materials, textures and other
* associated objects.
*
* @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 * @param scene is a reference to a Scene struct that will be filled with the
* content of the glTF file being loaded. * content of the glTF file being loaded.
* */ * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
int loadScene(const std::string &path, Scene &scene); */
int loadScene(const std::filesystem::path &path, Scene &scene);
struct TextureData {
int width; /**
int height; * Simply loads a single image at the given path and returns a Texture
int componentCount; * struct describing it. This is for special use cases only (eg.
std::vector<char*> data; * loading a font atlas) and not meant to be used for regular assets.
}; * The sampler is set to -1, signalling that this Texture was loaded
TextureData loadTexture(const std::filesystem::path& path); * outside the context of a glTF-file.
* If there was an error loading or decoding the image, the returned struct
* will be cleared to all 0 with path and data being empty; make sure to always
* check that !data.empty() before using the struct.
*
* @param path must be the path to an image file.
* @return Texture struct describing the loaded image.
*/
Texture loadTexture(const std::filesystem::path& path);
} } // end namespace vkcv::asset
#include "vkcv/asset/asset_loader.hpp" #include "vkcv/asset/asset_loader.hpp"
#include <iostream> #include <iostream>
#include <string.h> // memcpy(3) #include <string.h> // memcpy(3)
#include <set>
#include <stdlib.h> // calloc(3) #include <stdlib.h> // calloc(3)
#include <vulkan/vulkan.hpp>
#include <fx/gltf.h> #include <fx/gltf.h>
#include <stb_image.h> #include <stb_image.h>
#include <vkcv/Logger.hpp> #include <vkcv/Logger.hpp>
#include <algorithm> #include <algorithm>
namespace vkcv::asset { namespace vkcv::asset {
/** /**
* convert the accessor type from the fx-gltf library to an unsigned int * This function unrolls nested exceptions via recursion and prints them
* @param type * @param e The exception being thrown
* @return unsigned integer representation * @param path The path to the file that was responsible for the exception
*/ */
// TODO Return proper error code (we need to define those as macros or enums, static void recurseExceptionPrint(const std::exception& e, const std::string &path) {
// will discuss during the next core meeting if that should happen on the scope vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what());
// of the vkcv framework or just this module)
uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) { try {
switch (type) { std::rethrow_if_nested(e);
case fx::gltf::Accessor::Type::None : } catch (const std::exception& nested) {
return 0; recurseExceptionPrint(nested, path);
case fx::gltf::Accessor::Type::Scalar : }
return 1;
case fx::gltf::Accessor::Type::Vec2 :
return 2;
case fx::gltf::Accessor::Type::Vec3 :
return 3;
case fx::gltf::Accessor::Type::Vec4 :
return 4;
default: return 10; // TODO add cases for matrices (or maybe change the type in the struct itself)
} }
}
/** /**
* This function unrolls nested exceptions via recursion and prints them * Returns the component count for an accessor type of the fx-gltf library.
* @param e error code * @param type The accessor type
* @param path path to file that is responsible for error * @return An unsigned integer count
*/ */
void print_what (const std::exception& e, const std::string &path) { static uint32_t getComponentCount(const fx::gltf::Accessor::Type type) {
vkcv_log(LogLevel::ERROR, "Loading file %s: %s", switch (type) {
path.c_str(), e.what()); case fx::gltf::Accessor::Type::Scalar:
return 1;
case fx::gltf::Accessor::Type::Vec2:
return 2;
case fx::gltf::Accessor::Type::Vec3:
return 3;
case fx::gltf::Accessor::Type::Vec4:
case fx::gltf::Accessor::Type::Mat2:
return 4;
case fx::gltf::Accessor::Type::Mat3:
return 9;
case fx::gltf::Accessor::Type::Mat4:
return 16;
case fx::gltf::Accessor::Type::None:
default:
return 0;
}
}
try { static uint32_t getComponentSize(ComponentType type) {
std::rethrow_if_nested(e); switch (type) {
} catch (const std::exception& nested) { case ComponentType::INT8:
print_what(nested, path); case ComponentType::UINT8:
return 1;
case ComponentType::INT16:
case ComponentType::UINT16:
return 2;
case ComponentType::UINT32:
case ComponentType::FLOAT32:
return 4;
default:
return 0;
}
} }
}
/** 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 * Translate the component type used in the index accessor of fx-gltf to our
* needs translation is that only a subset of component types is valid for * enum for index type. The reason we have defined an incompatible enum that
* indices and we want to catch these incompatibilities here. */ * needs translation is that only a subset of component types is valid for
enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t) * indices and we want to catch these incompatibilities here.
{ * @param t The component type
switch (t) { * @return The vkcv::IndexType enum representation
case fx::gltf::Accessor::ComponentType::UnsignedByte: */
return IndexType::UINT8; enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) {
case fx::gltf::Accessor::ComponentType::UnsignedShort: switch (type) {
return IndexType::UINT16; case fx::gltf::Accessor::ComponentType::UnsignedByte:
case fx::gltf::Accessor::ComponentType::UnsignedInt: return IndexType::UINT8;
return IndexType::UINT32; case fx::gltf::Accessor::ComponentType::UnsignedShort:
default: return IndexType::UINT16;
vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(t)); case fx::gltf::Accessor::ComponentType::UnsignedInt:
return IndexType::UNDEFINED; return IndexType::UINT32;
default:
vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type));
return IndexType::UNDEFINED;
}
} }
}
/**
* 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 if (attrib.first == "TANGENT") {
attribute.type = PrimitiveType::TANGENT;
} 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); /**
} * This function fills the array of vertex attributes of a VertexGroup (usually
* part of a vkcv::asset::Mesh) object based on the description of attributes
for(int m = 0; m < sceneObjects.nodes.size(); m++) { * for a fx::gltf::Primitive.
meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation, *
sceneObjects.nodes[m].scale, * @param src The description of attribute objects from the fx-gltf library
sceneObjects.nodes[m].rotation, * @param gltf The main glTF document
sceneObjects.nodes[m].matrix); * @param dst The array of vertex attributes stored in an asset::Mesh object
} * @return ASSET_ERROR when at least one VertexAttribute could not be
* constructed properly, otherwise ASSET_SUCCESS
if (sceneObjects.textures.size() > 0){ */
textures.reserve(sceneObjects.textures.size()); static int loadVertexAttributes(const fx::gltf::Attributes &src,
const std::vector<fx::gltf::Accessor> &accessors,
for(int k = 0; k < sceneObjects.textures.size(); k++){ const std::vector<fx::gltf::BufferView> &bufferViews,
const fx::gltf::Texture &tex = sceneObjects.textures[k]; std::vector<VertexAttribute> &dst) {
const fx::gltf::Image &img = sceneObjects.images[tex.source]; for (const auto &attrib : src) {
std::string img_uri = dir + "/" + img.uri; VertexAttribute att;
int w, h, c;
uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4); if (attrib.first == "POSITION") {
c = 4; // FIXME hardcoded to always have RGBA channel layout att.type = PrimitiveType::POSITION;
if (!data) { } else if (attrib.first == "NORMAL") {
vkcv_log(LogLevel::ERROR, "Loading texture image data.") att.type = PrimitiveType::NORMAL;
return 0; } else if (attrib.first == "TANGENT") {
} att.type = PrimitiveType::TANGENT;
const size_t byteLen = w * h * c; } else if (attrib.first == "TEXCOORD_0") {
att.type = PrimitiveType::TEXCOORD_0;
std::vector<uint8_t> imgdata; } else if (attrib.first == "TEXCOORD_1") {
imgdata.resize(byteLen); att.type = PrimitiveType::TEXCOORD_1;
if (!memcpy(imgdata.data(), data, byteLen)) { } else if (attrib.first == "COLOR_0") {
vkcv_log(LogLevel::ERROR, "Copying texture image data") att.type = PrimitiveType::COLOR_0;
free(data); } else if (attrib.first == "COLOR_1") {
return 0; att.type = PrimitiveType::COLOR_1;
} } else if (attrib.first == "JOINTS_0") {
free(data); att.type = PrimitiveType::JOINTS_0;
} else if (attrib.first == "WEIGHTS_0") {
att.type = PrimitiveType::WEIGHTS_0;
} else {
att.type = PrimitiveType::UNDEFINED;
}
if (att.type != PrimitiveType::UNDEFINED) {
const fx::gltf::Accessor &accessor = accessors[attrib.second];
const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView];
att.offset = buf.byteOffset;
att.length = buf.byteLength;
att.stride = buf.byteStride;
att.componentType = static_cast<ComponentType>(accessor.componentType);
att.componentCount = getComponentCount(accessor.type);
/* Assume tightly packed stride as not explicitly provided */
if (att.stride == 0) {
att.stride = att.componentCount * getComponentSize(att.componentType);
}
}
if ((att.type == PrimitiveType::UNDEFINED) ||
(att.componentCount == 0)) {
return ASSET_ERROR;
}
dst.push_back(att);
}
return ASSET_SUCCESS;
}
textures.push_back({ /**
0, * This function calculates the modelMatrix out of the data given in the gltf file.
static_cast<uint8_t>(c), * It also checks, whether a modelMatrix was given.
static_cast<uint16_t>(w), *
static_cast<uint16_t>(h), * @param translation possible translation vector (default 0,0,0)
imgdata * @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
*/
static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation,
const std::array<float, 3>& scale,
const std::array<float, 4>& rotation,
const 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 Material::hasTexture(const PBRTextureTarget target) const {
} return textureMask & bitflag(target);
}
if (sceneObjects.materials.size() > 0){ /**
materials.reserve(sceneObjects.materials.size()); * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart.
* @param mode: wrapping mode of a sampler given as fx-gltf-enum
* @return int vulkan-enum representing the same wrapping mode
*/
static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) {
switch (mode) {
case fx::gltf::Sampler::WrappingMode::ClampToEdge:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case fx::gltf::Sampler::WrappingMode::MirroredRepeat:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case fx::gltf::Sampler::WrappingMode::Repeat:
default:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
}
for (int l = 0; l < sceneObjects.materials.size(); l++){ /**
fx::gltf::Material material = sceneObjects.materials[l]; * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf.
// TODO I think we shouldn't set the index for a texture target if * The following are details about the glTF/OpenGL to Vulkan translation.
// it isn't defined. So we need to test first if there is a normal * magFilter (VkFilter?):
// texture before assigning material.normalTexture.index. * GL_NEAREST -> VK_FILTER_NEAREST
// About the bitmask: If a normal texture is there, modify the * GL_LINEAR -> VK_FILTER_LINEAR
// materials textureMask like this: * minFilter (VkFilter?):
// mat.textureMask |= bitflag(asset::normal); * mipmapMode (VkSamplerMipmapMode?):
materials.push_back({ * Vulkans minFilter and mipmapMode combined correspond to OpenGLs
0, * GL_minFilter_MIPMAP_mipmapMode:
material.pbrMetallicRoughness.baseColorTexture.index, * GL_NEAREST_MIPMAP_NEAREST:
material.pbrMetallicRoughness.metallicRoughnessTexture.index, * minFilter=VK_FILTER_NEAREST
material.normalTexture.index, * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
material.occlusionTexture.index, * GL_LINEAR_MIPMAP_NEAREST:
material.emissiveTexture.index, * minFilter=VK_FILTER_LINEAR
{ * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
material.pbrMetallicRoughness.baseColorFactor[0], * GL_NEAREST_MIPMAP_LINEAR:
material.pbrMetallicRoughness.baseColorFactor[1], * minFilter=VK_FILTER_NEAREST
material.pbrMetallicRoughness.baseColorFactor[2], * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
material.pbrMetallicRoughness.baseColorFactor[3] * GL_LINEAR_MIPMAP_LINEAR:
}, * minFilter=VK_FILTER_LINEAR
material.pbrMetallicRoughness.metallicFactor, * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
material.pbrMetallicRoughness.roughnessFactor, * The modes of GL_LINEAR and GL_NEAREST have to be emulated using
material.normalTexture.scale, * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD:
material.occlusionTexture.strength, * GL_LINEAR:
{ * minFilter=VK_FILTER_LINEAR
material.emissiveFactor[0], * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
material.emissiveFactor[1], * minLOD=0, maxLOD=0.25
material.emissiveFactor[2] * GL_NEAREST:
} * minFilter=VK_FILTER_NEAREST
* mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
* minLOD=0, maxLOD=0.25
* Setting maxLOD=0 causes magnification to always be performed (using
* the defined magFilter), this may be valid if the min- and magFilter
* are equal, otherwise it won't be the expected behaviour from OpenGL
* and glTF; instead using maxLod=0.25 allows the minFilter to be
* performed while still always rounding to the base level.
* With other modes, minLOD and maxLOD default to:
* minLOD=0
* maxLOD=VK_LOD_CLAMP_NONE
* wrapping:
* gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while
* Vulkan has addressModeU, addressModeV, addressModeW with values
* VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE,
* CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE}
* Translation from glTF to Vulkan is straight forward for the 3 existing
* modes, default is repeat, the other modes aren't available.
*/
static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) {
Sampler dst;
dst.minLOD = 0;
dst.maxLOD = VK_LOD_CLAMP_NONE;
switch (src.minFilter) {
case fx::gltf::Sampler::MinFilter::None:
case fx::gltf::Sampler::MinFilter::Nearest:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
dst.maxLOD = 0.25;
break;
case fx::gltf::Sampler::MinFilter::Linear:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
dst.maxLOD = 0.25;
break;
case fx::gltf::Sampler::MinFilter::NearestMipMapNearest:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
break;
case fx::gltf::Sampler::MinFilter::LinearMipMapNearest:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
break;
case fx::gltf::Sampler::MinFilter::NearestMipMapLinear:
dst.minFilter = VK_FILTER_NEAREST;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
break;
case fx::gltf::Sampler::MinFilter::LinearMipMapLinear:
dst.minFilter = VK_FILTER_LINEAR;
dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
break;
default:
break;
}
switch (src.magFilter) {
case fx::gltf::Sampler::MagFilter::None:
case fx::gltf::Sampler::MagFilter::Nearest:
dst.magFilter = VK_FILTER_NEAREST;
break;
case fx::gltf::Sampler::MagFilter::Linear:
dst.magFilter = VK_FILTER_LINEAR;
break;
default:
break;
}
dst.addressModeU = translateSamplerMode(src.wrapS);
dst.addressModeV = translateSamplerMode(src.wrapT);
// There is no information about wrapping for a third axis in glTF and
// we have to hardcode this value.
dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
return dst;
}
}); /**
} * Initializes vertex groups of a Mesh, including copying the data to
} * index- and vertex-buffers.
*/
static int loadVertexGroups(const fx::gltf::Mesh &objectMesh,
const fx::gltf::Document &sceneObjects,
Scene &scene, Mesh &mesh) {
mesh.vertexGroups.reserve(objectMesh.primitives.size());
for (const auto &objectPrimitive : objectMesh.primitives) {
VertexGroup vertexGroup;
vertexGroup.vertexBuffer.attributes.reserve(
objectPrimitive.attributes.size()
);
if (ASSET_SUCCESS != loadVertexAttributes(
objectPrimitive.attributes,
sceneObjects.accessors,
sceneObjects.bufferViews,
vertexGroup.vertexBuffer.attributes)) {
vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'",
mesh.name.c_str());
return ASSET_ERROR;
}
// The accessor for the position attribute is used for
// 1) getting the vertex buffer view which is only needed to get
// the vertex buffer
// 2) getting the vertex count for the VertexGroup
// 3) getting the min/max of the bounding box for the VertexGroup
fx::gltf::Accessor posAccessor;
bool noPosition = true;
for (auto const& attrib : objectPrimitive.attributes) {
if (attrib.first == "POSITION") {
posAccessor = sceneObjects.accessors[attrib.second];
noPosition = false;
break;
}
}
if (noPosition) {
vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'",
mesh.name.c_str());
return ASSET_ERROR;
}
const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
int indexBufferURI;
if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf
const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
// Because the buffers are already preloaded into the memory by the gltf-library,
// it makes no sense to load them later on manually again into memory.
vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength);
memcpy(vertexGroup.indexBuffer.data.data(),
indexBuffer.data.data() + indexBufferView.byteOffset,
indexBufferView.byteLength);
} else {
indexBufferURI = -1;
}
vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType);
if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) {
vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported.");
return ASSET_ERROR;
}
if (posAccessor.bufferView >= sceneObjects.bufferViews.size()) {
vkcv_log(LogLevel::ERROR, "Access to bufferView out of bounds: %lu",
posAccessor.bufferView);
return ASSET_ERROR;
}
const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
if (vertexBufferView.buffer >= sceneObjects.buffers.size()) {
vkcv_log(LogLevel::ERROR, "Access to buffer out of bounds: %lu",
vertexBufferView.buffer);
return ASSET_ERROR;
}
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 : vertexGroup.vertexBuffer.attributes) {
relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length);
}
const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
vertexGroup.vertexBuffer.data.resize(relevantBufferSize);
memcpy(vertexGroup.vertexBuffer.data.data(),
vertexBuffer.data.data() + relevantBufferOffset,
relevantBufferSize);
// make vertex attributes relative to copied section
for (auto& attribute : vertexGroup.vertexBuffer.attributes) {
attribute.offset -= relevantBufferOffset;
}
vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode);
vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count;
vertexGroup.numVertices = posAccessor.count;
memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min));
memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max));
vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material);
mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size()));
scene.vertexGroups.push_back(vertexGroup);
}
return ASSET_SUCCESS;
}
scene = { /**
meshes, * Returns an integer with specific bits set corresponding to the
vertexGroups, * textures that appear in the given material. This mask is used in the
materials, * vkcv::asset::Material struct and can be tested via the hasTexture
textures, * method.
samplers */
}; static uint16_t generateTextureMask(fx::gltf::Material &material) {
uint16_t textureMask = 0;
if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::baseColor);
}
if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::metalRough);
}
if (material.normalTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::normal);
}
if (material.occlusionTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::occlusion);
}
if (material.emissiveTexture.index >= 0) {
textureMask |= bitflag(asset::PBRTextureTarget::emissive);
}
return textureMask;
}
return 1; int probeScene(const std::filesystem::path& path, Scene& scene) {
} fx::gltf::Document sceneObjects;
try {
if (path.extension() == ".glb") {
sceneObjects = fx::gltf::LoadFromBinary(path.string());
} else {
sceneObjects = fx::gltf::LoadFromText(path.string());
}
} catch (const std::system_error& err) {
recurseExceptionPrint(err, path.string());
return ASSET_ERROR;
} catch (const std::exception& e) {
recurseExceptionPrint(e, path.string());
return ASSET_ERROR;
}
const auto directory = path.parent_path();
scene.meshes.clear();
scene.vertexGroups.clear();
scene.materials.clear();
scene.textures.clear();
scene.samplers.clear();
// file has to contain at least one mesh
if (sceneObjects.meshes.empty()) {
vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str());
return ASSET_ERROR;
} else {
scene.meshes.reserve(sceneObjects.meshes.size());
for (size_t i = 0; i < sceneObjects.meshes.size(); i++) {
Mesh mesh;
mesh.name = sceneObjects.meshes[i].name;
if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)",
mesh.name.c_str(), path.c_str());
return ASSET_ERROR;
}
scene.meshes.push_back(mesh);
}
// This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights
for (const auto& node : sceneObjects.nodes) {
if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) {
scene.meshes[node.mesh].modelMatrix = calculateModelMatrix(
node.translation,
node.scale,
node.rotation,
node.matrix
);
}
}
}
if (sceneObjects.samplers.empty()) {
vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str());
} else {
scene.samplers.reserve(sceneObjects.samplers.size());
for (const auto &samplerObject : sceneObjects.samplers) {
scene.samplers.push_back(loadSampler(samplerObject));
}
}
if (sceneObjects.textures.empty()) {
vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str());
} else {
scene.textures.reserve(sceneObjects.textures.size());
for (const auto& textureObject : sceneObjects.textures) {
Texture texture;
if (textureObject.sampler < 0) {
texture.sampler = -1;
} else
if (static_cast<size_t>(textureObject.sampler) >= scene.samplers.size()) {
vkcv_log(LogLevel::ERROR, "Sampler of texture '%s' missing (%s) %d",
textureObject.name.c_str(), path.c_str());
return ASSET_ERROR;
} else {
texture.sampler = textureObject.sampler;
}
if ((textureObject.source < 0) ||
(static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) {
vkcv_log(LogLevel::ERROR, "Failed to load texture '%s' (%s)",
textureObject.name.c_str(), path.c_str());
return ASSET_ERROR;
}
const auto& image = sceneObjects.images[textureObject.source];
if (image.uri.empty()) {
const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView];
texture.path.clear();
texture.data.resize(bufferView.byteLength);
memcpy(texture.data.data(),
sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset,
bufferView.byteLength);
} else {
texture.path = directory / image.uri;
}
scene.textures.push_back(texture);
}
}
if (sceneObjects.materials.empty()) {
vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str());
} else {
scene.materials.reserve(sceneObjects.materials.size());
for (auto material : sceneObjects.materials) {
scene.materials.push_back({
generateTextureMask(material),
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]
}
});
}
}
return ASSET_SUCCESS;
}
/**
* Loads and decodes the textures data based on the textures file path.
* The path member is the only one that has to be initialized before
* calling this function, the others (width, height, channels, data)
* are set by this function and the sampler is of no concern here.
*/
static int loadTextureData(Texture& texture) {
if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) &&
(!texture.data.empty())) {
return ASSET_SUCCESS; // Texture data was loaded already!
}
uint8_t* data;
if (texture.path.empty()) {
data = stbi_load_from_memory(
reinterpret_cast<uint8_t*>(texture.data.data()),
static_cast<int>(texture.data.size()),
&texture.width,
&texture.height,
&texture.channels, 4
);
} else {
data = stbi_load(
texture.path.string().c_str(),
&texture.width,
&texture.height,
&texture.channels, 4
);
}
if (!data) {
vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'",
texture.path.c_str());
texture.width = 0;
texture.height = 0;
texture.channels = 0;
return ASSET_ERROR;
}
texture.data.resize(texture.width * texture.height * 4);
memcpy(texture.data.data(), data, texture.data.size());
stbi_image_free(data);
return ASSET_SUCCESS;
}
TextureData loadTexture(const std::filesystem::path& path) { int loadMesh(Scene &scene, int index) {
TextureData texture; if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) {
vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index);
uint8_t* data = stbi_load(path.string().c_str(), &texture.width, &texture.height, &texture.componentCount, 4); return ASSET_ERROR;
}
if (!data) {
vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", path.c_str()); const Mesh &mesh = scene.meshes[index];
texture.width = 0; for (const auto& vg : mesh.vertexGroups) {
texture.height = 0; const VertexGroup &vertexGroup = scene.vertexGroups[vg];
texture.componentCount = 0; const Material& material = scene.materials[vertexGroup.materialIndex];
return texture;
} if (material.hasTexture(PBRTextureTarget::baseColor)) {
const int result = loadTextureData(scene.textures[material.baseColor]);
texture.data.resize(texture.width * texture.height * 4); if (ASSET_SUCCESS != result) {
memcpy(texture.data.data(), data, texture.data.size()); vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'",
return texture; mesh.name.c_str())
} return result;
}
}
if (material.hasTexture(PBRTextureTarget::metalRough)) {
const int result = loadTextureData(scene.textures[material.metalRough]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::normal)) {
const int result = loadTextureData(scene.textures[material.normal]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::occlusion)) {
const int result = loadTextureData(scene.textures[material.occlusion]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
if (material.hasTexture(PBRTextureTarget::emissive)) {
const int result = loadTextureData(scene.textures[material.emissive]);
if (ASSET_SUCCESS != result) {
vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'",
mesh.name.c_str())
return result;
}
}
}
return ASSET_SUCCESS;
}
int loadScene(const std::filesystem::path &path, Scene &scene) {
int result = probeScene(path, scene);
if (result != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'",
path.c_str());
return result;
}
for (size_t i = 0; i < scene.meshes.size(); i++) {
result = loadMesh(scene, static_cast<int>(i));
if (result != ASSET_SUCCESS) {
vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'",
static_cast<int>(i), path.c_str());
return result;
}
}
return ASSET_SUCCESS;
}
Texture loadTexture(const std::filesystem::path& path) {
Texture texture;
texture.path = path;
texture.sampler = -1;
if (loadTextureData(texture) != ASSET_SUCCESS) {
texture.path.clear();
texture.w = texture.h = texture.channels = 0;
texture.data.clear();
}
return texture;
}
} }
...@@ -4,18 +4,20 @@ find_package(glm QUIET) ...@@ -4,18 +4,20 @@ find_package(glm QUIET)
if (glm_FOUND) if (glm_FOUND)
list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS}) list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS})
list(APPEND vkcv_camera_libraries glm) list(APPEND vkcv_camera_libraries glm)
list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
else() else()
if (EXISTS "${vkcv_camera_lib_path}/glm") if (EXISTS "${vkcv_camera_lib_path}/glm")
add_subdirectory(${vkcv_camera_lib}/glm) add_subdirectory(${vkcv_camera_lib}/glm)
list(APPEND vkcv_camera_includes ${vkcv_camera_lib_path}/glm)
list(APPEND vkcv_camera_libraries glm) list(APPEND vkcv_camera_libraries glm)
list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
else() else()
message(WARNING "GLM is required..! Update the submodules!") message(WARNING "GLM is required..! Update the submodules!")
endif () endif ()
endif () endif ()
list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
if ((WIN32) AND (${CMAKE_SIZEOF_VOID_P} MATCHES 4))
list(APPEND vkcv_camera_definitions GLM_ENABLE_EXPERIMENTAL)
endif()
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp> #include <glm/gtc/matrix_access.hpp>
#include <glm/vec3.hpp>
#include <glm/mat4x4.hpp>
namespace vkcv::camera { namespace vkcv::camera {
...@@ -20,9 +22,6 @@ namespace vkcv::camera { ...@@ -20,9 +22,6 @@ namespace vkcv::camera {
glm::vec3 m_up; glm::vec3 m_up;
glm::vec3 m_position; glm::vec3 m_position;
glm::vec3 m_center; glm::vec3 m_center;
float m_pitch;
float m_yaw;
/** /**
* @brief Sets the view matrix of the camera to @p view * @brief Sets the view matrix of the camera to @p view
...@@ -75,7 +74,7 @@ namespace vkcv::camera { ...@@ -75,7 +74,7 @@ namespace vkcv::camera {
* @brief Gets the current projection of the camera * @brief Gets the current projection of the camera
* @return The current projection matrix * @return The current projection matrix
*/ */
glm::mat4 getProjection() const; const glm::mat4& getProjection() const;
/** /**
* @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied
...@@ -156,6 +155,20 @@ namespace vkcv::camera { ...@@ -156,6 +155,20 @@ namespace vkcv::camera {
* @param[in] center The new center point. * @param[in] center The new center point.
*/ */
void setCenter(const glm::vec3& center); void setCenter(const glm::vec3& center);
/**
* @brief Gets the angles of the camera.
* @param[out] pitch The pitch value in radians
* @param[out] yaw The yaw value in radians
*/
void getAngles(float& pitch, float& yaw);
/**
* @brief Sets the angles of the camera.
* @param pitch The new pitch value in radians
* @param yaw The new yaw value in radians
*/
void setAngles(float pitch, float yaw);
/** /**
* @brief Gets the pitch value of the camera in degrees. * @brief Gets the pitch value of the camera in degrees.
......
...@@ -29,42 +29,6 @@ namespace vkcv::camera { ...@@ -29,42 +29,6 @@ namespace vkcv::camera {
float m_fov_min; float m_fov_min;
float m_fov_max; float m_fov_max;
/**
* @brief Indicates forward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveForward(int action);
/**
* @brief Indicates backward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveBackward(int action);
/**
* @brief Indicates left movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveLeft(int action);
/**
* @brief Indicates right movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveRight(int action);
/**
* @brief Indicates upward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveUpward(int action);
/**
* @brief Indicates downward movement of the camera depending on the performed @p action.
* @param[in] action The performed action.
*/
void moveDownward(int action);
public: public:
/** /**
......
...@@ -14,6 +14,8 @@ namespace vkcv::camera { ...@@ -14,6 +14,8 @@ namespace vkcv::camera {
float m_cameraSpeed; float m_cameraSpeed;
float m_scrollSensitivity; float m_scrollSensitivity;
float m_radius; float m_radius;
float m_pitch;
float m_yaw;
/** /**
* @brief Updates the current radius of @p camera in respect to the @p offset. * @brief Updates the current radius of @p camera in respect to the @p offset.
......
...@@ -10,8 +10,6 @@ namespace vkcv::camera { ...@@ -10,8 +10,6 @@ namespace vkcv::camera {
glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f) glm::vec3(0.0f, 1.0f, 0.0f)
); );
setFront(glm::normalize(m_center - m_position));
} }
Camera::~Camera() = default; Camera::~Camera() = default;
...@@ -44,20 +42,20 @@ namespace vkcv::camera { ...@@ -44,20 +42,20 @@ namespace vkcv::camera {
0.0f, 0.0f, 0.0f, 1.0f 0.0f, 0.0f, 0.0f, 1.0f
); );
glm::mat4 Camera::getProjection() const { const glm::mat4& Camera::getProjection() const {
return y_correction * m_projection; return m_projection;
} }
void Camera::setProjection(const glm::mat4& projection) { void Camera::setProjection(const glm::mat4& projection) {
m_projection = glm::inverse(y_correction) * projection; m_projection = y_correction * projection;
} }
glm::mat4 Camera::getMVP() const { glm::mat4 Camera::getMVP() const {
return y_correction * m_projection * m_view; return m_projection * m_view;
} }
float Camera::getFov() const { float Camera::getFov() const {
const float tanHalfFovy = 1.0f / m_projection[1][1]; const float tanHalfFovy = -1.0f / m_projection[1][1];
float halfFovy = std::atan(tanHalfFovy); float halfFovy = std::atan(tanHalfFovy);
if (halfFovy < 0) { if (halfFovy < 0) {
...@@ -73,7 +71,7 @@ namespace vkcv::camera { ...@@ -73,7 +71,7 @@ namespace vkcv::camera {
float Camera::getRatio() const { float Camera::getRatio() const {
const float aspectProduct = 1.0f / m_projection[0][0]; const float aspectProduct = 1.0f / m_projection[0][0];
const float tanHalfFovy = 1.0f / m_projection[1][1]; const float tanHalfFovy = -1.0f / m_projection[1][1];
return aspectProduct / tanHalfFovy; return aspectProduct / tanHalfFovy;
} }
...@@ -93,16 +91,11 @@ namespace vkcv::camera { ...@@ -93,16 +91,11 @@ namespace vkcv::camera {
} }
glm::vec3 Camera::getFront() const { glm::vec3 Camera::getFront() const {
glm::vec3 direction; return glm::normalize(m_center - m_position);
direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
direction.y = std::sin(glm::radians(m_pitch));
direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
return glm::normalize(direction);
} }
void Camera::setFront(const glm::vec3 &front) { void Camera::setFront(const glm::vec3 &front) {
m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z)); setCenter(m_position + front);
m_yaw = std::atan2(front.x, front.z);
} }
const glm::vec3& Camera::getPosition() const { const glm::vec3& Camera::getPosition() const {
...@@ -128,21 +121,47 @@ namespace vkcv::camera { ...@@ -128,21 +121,47 @@ namespace vkcv::camera {
void Camera::setUp(const glm::vec3 &up) { void Camera::setUp(const glm::vec3 &up) {
lookAt(m_position, m_center, up); lookAt(m_position, m_center, up);
} }
float Camera::getPitch() const { void Camera::getAngles(float& pitch, float& yaw) {
return m_pitch; const auto front = getFront();
pitch = std::atan2(front[1], std::sqrt(
front[0] * front[0] + front[2] * front[2]
));
yaw = std::atan2(front[0], front[2]);
}
void Camera::setAngles(float pitch, float yaw) {
float cosPitch = std::cos(pitch);
setFront(glm::vec3(
std::sin(yaw) * cosPitch,
std::sin(pitch),
std::cos(yaw) * cosPitch
));
}
float Camera::getPitch() const {
const auto front = getFront();
return glm::degrees(std::atan2(front[1], std::sqrt(
front[0] * front[0] + front[2] * front[2]
)));
} }
void Camera::setPitch(float pitch) { void Camera::setPitch(float pitch) {
m_pitch = pitch; setAngles(glm::radians(pitch), glm::radians(getYaw()));
} }
float Camera::getYaw() const { float Camera::getYaw() const {
return m_yaw; const auto front = getFront();
return glm::degrees(std::atan2(front[0], front[2]));
} }
void Camera::setYaw(float yaw) { void Camera::setYaw(float yaw) {
m_yaw = yaw; setAngles(glm::radians(getPitch()), glm::radians(yaw));
} }
} }
\ No newline at end of file
...@@ -52,8 +52,8 @@ namespace vkcv::camera { ...@@ -52,8 +52,8 @@ namespace vkcv::camera {
} }
void CameraManager::mouseMoveCallback(double x, double y){ void CameraManager::mouseMoveCallback(double x, double y){
auto xoffset = static_cast<float>(x - m_lastX); auto xoffset = static_cast<float>(x - m_lastX) / m_window.getWidth();
auto yoffset = static_cast<float>(y - m_lastY); auto yoffset = static_cast<float>(y - m_lastY) / m_window.getHeight();
m_lastX = x; m_lastX = x;
m_lastY = y; m_lastY = y;
getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera()); getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
......
...@@ -50,12 +50,11 @@ namespace vkcv::camera { ...@@ -50,12 +50,11 @@ namespace vkcv::camera {
} }
// handle yaw rotation // handle yaw rotation
float yaw = camera.getYaw() + static_cast<float>(xOffset); float yaw = camera.getYaw() + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed;
yaw += 360.0f * (yaw < -180.0f) - 360.0f * (yaw > 180.0f);
camera.setYaw(yaw); camera.setYaw(yaw);
// handle pitch rotation // handle pitch rotation
float pitch = camera.getPitch() - static_cast<float>(yOffset); float pitch = camera.getPitch() - static_cast<float>(yOffset) * 90.0f * m_cameraSpeed;
pitch = glm::clamp(pitch, -89.0f, 89.0f); pitch = glm::clamp(pitch, -89.0f, 89.0f);
camera.setPitch(pitch); camera.setPitch(pitch);
} }
...@@ -83,22 +82,22 @@ namespace vkcv::camera { ...@@ -83,22 +82,22 @@ namespace vkcv::camera {
void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) { void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {
switch (key) { switch (key) {
case GLFW_KEY_W: case GLFW_KEY_W:
moveForward(action); m_forward = static_cast<bool>(action);
break; break;
case GLFW_KEY_S: case GLFW_KEY_S:
moveBackward(action); m_backward = static_cast<bool>(action);
break; break;
case GLFW_KEY_A: case GLFW_KEY_A:
moveLeft(action); m_left = static_cast<bool>(action);
break; break;
case GLFW_KEY_D: case GLFW_KEY_D:
moveRight(action); m_right = static_cast<bool>(action);
break; break;
case GLFW_KEY_E: case GLFW_KEY_E:
moveUpward(action); m_upward = static_cast<bool>(action);
break; break;
case GLFW_KEY_Q: case GLFW_KEY_Q:
moveDownward(action); m_downward = static_cast<bool>(action);
break; break;
default: default:
break; break;
...@@ -110,31 +109,25 @@ namespace vkcv::camera { ...@@ -110,31 +109,25 @@ namespace vkcv::camera {
} }
void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) { void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
if(!m_rotationActive){ xoffset *= static_cast<float>(m_rotationActive);
return; yoffset *= static_cast<float>(m_rotationActive);
}
float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;
panView(xoffset , yoffset, camera); panView(xoffset , yoffset, camera);
} }
void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) { void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){ if (button == GLFW_MOUSE_BUTTON_2) {
m_rotationActive = true; if (m_rotationActive != (action == GLFW_PRESS)) {
} m_rotationActive = (action == GLFW_PRESS);
else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){ }
m_rotationActive = false; }
}
} }
void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) { void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) {
GLFWgamepadstate gamepadState; GLFWgamepadstate gamepadState;
glfwGetGamepadState(gamepadIndex, &gamepadState); glfwGetGamepadState(gamepadIndex, &gamepadState);
float sensitivity = 100.0f; float sensitivity = 1.0f;
double threshold = 0.1; double threshold = 0.1;
// handle rotations // handle rotations
...@@ -163,29 +156,4 @@ namespace vkcv::camera { ...@@ -163,29 +156,4 @@ namespace vkcv::camera {
* -copysign(1.0, stickLeftX); * -copysign(1.0, stickLeftX);
} }
void PilotCameraController::moveForward(int action){
m_forward = static_cast<bool>(action);
}
void PilotCameraController::moveBackward(int action){
m_backward = static_cast<bool>(action);
}
void PilotCameraController::moveLeft(int action){
m_left = static_cast<bool>(action);
}
void PilotCameraController::moveRight(int action){
m_right = static_cast<bool>(action);
}
void PilotCameraController::moveUpward(int action){
m_upward = static_cast<bool>(action);
}
void PilotCameraController::moveDownward(int action){
m_downward = static_cast<bool>(action);
}
} }
\ No newline at end of file
...@@ -8,6 +8,8 @@ namespace vkcv::camera { ...@@ -8,6 +8,8 @@ namespace vkcv::camera {
m_radius = 3.0f; m_radius = 3.0f;
m_cameraSpeed = 2.5f; m_cameraSpeed = 2.5f;
m_scrollSensitivity = 0.2f; m_scrollSensitivity = 0.2f;
m_pitch = 0.0f;
m_yaw = 0.0f;
} }
void TrackballCameraController::setRadius(const float radius) { void TrackballCameraController::setRadius(const float radius) {
...@@ -21,14 +23,10 @@ namespace vkcv::camera { ...@@ -21,14 +23,10 @@ namespace vkcv::camera {
} }
// handle yaw rotation // handle yaw rotation
float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed; m_yaw = m_yaw + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed;
yaw += 360.0f * (yaw < 0.0f) - 360.0f * (yaw > 360.0f);
camera.setYaw(yaw);
// handle pitch rotation // handle pitch rotation
float pitch = camera.getPitch() + static_cast<float>(yOffset) * m_cameraSpeed; m_pitch = m_pitch + static_cast<float>(yOffset) * 90.0f * m_cameraSpeed;
pitch += 360.0f * (pitch < 0.0f) - 360.0f * (pitch > 360.0f);
camera.setPitch(pitch);
} }
void TrackballCameraController::updateRadius(double offset, Camera &camera) { void TrackballCameraController::updateRadius(double offset, Camera &camera) {
...@@ -44,14 +42,11 @@ namespace vkcv::camera { ...@@ -44,14 +42,11 @@ namespace vkcv::camera {
} }
void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) { void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) {
float yaw = camera.getYaw();
float pitch = camera.getPitch();
const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f); const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f); const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis); const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(m_yaw), yAxis);
const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis); const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(m_pitch), xAxis);
const glm::vec3 translation = glm::vec3( const glm::vec3 translation = glm::vec3(
rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f) rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f)
); );
...@@ -72,15 +67,10 @@ namespace vkcv::camera { ...@@ -72,15 +67,10 @@ namespace vkcv::camera {
} }
void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) { void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
if(!m_rotationActive){ xoffset *= static_cast<float>(m_rotationActive);
return; yoffset *= static_cast<float>(m_rotationActive);
}
float sensitivity = 0.025f;
xoffset *= sensitivity;
yoffset *= sensitivity;
panView(xoffset , yoffset, camera); panView(xoffset, yoffset, camera);
} }
void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) { void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
...@@ -96,7 +86,7 @@ namespace vkcv::camera { ...@@ -96,7 +86,7 @@ namespace vkcv::camera {
GLFWgamepadstate gamepadState; GLFWgamepadstate gamepadState;
glfwGetGamepadState(gamepadIndex, &gamepadState); glfwGetGamepadState(gamepadIndex, &gamepadState);
float sensitivity = 100.0f; float sensitivity = 1.0f;
double threshold = 0.1; double threshold = 0.1;
// handle rotations // handle rotations
......
...@@ -12,8 +12,6 @@ set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include) ...@@ -12,8 +12,6 @@ set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include)
set(vkcv_material_sources set(vkcv_material_sources
${vkcv_material_include}/vkcv/material/Material.hpp ${vkcv_material_include}/vkcv/material/Material.hpp
${vkcv_material_source}/vkcv/material/Material.cpp ${vkcv_material_source}/vkcv/material/Material.cpp
${vkcv_material_include}/vkcv/material/PBRMaterial.hpp
${vkcv_material_source}/vkcv/material/PBRMaterial.cpp
) )
# adding source files to the module # adding source files to the module
......