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

Target

Select target project
  • vulkan2021/vkcv-framework
1 result
Show changes
Showing
with 1709 additions and 304 deletions
#pragma once
#include <vector>
#include <vkcv/Core.hpp>
#include <vkcv/Handles.hpp>
namespace vkcv::material {
enum class MaterialType {
PBR_MATERIAL = 1,
UNKNOWN = 0
};
class Material {
private:
struct Texture {
ImageHandle m_Image;
SamplerHandle m_Sampler;
std::vector<float> m_Factors;
};
MaterialType m_Type;
DescriptorSetHandle m_DescriptorSet;
std::vector<Texture> m_Textures;
public:
const DescriptorSetHandle m_DescriptorSetHandle;
protected:
Material(const DescriptorSetHandle& setHandle);
Material();
~Material() = default;
Material(const Material& other) = default;
Material(Material&& other) = default;
Material& operator=(const Material& other) = default;
Material& operator=(Material&& other) = default;
[[nodiscard]]
MaterialType getType() const;
[[nodiscard]]
const DescriptorSetHandle& getDescriptorSet() const;
explicit operator bool() const;
bool operator!() const;
static const std::vector<DescriptorBinding>& getDescriptorBindings(MaterialType type);
static Material createPBR(Core &core,
const ImageHandle &colorImg,
const SamplerHandle &colorSmp,
const ImageHandle &normalImg,
const SamplerHandle &normalSmp,
const ImageHandle &metRoughImg,
const SamplerHandle &metRoughSmp,
const ImageHandle &occlusionImg,
const SamplerHandle &occlusionSmp,
const ImageHandle &emissiveImg,
const SamplerHandle &emissiveSmp,
const float baseColorFactor [4],
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
const float emissiveFactor [3]);
};
}
#pragma once
#include <vector>
#include <vkcv/DescriptorConfig.hpp>
#include <vkcv/Core.hpp>
#include "Material.hpp"
namespace vkcv::material
{
class PBRMaterial : Material
{
private:
struct vec3 {
float x, y, z;
};
struct vec4 {
float x, y, z, a;
};
PBRMaterial(const ImageHandle& colorImg,
const SamplerHandle& colorSmp,
const ImageHandle& normalImg,
const SamplerHandle& normalSmp,
const ImageHandle& metRoughImg,
const SamplerHandle& metRoughSmp,
const ImageHandle& occlusionImg,
const SamplerHandle& occlusionSmp,
const ImageHandle& emissiveImg,
const SamplerHandle& emissiveSmp,
const DescriptorSetHandle& setHandle,
vec4 baseColorFactor,
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
vec3 emissiveFactor) noexcept;
public:
PBRMaterial() = delete;
const ImageHandle m_ColorTexture;
const SamplerHandle m_ColorSampler;
const ImageHandle m_NormalTexture;
const SamplerHandle m_NormalSampler;
const ImageHandle m_MetRoughTexture;
const SamplerHandle m_MetRoughSampler;
const ImageHandle m_OcclusionTexture;
const SamplerHandle m_OcclusionSampler;
const ImageHandle m_EmissiveTexture;
const SamplerHandle m_EmissiveSampler;
//
const vec4 m_BaseColorFactor;
const float m_MetallicFactor;
const float m_RoughnessFactor;
const float m_NormalScale;
const float m_OcclusionStrength;
const vec3 m_EmissiveFactor;
/*
* Returns the material's necessary descriptor bindings which serves as its descriptor layout
* The binding is in the following order:
* 0 - diffuse texture
* 1 - diffuse sampler
* 2 - normal texture
* 3 - normal sampler
* 4 - metallic roughness texture
* 5 - metallic roughness sampler
* 6 - occlusion texture
* 7 - occlusion sampler
* 8 - emissive texture
* 9 - emissive sampler
*/
static std::vector<DescriptorBinding> getDescriptorBindings() noexcept;
static PBRMaterial create(
vkcv::Core* core,
ImageHandle &colorImg,
SamplerHandle &colorSmp,
ImageHandle &normalImg,
SamplerHandle &normalSmp,
ImageHandle &metRoughImg,
SamplerHandle &metRoughSmp,
ImageHandle &occlusionImg,
SamplerHandle &occlusionSmp,
ImageHandle &emissiveImg,
SamplerHandle &emissiveSmp,
vec4 baseColorFactor,
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
vec3 emissiveFactor);
};
}
\ No newline at end of file
......@@ -2,11 +2,185 @@
#include "vkcv/material/Material.hpp"
namespace vkcv::material {
Material::Material() {
m_Type = MaterialType::UNKNOWN;
}
//TODO
Material::Material(const DescriptorSetHandle& setHandle) : m_DescriptorSetHandle(setHandle)
MaterialType Material::getType() const {
return m_Type;
}
const DescriptorSetHandle & Material::getDescriptorSet() const {
return m_DescriptorSet;
}
Material::operator bool() const {
return (m_Type != MaterialType::UNKNOWN);
}
bool Material::operator!() const {
return (m_Type == MaterialType::UNKNOWN);
}
const std::vector<DescriptorBinding>& Material::getDescriptorBindings(MaterialType type)
{
static std::vector<DescriptorBinding> pbr_bindings;
static std::vector<DescriptorBinding> default_bindings;
switch (type) {
case MaterialType::PBR_MATERIAL:
if (pbr_bindings.empty()) {
pbr_bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
pbr_bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
}
return pbr_bindings;
default:
return default_bindings;
}
}
static void fillImage(Image& image, float data [4]) {
std::vector<float> vec (image.getWidth() * image.getHeight() * image.getDepth() * 4);
for (size_t i = 0; i < vec.size(); i++) {
vec[i] = data[i % 4];
}
image.fill(data);
}
Material Material::createPBR(Core &core,
const ImageHandle &colorImg, const SamplerHandle &colorSmp,
const ImageHandle &normalImg, const SamplerHandle &normalSmp,
const ImageHandle &metRoughImg, const SamplerHandle &metRoughSmp,
const ImageHandle &occlusionImg, const SamplerHandle &occlusionSmp,
const ImageHandle &emissiveImg, const SamplerHandle &emissiveSmp,
const float baseColorFactor [4],
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
const float emissiveFactor [3]) {
ImageHandle images [5] = {
colorImg, normalImg, metRoughImg, occlusionImg, emissiveImg
};
SamplerHandle samplers [5] = {
colorSmp, normalSmp, metRoughSmp, occlusionSmp, emissiveSmp
};
if (!colorImg) {
vkcv::Image defaultColor = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
float colorData [4] = { 228, 51, 255, 1 };
fillImage(defaultColor, colorData);
images[0] = defaultColor.getHandle();
}
if (!normalImg) {
vkcv::Image defaultNormal = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
float normalData [4] = { 0, 0, 1, 0 };
fillImage(defaultNormal, normalData);
images[1] = defaultNormal.getHandle();
}
if (!metRoughImg) {
vkcv::Image defaultRough = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
float roughData [4] = { 228, 51, 255, 1 };
fillImage(defaultRough, roughData);
images[2] = defaultRough.getHandle();
}
if (!occlusionImg) {
vkcv::Image defaultOcclusion = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
float occlusionData [4] = { 228, 51, 255, 1 };
fillImage(defaultOcclusion, occlusionData);
images[3] = defaultOcclusion.getHandle();
}
if (!emissiveImg) {
vkcv::Image defaultEmissive = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
float emissiveData [4] = { 0, 0, 0, 1 };
fillImage(defaultEmissive, emissiveData);
images[4] = defaultEmissive.getHandle();
}
if (!colorSmp) {
samplers[0] = core.createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!normalSmp) {
samplers[1] = core.createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!metRoughSmp) {
samplers[2] = core.createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!occlusionSmp) {
samplers[3] = core.createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!emissiveSmp) {
samplers[4] = core.createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
Material material;
material.m_Type = MaterialType::PBR_MATERIAL;
const auto& bindings = getDescriptorBindings(material.m_Type);
material.m_DescriptorSet = core.createDescriptorSet(bindings);;
material.m_Textures.reserve(bindings.size());
material.m_Textures.push_back({ images[0], samplers[0], std::vector<float>(baseColorFactor, baseColorFactor+4) });
material.m_Textures.push_back({ images[1], samplers[1], { normalScale } });
material.m_Textures.push_back({ images[2], samplers[2], { metallicFactor, roughnessFactor } });
material.m_Textures.push_back({ images[3], samplers[3], { occlusionStrength } });
material.m_Textures.push_back({ images[4], samplers[4], std::vector<float>(emissiveFactor, emissiveFactor+3) });
vkcv::DescriptorWrites setWrites;
for (size_t i = 0; i < material.m_Textures.size(); i++) {
setWrites.sampledImageWrites.emplace_back(i * 2, material.m_Textures[i].m_Image);
setWrites.samplerWrites.emplace_back(i * 2 + 1, material.m_Textures[i].m_Sampler);
}
core.writeDescriptorSet(material.m_DescriptorSet, setWrites);
return material;
}
}
#include "vkcv/material/PBRMaterial.hpp"
namespace vkcv::material
{
PBRMaterial::PBRMaterial(
const ImageHandle& colorImg,
const SamplerHandle& colorSmp,
const ImageHandle& normalImg,
const SamplerHandle& normalSmp,
const ImageHandle& metRoughImg,
const SamplerHandle& metRoughSmp,
const ImageHandle& occlusionImg,
const SamplerHandle& occlusionSmp,
const ImageHandle& emissiveImg,
const SamplerHandle& emissiveSmp,
const DescriptorSetHandle& setHandle,
vec4 baseColorFactor,
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
vec3 emissiveFactor) noexcept :
m_ColorTexture(colorImg),
m_ColorSampler(colorSmp),
m_NormalTexture(normalImg),
m_NormalSampler(normalSmp),
m_MetRoughTexture(metRoughImg),
m_MetRoughSampler(metRoughSmp),
m_OcclusionTexture(occlusionImg),
m_OcclusionSampler(occlusionSmp),
m_EmissiveTexture(emissiveImg),
m_EmissiveSampler(emissiveSmp),
Material(setHandle),
m_BaseColorFactor(baseColorFactor),
m_MetallicFactor(metallicFactor),
m_RoughnessFactor(roughnessFactor),
m_NormalScale(normalScale),
m_OcclusionStrength(occlusionStrength),
m_EmissiveFactor(emissiveFactor)
{
}
std::vector<DescriptorBinding> PBRMaterial::getDescriptorBindings() noexcept
{
static std::vector<DescriptorBinding> bindings;
if (bindings.empty()) {
bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
}
return bindings;
}
PBRMaterial PBRMaterial::create(
vkcv::Core* core,
ImageHandle& colorImg,
SamplerHandle& colorSmp,
ImageHandle& normalImg,
SamplerHandle& normalSmp,
ImageHandle& metRoughImg,
SamplerHandle& metRoughSmp,
ImageHandle& occlusionImg,
SamplerHandle& occlusionSmp,
ImageHandle& emissiveImg,
SamplerHandle& emissiveSmp,
vec4 baseColorFactor,
float metallicFactor,
float roughnessFactor,
float normalScale,
float occlusionStrength,
vec3 emissiveFactor)
{
//Test if Images and samplers valid, if not use default
if (!colorImg) {
vkcv::Image defaultColor = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
vec4 colorData{ 228, 51, 255,1 };
defaultColor.fill(&colorData);
colorImg = defaultColor.getHandle();
}
if (!normalImg) {
vkcv::Image defaultNormal = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
vec4 normalData{ 0, 0, 1,0 };
defaultNormal.fill(&normalData);
normalImg = defaultNormal.getHandle();
}
if (!metRoughImg) {
vkcv::Image defaultRough = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
vec4 roughData{ 228, 51, 255,1 };
defaultRough.fill(&roughData);
metRoughImg = defaultRough.getHandle();
}
if (!occlusionImg) {
vkcv::Image defaultOcclusion = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
vec4 occlusionData{ 228, 51, 255,1 };
defaultOcclusion.fill(&occlusionData);
occlusionImg = defaultOcclusion.getHandle();
}
if (!emissiveImg) {
vkcv::Image defaultEmissive = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
vec4 emissiveData{ 0, 0, 0,1 };
defaultEmissive.fill(&emissiveData);
emissiveImg = defaultEmissive.getHandle();
}
if (!colorSmp) {
colorSmp = core->createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!normalSmp) {
normalSmp = core->createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!metRoughSmp) {
metRoughSmp = core->createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!occlusionSmp) {
occlusionSmp = core->createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
if (!emissiveSmp) {
emissiveSmp = core->createSampler(
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerFilterType::LINEAR,
vkcv::SamplerMipmapMode::LINEAR,
vkcv::SamplerAddressMode::REPEAT
);
}
//create descriptorset
vkcv::DescriptorSetHandle descriptorSetHandle = core->createDescriptorSet(getDescriptorBindings());
//writes
vkcv::DescriptorWrites setWrites;
setWrites.sampledImageWrites = {
vkcv::SampledImageDescriptorWrite(0, colorImg),
vkcv::SampledImageDescriptorWrite(2, normalImg),
vkcv::SampledImageDescriptorWrite(4, metRoughImg),
vkcv::SampledImageDescriptorWrite(6, occlusionImg),
vkcv::SampledImageDescriptorWrite(8, emissiveImg) };
setWrites.samplerWrites = {
vkcv::SamplerDescriptorWrite(1, colorSmp),
vkcv::SamplerDescriptorWrite(3, normalSmp),
vkcv::SamplerDescriptorWrite(5, metRoughSmp),
vkcv::SamplerDescriptorWrite(7, occlusionSmp),
vkcv::SamplerDescriptorWrite(9, emissiveSmp) };
core->writeDescriptorSet(descriptorSetHandle, setWrites);
return PBRMaterial(
colorImg,
colorSmp,
normalImg,
normalSmp,
metRoughImg,
metRoughSmp,
occlusionImg,
occlusionSmp,
emissiveImg,
emissiveSmp,
descriptorSetHandle,
baseColorFactor,
metallicFactor,
roughnessFactor,
normalScale,
occlusionStrength,
emissiveFactor);
}
}
\ No newline at end of file
cmake_minimum_required(VERSION 3.16)
project(vkcv_meshlet)
# setting c++ standard for the module
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(vkcv_meshlet_source ${PROJECT_SOURCE_DIR}/src)
set(vkcv_meshlet_include ${PROJECT_SOURCE_DIR}/include)
# Add source and header files to the module
set(vkcv_meshlet_sources
${vkcv_meshlet_include}/vkcv/meshlet/Meshlet.hpp
${vkcv_meshlet_source}/vkcv/meshlet/Meshlet.cpp
${vkcv_meshlet_include}/vkcv/meshlet/Tipsify.hpp
${vkcv_meshlet_source}/vkcv/meshlet/Tipsify.cpp
${vkcv_meshlet_include}/vkcv/meshlet/Forsyth.hpp
${vkcv_meshlet_source}/vkcv/meshlet/Forsyth.cpp)
# adding source files to the module
add_library(vkcv_meshlet STATIC ${vkcv_meshlet_sources})
# link the required libraries to the module
target_link_libraries(vkcv_meshlet vkcv ${vkcv_libraries})
# including headers of dependencies and the VkCV framework
target_include_directories(vkcv_meshlet SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
# add the own include directory for public headers
target_include_directories(vkcv_meshlet BEFORE PUBLIC ${vkcv_meshlet_include})
# linking with libraries from all dependencies and the VkCV framework
target_link_libraries(vkcv_meshlet vkcv vkcv_asset_loader vkcv_camera)
#pragma once
#include "Meshlet.hpp"
namespace vkcv::meshlet
{
/**
* Reorders the index buffer, simulating a LRU cache, so that vertices are grouped together in close triangle patches
* @param idxBuf current IndexBuffer
* @param vertexCount of the mesh
* @return new reordered index buffer to replace the input index buffer
* References:
* https://tomforsyth1000.github.io/papers/fast_vert_cache_opt.html
* https://www.martin.st/thesis/efficient_triangle_reordering.pdf
* https://github.com/vivkin/forsyth/blob/master/forsyth.h
*/
VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount);
}
#pragma once
#include <vector>
#include <map>
#include <glm/glm.hpp>
#include <vkcv/asset/asset_loader.hpp>
namespace vkcv::meshlet {
struct Vertex {
glm::vec3 position;
float padding0;
glm::vec3 normal;
float padding1;
};
struct Meshlet {
uint32_t vertexOffset;
uint32_t vertexCount;
uint32_t indexOffset;
uint32_t indexCount;
glm::vec3 meanPosition;
float boundingSphereRadius;
};
struct VertexCacheReorderResult {
/**
* @param indexBuffer new indexBuffer
* @param skippedIndices indices that have a spacial break
*/
VertexCacheReorderResult(const std::vector<uint32_t> indexBuffer, const std::vector<uint32_t> skippedIndices)
:indexBuffer(indexBuffer), skippedIndices(skippedIndices) {}
std::vector<uint32_t> indexBuffer;
std::vector<uint32_t> skippedIndices;
};
struct MeshShaderModelData {
std::vector<Vertex> vertices;
std::vector<uint32_t> localIndices;
std::vector<Meshlet> meshlets;
};
std::vector<Vertex> convertToVertices(
const std::vector<uint8_t>& vertexData,
const uint64_t vertexCount,
const vkcv::asset::VertexAttribute& positionAttribute,
const vkcv::asset::VertexAttribute& normalAttribute);
MeshShaderModelData createMeshShaderModelData(
const std::vector<Vertex>& inVertices,
const std::vector<uint32_t>& inIndices,
const std::vector<uint32_t>& deadEndIndices = {});
std::vector<uint32_t> assetLoaderIndicesTo32BitIndices(
const std::vector<uint8_t>& indexData,
vkcv::asset::IndexType indexType);
}
\ No newline at end of file
#pragma once
#include "Meshlet.hpp"
#include <algorithm>
#include <iostream>
namespace vkcv::meshlet {
/**
* reorders the IndexBuffer, so all usages of vertices to triangle are as close as possible
* @param indexBuffer32Bit current IndexBuffer
* @param vertexCount of the mesh
* @param cacheSize of the priority cache <br>
* Recommended: 20. Keep the value between 5 and 50 <br>
* low: more random and patchy<br>
* high: closer vertices have higher chance -> leads to sinuous lines
* @return new IndexBuffer that replaces the input IndexBuffer, and the indices that are skipped
*
* https://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf
* https://www.martin.st/thesis/efficient_triangle_reordering.pdf
*/
VertexCacheReorderResult tipsifyMesh(const std::vector<uint32_t> &indexBuffer32Bit,
const int vertexCount, const unsigned int cacheSize = 20);
}
\ No newline at end of file
#include "vkcv/meshlet/Forsyth.hpp"
#include <vkcv/Logger.hpp>
#include <array>
#include <cmath>
namespace vkcv::meshlet
{
/*
* CACHE AND VALENCE
* SIZE AND SCORE CONSTANTS
* CHANGE AS NEEDED
*/
// set these to adjust performance and result quality
const size_t VERTEX_CACHE_SIZE = 8;
const size_t CACHE_FUNCTION_LENGTH = 32;
// score function constants
const float CACHE_DECAY_POWER = 1.5f;
const float LAST_TRI_SCORE = 0.75f;
const float VALENCE_BOOST_SCALE = 2.0f;
const float VALENCE_BOOST_POWER = 0.5f;
// sizes for precalculated tables
// make sure that cache score is always >= vertex_cache_size
const size_t CACHE_SCORE_TABLE_SIZE = 32;
const size_t VALENCE_SCORE_TABLE_SIZE = 32;
// precalculated tables
std::array<float, CACHE_SCORE_TABLE_SIZE> cachePositionScore = {};
std::array<float, VALENCE_SCORE_TABLE_SIZE> valenceScore = {};
// function to populate the cache position and valence score tables
void initScoreTables()
{
for(size_t i = 0; i < CACHE_SCORE_TABLE_SIZE; i++)
{
float score = 0.0f;
if (i < 3)
{
score = LAST_TRI_SCORE;
}
else
{
const float scaler = 1.0f / static_cast<float>(CACHE_FUNCTION_LENGTH - 3);
score = 1.0f - (i - 3) * scaler;
score = std::pow(score, CACHE_DECAY_POWER);
}
cachePositionScore[i] = score;
}
for(size_t i = 0; i < VALENCE_SCORE_TABLE_SIZE; i++)
{
const float valenceBoost = std::pow(i, -VALENCE_BOOST_POWER);
const float score = VALENCE_BOOST_SCALE * valenceBoost;
valenceScore[i] = score;
}
}
/**
* Return the vertex' score, depending on its current active triangle count and cache position
* Add a valence boost to score, if active triangles are below VALENCE_SCORE_TABLE_SIZE
* @param numActiveTris the active triangles on this vertex
* @param cachePos the vertex' position in the cache
* @return vertex' score
*/
float findVertexScore(uint32_t numActiveTris, int32_t cachePos)
{
if(numActiveTris == 0)
return 0.0f;
float score = 0.0f;
if (cachePos >= 0)
score = cachePositionScore[cachePos];
if (numActiveTris < VALENCE_SCORE_TABLE_SIZE)
score += valenceScore[numActiveTris];
return score;
}
VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount)
{
std::vector<uint32_t> skippedIndices;
initScoreTables();
// get the total triangle count from the index buffer
const size_t triangleCount = idxBuf.size() / 3;
// per-vertex active triangle count
std::vector<uint8_t> numActiveTris(vertexCount, 0);
// iterate over indices, count total occurrences of each vertex
for(const auto index : idxBuf)
{
if(numActiveTris[index] == UINT8_MAX)
{
vkcv_log(LogLevel::ERROR, "Unsupported mesh.");
vkcv_log(LogLevel::ERROR, "Vertex shared by too many triangles.");
return VertexCacheReorderResult({}, {});
}
numActiveTris[index]++;
}
// allocate remaining vectors
/**
* offsets: contains the vertices' offset into the triangleIndices vector
* Offset itself is the sum of triangles required by the previous vertices
*
* lastScore: the vertices' most recent calculated score
*
* cacheTag: the vertices' most recent cache score
*
* triangleAdded: boolean flags to denote whether a triangle has been processed or not
*
* triangleScore: total score of the three vertices making up the triangle
*
* triangleIndices: indices for the triangles
*/
std::vector<uint32_t> offsets(vertexCount, 0);
std::vector<float> lastScore(vertexCount, 0.0f);
std::vector<int8_t> cacheTag(vertexCount, -1);
std::vector<bool> triangleAdded(triangleCount, false);
std::vector<float> triangleScore(triangleCount, 0.0f);
std::vector<int32_t> triangleIndices(idxBuf.size(), 0);
// sum the number of active triangles for all previous vertices
// null the number of active triangles afterwards for recalculation in second loop
uint32_t sum = 0;
for(size_t i = 0; i < vertexCount; i++)
{
offsets[i] = sum;
sum += numActiveTris[i];
numActiveTris[i] = 0;
}
// create the triangle indices, using the newly calculated offsets, and increment numActiveTris
// every vertex should be referenced by a triangle index now
for(size_t i = 0; i < triangleCount; i++)
{
for(size_t j = 0; j < 3; j++)
{
uint32_t v = idxBuf[3 * i + j];
triangleIndices[offsets[v] + numActiveTris[v]] = static_cast<int32_t>(i);
numActiveTris[v]++;
}
}
// calculate and initialize the triangle score, by summing the vertices' score
for (size_t i = 0; i < vertexCount; i++)
{
lastScore[i] = findVertexScore(numActiveTris[i], static_cast<int32_t>(cacheTag[i]));
for(size_t j = 0; j < numActiveTris[i]; j++)
{
triangleScore[triangleIndices[offsets[i] + j]] += lastScore[i];
}
}
// find best triangle to start reordering with
int32_t bestTriangle = -1;
float bestScore = -1.0f;
for(size_t i = 0; i < triangleCount; i++)
{
if(triangleScore[i] > bestScore)
{
bestScore = triangleScore[i];
bestTriangle = static_cast<int32_t>(i);
}
}
// allocate output triangles
std::vector<int32_t> outTriangles(triangleCount, 0);
uint32_t outPos = 0;
// initialize cache (with -1)
std::array<int32_t, VERTEX_CACHE_SIZE + 3> cache = {};
for(auto &element : cache)
{
element = -1;
}
uint32_t scanPos = 0;
// begin reordering routine
// output the currently best triangle, as long as there are triangles left to output
while(bestTriangle >= 0)
{
// mark best triangle as added
triangleAdded[bestTriangle] = true;
// output this triangle
outTriangles[outPos++] = bestTriangle;
// push best triangle's vertices into the cache
for(size_t i = 0; i < 3; i++)
{
uint32_t v = idxBuf[3 * bestTriangle + i];
// get vertex' cache position, if its -1, set its position to the end
int8_t endPos = cacheTag[v];
if(endPos < 0)
endPos = static_cast<int8_t>(VERTEX_CACHE_SIZE + i);
// shift vertices' cache entries forward by one
for(int8_t j = endPos; j > i; j--)
{
cache[j] = cache[j - 1];
// if cache slot is valid vertex,
// update the vertex cache tag accordingly
if (cache[j] >= 0)
cacheTag[cache[j]]++;
}
// insert current vertex into its new target slot
cache[i] = static_cast<int32_t>(v);
cacheTag[v] = static_cast<int8_t>(i);
// find current triangle in the list of active triangles
// remove it by moving the last triangle into the slot the current triangle is holding.
for (size_t j = 0; j < numActiveTris[v]; j++)
{
if(triangleIndices[offsets[v] + j] == bestTriangle)
{
triangleIndices[offsets[v] + j] = triangleIndices[offsets[v] + numActiveTris[v] - 1];
break;
}
}
// shorten the list
numActiveTris[v]--;
}
// update scores of all triangles in cache
for (size_t i = 0; i < cache.size(); i++)
{
int32_t v = cache[i];
if (v < 0)
break;
// this vertex has been pushed outside of the actual cache
if(i >= VERTEX_CACHE_SIZE)
{
cacheTag[v] = -1;
cache[i] = -1;
}
float newScore = findVertexScore(numActiveTris[v], cacheTag[v]);
float diff = newScore - lastScore[v];
for(size_t j = 0; j < numActiveTris[v]; j++)
{
triangleScore[triangleIndices[offsets[v] + j]] += diff;
}
lastScore[v] = newScore;
}
// find best triangle reference by vertices in cache
bestTriangle = -1;
bestScore = -1.0f;
for(size_t i = 0; i < VERTEX_CACHE_SIZE; i++)
{
if (cache[i] < 0)
break;
int32_t v = cache[i];
for(size_t j = 0; j < numActiveTris[v]; j++)
{
int32_t t = triangleIndices[offsets[v] + j];
if(triangleScore[t] > bestScore)
{
bestTriangle = t;
bestScore = triangleScore[t];
}
}
}
// if no triangle was found at all, continue scanning whole list of triangles
if (bestTriangle < 0)
{
for(; scanPos < triangleCount; scanPos++)
{
if(!triangleAdded[scanPos])
{
bestTriangle = scanPos;
skippedIndices.push_back(3 * outPos);
break;
}
}
}
}
// convert triangle index array into full triangle list
std::vector<uint32_t> outIndices(idxBuf.size(), 0);
outPos = 0;
for(size_t i = 0; i < triangleCount; i++)
{
int32_t t = outTriangles[i];
for(size_t j = 0; j < 3; j++)
{
int32_t v = idxBuf[3 * t + j];
outIndices[outPos++] = static_cast<uint32_t>(v);
}
}
return VertexCacheReorderResult(outIndices, skippedIndices);
}
}
\ No newline at end of file
#include "vkcv/meshlet/Meshlet.hpp"
#include <vkcv/Logger.hpp>
#include <cassert>
#include <iostream>
namespace vkcv::meshlet {
std::vector<vkcv::meshlet::Vertex> convertToVertices(
const std::vector<uint8_t>& vertexData,
const uint64_t vertexCount,
const vkcv::asset::VertexAttribute& positionAttribute,
const vkcv::asset::VertexAttribute& normalAttribute) {
assert(positionAttribute.type == vkcv::asset::PrimitiveType::POSITION);
assert(normalAttribute.type == vkcv::asset::PrimitiveType::NORMAL);
std::vector<vkcv::meshlet::Vertex> vertices;
vertices.reserve(vertexCount);
const size_t positionStepSize = positionAttribute.stride == 0 ? sizeof(glm::vec3) : positionAttribute.stride;
const size_t normalStepSize = normalAttribute.stride == 0 ? sizeof(glm::vec3) : normalAttribute.stride;
for (int i = 0; i < vertexCount; i++) {
Vertex v;
const size_t positionOffset = positionAttribute.offset + positionStepSize * i;
const size_t normalOffset = normalAttribute.offset + normalStepSize * i;
v.position = *reinterpret_cast<const glm::vec3*>(&(vertexData[positionOffset]));
v.normal = *reinterpret_cast<const glm::vec3*>(&(vertexData[normalOffset]));
vertices.push_back(v);
}
return vertices;
}
MeshShaderModelData createMeshShaderModelData(
const std::vector<Vertex>& inVertices,
const std::vector<uint32_t>& inIndices,
const std::vector<uint32_t>& deadEndIndices) {
MeshShaderModelData data;
size_t currentIndex = 0;
const size_t maxVerticesPerMeshlet = 64;
const size_t maxIndicesPerMeshlet = 126 * 3;
bool indicesAreLeft = true;
size_t deadEndIndicesIndex = 0;
while (indicesAreLeft) {
Meshlet meshlet;
meshlet.indexCount = 0;
meshlet.vertexCount = 0;
meshlet.indexOffset = data.localIndices.size();
meshlet.vertexOffset = data.vertices.size();
std::map<uint32_t, uint32_t> globalToLocalIndexMap;
std::vector<uint32_t> globalIndicesOrdered;
while (true) {
if (deadEndIndicesIndex < deadEndIndices.size()) {
const uint32_t deadEndIndex = deadEndIndices[deadEndIndicesIndex];
if (deadEndIndex == currentIndex) {
deadEndIndicesIndex++;
break;
}
}
indicesAreLeft = currentIndex + 1 <= inIndices.size();
if (!indicesAreLeft) {
break;
}
bool enoughSpaceForIndices = meshlet.indexCount + 3 < maxIndicesPerMeshlet;
if (!enoughSpaceForIndices) {
break;
}
size_t vertexCountToAdd = 0;
for (int i = 0; i < 3; i++) {
const uint32_t globalIndex = inIndices[currentIndex + i];
const bool containsVertex = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end();
if (!containsVertex) {
vertexCountToAdd++;
}
}
bool enoughSpaceForVertices = meshlet.vertexCount + vertexCountToAdd < maxVerticesPerMeshlet;
if (!enoughSpaceForVertices) {
break;
}
for (int i = 0; i < 3; i++) {
const uint32_t globalIndex = inIndices[currentIndex + i];
uint32_t localIndex;
const bool indexAlreadyExists = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end();
if (indexAlreadyExists) {
localIndex = globalToLocalIndexMap[globalIndex];
}
else {
localIndex = globalToLocalIndexMap.size();
globalToLocalIndexMap[globalIndex] = localIndex;
globalIndicesOrdered.push_back(globalIndex);
}
data.localIndices.push_back(localIndex);
}
meshlet.indexCount += 3;
currentIndex += 3;
meshlet.vertexCount += vertexCountToAdd;
}
for (const uint32_t globalIndex : globalIndicesOrdered) {
const Vertex v = inVertices[globalIndex];
data.vertices.push_back(v);
}
// compute mean position
meshlet.meanPosition = glm::vec3(0);
const uint32_t meshletLastVertexIndex = meshlet.vertexOffset + meshlet.vertexCount;
for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) {
const Vertex& v = data.vertices[vertexIndex];
meshlet.meanPosition += v.position;
}
meshlet.meanPosition /= meshlet.vertexCount;
// compute bounding sphere radius
meshlet.boundingSphereRadius = 0.f;
for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) {
const Vertex& v = data.vertices[vertexIndex];
const float d = glm::distance(v.position, meshlet.meanPosition);
meshlet.boundingSphereRadius = glm::max(meshlet.boundingSphereRadius, d);
}
data.meshlets.push_back(meshlet);
}
return data;
}
std::vector<uint32_t> assetLoaderIndicesTo32BitIndices(const std::vector<uint8_t>& indexData, vkcv::asset::IndexType indexType) {
std::vector<uint32_t> indices;
if (indexType == vkcv::asset::IndexType::UINT16) {
for (int i = 0; i < indexData.size(); i += 2) {
const uint16_t index16Bit = *reinterpret_cast<const uint16_t *>(&(indexData[i]));
const uint32_t index32Bit = static_cast<uint32_t>(index16Bit);
indices.push_back(index32Bit);
}
} else if (indexType == vkcv::asset::IndexType::UINT32) {
for (int i = 0; i < indexData.size(); i += 4) {
const uint32_t index32Bit = *reinterpret_cast<const uint32_t *>(&(indexData[i]));
indices.push_back(index32Bit);
}
} else {
vkcv_log(vkcv::LogLevel::ERROR, "Unsupported index type");
}
return indices;
}
}
\ No newline at end of file
#include <vkcv/Logger.hpp>
#include "vkcv/meshlet/Tipsify.hpp"
#include <iostream>
namespace vkcv::meshlet {
const int maxUsedVertices = 128;
/**
* modulo operation with maxUsedVertices
* @param number for modulo operation
* @return number between 0 and maxUsedVertices - 1
*/
int mod( int number ){
return (number + maxUsedVertices) % maxUsedVertices;
}
/**
* searches for the next VertexIndex that was used before or returns any vertexIndex if no used was found
* @param livingTriangles
* @param usedVerticeStack
* @param usedVerticeCount
* @param usedVerticeOffset
* @param vertexCount
* @param lowestLivingVertexIndex
* @param currentTriangleIndex
* @param skippedIndices
* @return a VertexIndex to be used as fanningVertexIndex
*/
int skipDeadEnd(
const std::vector<uint8_t> &livingTriangles,
const std::vector<uint32_t> &usedVerticeStack,
int &usedVerticeCount,
int &usedVerticeOffset,
int vertexCount,
int &lowestLivingVertexIndex,
int &currentTriangleIndex,
std::vector<uint32_t> &skippedIndices) {
// returns the latest vertex used that has a living triangle
while (mod(usedVerticeCount) != usedVerticeOffset) {
// iterate from the latest to the oldest. + maxUsedVertices to always make it a positive number in the range 0 to maxUsedVertices -1
int nextVertex = usedVerticeStack[mod(--usedVerticeCount)];
if (livingTriangles[nextVertex] > 0) {
return nextVertex;
}
}
// returns any vertexIndex since no last used has a living triangle
while (lowestLivingVertexIndex + 1 < vertexCount) {
lowestLivingVertexIndex++;
if (livingTriangles[lowestLivingVertexIndex] > 0) {
// add index of the vertex to skippedIndices
skippedIndices.push_back(static_cast<uint32_t>(currentTriangleIndex * 3));
return lowestLivingVertexIndex;
}
}
return -1;
}
/**
* searches for the best next candidate as a fanningVertexIndex
* @param vertexCount
* @param lowestLivingVertexIndex
* @param cacheSize
* @param possibleCandidates
* @param numPossibleCandidates
* @param lastTimestampCache
* @param currentTimeStamp
* @param livingTriangles
* @param usedVerticeStack
* @param usedVerticeCount
* @param usedVerticeOffset
* @param currentTriangleIndex
* @param skippedIndices
* @return a VertexIndex to be used as fanningVertexIndex
*/
int getNextVertexIndex(int vertexCount,
int &lowestLivingVertexIndex,
int cacheSize,
const std::vector<uint32_t> &possibleCandidates,
int numPossibleCandidates,
const std::vector<uint32_t> &lastTimestampCache,
int currentTimeStamp,
const std::vector<uint8_t> &livingTriangles,
const std::vector<uint32_t> &usedVerticeStack,
int &usedVerticeCount,
int &usedVerticeOffset,
int &currentTriangleIndex,
std::vector<uint32_t> &skippedIndices) {
int nextVertexIndex = -1;
int maxPriority = -1;
// calculates the next possibleCandidates that is recently used
for (int j = 0; j < numPossibleCandidates; j++) {
int vertexIndex = possibleCandidates[j];
// the candidate needs to be not fanned out yet
if (livingTriangles[vertexIndex] > 0) {
int priority = -1;
// prioritizes recent used vertices, but tries not to pick one that has many triangles -> fills holes better
if ( currentTimeStamp - lastTimestampCache[vertexIndex] + 2 * livingTriangles[vertexIndex] <=
cacheSize) {
priority = currentTimeStamp - lastTimestampCache[vertexIndex];
}
// select the vertexIndex with the highest priority
if (priority > maxPriority) {
maxPriority = priority;
nextVertexIndex = vertexIndex;
}
}
}
// if no candidate is alive, try and find another one
if (nextVertexIndex == -1) {
nextVertexIndex = skipDeadEnd(
livingTriangles,
usedVerticeStack,
usedVerticeCount,
usedVerticeOffset,
vertexCount,
lowestLivingVertexIndex,
currentTriangleIndex,
skippedIndices);
}
return nextVertexIndex;
}
VertexCacheReorderResult tipsifyMesh(
const std::vector<uint32_t> &indexBuffer32Bit,
const int vertexCount,
const unsigned int cacheSize) {
if (indexBuffer32Bit.empty() || vertexCount <= 0) {
vkcv_log(LogLevel::ERROR, "Invalid Input.");
return VertexCacheReorderResult(indexBuffer32Bit , {});
}
int triangleCount = indexBuffer32Bit.size() / 3;
// dynamic array for vertexOccurrence
std::vector<uint8_t> vertexOccurrence(vertexCount, 0);
// count the occurrence of a vertex in all among all triangles
for (size_t i = 0; i < triangleCount * 3; i++) {
vertexOccurrence[indexBuffer32Bit[i]]++;
}
int sum = 0;
std::vector<uint32_t> offsetVertexOccurrence(vertexCount + 1, 0);
// highest offset for later iteration
int maxOffset = 0;
// calculate the offset of each vertex from the start
for (int i = 0; i < vertexCount; i++) {
offsetVertexOccurrence[i] = sum;
sum += vertexOccurrence[i];
if (vertexOccurrence[i] > maxOffset) {
maxOffset = vertexOccurrence[i];
}
// reset for reuse
vertexOccurrence[i] = 0;
}
offsetVertexOccurrence[vertexCount] = sum;
// vertexIndexToTriangle = which vertex belongs to which triangle
std::vector<uint32_t> vertexIndexToTriangle(3 * triangleCount, 0);
// vertexOccurrence functions as number of usages in all triangles
// lowestLivingVertexIndex = number of a triangle
for (int i = 0; i < triangleCount; i++) {
// get the pointer to the first vertex of the triangle
// this allows us to iterate over the indexBuffer with the first vertex of the triangle as start
const uint32_t *vertexIndexOfTriangle = &indexBuffer32Bit[i * 3];
vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[0]] + vertexOccurrence[vertexIndexOfTriangle[0]]] = i;
vertexOccurrence[vertexIndexOfTriangle[0]]++;
vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[1]] + vertexOccurrence[vertexIndexOfTriangle[1]]] = i;
vertexOccurrence[vertexIndexOfTriangle[1]]++;
vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[2]] + vertexOccurrence[vertexIndexOfTriangle[2]]] = i;
vertexOccurrence[vertexIndexOfTriangle[2]]++;
}
// counts if a triangle still uses this vertex
std::vector<uint8_t> livingVertices = vertexOccurrence;
std::vector<uint32_t> lastTimestampCache(vertexCount, 0);
// stack of already used vertices, if it'currentTimeStamp full it will write to 0 again
std::vector<uint32_t> usedVerticeStack(maxUsedVertices, 0);
//currently used vertices
int usedVerticeCount = 0;
// offset if maxUsedVertices was reached and it loops back to 0
int usedVerticeOffset = 0;
// saves if a triangle was emitted (used in the IndexBuffer)
std::vector<bool> isEmittedTriangles(triangleCount, false);
// reordered Triangles that get rewritten to the new IndexBuffer
std::vector<uint32_t> reorderedTriangleIndexBuffer(triangleCount, 0);
// offset to the latest not used triangleIndex
int triangleOutputOffset = 0;
// vertexIndex to fan out from (fanning VertexIndex)
int currentVertexIndex = 0;
int currentTimeStamp = cacheSize + 1;
int lowestLivingVertexIndex = 0;
std::vector<uint32_t> possibleCandidates(3 * maxOffset);
int currentTriangleIndex = 0;
// list of vertex indices where a deadEnd was reached
// useful to know where the mesh is potentially not contiguous
std::vector<uint32_t> skippedIndices;
// run while not all indices are fanned out, -1 equals all are fanned out
while (currentVertexIndex >= 0) {
// number of possible candidates for a fanning VertexIndex
int numPossibleCandidates = 0;
// offset of currentVertexIndex and the next VertexIndex
int startOffset = offsetVertexOccurrence[currentVertexIndex];
int endOffset = offsetVertexOccurrence[currentVertexIndex + 1];
// iterates over every triangle of currentVertexIndex
for (int offset = startOffset; offset < endOffset; offset++) {
int triangleIndex = vertexIndexToTriangle[offset];
// checks if the triangle is already emitted
if (!isEmittedTriangles[triangleIndex]) {
// get the pointer to the first vertex of the triangle
// this allows us to iterate over the indexBuffer with the first vertex of the triangle as start
const uint32_t *vertexIndexOfTriangle = &indexBuffer32Bit[3 * triangleIndex];
currentTriangleIndex++;
// save emitted vertexIndexOfTriangle to reorderedTriangleIndexBuffer and set it to emitted
reorderedTriangleIndexBuffer[triangleOutputOffset++] = triangleIndex;
isEmittedTriangles[triangleIndex] = true;
// save all vertexIndices of the triangle to reuse as soon as possible
for (int j = 0; j < 3; j++) {
int vertexIndex = vertexIndexOfTriangle[j];
//save vertexIndex to reuseStack
usedVerticeStack[mod(usedVerticeCount++)] = vertexIndex;
// after looping back increase the start, so it only overrides the oldest vertexIndex
if ((mod(usedVerticeCount)) ==
(mod(usedVerticeOffset))) {
usedVerticeOffset = mod(usedVerticeOffset + 1);
}
// add vertex to next possibleCandidates as fanning vertex
possibleCandidates[numPossibleCandidates++] = vertexIndex;
// remove one occurrence of the vertex, since the triangle is used
livingVertices[vertexIndex]--;
// writes the timestamp (number of iteration) of the last usage, if it wasn't used within the last cacheSize iterations
if (currentTimeStamp - lastTimestampCache[vertexIndex] > cacheSize) {
lastTimestampCache[vertexIndex] = currentTimeStamp;
currentTimeStamp++;
}
}
}
}
// search for the next vertexIndex to fan out
currentVertexIndex = getNextVertexIndex(
vertexCount, lowestLivingVertexIndex, cacheSize, possibleCandidates, numPossibleCandidates, lastTimestampCache, currentTimeStamp,
livingVertices, usedVerticeStack, usedVerticeCount, usedVerticeOffset, currentTriangleIndex, skippedIndices);
}
std::vector<uint32_t> reorderedIndexBuffer(3 * triangleCount);
triangleOutputOffset = 0;
// rewriting the TriangleIndexBuffer to the new IndexBuffer
for (int i = 0; i < triangleCount; i++) {
int triangleIndex = reorderedTriangleIndexBuffer[i];
// rewriting the triangle index to vertices
for (int j = 0; j < 3; j++) {
int vertexIndex = indexBuffer32Bit[(3 * triangleIndex) + j];
reorderedIndexBuffer[triangleOutputOffset++] = vertexIndex;
}
}
return VertexCacheReorderResult(reorderedIndexBuffer, skippedIndices);
}
}
\ No newline at end of file
cmake_minimum_required(VERSION 3.16)
project(vkcv_scene)
# setting c++ standard for the module
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(vkcv_scene_source ${PROJECT_SOURCE_DIR}/src)
set(vkcv_scene_include ${PROJECT_SOURCE_DIR}/include)
# Add source and header files to the module
set(vkcv_scene_sources
${vkcv_scene_include}/vkcv/scene/Bounds.hpp
${vkcv_scene_source}/vkcv/scene/Bounds.cpp
${vkcv_scene_include}/vkcv/scene/Frustum.hpp
${vkcv_scene_source}/vkcv/scene/Frustum.cpp
${vkcv_scene_include}/vkcv/scene/MeshPart.hpp
${vkcv_scene_source}/vkcv/scene/MeshPart.cpp
${vkcv_scene_include}/vkcv/scene/Mesh.hpp
${vkcv_scene_source}/vkcv/scene/Mesh.cpp
${vkcv_scene_include}/vkcv/scene/Node.hpp
${vkcv_scene_source}/vkcv/scene/Node.cpp
${vkcv_scene_include}/vkcv/scene/Scene.hpp
${vkcv_scene_source}/vkcv/scene/Scene.cpp
)
# adding source files to the module
add_library(vkcv_scene STATIC ${vkcv_scene_sources})
# link the required libraries to the module
target_link_libraries(vkcv_scene vkcv)
# including headers of dependencies and the VkCV framework
target_include_directories(vkcv_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_material_include} ${vkcv_camera_include})
# add the own include directory for public headers
target_include_directories(vkcv_scene BEFORE PUBLIC ${vkcv_scene_include})
# linking with libraries from all dependencies and the VkCV framework
target_link_libraries(vkcv_scene vkcv vkcv_asset_loader vkcv_material vkcv_camera)
#pragma once
#include <array>
#include <iostream>
#include <glm/vec3.hpp>
namespace vkcv::scene {
class Bounds {
private:
glm::vec3 m_min;
glm::vec3 m_max;
public:
Bounds();
Bounds(const glm::vec3& min, const glm::vec3& max);
~Bounds() = default;
Bounds(const Bounds& other) = default;
Bounds(Bounds&& other) = default;
Bounds& operator=(const Bounds& other) = default;
Bounds& operator=(Bounds&& other) = default;
void setMin(const glm::vec3& min);
[[nodiscard]]
const glm::vec3& getMin() const;
void setMax(const glm::vec3& max);
[[nodiscard]]
const glm::vec3& getMax() const;
void setCenter(const glm::vec3& center);
[[nodiscard]]
glm::vec3 getCenter() const;
void setSize(const glm::vec3& size);
[[nodiscard]]
glm::vec3 getSize() const;
[[nodiscard]]
std::array<glm::vec3, 8> getCorners() const;
void extend(const glm::vec3& point);
[[nodiscard]]
bool contains(const glm::vec3& point) const;
[[nodiscard]]
bool contains(const Bounds& other) const;
[[nodiscard]]
bool intersects(const Bounds& other) const;
[[nodiscard]]
explicit operator bool() const;
[[nodiscard]]
bool operator!() const;
};
std::ostream& operator << (std::ostream& out, const Bounds& bounds);
}
#pragma once
#include <glm/mat4x4.hpp>
#include "vkcv/scene/Bounds.hpp"
namespace vkcv::scene {
Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool* negative_w = nullptr);
bool checkFrustum(const glm::mat4& transform, const Bounds& bounds);
}
#pragma once
#include <glm/mat4x4.hpp>
#include <vkcv/camera/Camera.hpp>
#include "MeshPart.hpp"
namespace vkcv::scene {
typedef typename event_function<const glm::mat4&, const glm::mat4&, PushConstants&, vkcv::DrawcallInfo&>::type RecordMeshDrawcallFunction;
class Node;
class Mesh {
friend class Node;
private:
Scene& m_scene;
std::vector<MeshPart> m_parts;
std::vector<DrawcallInfo> m_drawcalls;
glm::mat4 m_transform;
Bounds m_bounds;
explicit Mesh(Scene& scene);
void load(const asset::Scene& scene,
const asset::Mesh& mesh);
void recordDrawcalls(const glm::mat4& viewProjection,
PushConstants& pushConstants,
std::vector<DrawcallInfo>& drawcalls,
const RecordMeshDrawcallFunction& record);
[[nodiscard]]
size_t getDrawcallCount() const;
public:
~Mesh();
Mesh(const Mesh& other) = default;
Mesh(Mesh&& other) = default;
Mesh& operator=(const Mesh& other);
Mesh& operator=(Mesh&& other) noexcept;
[[nodiscard]]
const Bounds& getBounds() const;
};
}
#pragma once
#include <vector>
#include <vkcv/Buffer.hpp>
#include <vkcv/asset/asset_loader.hpp>
#include <vkcv/material/Material.hpp>
#include "Bounds.hpp"
namespace vkcv::scene {
class Scene;
class Mesh;
class MeshPart {
friend class Mesh;
private:
Scene& m_scene;
BufferHandle m_vertices;
std::vector<VertexBufferBinding> m_vertexBindings;
BufferHandle m_indices;
size_t m_indexCount;
Bounds m_bounds;
size_t m_materialIndex;
explicit MeshPart(Scene& scene);
void load(const asset::Scene& scene,
const asset::VertexGroup& vertexGroup,
std::vector<DrawcallInfo>& drawcalls);
public:
~MeshPart();
MeshPart(const MeshPart& other);
MeshPart(MeshPart&& other);
MeshPart& operator=(const MeshPart& other);
MeshPart& operator=(MeshPart&& other) noexcept;
[[nodiscard]]
const material::Material& getMaterial() const;
[[nodiscard]]
const Bounds& getBounds() const;
explicit operator bool() const;
bool operator!() const;
};
}
#pragma once
#include <vector>
#include <vkcv/camera/Camera.hpp>
#include "Bounds.hpp"
#include "Mesh.hpp"
namespace vkcv::scene {
class Scene;
class Node {
friend class Scene;
private:
Scene& m_scene;
std::vector<Mesh> m_meshes;
std::vector<Node> m_nodes;
Bounds m_bounds;
explicit Node(Scene& scene);
void addMesh(const Mesh& mesh);
void loadMesh(const asset::Scene& asset_scene, const asset::Mesh& asset_mesh);
void recordDrawcalls(const glm::mat4& viewProjection,
PushConstants& pushConstants,
std::vector<DrawcallInfo>& drawcalls,
const RecordMeshDrawcallFunction& record);
void splitMeshesToSubNodes(size_t maxMeshesPerNode);
[[nodiscard]]
size_t getDrawcallCount() const;
size_t addNode();
Node& getNode(size_t index);
[[nodiscard]]
const Node& getNode(size_t index) const;
public:
~Node();
Node(const Node& other) = default;
Node(Node&& other) = default;
Node& operator=(const Node& other);
Node& operator=(Node&& other) noexcept;
[[nodiscard]]
const Bounds& getBounds() const;
};
}
#pragma once
#include <filesystem>
#include <mutex>
#include <vkcv/Core.hpp>
#include <vkcv/Event.hpp>
#include <vkcv/camera/Camera.hpp>
#include <vkcv/material/Material.hpp>
#include "Node.hpp"
namespace vkcv::scene {
class Scene {
friend class MeshPart;
private:
struct Material {
size_t m_usages;
material::Material m_data;
};
Core* m_core;
std::vector<Material> m_materials;
std::vector<Node> m_nodes;
explicit Scene(Core* core);
size_t addNode();
Node& getNode(size_t index);
const Node& getNode(size_t index) const;
void increaseMaterialUsage(size_t index);
void decreaseMaterialUsage(size_t index);
void loadMaterial(size_t index, const asset::Scene& scene,
const asset::Material& material);
public:
~Scene();
Scene(const Scene& other);
Scene(Scene&& other) noexcept;
Scene& operator=(const Scene& other);
Scene& operator=(Scene&& other) noexcept;
size_t getMaterialCount() const;
[[nodiscard]]
const material::Material& getMaterial(size_t index) const;
void recordDrawcalls(CommandStreamHandle &cmdStream,
const camera::Camera &camera,
const PassHandle &pass,
const PipelineHandle &pipeline,
size_t pushConstantsSizePerDrawcall,
const RecordMeshDrawcallFunction &record,
const std::vector<ImageHandle> &renderTargets);
static Scene create(Core& core);
static Scene load(Core& core, const std::filesystem::path &path);
};
}
\ No newline at end of file
#include "vkcv/scene/Bounds.hpp"
namespace vkcv::scene {
Bounds::Bounds() :
m_min(glm::vec3(0)),
m_max(glm::vec3(0)) {}
Bounds::Bounds(const glm::vec3 &min, const glm::vec3 &max) :
m_min(min),
m_max(max)
{}
void Bounds::setMin(const glm::vec3 &min) {
m_min = min;
}
const glm::vec3 & Bounds::getMin() const {
return m_min;
}
void Bounds::setMax(const glm::vec3 &max) {
m_max = max;
}
const glm::vec3 & Bounds::getMax() const {
return m_max;
}
void Bounds::setCenter(const glm::vec3 &center) {
const glm::vec3 size = getSize();
m_min = center - size / 2.0f;
m_max = center + size / 2.0f;
}
glm::vec3 Bounds::getCenter() const {
return (m_min + m_max) / 2.0f;
}
void Bounds::setSize(const glm::vec3 &size) {
const glm::vec3 center = getCenter();
m_min = center - size / 2.0f;
m_max = center + size / 2.0f;
}
glm::vec3 Bounds::getSize() const {
return (m_max - m_min);
}
std::array<glm::vec3, 8> Bounds::getCorners() const {
return {
m_min,
glm::vec3(m_min[0], m_min[1], m_max[2]),
glm::vec3(m_min[0], m_max[1], m_min[2]),
glm::vec3(m_min[0], m_max[1], m_max[2]),
glm::vec3(m_max[0], m_min[1], m_min[2]),
glm::vec3(m_max[0], m_min[1], m_max[2]),
glm::vec3(m_max[0], m_max[1], m_min[2]),
m_max
};
}
void Bounds::extend(const glm::vec3 &point) {
m_min = glm::vec3(
std::min(m_min[0], point[0]),
std::min(m_min[1], point[1]),
std::min(m_min[2], point[2])
);
m_max = glm::vec3(
std::max(m_max[0], point[0]),
std::max(m_max[1], point[1]),
std::max(m_max[2], point[2])
);
}
bool Bounds::contains(const glm::vec3 &point) const {
return (
(point[0] >= m_min[0]) && (point[0] <= m_max[0]) &&
(point[1] >= m_min[1]) && (point[1] <= m_max[1]) &&
(point[2] >= m_min[2]) && (point[2] <= m_max[2])
);
}
bool Bounds::contains(const Bounds &other) const {
return (
(other.m_min[0] >= m_min[0]) && (other.m_max[0] <= m_max[0]) &&
(other.m_min[1] >= m_min[1]) && (other.m_max[1] <= m_max[1]) &&
(other.m_min[2] >= m_min[2]) && (other.m_max[2] <= m_max[2])
);
}
bool Bounds::intersects(const Bounds &other) const {
return (
(other.m_max[0] >= m_min[0]) && (other.m_min[0] <= m_max[0]) &&
(other.m_max[1] >= m_min[1]) && (other.m_min[1] <= m_max[1]) &&
(other.m_max[2] >= m_min[2]) && (other.m_min[2] <= m_max[2])
);
}
Bounds::operator bool() const {
return (
(m_min[0] <= m_max[0]) &&
(m_min[1] <= m_max[1]) &&
(m_min[2] <= m_max[2])
);
}
bool Bounds::operator!() const {
return (
(m_min[0] > m_max[0]) ||
(m_min[1] > m_max[1]) ||
(m_min[2] > m_max[2])
);
}
std::ostream& operator << (std::ostream& out, const Bounds& bounds) {
const auto& min = bounds.getMin();
const auto& max = bounds.getMax();
return out << "[Bounds: (" << min[0] << ", " << min[1] << ", " << min[2] << ") ("
<< max[0] << ", " << max[1] << ", " << max[2] << ") ]";
}
}
This diff is collapsed.