This commit is contained in:
zack 2025-05-23 21:13:53 -04:00
commit 444f800536
No known key found for this signature in database
GPG key ID: EE8A2B709E2401D1
122 changed files with 17137 additions and 0 deletions

1
.bootstrap.json Normal file
View file

@ -0,0 +1 @@
[{"name": "libjpeg", "source": {"type": "archive", "url": "http://www.ijg.org/files/jpegsrc.v9d.tar.gz", "sha1": "19b32a12988eae920d142243373841ed78cd4374", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36"}}, {"name": "nestegg", "source": {"type": "git", "url": "https://github.com/kinetiknz/nestegg", "revision": "8374e436ad90afd61919ffe27aa5ff2887feacba"}, "postprocess": {"type": "script", "file": "libnestegg.py"}}, {"name": "libmodplug", "source": {"type": "archive", "url": "http://downloads.sourceforge.net/project/modplug-xmms/libmodplug/0.8.8.5/libmodplug-0.8.8.5.tar.gz", "sha1": "771ee75bb8bfcfe95eae434ed1f3b2c5b63b2cb3"}, "postprocess": {"type": "patch", "file": "libmodplug_771ee75bb8bfcfe95eae434ed1f3b2c5b63b2cb3.patch"}}, {"name": "stb", "predicate": "platform.system() != 'Windows' or os.getenv('DOWNLOAD_STB') != None", "source": {"type": "git", "url": "https://github.com/nothings/stb.git", "revision": "ae721c50eaf761660b4f90cc590453cdb0c2acd0"}}]

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
reference/
build/
bin/

57
CMake/CommonMacros.txt Normal file
View file

@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.16)
macro(SETUP_GROUPS src_files)
foreach(FILE ${src_files})
get_filename_component(PARENT_DIR "${FILE}" PATH)
# skip src or include and changes /'s to \\'s
set(GROUP "${PARENT_DIR}")
string(REPLACE "/" "\\" GROUP "${GROUP}")
source_group("${GROUP}" FILES "${FILE}")
endforeach()
endmacro()
macro(SET_OUTPUT_NAMES projname)
set_target_properties(${projname} PROPERTIES OUTPUT_NAME_DEBUG ${projname}_Debug)
set_target_properties(${projname} PROPERTIES OUTPUT_NAME_RELEASE ${projname}_Release)
set_target_properties(${projname} PROPERTIES OUTPUT_NAME_RELWITHDEBINFO ${projname}_ReleaseDebInfo)
# On Linux/macOS the binaries go to <root>/bin folder
if (UNIX)
set_target_properties(${projname} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")
endif()
endmacro()
macro(SETUP_APP projname chapter)
set(FOLDER_NAME ${chapter})
set(PROJECT_NAME ${projname})
project(${PROJECT_NAME} CXX)
file(GLOB_RECURSE SRC_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/*.c??)
file(GLOB_RECURSE HEADER_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/*.h)
file(GLOB_RECURSE SHADER_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/*.vert src/*.frag src/*.geom src/*.sp src/*.comp src/*.tesc src/*.tese)
include_directories(src)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEADER_FILES} ${SHADER_FILES})
SETUP_GROUPS("${SRC_FILES}")
SETUP_GROUPS("${HEADER_FILES}")
SOURCE_GROUP(shaders FILES "${SHADER_FILES}")
SET_OUTPUT_NAMES(${PROJECT_NAME})
set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER ${FOLDER_NAME})
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
if(MSVC)
add_definitions(-D_CONSOLE)
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
endif()
if(APPLE)
set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_GENERATE_SCHEME TRUE XCODE_SCHEME_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
endif()
endmacro()

106
CMakeLists.txt Normal file
View file

@ -0,0 +1,106 @@
cmake_minimum_required(VERSION 3.19)
project(RenderingCookbook2 CXX C)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
add_subdirectory(deps/src/lightweightvk/third-party/deps/src/ldrutils)
set(KTX_FEATURE_DOC OFF CACHE BOOL "")
set(KTX_FEATURE_GL_UPLOAD OFF CACHE BOOL "")
set(KTX_FEATURE_JNI OFF CACHE BOOL "")
set(KTX_FEATURE_KTX1 ON CACHE BOOL "")
set(KTX_FEATURE_KTX2 ON CACHE BOOL "")
set(KTX_FEATURE_LOADTEST_APPS OFF CACHE BOOL "")
set(KTX_FEATURE_STATIC_LIBRARY ON CACHE BOOL "")
set(KTX_FEATURE_TESTS OFF CACHE BOOL "")
set(KTX_FEATURE_TOOLS OFF CACHE BOOL "")
set(KTX_FEATURE_VK_UPLOAD OFF CACHE BOOL "")
add_subdirectory(deps/src/lightweightvk/third-party/deps/src/ktx-software)
set(LVK_WITH_SAMPLES OFF CACHE BOOL "")
set(LVK_WITH_TRACY ON CACHE BOOL "")
set(LVK_WITH_TRACY_GPU ON CACHE BOOL "")
add_subdirectory(deps/src/lightweightvk)
lvk_set_folder(ktx "third-party/ktx-software")
lvk_set_folder(ktx_read "third-party/ktx-software")
lvk_set_folder(ktx_version "third-party/ktx-software")
lvk_set_folder(obj_basisu_cbind "third-party/ktx-software")
lvk_set_folder(objUtil "third-party/ktx-software")
if(TARGET astcenc-avx2-static)
lvk_set_folder(astcenc-avx2-static "third-party/ktx-software")
endif()
# # cmake-format: on
#
set(ASSIMP_NO_EXPORT ON CACHE BOOL "")
set(ASSIMP_BUILD_DRACO OFF CACHE BOOL "")
set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "")
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "")
set(ASSIMP_INSTALL_PDB OFF CACHE BOOL "")
set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "")
set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_ZLIB ON CACHE BOOL "")
#
add_subdirectory(deps/src/assimp)
if(APPLE)
target_compile_options(assimp PRIVATE -Wno-deprecated-declarations)
endif()
# set(MESHOPT_BUILD_DEMO OFF CACHE BOOL "")
# set(MESHOPT_BUILD_TOOLS OFF CACHE BOOL "")
# set(MESHOPT_BUILD_SHARED_LIBS OFF CACHE BOOL "")
add_subdirectory(deps/src/lightweightvk/third-party/deps/src/meshoptimizer)
if(WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
add_definitions(-DGLM_ENABLE_EXPERIMENTAL)
include_directories(deps/src/lightweightvk/third-party/deps/src/taskflow)
include_directories(deps/src/lightweightvk/third-party/deps/src/glm)
include_directories(deps/src/lightweightvk/third-party/deps/src/imgui)
#
# add_subdirectory(deps/cmake/ImGuizmo)
#
set_property(TARGET assimp PROPERTY FOLDER "third-party")
set_property(TARGET LUtils PROPERTY FOLDER "third-party")
set_property(TARGET meshoptimizer PROPERTY FOLDER "third-party")
if(WIN32)
if(TARGET zlibstatic)
set_property(TARGET zlibstatic PROPERTY FOLDER "third-party")
endif()
if(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs)
set_property(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs PROPERTY FOLDER "third-party")
endif()
endif()
set_property(TARGET uninstall PROPERTY FOLDER "third-party")
#
# # shared utils code
add_subdirectory(shared)
# add_subdirectory(data)
#
# add_dependencies(SharedUtils Dependencies)
#
# disable warnings in third-party code
if(MSVC)
target_compile_options(assimp PRIVATE /wd4267)
target_compile_options(SPIRV PRIVATE /wd4267)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
if(CMAKE_EXPORT_COMPILE_COMMANDS)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()
# BINARIES
add_subdirectory(Chapter01/01_CMake)
add_subdirectory(Chapter01/02_GLFW)
add_subdirectory(Chapter01/03_Taskflow)
add_subdirectory(Chapter01/04_GLSLang)
add_subdirectory(Chapter01/05_BC7Compression)
add_subdirectory(Chapter02/01_Swapchain)
add_subdirectory(Chapter03/01_Assimp)
add_subdirectory(Chapter04/04_CubeMap)
add_subdirectory(Chapter05/01_MeshOptimizer)

View file

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter01)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch01_Sample01_Cmake "Chapter 01")

View file

@ -0,0 +1,6 @@
#include "stdio.h"
int main() {
printf("Hello World!\n");
return 0;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter01)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch01_Sample02_GLFW "Chapter 01")
target_include_directories(Ch01_Sample02_GLFW PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch01_Sample02_GLFW SharedUtils)

View file

@ -0,0 +1,17 @@
#include <cstdint>
#include <shared/HelpersGLFW.h>
int main() {
uint32_t width = 1280;
uint32_t height = 720;
GLFWwindow *window = initWindow("GLFW Example", width, height);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter01)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch01_03_Taskflow "Chapter 01")
target_include_directories(Ch01_03_Taskflow PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch01_03_Taskflow SharedUtils)

View file

@ -0,0 +1,17 @@
#include <cstdio>
#include <taskflow/taskflow.hpp>
int main(int argc, char *argv[]) {
tf::Executor executor;
tf::Taskflow taskflow;
auto [A, B, C, D] = taskflow.emplace(
[]() { std::cout << "TaskA\n"; }, []() { std::cout << "TaskB\n"; },
[]() { std::cout << "TaskC\n"; }, []() { std::cout << "TaskD\n"; });
A.precede(B, C);
D.succeed(B, C);
executor.run(taskflow).wait();
return 0;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter01)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch01_04_GLSLang "Chapter 01")
target_include_directories(Ch01_04_GLSLang PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch01_04_GLSLang SharedUtils)

View file

@ -0,0 +1,39 @@
#include "glslang/Include/glslang_c_interface.h"
#include "glslang/Public/resource_limits_c.h"
#include "vulkan/VulkanUtils.h"
#include <cstdio>
#include <shared/HelpersGLFW.h>
#include <shared/Utils.h>
#include <sys/types.h>
#include <vector>
void saveSpirvBinaryFile(const char *filename, const uint8_t *code,
size_t size) {
FILE *f = fopen(filename, "wb");
fwrite(code, sizeof(uint8_t), size, f);
fclose(f);
}
void testShaderCompilation(const char *sourceFilename,
const char *destFilename) {
std::string shaderSource = readShaderFile(sourceFilename);
std::vector<uint8_t> spirv;
lvk::Result res = lvk::compileShader(
vkShaderStageFromFileName(sourceFilename), shaderSource.c_str(), &spirv,
glslang_default_resource());
saveSpirvBinaryFile(destFilename, spirv.data(), spirv.size());
}
int main(int argc, char *argv[]) {
glslang_initialize_process();
testShaderCompilation("Chapter01/04_GLSLang/src/main.vert",
".cache/04_GLSLang.vert.bin");
testShaderCompilation("Chapter01/04_GLSLang/src/main.frag",
".cache/04_GLSLang.frag.bin");
glslang_finalize_process();
return 0;
}

View file

@ -0,0 +1,11 @@
//
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 texCoord;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(texCoord.x, texCoord.y, 1.0, 1.0);
}

View file

@ -0,0 +1,28 @@
#version 450
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 texCoord;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
vec2 texcoords[3] = vec2[](
vec2(1.0f, 0.0f),
vec2(0.0f, 0.0f),
vec2(0.0f, 1.0f)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
texCoord = texcoords[gl_VertexIndex];
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter01)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch01_05_BC7Compression "Chapter 01")
target_include_directories(Ch01_05_BC7Compression PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch01_05_BC7Compression SharedUtils)

View file

@ -0,0 +1,95 @@
#include <stdint.h>
#include <stdio.h>
#include <ktx-software/lib/gl_format.h>
#include <ktx-software/lib/vkformat_enum.h>
#include <ktx.h>
#include <stb/stb_image.h>
#include <stb/stb_image_resize2.h>
#include <lvk/LVK.h>
int main(int argc, char *argv[]) {
const int numChannels = 4;
int origW, origH;
const uint8_t *pixels =
stbi_load("data/wood.jpg", &origW, &origH, nullptr, numChannels);
assert(pixels);
const uint32_t numMipLevels = lvk::calcNumMipLevels(origW, origH);
ktxTextureCreateInfo createInfoKTX2 = {
.glInternalformat = GL_RGBA8,
.vkFormat = VK_FORMAT_BC7_UNORM_BLOCK,
.baseWidth = (uint32_t)origW,
.baseHeight = (uint32_t)origH,
.baseDepth = 1u,
.numDimensions = 2u,
.numLevels = numMipLevels,
.numLayers = 1u,
.numFaces = 1u,
.generateMipmaps = KTX_FALSE,
};
ktxTexture2 *textureKTX2 = nullptr;
(void)LVK_VERIFY(ktxTexture2_Create(&createInfoKTX2,
KTX_TEXTURE_CREATE_ALLOC_STORAGE,
&textureKTX2) == KTX_SUCCESS);
int w = origW;
int h = origH;
for (uint32_t i = 0; i != numMipLevels; ++i) {
size_t offset = 0;
ktxTexture_GetImageOffset(ktxTexture(textureKTX2), i, 0, 0, &offset);
stbir_resize_uint8_linear((const unsigned char *)pixels, origW, origH, 0,
ktxTexture_GetData(ktxTexture(textureKTX2)) +
offset,
w, h, 0, STBIR_RGBA);
h = h > 1 ? h >> 1 : 1;
w = w > 1 ? w >> 1 : 1;
}
(void)LVK_VERIFY(ktxTexture2_CompressBasis(textureKTX2, 255) == KTX_SUCCESS);
(void)LVK_VERIFY(ktxTexture2_TranscodeBasis(textureKTX2, KTX_TTF_BC7_RGBA,
0) == KTX_SUCCESS);
// convert to KTX1
ktxTextureCreateInfo createInfoKTX1 = {
.glInternalformat = GL_COMPRESSED_RGBA_BPTC_UNORM,
.vkFormat = VK_FORMAT_BC7_UNORM_BLOCK,
.baseWidth = (uint32_t)origW,
.baseHeight = (uint32_t)origH,
.baseDepth = 1u,
.numDimensions = 2u,
.numLevels = numMipLevels,
.numLayers = 1u,
.numFaces = 1u,
.generateMipmaps = KTX_FALSE,
};
ktxTexture1 *textureKTX1 = nullptr;
(void)LVK_VERIFY(ktxTexture1_Create(&createInfoKTX1,
KTX_TEXTURE_CREATE_ALLOC_STORAGE,
&textureKTX1) == KTX_SUCCESS);
for (uint32_t i = 0; i != numMipLevels; ++i) {
size_t offset1 = 0;
(void)LVK_VERIFY(ktxTexture_GetImageOffset(ktxTexture(textureKTX1), i, 0, 0,
&offset1) == KTX_SUCCESS);
size_t offset2 = 0;
(void)LVK_VERIFY(ktxTexture_GetImageOffset(ktxTexture(textureKTX2), i, 0, 0,
&offset2) == KTX_SUCCESS);
memcpy(ktxTexture_GetData(ktxTexture(textureKTX1)) + offset1,
ktxTexture_GetData(ktxTexture(textureKTX2)) + offset2,
ktxTexture_GetImageSize(ktxTexture(textureKTX1), i));
}
ktxTexture_WriteToNamedFile(ktxTexture(textureKTX1), ".cache/image.ktx");
ktxTexture_Destroy(ktxTexture(textureKTX1));
ktxTexture_Destroy(ktxTexture(textureKTX2));
return 0;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter02)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch02_01_Swapchain "Chapter 02")
target_include_directories(Ch02_01_Swapchain PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch02_01_Swapchain SharedUtils)

View file

@ -0,0 +1,26 @@
#include "LVK.h"
#include "minilog/minilog.h"
#include <memory>
#include <shared/HelpersGLFW.h>
int main(int argc, char *argv[]) {
minilog::initialize(nullptr, {.threadNames = false});
int width = 960;
int height = 540;
GLFWwindow *window = lvk::initWindow("Simple Example", width, height);
std::unique_ptr<lvk::IContext> ctx =
lvk::createVulkanContextWithSwapchain(window, width, height, {});
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glfwGetFramebufferSize(window, &width, &height);
if (!width || !height)
continue;
lvk::ICommandBuffer &buf = ctx->acquireCommandBuffer();
ctx->submit(buf, ctx->getCurrentSwapchainTexture());
}
ctx.reset();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter03)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch03_01_Assimp "Chapter 03")
target_include_directories(Ch03_01_Assimp PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch03_01_Assimp SharedUtils)

View file

@ -0,0 +1,176 @@
#include "shared/HelpersGLFW.h"
#include <iostream>
#include <lvk/LVK.h>
#include <GLFW/glfw3.h>
#include <shared/Utils.h>
#include <glm/ext.hpp>
#include <glm/glm.hpp>
#include <assimp/cimport.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/version.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
using glm::mat4;
using glm::vec3;
int main(int argc, char *argv[]) {
minilog::initialize(nullptr, {.threadNames = false});
int width = -65;
int height = -60;
GLFWwindow *window = lvk::initWindow("Ch03_01: Assimp", width, height);
std::unique_ptr<lvk::IContext> ctx =
lvk::createVulkanContextWithSwapchain(window, width, height, {});
const aiScene *scene =
aiImportFile("data/rubber_duck/scene.gltf", aiProcess_Triangulate);
if (!scene || !scene->HasMeshes()) {
printf("Unable to load data/rubber_duck/scene.gltf\n");
exit(255);
}
const aiMesh *mesh = scene->mMeshes[0];
std::vector<vec3> positions;
std::vector<uint32_t> indicies;
for (unsigned int i = 0; i != mesh->mNumVertices; i++) {
const aiVector3D v = mesh->mVertices[i];
positions.push_back(vec3(v.x, v.y, v.z));
}
for (unsigned int i = 0; i != mesh->mNumFaces; i++) {
for (int j = 0; j != 3; j++) {
indicies.push_back(mesh->mFaces[i].mIndices[j]);
}
}
aiReleaseImport(scene);
lvk::Holder<lvk::BufferHandle> vertexBuffer =
ctx->createBuffer({.usage = lvk::BufferUsageBits_Vertex,
.storage = lvk::StorageType_Device,
.size = sizeof(vec3) * positions.size(),
.data = positions.data(),
.debugName = "Buffer: vertex"});
lvk::Holder<lvk::BufferHandle> indexBuffer =
ctx->createBuffer({.usage = lvk::BufferUsageBits_Index,
.storage = lvk::StorageType_Device,
.size = sizeof(uint32_t) * indicies.size(),
.data = indicies.data(),
.debugName = "Buffer: index"});
lvk::Holder<lvk::TextureHandle> depthTexture =
ctx->createTexture({.type = lvk::TextureType_2D,
.format = lvk::Format_Z_F32,
.dimensions = {(uint32_t)width, (uint32_t)height},
.usage = lvk::TextureUsageBits_Attachment,
.debugName = "Depth buffer"});
const lvk::VertexInput vdesc = {
.attributes = {{.location = 0, .format = lvk::VertexFormat::Float3}},
.inputBindings = {{.stride = sizeof(vec3)}},
};
lvk::Holder<lvk::ShaderModuleHandle> vert =
loadShaderModule(ctx, "Chapter03/01_Assimp/src/main.vert");
lvk::Holder<lvk::ShaderModuleHandle> frag =
loadShaderModule(ctx, "Chapter03/01_Assimp/src/main.frag");
lvk::Holder<lvk::RenderPipelineHandle> pipelineSolid =
ctx->createRenderPipeline(
{.vertexInput = vdesc,
.smVert = vert,
.smFrag = frag,
.color = {{.format = ctx->getSwapchainFormat()}},
.depthFormat = ctx->getFormat(depthTexture),
.cullMode = lvk::CullMode_Back});
const uint32_t isWireframe = 1;
lvk::Holder<lvk::RenderPipelineHandle> pipelineWireframe =
ctx->createRenderPipeline(
{.vertexInput = vdesc,
.smVert = vert,
.smFrag = frag,
.specInfo = {.entries = {{.constantId = 0,
.size = sizeof(uint32_t)}},
.data = &isWireframe,
.dataSize = sizeof(isWireframe)},
.color = {{.format = ctx->getSwapchainFormat()}},
.depthFormat = ctx->getFormat(depthTexture),
.cullMode = lvk::CullMode_Back,
.polygonMode = lvk::PolygonMode_Line});
LVK_ASSERT(pipelineSolid.valid());
LVK_ASSERT(pipelineWireframe.valid());
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
if (!width || !height)
continue;
const float ratio = width / (float)height;
const mat4 m = glm::rotate(mat4(1.0f), glm::radians(-90.0f), vec3(1, 0, 0));
const mat4 v =
glm::rotate(glm::translate(mat4(1.0f), vec3(0.0f, -0.5f, -1.5f)),
(float)glfwGetTime(), vec3(0.0f, 1.0f, 0.0f));
const mat4 p = glm::perspective(45.0f, ratio, 0.1f, 1000.0f);
const lvk::RenderPass renderPass = {
.color = {{.loadOp = lvk::LoadOp_Clear,
.clearColor = {1.0f, 1.0f, 1.0f, 1.0f}}},
.depth = {.loadOp = lvk::LoadOp_Clear, .clearDepth = 1.0f}};
const lvk::Framebuffer framebuffer = {
.color = {{.texture = ctx->getCurrentSwapchainTexture()}},
.depthStencil = {.texture = depthTexture},
};
lvk::ICommandBuffer &buf = ctx->acquireCommandBuffer();
{
buf.cmdBeginRendering(renderPass, framebuffer);
{
buf.cmdPushDebugGroupLabel("Mesh", 0xff0000ff);
{
buf.cmdBindVertexBuffer(0, vertexBuffer);
buf.cmdBindIndexBuffer(indexBuffer, lvk::IndexFormat_UI32);
buf.cmdBindRenderPipeline(pipelineSolid);
buf.cmdBindDepthState(
{.compareOp = lvk::CompareOp_Less, .isDepthWriteEnabled = true});
buf.cmdPushConstants(p * v * m);
buf.cmdDrawIndexed(indicies.size());
buf.cmdBindRenderPipeline(pipelineWireframe);
buf.cmdSetDepthBiasEnable(true);
buf.cmdSetDepthBias(0.0f, -1.0f, 0.0f);
buf.cmdDrawIndexed(indicies.size());
}
buf.cmdPopDebugGroupLabel();
buf.cmdEndRendering();
}
}
ctx->submit(buf, ctx->getCurrentSwapchainTexture());
}
vert.reset();
frag.reset();
depthTexture.reset();
pipelineSolid.reset();
pipelineWireframe.reset();
vertexBuffer.reset();
indexBuffer.reset();
ctx.reset();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

View file

@ -0,0 +1,9 @@
//
#version 460 core
layout (location = 0) in vec3 color;
layout (location = 0) out vec4 out_FragColor;
void main() {
out_FragColor = vec4(color, 1.0);
}

View file

@ -0,0 +1,16 @@
//
#version 460 core
layout(push_constant) uniform PerFrameData {
mat4 MVP;
};
layout (constant_id = 0) const bool isWireframe = false;
layout (location=0) in vec3 pos;
layout (location=0) out vec3 color;
void main() {
gl_Position = MVP * vec4(pos, 1.0);
color = isWireframe ? vec3(0.0) : pos.xzy;
}

View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter04)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch04_04_CubeMap "Chapter 04")
target_include_directories(Ch04_04_CubeMap PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch04_04_CubeMap SharedUtils)

View file

@ -0,0 +1,30 @@
#include "GLFW/glfw3.h"
#include "LVK.h"
#include "minilog/minilog.h"
#include <memory>
#include <shared/HelpersGLFW.h>
int main(int argc, char *argv[]) {
minilog::initialize(nullptr, {.threadNames = false});
int width = 1280;
int height = 720;
GLFWwindow *window = lvk::initWindow("Ch04_04: CubeMap", width, height);
std::unique_ptr<lvk::IContext> ctx =
lvk::createVulkanContextWithSwapchain(window, width, height, {});
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glfwGetFramebufferSize(window, &width, &height);
if (!width || !height)
continue;
lvk::ICommandBuffer &buf = ctx->acquireCommandBuffer();
ctx->submit(buf, ctx->getCurrentSwapchainTexture());
}
ctx.reset();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

View file

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.19)
project(Chapter05)
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch05_01_MeshOptimizer "Chapter 05")
target_include_directories(Ch05_01_MeshOptimizer PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(Ch05_01_MeshOptimizer SharedUtils)
target_link_libraries(Ch05_01_MeshOptimizer meshoptimizer)

View file

@ -0,0 +1,221 @@
#include <cstdint>
#include <lvk/LVK.h>
#include <GLFW/glfw3.h>
#include <shared/Utils.h>
#include <glm/ext.hpp>
#include <glm/glm.hpp>
#include <assimp/cimport.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/version.h>
#include <meshoptimizer/src/meshoptimizer.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
using glm::mat4;
using glm::vec3;
int main(int argc, char *argv[]) {
minilog::initialize(nullptr, {.threadNames = false});
int width = -65;
int height = -60;
GLFWwindow *window = lvk::initWindow("Ch05_01: MeshOpt", width, height);
std::unique_ptr<lvk::IContext> ctx =
lvk::createVulkanContextWithSwapchain(window, width, height, {});
const aiScene *scene =
aiImportFile("data/rubber_duck/scene.gltf", aiProcess_Triangulate);
if (!scene || !scene->HasMeshes()) {
printf("Unable to load data/rubber_duck/scene.gltf\n");
exit(255);
}
const aiMesh *mesh = scene->mMeshes[0];
std::vector<vec3> positions;
std::vector<uint32_t> indicies;
for (unsigned int i = 0; i != mesh->mNumVertices; i++) {
const aiVector3D v = mesh->mVertices[i];
positions.push_back(vec3(v.x, v.y, v.z));
}
for (unsigned int i = 0; i != mesh->mNumFaces; i++) {
for (int j = 0; j != 3; j++) {
indicies.push_back(mesh->mFaces[i].mIndices[j]);
}
}
aiReleaseImport(scene);
std::vector<uint32_t> remap(indicies.size());
const size_t vertexCount = meshopt_generateVertexRemap(
remap.data(), indicies.data(), indicies.size(), positions.data(),
indicies.size(), sizeof(vec3));
std::vector<uint32_t> remappedIndices(indicies.size());
std::vector<vec3> remappedVertices(vertexCount);
meshopt_remapIndexBuffer(remappedIndices.data(), indicies.data(),
indicies.size(), remap.data());
meshopt_remapVertexBuffer(remappedVertices.data(), positions.data(),
positions.size(), sizeof(vec3), remap.data());
meshopt_optimizeVertexCache(remappedIndices.data(), remappedIndices.data(),
indicies.size(), vertexCount);
meshopt_optimizeOverdraw(remappedIndices.data(), remappedIndices.data(),
indicies.size(), glm::value_ptr(remappedVertices[0]),
vertexCount, sizeof(vec3), 1.05f);
meshopt_optimizeVertexFetch(remappedVertices.data(), remappedIndices.data(),
indicies.size(), remappedVertices.data(),
vertexCount, sizeof(vec3));
const float threshold = 0.2f;
const float target_index_count = size_t(remappedIndices.size() * threshold);
const float target_error = 0.01f;
std::vector<uint32_t> indiciesLod;
indiciesLod.resize(remappedIndices.size());
indiciesLod.resize(meshopt_simplify(
&indiciesLod[0], remappedIndices.data(), remappedIndices.size(),
&remappedVertices[0].x, vertexCount, sizeof(vec3), target_index_count,
target_error));
indicies = remappedIndices;
positions = remappedVertices;
lvk::Holder<lvk::BufferHandle> vertexBuffer =
ctx->createBuffer({.usage = lvk::BufferUsageBits_Vertex,
.storage = lvk::StorageType_Device,
.size = sizeof(vec3) * positions.size(),
.data = positions.data(),
.debugName = "Buffer: vertex"});
lvk::Holder<lvk::BufferHandle> indexBuffer =
ctx->createBuffer({.usage = lvk::BufferUsageBits_Index,
.storage = lvk::StorageType_Device,
.size = sizeof(uint32_t) * indicies.size(),
.data = indicies.data(),
.debugName = "Buffer: index"});
lvk::Holder<lvk::BufferHandle> indexBufferLod =
ctx->createBuffer({.usage = lvk::BufferUsageBits_Index,
.storage = lvk::StorageType_Device,
.size = sizeof(uint32_t) * indiciesLod.size(),
.data = indiciesLod.data(),
.debugName = "Buffer: index LOD"});
lvk::Holder<lvk::TextureHandle> depthTexture =
ctx->createTexture({.type = lvk::TextureType_2D,
.format = lvk::Format_Z_F32,
.dimensions = {(uint32_t)width, (uint32_t)height},
.usage = lvk::TextureUsageBits_Attachment,
.debugName = "Depth buffer"});
const lvk::VertexInput vdesc = {
.attributes = {{.location = 0, .format = lvk::VertexFormat::Float3}},
.inputBindings = {{.stride = sizeof(vec3)}},
};
lvk::Holder<lvk::ShaderModuleHandle> vert =
loadShaderModule(ctx, "Chapter03/01_Assimp/src/main.vert");
lvk::Holder<lvk::ShaderModuleHandle> frag =
loadShaderModule(ctx, "Chapter03/01_Assimp/src/main.frag");
lvk::Holder<lvk::RenderPipelineHandle> pipelineSolid =
ctx->createRenderPipeline(
{.vertexInput = vdesc,
.smVert = vert,
.smFrag = frag,
.color = {{.format = ctx->getSwapchainFormat()}},
.depthFormat = ctx->getFormat(depthTexture),
.cullMode = lvk::CullMode_Back});
const uint32_t isWireframe = 1;
lvk::Holder<lvk::RenderPipelineHandle> pipelineWireframe =
ctx->createRenderPipeline(
{.vertexInput = vdesc,
.smVert = vert,
.smFrag = frag,
.specInfo = {.entries = {{.constantId = 0,
.size = sizeof(uint32_t)}},
.data = &isWireframe,
.dataSize = sizeof(isWireframe)},
.color = {{.format = ctx->getSwapchainFormat()}},
.depthFormat = ctx->getFormat(depthTexture),
.cullMode = lvk::CullMode_Back,
.polygonMode = lvk::PolygonMode_Line});
LVK_ASSERT(pipelineSolid.valid());
LVK_ASSERT(pipelineWireframe.valid());
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
if (!width || !height)
continue;
const float ratio = width / (float)height;
const mat4 m = glm::rotate(mat4(1.0f), glm::radians(-90.0f), vec3(1, 0, 0));
const mat4 v =
glm::rotate(glm::translate(mat4(1.0f), vec3(0.0f, -0.5f, -1.5f)),
(float)glfwGetTime(), vec3(0.0f, 1.0f, 0.0f));
const mat4 p = glm::perspective(45.0f, ratio, 0.1f, 1000.0f);
const lvk::RenderPass renderPass = {
.color = {{.loadOp = lvk::LoadOp_Clear,
.clearColor = {1.0f, 1.0f, 1.0f, 1.0f}}},
.depth = {.loadOp = lvk::LoadOp_Clear, .clearDepth = 1.0f}};
const lvk::Framebuffer framebuffer = {
.color = {{.texture = ctx->getCurrentSwapchainTexture()}},
.depthStencil = {.texture = depthTexture},
};
lvk::ICommandBuffer &buf = ctx->acquireCommandBuffer();
{
buf.cmdBeginRendering(renderPass, framebuffer);
{
buf.cmdPushDebugGroupLabel("Mesh", 0xff0000ff);
{
buf.cmdBindVertexBuffer(0, vertexBuffer);
buf.cmdBindIndexBuffer(indexBufferLod, lvk::IndexFormat_UI32);
buf.cmdBindRenderPipeline(pipelineSolid);
buf.cmdBindDepthState(
{.compareOp = lvk::CompareOp_Less, .isDepthWriteEnabled = true});
buf.cmdPushConstants(p * v * m);
buf.cmdDrawIndexed(indiciesLod.size());
buf.cmdBindRenderPipeline(pipelineWireframe);
buf.cmdSetDepthBiasEnable(true);
buf.cmdSetDepthBias(0.0f, -1.0f, 0.0f);
buf.cmdDrawIndexed(indiciesLod.size());
}
buf.cmdPopDebugGroupLabel();
buf.cmdEndRendering();
}
}
ctx->submit(buf, ctx->getCurrentSwapchainTexture());
}
vert.reset();
frag.reset();
depthTexture.reset();
pipelineSolid.reset();
pipelineWireframe.reset();
vertexBuffer.reset();
indexBuffer.reset();
indexBufferLod.reset();
ctx.reset();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

14
LICENSE Normal file
View file

@ -0,0 +1,14 @@
The MIT License (MIT)
Copyright (c) 2016-2025, Sergey Kosarevsky
---
Based on https://bitbucket.org/blippar/bootstrapping-external-libs
The MIT License (MIT)
Copyright (c) 2016 Blippar.com Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

290
README.md Normal file
View file

@ -0,0 +1,290 @@
# 👢 Bootstrap
The Bootstrap script is a versatile dependencies manager for your C++
projects. You can think of it as a portable (Windows, Linux, OSX) and a more
feature-complete alternative to Google's Repo tool. The script itself is written
in Python and should "just work" using any standard Python&nbsp;3 installation.
## Introduction
Main features of Bootstrap:
- One-button-download philosophy. Just run the script to download and update all your dependencies.
- Cross-platform. Runs on Windows, Linux, and OSX.
- Full support of Git, Mercurial, SVN repositories.
- Full support of `.zip`, `.tar`, `.gz`, `.bz2`, `.xz` archives.
- Caching and fallback mechanisms.
- Rich error reporting.
## Usage
For instance, this minimalistic JSON snippet will clone the GLFW library from its Git repository
and check out the revision which is tagged `3.3`.
```JSON
[{
"name": "glfw",
"source": {
"type": "git",
"url": "https://github.com/glfw/glfw.git",
"revision": "3.3"
}
}]
```
This simple JSON snippet will download the `libjpeg` library from
the specified URL (via a custom `user-agent` string), check the archive integrity
via SHA1, unpack the archive, and put its content into the `src/libjpeg` folder:
```JSON
[{
"name": "libjpeg",
"source": {
"type": "archive",
"url": "http://www.ijg.org/files/jpegsrc.v9a.tar.gz",
"sha1": "d65ed6f88d318f7380a3a5f75d578744e732daca",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36"
}
}]
```
This JSON snippet will download the `nestegg` library from the Git repository,
checkout the specified revision, and apply a patch via a custom Python script.
You can also provide an optional `predicate` string to run custom Python code
to determine whether the library needs to be downloaded.
```JSON
[{
"name": "nestegg",
"predicate": "platform.system() != 'Windows' or os.getenv('DOWNLOAD_STB') != None",
"source": {
"type": "git",
"url": "https://github.com/kinetiknz/nestegg",
"revision": "8374e436ad90afd61919ffe27aa5ff2887feacba"
},
"postprocess": {
"type": "script",
"file": "libnestegg.py"
}
}]
```
Read the comprehensive documentation below for further details.
# Projects using this script
- **[IGL](https://github.com/facebook/igl/)** - Meta's Intermediate Graphics Library (IGL) is a cross-platform library that commands the GPU. License: MIT.
- **[LightweightVK](https://github.com/corporateshark/lightweightvk)** - lightweight C++ bindless Vulkan 1.3 wrapper. License: MIT.
- **[Mastering Graphics Programming with Vulkan](https://github.com/PacktPublishing/Mastering-Graphics-Programming-with-Vulkan)**
- **[3D Graphics Rendering Cookbook](https://github.com/PacktPublishing/3D-Graphics-Rendering-Cookbook)**
- **[Vulkan 3D Graphics Rendering Cookbook](https://github.com/PacktPublishing/3D-Graphics-Rendering-Cookbook-Second-Edition)**
## P.S.
This is a fork of an abandoned library https://bitbucket.org/blippar/bootstrapping-external-libs
------------------------------------------------------------------------------
Original documentation:
------------------------------------------------------------------------------
This repository holds our external (i.e. third party) libraries. After a fresh
clone, the repository contains *only* metadata about the libraries, i.e. their
names, where to retrieve them from, etc. In order to actually obtain or update
the libraries, the user must run a bootstrapping script, which downloads all
third-party libraries and places them into a src/ directory.
Prerequisites
-------------
The script itself is written in Python and should "just work" using any standard
Python 2 or 3 installation. The version control tools Git, Mercurial and
Subversion must be installed and available on the environment path; in addition,
the 'patch' program must be present on the user's system. On Windows, the script
can be run from the command line (for patching to work, ensure you have the Cygwin
patch tool installed).
Obtaining the libraries
-----------------------
Run the bootstrapping script from the repository's top-level directory:
> python bootstrap.py
or just:
> ./bootstrap.py
The script should run without any errors or exceptions raised. Third-party
library sources are either downloaded as a packaged archive file (e.g. .zip,
.tar.gz, .tar.bz2) and then uncompressed, or cloned directly from the original
repository. All source code is obtained from the respective authorative sources,
i.e. directly from the authors' websites or repositories.
After script execution has finished, the following files and directories should
have been added to the repository folder:
```
|- external
|- .bootstrap.json
|- archives/
|- ...
|- snapshots/
|- ...
|- src/
|- ...
```
- The file .bootstrap.json contains the cached state of the last bootstrapping/
pdating operation. The script always compares against this state, in order to
decide whether to update a library. For example, if boostrap.py has executed
successfully and is then immediately re-run, no further action will take place.
- The directory archives/ contains the archive files of all libraries that have
been downloaded as archives. It serves as a cache to prevent multiple downloads.
- The directory snapshots/ serves as a cache for snapshots of a complete
repository. It will only be created if the --repo-snapshots option was specified
on the command line. This will enable the respective copy of the 'external'
repository to serve as a fallback location.
- The directory src/ contains all third-party library sources. Libraries that
were obtained in archive form will have been uncompressed into this directory.
Libraries that were obtained from a repository (Git, Mercurial or SVN) will have
been cloned into this directory.
Adding or changing the version of a library
-------------------------------------------
All metadata about the third-party libraries and their versions is contained in
a single JSON file (bootstrap.json) that is being read by the script.
The file should contain exactly one JSON array of objects, where each object
contained in this array describes one library. This JSON schema gives an
overview of the format:
```
[
{
"name": "LibraryName",
"source": {
"type": "archive|git|hg|svn",
"url": "http://...",
"sha1": "0123456789...0123456789", # for type == archive
"revision": "0123456789" # for type == git|hg|svn
},
"postprocess": {
"type": "patch|script",
"file": "filename"
}
},
{
...
},
...
]
```
The library "name" specifies the name of the library, which in turn specifies
the subdirectory name under the src/ directory. The name should be the common
name of the library (e.g. "libjpeg") and *not* contain any particular version
numbers or other information that may change between versions.
For each library, the "source" field contains information about where to obtain
the library from, in the form of a JSON object.
The source "type" field can be one of the following types: "archive", "git",
"hg", or "svn". The first type describes an archive file (such as .zip, .tar.gz,
.tar.bz2 files), while the last three types describe different repository types.
The "url" value contains the URL of the archive to be downloaded in case the
type is 'archive', and the respository URL otherwise.
If the source type is 'archive', then the optional "sha1" field can (and should)
be used to add the SHA1 hash of the archive, for verification purposes.
For repositories (i.e. type is 'git|hg|svn'), an optional "revision" field can
specify the particular revision/commit to be checked out. If the revision field
is omitted, the default is to check out the HEAD revision of the master branch
(for Git repositories), or the tip of the default branch (for Mercurial
repositories).
The "postprocess" field contains an object which describes any optional post-
processing steps, once the original library sources have been put into the src/
directory. Post-processing can be of type "patch" or "script"; in both cases,
the filename has to be given in the "file" field.
For type 'patch', the file field specifies a patch file to be contained in the
patches/ directory. For type 'file', the file field specified a script that is
run from the patches/ directory. Patches can be used to make minor modifications
to a library, such as silencing warning or to fix bugs which have not been
included in the upstream version of the library. Scripts can embody any more
complex operations, but should mainly be used for simple library-specific
prerequisites (such as copying header prototype files). All scripts have to
be written in Python, in order to be compatible with all platforms, including
Windows.
Patches should be created using the `diff` program from the external/ directory,
similar to this example:
```
> diff --exclude=".git" --exclude=".hg" -rupN \
./src/AGAST/ ./src/AGAST_patched/ > ./patches/agast.patch
```
The default -p argument for the patch command is 3, but this can be changed by
an optional "pnum" field inside the postprocessing JSON object, containing a
numeric value.
For example, for patches created using `git diff` or `hg diff`, a "pnum"
argument of "1" is likely needed. This method of creating a patch is
discouraged, however, in favor of the cleaner method using the plain 'diff'
command described above.
In general, only ever add or modify libraries in a way that is compatible with
how it's already done. See also the next section.
Policies for adding or updating a library
-----------------------------------------
- IMPORTANT: There shall be NO committed source code of any kind in this
repository (besides patches). All source code shall be downloaded from the
respective authorative sources, i.e. the library authors' web sites (zipped or
tarred packages) or repositories.
- IMPORTANT: There shall be NO committed binary files of any kind in this
repository. Binary files are to be built using the respective platform-specific
build system, and are to not be committed anywhere in the first place.
- The bootstrapping script should always run cleanly, with no further action
required from the user.
- All repositories should be in their final usable state after running the
bootstrapping script, i.e. the committed makefiles should be usable.
- Any patches to a library should be kept minimal. Larger changes to a
third-party code base are discouraged.
- If we really have to patch a library, e.g. to fix a bug or to silence a
warning, let's try to get our patch accepted upstream. If we then update to a
newer version of the library, we won't need a patch file anymore.
- All patch files should adhere to the naming
<library_name>_<sha1_hash>.patch
where <sha1_hash> is the hash of either the archive file or the repository.
This enables to keep multiple patches for the same library, in case different
project need different versions of a library (via using local bootstrapping
files).
- The canonical bootstrapping JSON file should always contain the respective
latest version of each library that is used across our codebase. If a library
is updated, it should be updated to the respective latest version available.
- We should be keeping the contained library versions reasonably up-to-date.
License
-------
See the LICENSE file
Bug reports, comments
---------------------
Should go to omar@blippar.com.

1922
compile_commands.json Normal file

File diff suppressed because it is too large Load diff

9
data/CMakeLists.txt Normal file
View file

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.19)
project(SharedUtils CXX C)
include(../CMake/CommonMacros.txt)
file(GLOB_RECURSE SHADER_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} shaders/gltf/*.vert shaders/gltf/*.frag shaders/gltf/*.geom shaders/gltf/*.sp src/*.comp)
add_library(Shaders INTERFACE ${SHADER_FILES})

BIN
data/OpenSans-Light.ttf Normal file

Binary file not shown.

BIN
data/brdfLUT.ktx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,11 @@
Model Information:
* title: Medieval Fantasy Book
* source: https://sketchfab.com/3d-models/medieval-fantasy-book-06d5a80a04fc4c5ab552759e9a97d91a
* author: Pixel (https://sketchfab.com/stefan.lengyel1)
Model License:
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
* requirements: Author must be credited. Commercial use is allowed.
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
This work is based on "Medieval Fantasy Book" (https://sketchfab.com/3d-models/medieval-fantasy-book-06d5a80a04fc4c5ab552759e9a97d91a) by Pixel (https://sketchfab.com/stefan.lengyel1) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
https://sketchfab.com/3d-models/orrery-44849150982d4c228fca951a66df604f
Eranekao

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
data/piazza_bologni_1k.hdr Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
data/rot_texture.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
data/rubber_duck/scene.bin Normal file

Binary file not shown.

231
data/rubber_duck/scene.gltf Normal file
View file

@ -0,0 +1,231 @@
{
"accessors": [
{
"bufferView": 2,
"componentType": 5126,
"count": 5676,
"max": [
0.36252099275588989,
0.51303797960281372,
1.0525829792022705
],
"min": [
-0.36252099275588989,
-0.48232200741767883,
0.00014600000577047467
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 68112,
"componentType": 5126,
"count": 5676,
"max": [
0.99994742870330811,
0.99997341632843018,
0.9999920129776001
],
"min": [
-0.99994742870330811,
-0.99984163045883179,
-1
],
"type": "VEC3"
},
{
"bufferView": 3,
"componentType": 5126,
"count": 5676,
"max": [
0.99985033273696899,
0.99970757961273193,
0.99825453758239746,
1
],
"min": [
-1,
-0.99970918893814087,
-0.99825495481491089,
-1
],
"type": "VEC4"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 5676,
"max": [
0.9983140230178833,
0.87805598974227905
],
"min": [
0.00010900000052060932,
0.00010900000052060932
],
"type": "VEC2"
},
{
"bufferView": 0,
"componentType": 5125,
"count": 33216,
"max": [
5675
],
"min": [
0
],
"type": "SCALAR"
}
],
"asset": {
"extras": {
"author": "emilsvfx (https://sketchfab.com/emilsvfx)",
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
"source": "https://sketchfab.com/models/a84cecb600c04eeba60d02f99b8b154b",
"title": "Rubber Duck"
},
"generator": "Sketchfab-3.37.2",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 132864,
"byteOffset": 0,
"name": "floatBufferViews",
"target": 34963
},
{
"buffer": 0,
"byteLength": 45408,
"byteOffset": 132864,
"byteStride": 8,
"name": "floatBufferViews",
"target": 34962
},
{
"buffer": 0,
"byteLength": 136224,
"byteOffset": 178272,
"byteStride": 12,
"name": "floatBufferViews",
"target": 34962
},
{
"buffer": 0,
"byteLength": 90816,
"byteOffset": 314496,
"byteStride": 16,
"name": "floatBufferViews",
"target": 34962
}
],
"buffers": [
{
"byteLength": 405312,
"uri": "scene.bin"
}
],
"images": [
{
"uri": "textures/Duck_baseColor.png"
}
],
"materials": [
{
"doubleSided": true,
"emissiveFactor": [
0,
0,
0
],
"name": "Duck",
"pbrMetallicRoughness": {
"baseColorFactor": [
1,
1,
1,
1
],
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"metallicFactor": 0,
"roughnessFactor": 0.84349154199999998
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 0,
"TANGENT": 2,
"TEXCOORD_0": 3
},
"indices": 4,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"children": [
1
],
"name": "RootNode (gltf orientation matrix)",
"rotation": [
-0.70710678118654746,
-0,
-0,
0.70710678118654757
]
},
{
"children": [
2
],
"name": "RootNode (model correction matrix)"
},
{
"children": [
3
],
"name": "duck.obj.cleaner.materialmerger.gles"
},
{
"mesh": 0,
"name": ""
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987,
"wrapS": 10497,
"wrapT": 10497
}
],
"scene": 0,
"scenes": [
{
"name": "OSG_Scene",
"nodes": [
0
]
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

20
data/shaders/AlphaTest.sp Normal file
View file

@ -0,0 +1,20 @@
//
void runAlphaTest(float alpha, float alphaThreshold)
{
if (alphaThreshold > 0.0) {
// http://alex-charlton.com/posts/Dithering_on_the_GPU/
// https://forums.khronos.org/showthread.php/5091-screen-door-transparency
mat4 thresholdMatrix = mat4(
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
);
alpha = clamp(alpha - 0.5 * thresholdMatrix[int(mod(gl_FragCoord.x, 4.0))][int(mod(gl_FragCoord.y, 4.0))], 0.0, 1.0);
if (alpha < alphaThreshold)
discard;
}
}

76
data/shaders/Blur.comp Normal file
View file

@ -0,0 +1,76 @@
//
layout (local_size_x = 16, local_size_y = 16) in;
layout (set = 0, binding = 0) uniform texture2D kTextures2D[];
layout (set = 0, binding = 1) uniform sampler kSamplers[];
layout (set = 0, binding = 2, rgba8) uniform writeonly image2D kTextures2DOut[];
ivec2 textureBindlessSize2D(uint textureid) {
return textureSize(nonuniformEXT(kTextures2D[textureid]), 0);
}
vec4 textureBindless2D(uint textureid, vec2 uv) {
return textureLod(nonuniformEXT(sampler2D(kTextures2D[textureid], kSamplers[0])), uv, 0);
}
layout (constant_id = 0) const bool kIsHorizontal = true;
layout(push_constant) uniform PushConstants {
uint texDepth;
uint texIn;
uint texOut;
float depthThreshold;
} pc;
const int kFilterSize = 17;
// https://drdesten.github.io/web/tools/gaussian_kernel/
const float gaussWeights[kFilterSize] = float[](
0.00001525878906,
0.0002441406250,
0.001831054688,
0.008544921875,
0.02777099609,
0.06665039063,
0.1221923828,
0.1745605469,
0.1963806152,
0.1745605469,
0.1221923828,
0.06665039063,
0.02777099609,
0.008544921875,
0.001831054688,
0.0002441406250,
0.00001525878906
);
void main() {
const vec2 size = textureBindlessSize2D(pc.texIn).xy;
const vec2 xy = gl_GlobalInvocationID.xy;
if (xy.x > size.x || xy.y > size.y)
return;
const vec2 texCoord = (gl_GlobalInvocationID.xy + vec2(0.5)) / size;
const float texScaler = 1.0 / (kIsHorizontal ? size.x : size.y);
vec3 c = vec3(0.0);
vec3 fragColor = textureBindless2D(pc.texIn, texCoord).rgb;
float fragDepth = textureBindless2D(pc.texDepth, texCoord).r;
for ( int i = 0; i != kFilterSize; i++ ) {
float offset = float(i - kFilterSize/2);
vec2 uv = texCoord + texScaler * (kIsHorizontal ? vec2(offset, 0) : vec2(0, offset));
vec3 color = textureBindless2D(pc.texIn, uv).rgb;
float depth = textureBindless2D(pc.texDepth, uv).r;
// bilateral blur
float weight = clamp(abs(depth - fragDepth) * pc.depthThreshold, 0.0, 1.0);
c += mix(color, fragColor, weight) * gaussWeights[i];
}
imageStore(kTextures2DOut[pc.texOut], ivec2(xy), vec4(c, 1.0) );
}

13
data/shaders/Grid.frag Normal file
View file

@ -0,0 +1,13 @@
//
#version 460 core
#include <data/shaders/GridParameters.h>
#include <data/shaders/GridCalculation.h>
layout (location=0) in vec2 uv;
layout (location=1) in vec2 camPos;
layout (location=0) out vec4 out_FragColor;
void main() {
out_FragColor = gridColor(uv, camPos);
}

39
data/shaders/Grid.vert Normal file
View file

@ -0,0 +1,39 @@
//
#version 460 core
#include <data/shaders/GridParameters.h>
layout(push_constant) uniform PerFrameData {
mat4 MVP;
vec4 cameraPos;
vec4 origin;
};
layout (location=0) out vec2 uv;
layout (location=1) out vec2 out_camPos;
const vec3 pos[4] = vec3[4](
vec3(-1.0, 0.0, -1.0),
vec3( 1.0, 0.0, -1.0),
vec3( 1.0, 0.0, 1.0),
vec3(-1.0, 0.0, 1.0)
);
const int indices[6] = int[6](
0, 1, 2, 2, 3, 0
);
void main() {
int idx = indices[gl_VertexIndex];
vec3 position = pos[idx] * gridSize;
position.x += cameraPos.x;
position.z += cameraPos.z;
position += origin.xyz;
out_camPos = cameraPos.xz;
gl_Position = MVP * vec4(position, 1.0);
uv = position.xz;
}

View file

@ -0,0 +1,54 @@
float log10(float x) {
return log(x) / log(10.0);
}
float satf(float x) {
return clamp(x, 0.0, 1.0);
}
vec2 satv(vec2 x) {
return clamp(x, vec2(0.0), vec2(1.0));
}
float max2(vec2 v) {
return max(v.x, v.y);
}
vec4 gridColor(vec2 uv, vec2 camPos) {
vec2 dudv = vec2(
length(vec2(dFdx(uv.x), dFdy(uv.x))),
length(vec2(dFdx(uv.y), dFdy(uv.y)))
);
float lodLevel = max(0.0, log10((length(dudv) * gridMinPixelsBetweenCells) / gridCellSize) + 1.0);
float lodFade = fract(lodLevel);
// cell sizes for lod0, lod1 and lod2
float lod0 = gridCellSize * pow(10.0, floor(lodLevel));
float lod1 = lod0 * 10.0;
float lod2 = lod1 * 10.0;
// each anti-aliased line covers up to 4 pixels
dudv *= 4.0;
// set grid coordinates to the centers of anti-aliased lines for subsequent alpha calculations
uv += dudv * 0.5;
// calculate absolute distances to cell line centers for each lod and pick max X/Y to get coverage alpha value
float lod0a = max2( vec2(1.0) - abs(satv(mod(uv, lod0) / dudv) * 2.0 - vec2(1.0)) );
float lod1a = max2( vec2(1.0) - abs(satv(mod(uv, lod1) / dudv) * 2.0 - vec2(1.0)) );
float lod2a = max2( vec2(1.0) - abs(satv(mod(uv, lod2) / dudv) * 2.0 - vec2(1.0)) );
uv -= camPos;
// blend between falloff colors to handle LOD transition
vec4 c = lod2a > 0.0 ? gridColorThick : lod1a > 0.0 ? mix(gridColorThick, gridColorThin, lodFade) : gridColorThin;
// calculate opacity falloff based on distance to grid extents
float opacityFalloff = (1.0 - satf(length(uv) / gridSize));
// blend between LOD level alphas and scale with opacity falloff
c.a *= (lod2a > 0.0 ? lod2a : lod1a > 0.0 ? lod1a : (lod0a * (1.0-lodFade))) * opacityFalloff;
return c;
}

View file

@ -0,0 +1,14 @@
// extents of grid in world coordinates
float gridSize = 100.0;
// size of one cell
float gridCellSize = 0.025;
// color of thin lines
vec4 gridColorThin = vec4(0.5, 0.5, 0.5, 1.0);
// color of thick lines (every tenth line)
vec4 gridColorThick = vec4(0.0, 0.0, 0.0, 1.0);
// minimum number of pixels between cell lines before LOD switch should occur.
const float gridMinPixelsBetweenCells = 2.0;

12
data/shaders/Quad.frag Normal file
View file

@ -0,0 +1,12 @@
//
layout (location=0) in vec2 uv;
layout (location=0) out vec4 out_FragColor;
layout(push_constant) uniform PerFrameData {
uint textureId;
};
void main() {
out_FragColor = textureBindless2D(textureId, 0, uv);
};

11
data/shaders/Quad.vert Normal file
View file

@ -0,0 +1,11 @@
//
#version 460
layout (location=0) out vec2 uv;
// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/
void main() {
uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(uv * 2.0 + -1.0, 0.0, 1.0);
}

View file

@ -0,0 +1,11 @@
//
#version 460
layout (location=0) out vec2 uv;
// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/
void main() {
uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(uv * vec2(2, -2) + vec2(-1, 1), 0.0, 1.0);
}

19
data/shaders/Shadow.sp Normal file
View file

@ -0,0 +1,19 @@
//
float PCF3x3(vec3 uvw, uint textureid, uint samplerid) {
float size = 1.0 / textureBindlessSize2D(textureid).x; // assume square texture
float shadow = 0.0;
for (int v=-1; v<=+1; v++)
for (int u=-1; u<=+1; u++)
shadow += textureBindless2DShadow(textureid, samplerid, uvw + size * vec3(u, v, 0));
return shadow / 9;
}
float shadow(vec4 s, uint textureid, uint samplerid) {
s = s / s.w;
if (s.z > -1.0 && s.z < 1.0) {
float shadowSample = PCF3x3(vec3(s.x, 1.0 - s.y, s.z), textureid, samplerid);
return mix(0.3, 1.0, shadowSample);
}
return 1.0;
}

42
data/shaders/UtilsPBR.sp Normal file
View file

@ -0,0 +1,42 @@
//
const float M_PI = 3.141592653589793;
vec4 SRGBtoLINEAR(vec4 srgbIn) {
vec3 linOut = pow(srgbIn.xyz,vec3(2.2));
return vec4(linOut, srgbIn.a);
}
// http://www.thetenthplanet.de/archives/1180
// modified to fix handedness of the resulting cotangent frame
mat3 cotangentFrame( vec3 N, vec3 p, vec2 uv ) {
// get edge vectors of the pixel triangle
vec3 dp1 = dFdx( p );
vec3 dp2 = dFdy( p );
vec2 duv1 = dFdx( uv );
vec2 duv2 = dFdy( uv );
// solve the linear system
vec3 dp2perp = cross( dp2, N );
vec3 dp1perp = cross( N, dp1 );
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
// construct a scale-invariant frame
float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );
// calculate handedness of the resulting cotangent frame
float w = (dot(cross(N, T), B) < 0.0) ? -1.0 : 1.0;
// adjust tangent if needed
T = T * w;
return mat3( T * invmax, B * invmax, N );
}
vec3 perturbNormal(vec3 n, vec3 v, vec3 normalSample, vec2 uv) {
vec3 map = normalize( 2.0 * normalSample - vec3(1.0) );
mat3 TBN = cotangentFrame(n, v, uv);
return normalize(TBN * map);
}

635
data/shaders/gltf/PBR.sp Normal file
View file

@ -0,0 +1,635 @@
// Based on: https://github.com/KhronosGroup/glTF-WebGL-PBR/blob/master/shaders/pbr-frag.glsl
// Encapsulate the various inputs used by the various functions in the shading equation
// We store values in this struct to simplify the integration of alternative implementations
// of the shading terms, outlined in the Readme.MD Appendix.
struct PBRInfo {
// geometry properties
float NdotL; // cos angle between normal and light direction
float NdotV; // cos angle between normal and view direction
float NdotH; // cos angle between normal and half vector
float LdotH; // cos angle between light direction and half vector
float VdotH; // cos angle between view direction and half vector
// Normal
vec3 n; // Sahding normal
vec3 ng; // Geometry normal
vec3 t; // Geometry tangent
vec3 b; // Geometry bitangent
vec3 v; // vector from surface point to camera
// material properties
float perceptualRoughness; // roughness value, as authored by the model creator (input to shader)
vec3 reflectance0; // full reflectance color (normal incidence angle)
vec3 reflectance90; // reflectance color at grazing angle
float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2])
vec3 diffuseColor; // color contribution from diffuse lighting
vec3 specularColor; // color contribution from specular lighting
vec4 baseColor;
float metallic;
float sheenRoughnessFactor;
vec3 sheenColorFactor;
vec3 clearcoatF0;
vec3 clearcoatF90;
float clearcoatFactor;
vec3 clearcoatNormal;
float clearcoatRoughness;
// KHR_materials_specular
float specularWeight; // product of specularFactor and specularTexture.a
float transmissionFactor;
float thickness;
vec4 attenuation; // rgb - color, w - distance
// KHR_materials_iridescence
float iridescenceFactor;
float iridescenceIor;
float iridescenceThickness;
// KHR_materials_anisotropy
vec3 anisotropicT;
vec3 anisotropicB;
float anisotropyStrength;
float ior;
};
const float M_PI = 3.141592653589793;
vec4 SRGBtoLINEAR(vec4 srgbIn) {
vec3 linOut = pow(srgbIn.xyz,vec3(2.2));
return vec4(linOut, srgbIn.a);
}
float clampedDot(vec3 x, vec3 y) {
return clamp(dot(x, y), 0.0, 1.0);
}
//
// Fresnel
//
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// https://github.com/wdas/brdf/tree/master/src/brdfs
// https://google.github.io/filament/Filament.md.html
//
// The following equation models the Fresnel reflectance term of the spec equation (aka F())
// Implementation of fresnel from [4], Equation 15
vec3 F_Schlick(vec3 f0, vec3 f90, float VdotH) {
return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
}
float F_Schlick(float f0, float f90, float VdotH) {
float x = clamp(1.0 - VdotH, 0.0, 1.0);
float x2 = x * x;
float x5 = x * x2 * x2;
return f0 + (f90 - f0) * x5;
}
float F_Schlick(float f0, float VdotH) {
float f90 = 1.0; //clamp(50.0 * f0, 0.0, 1.0);
return F_Schlick(f0, f90, VdotH);
}
vec3 F_Schlick(vec3 f0, float f90, float VdotH) {
float x = clamp(1.0 - VdotH, 0.0, 1.0);
float x2 = x * x;
float x5 = x * x2 * x2;
return f0 + (f90 - f0) * x5;
}
vec3 F_Schlick(vec3 f0, float VdotH) {
float f90 = 1.0; //clamp(dot(f0, vec3(50.0 * 0.33)), 0.0, 1.0);
return F_Schlick(f0, f90, VdotH);
}
vec3 Schlick_to_F0(vec3 f, vec3 f90, float VdotH) {
float x = clamp(1.0 - VdotH, 0.0, 1.0);
float x2 = x * x;
float x5 = clamp(x * x2 * x2, 0.0, 0.9999);
return (f - f90 * x5) / (1.0 - x5);
}
float Schlick_to_F0(float f, float f90, float VdotH) {
float x = clamp(1.0 - VdotH, 0.0, 1.0);
float x2 = x * x;
float x5 = clamp(x * x2 * x2, 0.0, 0.9999);
return (f - f90 * x5) / (1.0 - x5);
}
vec3 Schlick_to_F0(vec3 f, float VdotH) {
return Schlick_to_F0(f, vec3(1.0), VdotH);
}
float Schlick_to_F0(float f, float VdotH) {
return Schlick_to_F0(f, 1.0, VdotH);
}
// Smith Joint GGX
// Note: Vis = G / (4 * NdotL * NdotV)
// see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3
// see Real-Time Rendering. Page 331 to 336.
// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)
float V_GGX(float NdotL, float NdotV, float alphaRoughness) {
float alphaRoughnessSq = alphaRoughness * alphaRoughness;
float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
float GGX = GGXV + GGXL;
return GGX > 0.0 ? 0.5 / GGX : 0.0;
}
// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
float D_GGX(float NdotH, float alphaRoughness) {
float alphaRoughnessSq = alphaRoughness * alphaRoughness;
float f = (NdotH * NdotH) * (alphaRoughnessSq - 1.0) + 1.0;
return alphaRoughnessSq / (M_PI * f * f);
}
// specularWeight is introduced with KHR_materials_specular
vec3 getIBLRadianceLambertian(float NdotV, vec3 n, float roughness, vec3 diffuseColor, vec3 F0, float specularWeight, EnvironmentMapDataGPU envMap) {
vec2 brdfSamplePoint = clamp(vec2(NdotV, roughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
vec2 f_ab = sampleBRDF_LUT(brdfSamplePoint, envMap).rg;
vec3 irradiance = sampleEnvMapIrradiance(n.xyz, envMap).rgb;
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
// Roughness dependent fresnel, from Fdez-Aguera
vec3 Fr = max(vec3(1.0 - roughness), F0) - F0;
vec3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
vec3 FssEss = specularWeight * k_S * f_ab.x + f_ab.y; // <--- GGX / specular light contribution (scale it down if the specularWeight is low)
// Multiple scattering, from Fdez-Aguera
float Ems = (1.0 - (f_ab.x + f_ab.y));
vec3 F_avg = specularWeight * (F0 + (1.0 - F0) / 21.0);
vec3 FmsEms = Ems * FssEss * F_avg / (1.0 - F_avg * Ems);
vec3 k_D = diffuseColor * (1.0 - FssEss + FmsEms); // we use +FmsEms as indicated by the formula in the blog post (might be a typo in the implementation)
return (FmsEms + k_D) * irradiance;
}
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
vec3 getBRDFLambertian(vec3 f0, vec3 f90, vec3 diffuseColor, float specularWeight, float VdotH) {
// see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
return (1.0 - specularWeight * F_Schlick(f0, f90, VdotH)) * (diffuseColor / M_PI);
}
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
vec3 getBRDFSpecularGGX(vec3 f0, vec3 f90, float alphaRoughness, float specularWeight, float VdotH, float NdotL, float NdotV, float NdotH) {
vec3 F = F_Schlick(f0, f90, VdotH);
float Vis = V_GGX(NdotL, NdotV, alphaRoughness);
float D = D_GGX(NdotH, alphaRoughness);
return specularWeight * F * Vis * D;
}
vec3 getIBLRadianceGGX(vec3 n, vec3 v, float roughness, vec3 F0, float specularWeight, EnvironmentMapDataGPU envMap ) {
float NdotV = clampedDot(n, v);
float mipCount = float(sampleEnvMapQueryLevels(envMap));
float lod = roughness * float(mipCount - 1);
vec3 reflection = normalize(reflect(-v, n));
vec2 brdfSamplePoint = clamp(vec2(NdotV, roughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
vec3 f_ab = sampleBRDF_LUT(brdfSamplePoint, envMap).rgb;
vec3 specularLight = sampleEnvMapLod(reflection.xyz, lod, envMap).rgb;
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
// Roughness dependent fresnel, from Fdez-Aguera
vec3 Fr = max(vec3(1.0 - roughness), F0) - F0;
vec3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
vec3 FssEss = k_S * f_ab.x + f_ab.y;
return specularWeight * specularLight * FssEss;
}
// Calculation of the lighting contribution from an optional Image Based Light source.
// Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
// See our README.md on Environment Maps [3] for additional discussion.
vec3 getIBLRadianceContributionGGX(PBRInfo pbrInputs, float specularWeight, EnvironmentMapDataGPU envMap) {
vec3 n = pbrInputs.n;
vec3 v = pbrInputs.v;
vec3 reflection = normalize(reflect(-v, n));
float mipCount = float(sampleEnvMapQueryLevels(envMap));
float lod = pbrInputs.perceptualRoughness * (mipCount - 1);
// retrieve a scale and bias to F0. See [1], Figure 3
vec2 brdfSamplePoint = clamp(vec2(pbrInputs.NdotV, pbrInputs.perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
vec3 brdf = sampleBRDF_LUT(brdfSamplePoint, envMap).rgb;
// HDR envmaps are already linear
vec3 specularLight = sampleEnvMapLod(reflection.xyz, lod, envMap).rgb;
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
// Roughness dependent fresnel, from Fdez-Aguera
vec3 Fr = max(vec3(1.0 - pbrInputs.perceptualRoughness), pbrInputs.reflectance0) - pbrInputs.reflectance0;
vec3 k_S = pbrInputs.reflectance0 + Fr * pow(1.0 - pbrInputs.NdotV, 5.0);
vec3 FssEss = k_S * brdf.x + brdf.y;
return specularWeight * specularLight * FssEss;
}
// Disney Implementation of diffuse from Physically-Based Shading at Disney by Brent Burley. See Section 5.3.
// http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
vec3 diffuseBurley(PBRInfo pbrInputs) {
float f90 = 2.0 * pbrInputs.LdotH * pbrInputs.LdotH * pbrInputs.alphaRoughness - 0.5;
return (pbrInputs.diffuseColor / M_PI) * (1.0 + f90 * pow((1.0 - pbrInputs.NdotL), 5.0)) * (1.0 + f90 * pow((1.0 - pbrInputs.NdotV), 5.0));
}
// The following equation models the Fresnel reflectance term of the spec equation (aka F())
// Implementation of fresnel from [4], Equation 15
vec3 specularReflection(PBRInfo pbrInputs) {
return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
}
// This calculates the specular geometric attenuation (aka G()),
// where rougher material will reflect less light back to the viewer.
// This implementation is based on [1] Equation 4, and we adopt their modifications to
// alphaRoughness as input as originally proposed in [2].
float geometricOcclusion(PBRInfo pbrInputs) {
float NdotL = pbrInputs.NdotL;
float NdotV = pbrInputs.NdotV;
float rSqr = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
float attenuationL = 2.0 * NdotL / (NdotL + sqrt(rSqr + (1.0 - rSqr) * (NdotL * NdotL)));
float attenuationV = 2.0 * NdotV / (NdotV + sqrt(rSqr + (1.0 - rSqr) * (NdotV * NdotV)));
return attenuationL * attenuationV;
}
// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
float microfacetDistribution(PBRInfo pbrInputs) {
float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
return roughnessSq / (M_PI * f * f);
}
vec3 getIBLRadianceCharlie(PBRInfo pbrInputs, EnvironmentMapDataGPU envMap) {
float sheenRoughness = pbrInputs.sheenRoughnessFactor;
vec3 sheenColor = pbrInputs.sheenColorFactor;
float mipCount = float(sampleEnvMapQueryLevels(envMap));
float lod = sheenRoughness * float(mipCount - 1);
vec3 reflection = normalize(reflect(-pbrInputs.v, pbrInputs.n));
vec2 brdfSamplePoint = clamp(vec2(pbrInputs.NdotV, sheenRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
float brdf = sampleBRDF_LUT(brdfSamplePoint, envMap).b;
vec3 sheenSample = sampleCharlieEnvMapLod(reflection.xyz, lod, envMap).rgb;
return sheenSample * sheenColor * brdf;
}
PBRInfo calculatePBRInputsMetallicRoughness(InputAttributes tc, vec4 albedo, vec4 mrSample, MetallicRoughnessDataGPU mat) {
PBRInfo pbrInputs;
bool isSpecularGlossiness = (getMaterialType(mat) & 2) != 0;
bool isSpecular = (getMaterialType(mat) & 0x10) != 0;
pbrInputs.ior = getIOR(mat);
vec3 f0 = isSpecularGlossiness ? getSpecularFactor(mat) * mrSample.rgb : vec3(pow((pbrInputs.ior - 1)/(pbrInputs.ior + 1), 2));
float metallic = getMetallicFactor(mat);
metallic = mrSample.b * metallic;
metallic = clamp(metallic, 0.0, 1.0);
pbrInputs.baseColor = albedo;
pbrInputs.metallic = metallic;
pbrInputs.specularWeight = 1.0;
if (isSpecular) {
vec3 dielectricSpecularF0 = min(f0 * getSpecularColorFactor(tc, mat), vec3(1.0));
f0 = mix(dielectricSpecularF0, pbrInputs.baseColor.rgb, metallic);
pbrInputs.specularWeight = getSpecularFactor(tc, mat);//u_KHR_materials_specular_specularFactor * specularTexture.a;
//pbrInputs.diffuseColor = mix(pbrInputs.baseColor.rgb, vec3(0), metallic);
}
float perceptualRoughness = isSpecularGlossiness ? getGlossinessFactor(mat): getRoughnessFactor(mat);
const float c_MinRoughness = 0.04;
// Metallic roughness:
// Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
// This layout intentionally reserves the 'r' channel for (optional) occlusion map data
// Specular Glossiness:
// Glossiness is stored in alpha channel
perceptualRoughness = isSpecularGlossiness ? 1.0 - mrSample.a * perceptualRoughness : clamp(mrSample.g * perceptualRoughness, c_MinRoughness, 1.0);
// Roughness is authored as perceptual roughness; as is convention,
// convert to material roughness by squaring the perceptual roughness [2].
float alphaRoughness = perceptualRoughness * perceptualRoughness;
vec3 diffuseColor = isSpecularGlossiness ? pbrInputs.baseColor.rgb * (1.0 - max(max(f0.r, f0.g), f0.b)) : mix(pbrInputs.baseColor.rgb, vec3(0), metallic);
vec3 specularColor = isSpecularGlossiness ? f0 : mix(f0, pbrInputs.baseColor.rgb, metallic);
// Compute reflectance.
float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
// For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
// For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
vec3 specularEnvironmentR0 = specularColor.rgb;
vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;
pbrInputs.alphaRoughness = alphaRoughness;
pbrInputs.perceptualRoughness = perceptualRoughness;
pbrInputs.reflectance0 = specularEnvironmentR0;
pbrInputs.reflectance90 = specularEnvironmentR90;
pbrInputs.diffuseColor = diffuseColor;
pbrInputs.specularColor = specularColor;
return pbrInputs;
}
vec3 calculatePBRLightContribution( inout PBRInfo pbrInputs, vec3 lightDirection, vec3 lightColor ) {
vec3 n = pbrInputs.n;
vec3 v = pbrInputs.v;
vec3 l = normalize(lightDirection); // Vector from surface point to light
vec3 h = normalize(l+v); // Half vector between both l and v
float NdotV = pbrInputs.NdotV;
float NdotL = clamp(dot(n, l), 0.001, 1.0);
float NdotH = clamp(dot(n, h), 0.0, 1.0);
float LdotH = clamp(dot(l, h), 0.0, 1.0);
float VdotH = clamp(dot(v, h), 0.0, 1.0);
vec3 color = vec3(0);
if (NdotL > 0.0 || NdotV > 0.0) {
pbrInputs.NdotL = NdotL;
pbrInputs.NdotH = NdotH;
pbrInputs.LdotH = LdotH;
pbrInputs.VdotH = VdotH;
// Calculate the shading terms for the microfacet specular shading model
vec3 F = specularReflection(pbrInputs);
float G = geometricOcclusion(pbrInputs);
float D = microfacetDistribution(pbrInputs);
// Calculation of analytical lighting contribution
vec3 diffuseContrib = (1.0 - F) * diffuseBurley(pbrInputs);
vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
// Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law)
color = NdotL * lightColor * (diffuseContrib + specContrib);
}
return color;
}
// http://www.thetenthplanet.de/archives/1180
// modified to fix handedness of the resulting cotangent frame
mat3 cotangentFrame( vec3 N, vec3 p, vec2 uv, inout PBRInfo pbrInputs ) {
// get edge vectors of the pixel triangle
vec3 dp1 = dFdx( p );
vec3 dp2 = dFdy( p );
vec2 duv1 = dFdx( uv );
vec2 duv2 = dFdy( uv );
if (length(duv1) <= 1e-2) {
duv1 = vec2(1.0, 0.0);
}
if (length(duv2) <= 1e-2) {
duv2 = vec2(0.0, 1.0);
}
// solve the linear system
vec3 dp2perp = cross( dp2, N );
vec3 dp1perp = cross( N, dp1 );
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
// construct a scale-invariant frame
float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );
// calculate handedness of the resulting cotangent frame
float w = dot(cross(N, T), B) < 0.0 ? -1.0 : 1.0;
// adjust tangent if needed
T = T * w;
if (gl_FrontFacing == false) {
N *= -1.0f;
T *= -1.0f;
B *= -1.0f;
}
pbrInputs.t = T * invmax;
pbrInputs.b = B * invmax;
pbrInputs.ng = N;
return mat3( pbrInputs.t, pbrInputs.b, N );
}
void perturbNormal(vec3 n, vec3 v, vec3 normalSample, vec2 uv, inout PBRInfo pbrInputs) {
vec3 map = normalize( 2.0 * normalSample - vec3(1.0) );
mat3 TBN = cotangentFrame(n, v, uv, pbrInputs);
pbrInputs.n = normalize(TBN * map);
}
// Check normals - model has tangents and a normal map!
vec3 getVolumeTransmissionRay(vec3 n, vec3 v, float thickness, float ior, mat4 modelMatrix) {
// direction of refracted light
vec3 refractionVector = refract(-v, n, 1.0 / ior);
// compute rotation-independent scaling of the model matrix
vec3 modelScale = vec3(length(modelMatrix[0].xyz),
length(modelMatrix[1].xyz),
length(modelMatrix[2].xyz));
// the thickness is specified in local space
return normalize(refractionVector) * thickness * modelScale.xyz;
}
vec3 applyVolumeAttenuation(vec3 radiance, float transmissionDistance, vec3 attenuationColor, float attenuationDistance) {
if (attenuationDistance == 0.0) {
// Attenuation distance is + (which we indicate by zero), i.e. the transmitted color is not attenuated at all.
return radiance;
}
// Compute light attenuation using Beer's law.
vec3 attenuationCoefficient = -log(attenuationColor) / attenuationDistance;
vec3 transmittance = exp(-attenuationCoefficient * transmissionDistance); // Beer's law
return transmittance * radiance;
}
float applyIorToRoughness(float roughness, float ior) {
// Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and
// an IOR of 1.5 results in the default amount of microfacet refraction.
return roughness * clamp(ior * 2.0 - 2.0, 0.0, 1.0);
}
vec3 getTransmissionSample(vec2 fragCoord, float roughness, float ior) {
const ivec2 size = textureBindlessSize2D(perFrame.transmissionFramebuffer);
const vec2 uv = fragCoord;
float framebufferLod = log2(float(size.x)) * applyIorToRoughness(roughness, ior);
vec3 transmittedLight = textureBindless2DLod(perFrame.transmissionFramebuffer, perFrame.transmissionFramebufferSampler, uv, framebufferLod).rgb;
// DEBUG:
// return vec3(uv, 0.0);
return transmittedLight;
}
vec3 getIBLVolumeRefraction(vec3 n, vec3 v, float perceptualRoughness, vec3 baseColor, vec3 f0, vec3 f90,
vec3 position, mat4 modelMatrix, mat4 viewProjMatrix, float ior, float thickness, vec3 attenuationColor, float attenuationDistance)
{
vec3 transmissionRay = getVolumeTransmissionRay(n, v, thickness, ior, modelMatrix);
vec3 refractedRayExit = position + transmissionRay;
// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
vec4 ndcPos = viewProjMatrix * vec4(refractedRayExit, 1.0);
vec2 refractionCoords = ndcPos.xy / ndcPos.w;
refractionCoords += 1.0;
refractionCoords /= 2.0;
refractionCoords.y = 1.0- refractionCoords.y;
// Sample framebuffer to get pixel the refracted ray hits.
vec3 transmittedLight = getTransmissionSample(refractionCoords, perceptualRoughness, ior);
vec3 attenuatedColor = applyVolumeAttenuation(transmittedLight, length(transmissionRay), attenuationColor, attenuationDistance);
// Sample GGX LUT to get the specular component.
float NdotV = clampedDot(n, v);
vec2 brdfSamplePoint = clamp(vec2(NdotV, perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
vec2 brdf = sampleBRDF_LUT(brdfSamplePoint, getEnvironmentMap(getEnvironmentId())).rg;
vec3 specularColor = f0 * brdf.x + f90 * brdf.y;
return (1.0 - specularColor) * attenuatedColor * baseColor;
}
// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#range-property
float getRangeAttenuation(float range, float distance) {
if (range <= 0.0) {
// negative range means unlimited
return 1.0 / pow(distance, 2.0);
}
return max(min(1.0 - pow(distance / range, 4.0), 1.0), 0.0) / pow(distance, 2.0);
}
// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#inner-and-outer-cone-angles
float getSpotAttenuation(vec3 pointToLight, vec3 spotDirection, float outerConeCos, float innerConeCos) {
float actualCos = dot(normalize(spotDirection), normalize(-pointToLight));
if (actualCos > outerConeCos) {
if (actualCos < innerConeCos) {
return smoothstep(outerConeCos, innerConeCos, actualCos);
}
return 1.0;
}
return 0.0;
}
vec3 getLightIntensity(Light light, vec3 pointToLight) {
float rangeAttenuation = 1.0;
float spotAttenuation = 1.0;
if (light.type != LightType_Directional) {
rangeAttenuation = getRangeAttenuation(light.range, length(pointToLight));
}
if (light.type == LightType_Spot) {
spotAttenuation = getSpotAttenuation(pointToLight, light.direction, light.outerConeCos, light.innerConeCos);
}
return rangeAttenuation * spotAttenuation * light.intensity * light.color;
}
// Estevez and Kulla http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
float D_Charlie(float sheenRoughness, float NdotH) {
sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
float alphaG = sheenRoughness * sheenRoughness;
float invR = 1.0 / alphaG;
float cos2h = NdotH * NdotH;
float sin2h = 1.0 - cos2h;
return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * M_PI);
}
float lambdaSheenNumericHelper(float x, float alphaG) {
float oneMinusAlphaSq = (1.0 - alphaG) * (1.0 - alphaG);
float a = mix(21.5473, 25.3245, oneMinusAlphaSq);
float b = mix(3.82987, 3.32435, oneMinusAlphaSq);
float c = mix(0.19823, 0.16801, oneMinusAlphaSq);
float d = mix(-1.97760, -1.27393, oneMinusAlphaSq);
float e = mix(-4.32054, -4.85967, oneMinusAlphaSq);
return a / (1.0 + b * pow(x, c)) + d * x + e;
}
float lambdaSheen(float cosTheta, float alphaG) {
if (abs(cosTheta) < 0.5) {
return exp(lambdaSheenNumericHelper(cosTheta, alphaG));
}
return exp(2.0 * lambdaSheenNumericHelper(0.5, alphaG) - lambdaSheenNumericHelper(1.0 - cosTheta, alphaG));
}
float V_Sheen(float NdotL, float NdotV, float sheenRoughness) {
sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
float alphaG = sheenRoughness * sheenRoughness;
return clamp(1.0 / ((1.0 + lambdaSheen(NdotV, alphaG) + lambdaSheen(NdotL, alphaG)) * (4.0 * NdotV * NdotL)), 0.0, 1.0);
}
// f_sheen
vec3 getBRDFSpecularSheen(vec3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH) {
float sheenDistribution = D_Charlie(sheenRoughness, NdotH);
float sheenVisibility = V_Sheen(NdotL, NdotV, sheenRoughness);
return sheenColor * sheenDistribution * sheenVisibility;
}
vec3 getPunctualRadianceSheen(vec3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH) {
return NdotL * getBRDFSpecularSheen(sheenColor, sheenRoughness, NdotL, NdotV, NdotH);
}
// https://github.com/DassaultSystemes-Technology/dspbr-pt/blob/e7cfa6e9aab2b99065a90694e1f58564d675c1a4/packages/lib/shader/bsdfs/sheen.glsl#L16C1-L20C2
float albedoSheenScalingFactor(float NdotV, float sheenRoughnessFactor) {
float c = 1.0 - NdotV;
float c3 = c * c * c;
return 0.65584461 * c3 + 1.0 / (4.16526551 + exp(-7.97291361 * sqrt(sheenRoughnessFactor) + 6.33516894));
}
float max3(vec3 v) {
return max(max(v.x, v.y), v.z);
}
vec3 getPunctualRadianceClearCoat(vec3 clearcoatNormal, vec3 v, vec3 l, vec3 h, float VdotH, vec3 f0, vec3 f90, float clearcoatRoughness) {
float NdotL = clampedDot(clearcoatNormal, l);
float NdotV = clampedDot(clearcoatNormal, v);
float NdotH = clampedDot(clearcoatNormal, h);
return NdotL * getBRDFSpecularGGX(f0, f90, clearcoatRoughness * clearcoatRoughness, 1.0, VdotH, NdotL, NdotV, NdotH);
}
vec3 getPunctualRadianceTransmission(vec3 normal, vec3 view, vec3 pointToLight, float alphaRoughness, vec3 f0, vec3 f90, vec3 baseColor, float ior) {
float transmissionRougness = applyIorToRoughness(alphaRoughness, ior);
vec3 n = normalize(normal); // Outward direction of surface point
vec3 v = normalize(view); // Direction from surface point to view
vec3 l = normalize(pointToLight);
vec3 l_mirror = normalize(l + 2.0*n*dot(-l, n)); // Mirror light reflection vector on surface
vec3 h = normalize(l_mirror + v); // Halfway vector between transmission light vector and v
float D = D_GGX(clamp(dot(n, h), 0.0, 1.0), transmissionRougness);
vec3 F = F_Schlick(f0, f90, clamp(dot(v, h), 0.0, 1.0));
float Vis = V_GGX(clamp(dot(n, l_mirror), 0.0, 1.0), clamp(dot(n, v), 0.0, 1.0), transmissionRougness);
// Transmission BTDF
return (1.0 - F) * baseColor * D * Vis;
}

View file

@ -0,0 +1,116 @@
//
#version 460
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_scalar_block_layout : require
layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in;
struct TransformsBuffer {
uint mtxId;
uint matId;
uint nodeRef; // for CPU only
uint meshRef; // for CPU only
uint opaque; // for CPU only
};
struct VertexSkinningData {
vec4 pos;
vec4 norm;
uint bones[8];
float weights[8];
uint meshId;
};
struct VertexData {
vec3 pos;
vec3 norm;
vec4 color;
vec4 uv;
float padding[2];
};
#define MAX_WEIGHTS 8
struct MorphState {
uint meshId;
uint morphTarget[MAX_WEIGHTS];
float weights[MAX_WEIGHTS];
};
layout(std430, buffer_reference) readonly buffer Matrices {
mat4 matrix[];
};
layout(scalar, buffer_reference) readonly buffer MorphStates {
MorphState morphStates[];
};
layout (scalar, buffer_reference) readonly buffer VertexSkinningBuffer {
VertexSkinningData vertices[];
};
layout (scalar, buffer_reference) writeonly buffer VertexBuffer {
VertexData vertices[];
};
layout (scalar, buffer_reference) readonly buffer MorphVertexBuffer {
VertexData vertices[];
};
layout(push_constant) uniform PerFrameData {
Matrices matrices;
MorphStates morphStates;
MorphVertexBuffer morphTargets;
VertexSkinningBuffer inBufferId;
VertexBuffer outBufferId;
uint numMorphStates;
} pc;
void main()
{
// our vertex buffers are always padded to a 16-vertex boundary
uint index = gl_GlobalInvocationID.x;
VertexSkinningData inVtx = pc.inBufferId.vertices[index];
vec4 inPos = vec4(inVtx.pos.xyz, 1.0);
vec4 inNorm = vec4(inVtx.norm.xyz, 0.0);
// morphing
if (inVtx.meshId < pc.numMorphStates) {
MorphState ms = pc.morphStates.morphStates[inVtx.meshId];
if (ms.meshId != ~0) {
for (int m = 0; m != MAX_WEIGHTS; m++) {
if (ms.weights[m] > 0) {
VertexData mVtx = pc.morphTargets.vertices[ms.morphTarget[m] + index];
inPos.xyz += mVtx.pos * ms.weights[m];
inNorm.xyz += mVtx.norm * ms.weights[m];
}
}
}
}
vec4 pos = vec4(0);
vec4 norm = vec4(0);
int i = 0;
// skinning
for (; i != MAX_WEIGHTS; i++) {
if (inVtx.bones[i] == ~0)
break;
mat4 boneMat = pc.matrices.matrix[inVtx.bones[i]];
pos += boneMat * inPos * inVtx.weights[i];
norm += transpose(inverse(boneMat)) * inNorm * inVtx.weights[i];
}
if (i == 0) {
pos.xyz = inPos.xyz;
norm.xyz = inNorm.xyz;
}
pc.outBufferId.vertices[index].pos = pos.xyz;
pc.outBufferId.vertices[index].norm = normalize(norm.xyz);
}

View file

@ -0,0 +1,51 @@
//
// gl_BaseInstance - transformId
layout(std430, buffer_reference) buffer Materials;
layout(std430, buffer_reference) buffer Environments;
layout(std430, buffer_reference) buffer Lights;
layout(std430, buffer_reference) buffer PerDrawData {
mat4 model;
mat4 view;
mat4 proj;
vec4 cameraPos;
};
struct TransformsBuffer {
uint mtxId;
uint matId;
uint nodeRef; // for CPU only
uint meshRef; // for CPU only
uint opaque; // for CPU only
};
layout(std430, buffer_reference) readonly buffer Transforms {
TransformsBuffer transforms[];
};
layout(std430, buffer_reference) readonly buffer Matrices {
mat4 matrix[];
};
layout(push_constant) uniform PerFrameData {
PerDrawData drawable;
Materials materials;
Environments environments;
Lights lights;
Transforms transforms;
Matrices matrices;
uint envId;
uint transmissionFramebuffer;
uint transmissionFramebufferSampler;
uint lightsCount;
} perFrame;
uint getEnvironmentId() {
return perFrame.envId;
}
mat4 getViewProjection() {
return perFrame.drawable.proj * perFrame.drawable.view;
}

View file

@ -0,0 +1,67 @@
//
struct MetallicRoughnessDataGPU {
vec4 baseColorFactor;
vec4 metallicRoughnessNormalOcclusion; // Packed metallicFactor, roughnessFactor, normalScale, occlusionStrength
vec4 specularGlossiness; // Packed specularFactor.xyz, glossiness
vec4 sheenFactors;
vec4 clearcoatTransmissionThickness;
vec4 specularFactors;
vec4 attenuation;
vec4 emissiveFactorAlphaCutoff; // vec3 emissiveFactor + float AlphaCutoff
uint occlusionTexture;
uint occlusionTextureSampler;
uint occlusionTextureUV;
uint emissiveTexture;
uint emissiveTextureSampler;
uint emissiveTextureUV;
uint baseColorTexture;
uint baseColorTextureSampler;
uint baseColorTextureUV;
uint metallicRoughnessTexture;
uint metallicRoughnessTextureSampler;
uint metallicRoughnessTextureUV;
uint normalTexture;
uint normalTextureSampler;
uint normalTextureUV;
uint sheenColorTexture;
uint sheenColorTextureSampler;
uint sheenColorTextureUV;
uint sheenRoughnessTexture;
uint sheenRoughnessTextureSampler;
uint sheenRoughnessTextureUV;
uint clearCoatTexture;
uint clearCoatTextureSampler;
uint clearCoatTextureUV;
uint clearCoatRoughnessTexture;
uint clearCoatRoughnessTextureSampler;
uint clearCoatRoughnessTextureUV;
uint clearCoatNormalTexture;
uint clearCoatNormalTextureSampler;
uint clearCoatNormalTextureUV;
uint specularTexture;
uint specularTextureSampler;
uint specularTextureUV;
uint specularColorTexture;
uint specularColorTextureSampler;
uint specularColorTextureUV;
uint transmissionTexture;
uint transmissionTextureSampler;
uint transmissionTextureUV;
uint thicknessTexture;
uint thicknessTextureSampler;
uint thicknessTextureUV;
uint iridescenceTexture;
uint iridescenceTextureSampler;
uint iridescenceTextureUV;
uint iridescenceThicknessTexture;
uint iridescenceThicknessTextureSampler;
uint iridescenceThicknessTextureUV;
uint anisotropyTexture;
uint anisotropyTextureSampler;
uint anisotropyTextureUV;
uint alphaMode;
uint materialType;
float ior;
uint padding[2];
};

View file

@ -0,0 +1,229 @@
#include <data/shaders/gltf/common.sp>
#include <data/shaders/gltf/common_material.sp>
const int kMaxAttributes = 2;
struct InputAttributes {
vec2 uv[kMaxAttributes];
};
struct Light {
vec3 direction;
float range;
vec3 color;
float intensity;
vec3 position;
float innerConeCos;
float outerConeCos;
int type;
int padding[2];
};
const int LightType_Directional = 0;
const int LightType_Point = 1;
const int LightType_Spot = 2;
struct EnvironmentMapDataGPU {
uint envMapTexture;
uint envMapTextureSampler;
uint envMapTextureIrradiance;
uint envMapTextureIrradianceSampler;
uint texBRDFLUT;
uint texBRDFLUTSampler;
uint envMapTextureCharlie;
uint envMapTextureCharlieSampler;
};
layout(std430, buffer_reference) readonly buffer Materials {
MetallicRoughnessDataGPU material[];
};
layout(std430, buffer_reference) readonly buffer Environments {
EnvironmentMapDataGPU environment[];
};
layout(std430, buffer_reference) readonly buffer Lights {
Light lights[];
};
MetallicRoughnessDataGPU getMaterial(uint idx) {
return perFrame.materials.material[idx];
}
EnvironmentMapDataGPU getEnvironmentMap(uint idx) {
return perFrame.environments.environment[idx];
}
float getMetallicFactor(MetallicRoughnessDataGPU mat) {
return mat.metallicRoughnessNormalOcclusion.x;
}
float getRoughnessFactor(MetallicRoughnessDataGPU mat) {
return mat.metallicRoughnessNormalOcclusion.y;
}
float getNormalScale(MetallicRoughnessDataGPU mat) {
return mat.metallicRoughnessNormalOcclusion.z;
}
float getOcclusionFactor(MetallicRoughnessDataGPU mat) {
return mat.metallicRoughnessNormalOcclusion.w;
}
uint getMaterialType(MetallicRoughnessDataGPU mat) {
return mat.materialType;
}
vec3 getSpecularFactor(MetallicRoughnessDataGPU mat) {
return mat.specularGlossiness.xyz;
}
float getGlossinessFactor(MetallicRoughnessDataGPU mat) {
return mat.specularGlossiness.w;
}
vec4 getEmissiveFactorAlphaCutoff(MetallicRoughnessDataGPU mat) {
return mat.emissiveFactorAlphaCutoff;
}
vec4 getSheenColorFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.sheenColorTexture, mat.sheenColorTextureSampler, tc.uv[mat.sheenColorTextureUV]) * vec4(mat.sheenFactors.xyz, 1.0f);
}
float getSheenRoughnessFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.sheenRoughnessTexture, mat.sheenRoughnessTextureSampler, tc.uv[mat.sheenRoughnessTextureUV]).a * mat.sheenFactors.a;
}
float getClearcoatFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.clearCoatTexture, mat.clearCoatTextureSampler, tc.uv[mat.clearCoatTextureUV]).r * mat.clearcoatTransmissionThickness.x;
}
float getClearcoatRoughnessFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.clearCoatRoughnessTexture, mat.clearCoatRoughnessTextureSampler, tc.uv[mat.clearCoatRoughnessTextureUV]).g * mat.clearcoatTransmissionThickness.y;
}
float getTransmissionFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.transmissionTexture, mat.transmissionTextureSampler, tc.uv[mat.transmissionTextureUV]).r * mat.clearcoatTransmissionThickness.z;
}
float getVolumeTickness(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.thicknessTexture, mat.thicknessTextureSampler, tc.uv[mat.thicknessTextureUV]).g * mat.clearcoatTransmissionThickness.w;
}
vec4 getVolumeAttenuation(MetallicRoughnessDataGPU mat) {
return mat.attenuation;
}
float getIOR(MetallicRoughnessDataGPU mat) {
return mat.ior;
}
vec3 getSpecularColorFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.specularColorTexture, mat.specularColorTextureSampler, tc.uv[mat.specularColorTextureUV]).rgb * mat.specularFactors.rgb;
}
float getSpecularFactor(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.specularTexture, mat.specularTextureSampler, tc.uv[mat.specularTextureUV]).a * mat.specularFactors.a;
}
vec2 getNormalUV(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return mat.normalTextureUV > -1 ? tc.uv[mat.normalTextureUV] : tc.uv[0];
}
vec4 sampleAO(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.occlusionTexture, mat.occlusionTextureSampler, tc.uv[mat.occlusionTextureUV]);
}
vec4 sampleEmissive(InputAttributes tc, MetallicRoughnessDataGPU mat) {
vec2 uv = mat.emissiveTextureUV > -1 ? tc.uv[mat.emissiveTextureUV] : tc.uv[0];
return textureBindless2D(mat.emissiveTexture, mat.emissiveTextureSampler, uv) * vec4(mat.emissiveFactorAlphaCutoff.xyz, 1.0f);
}
vec4 sampleAlbedo(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.baseColorTexture, mat.baseColorTextureSampler, tc.uv[mat.baseColorTextureUV]) * mat.baseColorFactor;
}
vec4 sampleMetallicRoughness(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.metallicRoughnessTexture, mat.metallicRoughnessTextureSampler, tc.uv[mat.metallicRoughnessTextureUV]);
}
vec4 sampleNormal(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.normalTexture, mat.normalTextureSampler, tc.uv[mat.normalTextureUV]);// * mat.metallicRoughnessNormalOcclusion.z;
}
vec4 sampleClearcoatNormal(InputAttributes tc, MetallicRoughnessDataGPU mat) {
return textureBindless2D(mat.clearCoatNormalTexture, mat.clearCoatNormalTextureSampler, tc.uv[mat.clearCoatNormalTextureUV]);
}
vec4 sampleBRDF_LUT(vec2 tc, EnvironmentMapDataGPU map) {
return textureBindless2D(map.texBRDFLUT, map.texBRDFLUTSampler, tc);
}
vec4 sampleEnvMap(vec3 tc, EnvironmentMapDataGPU map) {
return textureBindlessCube(map.envMapTexture, map.envMapTextureSampler, tc);
}
vec4 sampleEnvMapLod(vec3 tc, float lod, EnvironmentMapDataGPU map) {
return textureBindlessCubeLod(map.envMapTexture, map.envMapTextureSampler, tc, lod);
}
vec4 sampleCharlieEnvMapLod(vec3 tc, float lod, EnvironmentMapDataGPU map) {
return textureBindlessCubeLod(map.envMapTextureCharlie, map.envMapTextureCharlieSampler, tc, lod);
}
vec4 sampleEnvMapIrradiance(vec3 tc, EnvironmentMapDataGPU map) {
return textureBindlessCube(map.envMapTextureIrradiance, map.envMapTextureIrradianceSampler, tc);
}
int sampleEnvMapQueryLevels(EnvironmentMapDataGPU map) {
return textureBindlessQueryLevelsCube(map.envMapTexture);
}
vec4 sampleTransmissionFramebuffer(vec2 tc) {
return textureBindless2D(perFrame.transmissionFramebuffer, perFrame.transmissionFramebufferSampler, tc);
}
bool isMaterialTypeSheen(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x4) != 0;
}
bool isMaterialTypeClearCoat(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x8) != 0;
}
bool isMaterialTypeSpecular(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x10) != 0;
}
bool isMaterialTypeTransmission(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x20) != 0;
}
bool isMaterialTypeVolume(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x40) != 0;
}
bool isMaterialTypeUnlit(MetallicRoughnessDataGPU mat) {
return (getMaterialType(mat) & 0x80) != 0;
}
uint getLightsCount() {
return perFrame.lightsCount;
}
Light getLight(uint i) {
return perFrame.lights.lights[i];
}
mat4 getModel() {
uint mtxId = perFrame.transforms.transforms[oBaseInstance].mtxId;
return perFrame.drawable.model * perFrame.matrices.matrix[mtxId];
}
uint getMaterialId() {
return perFrame.transforms.transforms[oBaseInstance].matId;
}

View file

@ -0,0 +1,28 @@
#include <data/shaders/gltf/common.sp>
layout (location = 0) in vec3 pos;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec4 color;
layout (location = 3) in vec2 uv0;
layout (location = 4) in vec2 uv1;
vec3 getPosition() {
return pos;
}
vec3 getNormal() {
return normal;
}
vec4 getColor() {
return color;
}
vec2 getTexCoord(uint i) {
return i == 0 ? uv0 : uv1;
}
mat4 getModel() {
uint mtxId = perFrame.transforms.transforms[gl_BaseInstance].mtxId;
return perFrame.drawable.model * perFrame.matrices.matrix[mtxId];
}

216
data/shaders/gltf/main.frag Normal file
View file

@ -0,0 +1,216 @@
//
layout (location=0) in vec4 uv0uv1;
layout (location=1) in vec3 normal;
layout (location=2) in vec3 worldPos;
layout (location=3) in vec4 color;
layout (location=4) in flat int oBaseInstance;
layout (location=0) out vec4 out_FragColor;
#include <data/shaders/gltf/inputs.frag>
#include <data/shaders/gltf/PBR.sp>
void main()
{
InputAttributes tc;
tc.uv[0] = uv0uv1.xy;
tc.uv[1] = uv0uv1.zw;
MetallicRoughnessDataGPU mat = getMaterial(getMaterialId());
EnvironmentMapDataGPU envMap = getEnvironmentMap(getEnvironmentId());
vec4 Kd = sampleAlbedo(tc, mat) * color;
if ((mat.alphaMode == 1) && (mat.emissiveFactorAlphaCutoff.w > Kd.a)) {
discard;
}
if (isMaterialTypeUnlit(mat)) {
out_FragColor = Kd;
return;
}
vec4 Kao = sampleAO(tc, mat);
vec4 Ke = sampleEmissive(tc, mat);
vec4 mrSample = sampleMetallicRoughness(tc, mat);
bool isSheen = isMaterialTypeSheen(mat);
bool isClearCoat = isMaterialTypeClearCoat(mat);
bool isSpecular = isMaterialTypeSpecular(mat);
bool isTransmission = isMaterialTypeTransmission(mat);
bool isVolume = isMaterialTypeVolume(mat);
vec3 n = normalize(normal);
PBRInfo pbrInputs = calculatePBRInputsMetallicRoughness(tc, Kd, mrSample, mat);
pbrInputs.n = n;
pbrInputs.ng = n;
if (mat.normalTexture != ~0) {
vec3 normalSample = sampleNormal(tc, mat).xyz;
perturbNormal(n, worldPos, normalSample, getNormalUV(tc, mat), pbrInputs);
n = pbrInputs.n;
}
vec3 v = normalize(perFrame.drawable.cameraPos.xyz - worldPos); // Vector from surface point to camera
pbrInputs.v = v;
pbrInputs.NdotV = clamp(abs(dot(pbrInputs.n, pbrInputs.v)), 0.001, 1.0);
if (isSheen) {
pbrInputs.sheenColorFactor = getSheenColorFactor(tc, mat).rgb;
pbrInputs.sheenRoughnessFactor = getSheenRoughnessFactor(tc, mat);
}
vec3 clearCoatContrib = vec3(0);
if (isClearCoat) {
pbrInputs.clearcoatFactor = getClearcoatFactor(tc, mat);
pbrInputs.clearcoatRoughness = clamp(getClearcoatRoughnessFactor(tc, mat), 0.0, 1.0);
pbrInputs.clearcoatF0 = vec3(pow((pbrInputs.ior - 1.0) / (pbrInputs.ior + 1.0), 2.0));
pbrInputs.clearcoatF90 = vec3(1.0);
if (mat.clearCoatNormalTextureUV>-1) {
pbrInputs.clearcoatNormal = mat3(pbrInputs.t, pbrInputs.b, pbrInputs.ng) * sampleClearcoatNormal(tc, mat).rgb;
} else {
pbrInputs.clearcoatNormal =pbrInputs.ng;
}
clearCoatContrib = getIBLRadianceGGX(pbrInputs.clearcoatNormal, pbrInputs.v, pbrInputs.clearcoatRoughness, pbrInputs.clearcoatF0, 1.0, envMap);
}
if (isTransmission) {
pbrInputs.transmissionFactor = getTransmissionFactor(tc, mat);
}
if (isVolume) {
pbrInputs.thickness = getVolumeTickness(tc, mat);
pbrInputs.attenuation = getVolumeAttenuation(mat);
}
// IBL contribution
vec3 specularColor = getIBLRadianceContributionGGX(pbrInputs, pbrInputs.specularWeight, envMap);
vec3 diffuseColor = getIBLRadianceLambertian(pbrInputs.NdotV, n, pbrInputs.perceptualRoughness, pbrInputs.diffuseColor, pbrInputs.reflectance0, pbrInputs.specularWeight, envMap);
vec3 transmission = vec3(0,0,0);
if (isTransmission) {
transmission += getIBLVolumeRefraction(
pbrInputs.n, pbrInputs.v,
pbrInputs.perceptualRoughness,
pbrInputs.diffuseColor, pbrInputs.reflectance0, pbrInputs.reflectance90,
worldPos, getModel(), getViewProjection(),
pbrInputs.ior, pbrInputs.thickness, pbrInputs.attenuation.rgb, pbrInputs.attenuation.w);
}
vec3 sheenColor = vec3(0);
if (isSheen) {
sheenColor += getIBLRadianceCharlie(pbrInputs, envMap);
}
vec3 lights_diffuse = vec3(0);
vec3 lights_specular = vec3(0);
vec3 lights_sheen = vec3(0);
vec3 lights_clearcoat = vec3(0);
vec3 lights_transmission = vec3(0);
float albedoSheenScaling = 1.0;
for (uint i = 0; i < getLightsCount(); ++i)
{
Light light = getLight(i);
vec3 pointToLight = (light.type == LightType_Directional) ? -light.direction : light.position - worldPos;
// BSTF
vec3 l = normalize(pointToLight);
vec3 h = normalize(l + v);
float NdotL = clampedDot(n, l);
float NdotV = clampedDot(n, v);
float NdotH = clampedDot(n, h);
float LdotH = clampedDot(l, h);
float VdotH = clampedDot(v, h);
if (NdotL > 0.0 || NdotV > 0.0)
{
// Calculation of analytical light
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
vec3 intensity = getLightIntensity(light, pointToLight);
lights_diffuse += intensity * NdotL * getBRDFLambertian(pbrInputs.reflectance0, pbrInputs.reflectance90, pbrInputs.diffuseColor, pbrInputs.specularWeight, VdotH);
lights_specular += intensity * NdotL * getBRDFSpecularGGX(pbrInputs.reflectance0, pbrInputs.reflectance90, pbrInputs.alphaRoughness, pbrInputs.specularWeight, VdotH, NdotL, NdotV, NdotH);
if (isSheen) {
lights_sheen += intensity * getPunctualRadianceSheen(pbrInputs.sheenColorFactor, pbrInputs.sheenRoughnessFactor, NdotL, NdotV, NdotH);
albedoSheenScaling = min(1.0 - max3(pbrInputs.sheenColorFactor) * albedoSheenScalingFactor(NdotV, pbrInputs.sheenRoughnessFactor),
1.0 - max3(pbrInputs.sheenColorFactor) * albedoSheenScalingFactor(NdotL, pbrInputs.sheenRoughnessFactor));
}
if (isClearCoat) {
lights_clearcoat += intensity * getPunctualRadianceClearCoat(pbrInputs.clearcoatNormal, v, l, h, VdotH,
pbrInputs.clearcoatF0, pbrInputs.clearcoatF90, pbrInputs.clearcoatRoughness);
}
}
// BDTF
if (isTransmission) {
// If the light ray travels through the geometry, use the point it exits the geometry again.
// That will change the angle to the light source, if the material refracts the light ray.
vec3 transmissionRay = getVolumeTransmissionRay(n, v, pbrInputs.thickness, pbrInputs.ior, getModel());
pointToLight -= transmissionRay;
l = normalize(pointToLight);
vec3 intensity = getLightIntensity(light, pointToLight);
vec3 transmittedLight = intensity * getPunctualRadianceTransmission(n, v, l, pbrInputs.alphaRoughness, pbrInputs.reflectance0, pbrInputs.clearcoatF90, pbrInputs.diffuseColor, pbrInputs.ior);
if (isVolume) {
transmittedLight = applyVolumeAttenuation(transmittedLight, length(transmissionRay), pbrInputs.attenuation.rgb, pbrInputs.attenuation.w);
}
lights_transmission += transmittedLight;
}
}
// ambient occlusion
float occlusion = Kao.r < 0.01 ? 1.0 : Kao.r;
float occlusionStrength = getOcclusionFactor(mat);
diffuseColor = lights_diffuse + mix(diffuseColor, diffuseColor * occlusion, occlusionStrength);
specularColor = lights_specular + mix(specularColor, specularColor * occlusion, occlusionStrength);
sheenColor = lights_sheen + mix(sheenColor, sheenColor * occlusion, occlusionStrength);
vec3 emissiveColor = Ke.rgb * sampleEmissive(tc, mat).rgb;
vec3 clearcoatFresnel = vec3(0);
if (isClearCoat) {
clearcoatFresnel = F_Schlick(pbrInputs.clearcoatF0, pbrInputs.clearcoatF90, clampedDot(pbrInputs.clearcoatNormal, pbrInputs.v));
}
if (isTransmission) {
diffuseColor = mix(diffuseColor, transmission, pbrInputs.transmissionFactor);
}
vec3 color = specularColor + diffuseColor + emissiveColor + sheenColor;
color = color * (1.0 - pbrInputs.clearcoatFactor * clearcoatFresnel) + clearCoatContrib;
color = pow(color, vec3(1.0/2.2));
out_FragColor = vec4(color, 1.0);
// DEBUG
// out_FragColor = vec4((n + vec3(1.0))*0.5, 1.0);
// out_FragColor = vec4((pbrInputs.n + vec3(1.0))*0.5, 1.0);
// out_FragColor = vec4((normal + vec3(1.0))*0.5, 1.0);
// out_FragColor = Kao;
// out_FragColor = Ke;
// out_FragColor = Kd;
// vec2 MeR = mrSample.yz;
// MeR.x *= getMetallicFactor(mat);
// MeR.y *= getRoughnessFactor(mat);
// out_FragColor = vec4(MeR.y,MeR.y,MeR.y, 1.0);
// out_FragColor = vec4(MeR.x,MeR.x,MeR.x, 1.0);
// out_FragColor = mrSample;
// out_FragColor = vec4(transmission, 1.0);
// out_FragColor = vec4(punctualColor, 1.0);
}

View file

@ -0,0 +1,28 @@
//
layout (location=0) out vec4 oUV0UV1;
layout (location=1) out vec3 oNormal;
layout (location=2) out vec3 oWorldPos;
layout (location=3) out vec4 oColor;
layout (location=4) out flat int oBaseInstance;
#include <data/shaders/gltf/inputs.vert>
void main() {
mat4 model = getModel();
mat4 MVP = getViewProjection() * model;
vec3 pos = getPosition();
gl_Position = MVP * vec4(pos, 1.0);
oUV0UV1 = vec4(getTexCoord(0), getTexCoord(1));
oColor = getColor();
mat3 normalMatrix = transpose( inverse(mat3(model)) );
oNormal = normalMatrix * getNormal();
vec4 posClip = model * vec4(pos, 1.0);
oWorldPos = posClip.xyz/posClip.w;
oBaseInstance = gl_BaseInstance;
}

BIN
data/wood.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

1
deps Submodule

@ -0,0 +1 @@
Subproject commit f395ada8a23a3d1adbc3cd2cba08e74bf6b34d9a

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1746576598,
"narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

77
flake.nix Normal file
View file

@ -0,0 +1,77 @@
{
description = "3D Graphics Rendering Cookbook project setup";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
...
} @ inputs:
inputs.utils.lib.eachSystem [
"x86_64-linux"
"i686-linux"
"aarch64-linux"
"x86_64-darwin"
] (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [];
};
# Project management scripts
projectScripts = pkgs.writeShellScriptBin "vk" (builtins.readFile ./vk.sh);
in {
devShells.default = pkgs.mkShell.override {stdenv = pkgs.clangStdenv;} rec {
name = "vulkan-graphics-cookbook";
LD_LIBRARY_PATH = with pkgs;
lib.makeLibraryPath [
wayland
libxkbcommon
libffi
xorg.libX11
xorg.libXrandr
xorg.libXinerama
xorg.libXcursor
xorg.libXi
];
packages = with pkgs; [
cmake
wayland-scanner
pkg-config
wayland
libxkbcommon
libffi
xorg.libX11
xorg.libXrandr
xorg.libXinerama
xorg.libXcursor
xorg.libXi
vulkan-tools
vulkan-headers
vulkan-loader
vulkan-validation-layers
libglvnd
tbb
python3
ninja
# Add our project management scripts
projectScripts
];
shellHook = ''
echo "Vulkan Graphics Cookbook Development Environment"
echo "Run 'vk help' for available commands"
'';
};
});
}

121
shared/Bitmap.h Normal file
View file

@ -0,0 +1,121 @@
#pragma once
#include <string.h>
#include <vector>
#include <glm/glm.hpp>
enum eBitmapType
{
eBitmapType_2D,
eBitmapType_Cube
};
enum eBitmapFormat
{
eBitmapFormat_UnsignedByte,
eBitmapFormat_Float,
};
/// R/RG/RGB/RGBA bitmaps
struct Bitmap
{
Bitmap() = default;
Bitmap(int w, int h, int comp, eBitmapFormat fmt)
:w_(w), h_(h), comp_(comp), fmt_(fmt), data_(w * h * comp * getBytesPerComponent(fmt))
{
initGetSetFuncs();
}
Bitmap(int w, int h, int d, int comp, eBitmapFormat fmt)
:w_(w), h_(h), d_(d), comp_(comp), fmt_(fmt), data_(w * h * d * comp * getBytesPerComponent(fmt))
{
initGetSetFuncs();
}
Bitmap(int w, int h, int comp, eBitmapFormat fmt, const void* ptr)
:w_(w), h_(h), comp_(comp), fmt_(fmt), data_(w * h * comp * getBytesPerComponent(fmt))
{
initGetSetFuncs();
memcpy(data_.data(), ptr, data_.size());
}
int w_ = 0;
int h_ = 0;
int d_ = 1;
int comp_ = 3;
eBitmapFormat fmt_ = eBitmapFormat_UnsignedByte;
eBitmapType type_ = eBitmapType_2D;
std::vector<uint8_t> data_;
static int getBytesPerComponent(eBitmapFormat fmt)
{
if (fmt == eBitmapFormat_UnsignedByte) return 1;
if (fmt == eBitmapFormat_Float) return 4;
return 0;
}
void setPixel(int x, int y, const glm::vec4& c)
{
(*this.*setPixelFunc)(x, y, c);
}
glm::vec4 getPixel(int x, int y) const
{
return ((*this.*getPixelFunc)(x, y));
}
private:
using setPixel_t = void(Bitmap::*)(int, int, const glm::vec4&);
using getPixel_t = glm::vec4(Bitmap::*)(int, int) const;
setPixel_t setPixelFunc = &Bitmap::setPixelUnsignedByte;
getPixel_t getPixelFunc = &Bitmap::getPixelUnsignedByte;
void initGetSetFuncs()
{
switch (fmt_)
{
case eBitmapFormat_UnsignedByte:
setPixelFunc = &Bitmap::setPixelUnsignedByte;
getPixelFunc = &Bitmap::getPixelUnsignedByte;
break;
case eBitmapFormat_Float:
setPixelFunc = &Bitmap::setPixelFloat;
getPixelFunc = &Bitmap::getPixelFloat;
break;
}
}
void setPixelFloat(int x, int y, const glm::vec4& c)
{
const int ofs = comp_ * (y * w_ + x);
float* data = reinterpret_cast<float*>(data_.data());
if (comp_ > 0) data[ofs + 0] = c.x;
if (comp_ > 1) data[ofs + 1] = c.y;
if (comp_ > 2) data[ofs + 2] = c.z;
if (comp_ > 3) data[ofs + 3] = c.w;
}
glm::vec4 getPixelFloat(int x, int y) const
{
const int ofs = comp_ * (y * w_ + x);
const float* data = reinterpret_cast<const float*>(data_.data());
return glm::vec4(
comp_ > 0 ? data[ofs + 0] : 0.0f,
comp_ > 1 ? data[ofs + 1] : 0.0f,
comp_ > 2 ? data[ofs + 2] : 0.0f,
comp_ > 3 ? data[ofs + 3] : 0.0f);
}
void setPixelUnsignedByte(int x, int y, const glm::vec4& c)
{
const int ofs = comp_ * (y * w_ + x);
if (comp_ > 0) data_[ofs + 0] = uint8_t(c.x * 255.0f);
if (comp_ > 1) data_[ofs + 1] = uint8_t(c.y * 255.0f);
if (comp_ > 2) data_[ofs + 2] = uint8_t(c.z * 255.0f);
if (comp_ > 3) data_[ofs + 3] = uint8_t(c.w * 255.0f);
}
glm::vec4 getPixelUnsignedByte(int x, int y) const
{
const int ofs = comp_ * (y * w_ + x);
return glm::vec4(
comp_ > 0 ? float(data_[ofs + 0]) / 255.0f : 0.0f,
comp_ > 1 ? float(data_[ofs + 1]) / 255.0f : 0.0f,
comp_ > 2 ? float(data_[ofs + 2]) / 255.0f : 0.0f,
comp_ > 3 ? float(data_[ofs + 3]) / 255.0f : 0.0f);
}
};

33
shared/CMakeLists.txt Normal file
View file

@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.19)
project(SharedUtils CXX C)
include(../CMake/CommonMacros.txt)
file(GLOB_RECURSE SRC_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c??)
file(GLOB_RECURSE HEADER_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h)
add_library(SharedUtils ${SRC_FILES} ${HEADER_FILES})
set_property(TARGET SharedUtils PROPERTY CXX_STANDARD 20)
set_property(TARGET SharedUtils PROPERTY CXX_STANDARD_REQUIRED ON)
# Add include directory for the root of the project to fix include paths
target_include_directories(SharedUtils PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(SharedUtils PUBLIC assimp)
target_link_libraries(SharedUtils PUBLIC glslang)
target_link_libraries(SharedUtils PUBLIC glfw)
target_link_libraries(SharedUtils PUBLIC LUtils)
target_link_libraries(SharedUtils PUBLIC LVKLibrary)
target_link_libraries(SharedUtils PUBLIC LVKstb)
target_link_libraries(SharedUtils PUBLIC ktx)
if(WIN32)
target_compile_definitions(SharedUtils PUBLIC "NOMINMAX")
endif()
if(UNIX AND NOT APPLE)
target_link_libraries(SharedUtils PUBLIC tbb)
endif()

232
shared/Camera.h Normal file
View file

@ -0,0 +1,232 @@
#pragma once
#include <assert.h>
#include <algorithm>
#include "shared/UtilsMath.h"
#include "shared/Trackball.h"
#include "glm/gtx/euler_angles.hpp"
class CameraPositionerInterface
{
public:
virtual ~CameraPositionerInterface() = default;
virtual glm::mat4 getViewMatrix() const = 0;
virtual glm::vec3 getPosition() const = 0;
};
class Camera final
{
public:
explicit Camera(CameraPositionerInterface& positioner)
: positioner_(&positioner)
{}
Camera(const Camera&) = default;
Camera& operator = (const Camera&) = default;
glm::mat4 getViewMatrix() const { return positioner_->getViewMatrix(); }
glm::vec3 getPosition() const { return positioner_->getPosition(); }
glm::mat4 getProjMatrix() const { return proj_; }
private:
const CameraPositionerInterface* positioner_;
glm::mat4 proj_;
};
class CameraPositioner_FirstPerson final: public CameraPositionerInterface
{
public:
CameraPositioner_FirstPerson() = default;
CameraPositioner_FirstPerson(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up)
: cameraPosition_(pos)
, cameraOrientation_(glm::lookAt(pos, target, up))
, up_(up)
{}
void update(double deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
{
if (mousePressed)
{
const glm::vec2 delta = mousePos - mousePos_;
const glm::quat deltaQuat = glm::quat(glm::vec3(-mouseSpeed_ * delta.y, mouseSpeed_ * delta.x, 0.0f));
cameraOrientation_ = deltaQuat * cameraOrientation_;
cameraOrientation_ = glm::normalize(cameraOrientation_);
setUpVector(up_);
}
mousePos_ = mousePos;
const glm::mat4 v = glm::mat4_cast(cameraOrientation_);
const glm::vec3 forward = -glm::vec3(v[0][2], v[1][2], v[2][2]);
const glm::vec3 right = glm::vec3(v[0][0], v[1][0], v[2][0]);
const glm::vec3 up = glm::cross(right, forward);
glm::vec3 accel(0.0f);
if (movement_.forward_) accel += forward;
if (movement_.backward_) accel -= forward;
if (movement_.left_) accel -= right;
if (movement_.right_) accel += right;
if (movement_.up_) accel += up;
if (movement_.down_) accel -= up;
if (movement_.fastSpeed_) accel *= fastCoef_;
if (accel == glm::vec3(0))
{
// decelerate naturally according to the damping value
moveSpeed_ -= moveSpeed_ * std::min((1.0f / damping_) * static_cast<float>(deltaSeconds), 1.0f);
}
else
{
// acceleration
moveSpeed_ += accel * acceleration_ * static_cast<float>(deltaSeconds);
const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_;
if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed;
}
cameraPosition_ += moveSpeed_ * static_cast<float>(deltaSeconds);
}
virtual glm::mat4 getViewMatrix() const override
{
const glm::mat4 t = glm::translate(glm::mat4(1.0f), -cameraPosition_);
const glm::mat4 r = glm::mat4_cast(cameraOrientation_);
return r * t;
}
virtual glm::vec3 getPosition() const override
{
return cameraPosition_;
}
void setPosition(const glm::vec3& pos)
{
cameraPosition_ = pos;
}
void setSpeed(const glm::vec3& speed) {
moveSpeed_ = speed;
}
void resetMousePosition(const glm::vec2& p) { mousePos_ = p; };
void setUpVector(const glm::vec3& up)
{
const glm::mat4 view = getViewMatrix();
const glm::vec3 dir = -glm::vec3(view[0][2], view[1][2], view[2][2]);
cameraOrientation_ = glm::lookAt(cameraPosition_, cameraPosition_ + dir, up);
}
inline void lookAt(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up) {
cameraPosition_ = pos;
cameraOrientation_ = glm::lookAt(pos, target, up);
}
public:
struct Movement
{
bool forward_ = false;
bool backward_ = false;
bool left_ = false;
bool right_ = false;
bool up_ = false;
bool down_ = false;
//
bool fastSpeed_ = false;
} movement_;
public:
float mouseSpeed_ = 4.0f;
float acceleration_ = 150.0f;
float damping_ = 0.2f;
float maxSpeed_ = 10.0f;
float fastCoef_ = 10.0f;
private:
glm::vec2 mousePos_ = glm::vec2(0);
glm::vec3 cameraPosition_ = glm::vec3(0.0f, 10.0f, 10.0f);
glm::quat cameraOrientation_ = glm::quat(glm::vec3(0));
glm::vec3 moveSpeed_ = glm::vec3(0.0f);
glm::vec3 up_ = glm::vec3(0.0f, 0.0f, 1.0f);
};
class CameraPositioner_MoveTo final : public CameraPositionerInterface
{
public:
CameraPositioner_MoveTo(const glm::vec3& pos, const glm::vec3& angles)
: positionCurrent_(pos)
, positionDesired_(pos)
, anglesCurrent_(angles)
, anglesDesired_(angles)
{}
void update(float deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
{
positionCurrent_ += dampingLinear_ * deltaSeconds * (positionDesired_ - positionCurrent_);
// normalization is required to avoid "spinning" around the object 2pi times
anglesCurrent_ = clipAngles(anglesCurrent_);
anglesDesired_ = clipAngles(anglesDesired_);
// update angles
anglesCurrent_ -= angleDelta(anglesCurrent_, anglesDesired_) * dampingEulerAngles_ * deltaSeconds;
// normalize new angles
anglesCurrent_ = clipAngles(anglesCurrent_);
const glm::vec3 a = glm::radians(anglesCurrent_);
currentTransform_ = glm::translate(glm::yawPitchRoll(a.y, a.x, a.z), -positionCurrent_);
}
void setPosition(const glm::vec3& p) { positionCurrent_ = p; }
void setAngles(float pitch, float pan, float roll) { anglesCurrent_ = glm::vec3(pitch, pan, roll); }
void setAngles(const glm::vec3& angles) { anglesCurrent_ = angles; }
void setDesiredPosition(const glm::vec3& p) { positionDesired_ = p; }
void setDesiredAngles(float pitch, float pan, float roll) { anglesDesired_ = glm::vec3(pitch, pan, roll); }
void setDesiredAngles(const glm::vec3& angles) { anglesDesired_ = angles; }
virtual glm::vec3 getPosition() const override { return positionCurrent_; }
virtual glm::mat4 getViewMatrix() const override { return currentTransform_; }
public:
float dampingLinear_ = 10.0f;
glm::vec3 dampingEulerAngles_ = glm::vec3(5.0f, 5.0f, 5.0f);
private:
glm::vec3 positionCurrent_ = glm::vec3(0.0f);
glm::vec3 positionDesired_ = glm::vec3(0.0f);
/// pitch, pan, roll
glm::vec3 anglesCurrent_ = glm::vec3(0.0f);
glm::vec3 anglesDesired_ = glm::vec3(0.0f);
glm::mat4 currentTransform_ = glm::mat4(1.0f);
static inline float clipAngle(float d)
{
if (d < -180.0f) return d + 360.0f;
if (d > +180.0f) return d - 360.f;
return d;
}
static inline glm::vec3 clipAngles(const glm::vec3& angles)
{
return glm::vec3(
std::fmod(angles.x, 360.0f),
std::fmod(angles.y, 360.0f),
std::fmod(angles.z, 360.0f)
);
}
static inline glm::vec3 angleDelta(const glm::vec3& anglesCurrent, const glm::vec3& anglesDesired)
{
const glm::vec3 d = clipAngles(anglesCurrent) - clipAngles(anglesDesired);
return glm::vec3(clipAngle(d.x), clipAngle(d.y), clipAngle(d.z));
}
};

77
shared/Graph.h Normal file
View file

@ -0,0 +1,77 @@
#pragma once
#include <lvk/LVK.h>
#include <deque>
#include <vector>
class LinearGraph
{
public:
explicit LinearGraph(const char* name, size_t maxGraphPoints = 256)
: name_(name)
, maxPoints_(maxGraphPoints)
{
}
void addPoint(float value)
{
graph_.push_back(value);
if (graph_.size() > maxPoints_)
graph_.erase(graph_.begin());
}
void renderGraph(uint32_t x, uint32_t y, uint32_t width, uint32_t height, const glm::vec4& color = vec4(1.0)) const
{
LVK_PROFILER_FUNCTION();
float minVal = std::numeric_limits<float>::max();
float maxVal = std::numeric_limits<float>::min();
for (float f : graph_) {
if (f < minVal)
minVal = f;
if (f > maxVal)
maxVal = f;
}
const float range = maxVal - minVal;
float valX = 0.0;
std::vector<float> dataX_;
std::vector<float> dataY_;
dataX_.reserve(graph_.size());
dataY_.reserve(graph_.size());
for (float f : graph_) {
const float valY = (f - minVal) / range;
valX += 1.0f / maxPoints_;
dataX_.push_back(valX);
dataY_.push_back(valY);
}
ImGui::SetNextWindowPos(ImVec2(x, y));
ImGui::SetNextWindowSize(ImVec2(width, height));
ImGui::Begin(
name_, nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
if (ImPlot::BeginPlot(name_, ImVec2(width, height), ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations);
ImPlot::PushStyleColor(ImPlotCol_Line, ImVec4(color.r, color.g, color.b, color.a));
ImPlot::PushStyleColor(ImPlotCol_PlotBg, ImVec4(0, 0, 0, 0));
ImPlot::PlotLine("#line", dataX_.data(), dataY_.data(), (int)graph_.size(), ImPlotLineFlags_None);
ImPlot::PopStyleColor(2);
ImPlot::EndPlot();
}
ImGui::End();
}
private:
const char* name_ = nullptr;
const size_t maxPoints_;
std::deque<float> graph_;
};

55
shared/HelpersGLFW.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "HelpersGLFW.h"
GLFWwindow* initWindow(const char* windowTitle, uint32_t& outWidth, uint32_t& outHeight)
{
glfwSetErrorCallback([](int error, const char* description) { printf("GLFW Error (%i): %s\n", error, description); });
if (!glfwInit()) {
return nullptr;
}
const bool wantsWholeArea = !outWidth || !outHeight;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, wantsWholeArea ? GLFW_FALSE : GLFW_TRUE);
// render full screen without overlapping taskbar
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
int x = 0;
int y = 0;
int w = mode->width;
int h = mode->height;
if (wantsWholeArea) {
glfwGetMonitorWorkarea(monitor, &x, &y, &w, &h);
} else {
w = outWidth;
h = outHeight;
}
GLFWwindow* window = glfwCreateWindow(w, h, windowTitle, nullptr, nullptr);
if (!window) {
glfwTerminate();
return nullptr;
}
if (wantsWholeArea) {
glfwSetWindowPos(window, x, y);
}
glfwGetWindowSize(window, &w, &h);
glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int, int action, int) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
});
outWidth = (uint32_t)w;
outHeight = (uint32_t)h;
return window;
}

24
shared/HelpersGLFW.h Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <stdio.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
// clang-format off
#ifdef _WIN32
# define GLFW_EXPOSE_NATIVE_WIN32
# define GLFW_EXPOSE_NATIVE_WGL
#elif __APPLE__
# define GLFW_EXPOSE_NATIVE_COCOA
#elif defined(__linux__)
# define GLFW_EXPOSE_NATIVE_X11
#else
# error Unsupported OS
#endif
// clang-format on
#include <GLFW/glfw3native.h>
GLFWwindow *initWindow(const char *windowTitle, uint32_t &outWidth,
uint32_t &outHeight);

239
shared/LineCanvas.cpp Normal file
View file

@ -0,0 +1,239 @@
#include "LineCanvas.h"
static const char* codeVS = R"(
layout (location = 0) out vec4 out_color;
struct Vertex {
vec4 pos;
vec4 rgba;
};
layout(std430, buffer_reference) readonly buffer VertexBuffer {
Vertex vertices[];
};
layout(push_constant) uniform PushConstants {
mat4 mvp;
VertexBuffer vb;
};
void main() {
// Vertex v = vb.vertices[gl_VertexIndex]; <--- does not work on Snapdragon Adreno
out_color = vb.vertices[gl_VertexIndex].rgba;
gl_Position = mvp * vb.vertices[gl_VertexIndex].pos;
})";
static const char* codeFS = R"(
layout (location = 0) in vec4 in_color;
layout (location = 0) out vec4 out_color;
void main() {
out_color = in_color;
})";
void LineCanvas2D::render(const char* nameImGuiWindow)
{
LVK_PROFILER_FUNCTION();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
ImGui::Begin(
nameImGuiWindow, nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
ImDrawList* drawList = ImGui::GetBackgroundDrawList();
for (const LineData& l : lines_) {
drawList->AddLine(ImVec2(l.p1.x, l.p1.y), ImVec2(l.p2.x, l.p2.y), ImColor(l.color.r, l.color.g, l.color.b, l.color.a));
}
ImGui::End();
}
void LineCanvas3D::line(const vec3& p1, const vec3& p2, const vec4& c)
{
lines_.push_back({ .pos = vec4(p1, 1.0f), .color = c });
lines_.push_back({ .pos = vec4(p2, 1.0f), .color = c });
}
void LineCanvas3D::plane(
const vec3& o, const vec3& v1, const vec3& v2, int n1, int n2, float s1, float s2, const vec4& color, const vec4& outlineColor)
{
line(o - s1 / 2.0f * v1 - s2 / 2.0f * v2, o - s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
line(o + s1 / 2.0f * v1 - s2 / 2.0f * v2, o + s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
line(o - s1 / 2.0f * v1 + s2 / 2.0f * v2, o + s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
line(o - s1 / 2.0f * v1 - s2 / 2.0f * v2, o + s1 / 2.0f * v1 - s2 / 2.0f * v2, outlineColor);
for (int i = 1; i < n1; i++) {
float t = ((float)i - (float)n1 / 2.0f) * s1 / (float)n1;
const vec3 o1 = o + t * v1;
line(o1 - s2 / 2.0f * v2, o1 + s2 / 2.0f * v2, color);
}
for (int i = 1; i < n2; i++) {
const float t = ((float)i - (float)n2 / 2.0f) * s2 / (float)n2;
const vec3 o2 = o + t * v2;
line(o2 - s1 / 2.0f * v1, o2 + s1 / 2.0f * v1, color);
}
}
void LineCanvas3D::box(const mat4& m, const vec3& size, const vec4& c)
{
vec3 pts[8] = {
vec3(+size.x, +size.y, +size.z), vec3(+size.x, +size.y, -size.z), vec3(+size.x, -size.y, +size.z), vec3(+size.x, -size.y, -size.z),
vec3(-size.x, +size.y, +size.z), vec3(-size.x, +size.y, -size.z), vec3(-size.x, -size.y, +size.z), vec3(-size.x, -size.y, -size.z),
};
for (auto& p : pts)
p = vec3(m * vec4(p, 1.f));
line(pts[0], pts[1], c);
line(pts[2], pts[3], c);
line(pts[4], pts[5], c);
line(pts[6], pts[7], c);
line(pts[0], pts[2], c);
line(pts[1], pts[3], c);
line(pts[4], pts[6], c);
line(pts[5], pts[7], c);
line(pts[0], pts[4], c);
line(pts[1], pts[5], c);
line(pts[2], pts[6], c);
line(pts[3], pts[7], c);
}
void LineCanvas3D::box(const mat4& m, const BoundingBox& box, const glm::vec4& color)
{
this->box(m * glm::translate(mat4(1.f), .5f * (box.min_ + box.max_)), 0.5f * vec3(box.max_ - box.min_), color);
}
void LineCanvas3D::frustum(const mat4& camView, const mat4& camProj, const vec4& color)
{
const vec3 corners[] = { vec3(-1, -1, -1), vec3(+1, -1, -1), vec3(+1, +1, -1), vec3(-1, +1, -1),
vec3(-1, -1, +1), vec3(+1, -1, +1), vec3(+1, +1, +1), vec3(-1, +1, +1) };
vec3 pp[8];
for (int i = 0; i < 8; i++) {
vec4 q = glm::inverse(camView) * glm::inverse(camProj) * vec4(corners[i], 1.0f);
pp[i] = vec3(q.x / q.w, q.y / q.w, q.z / q.w);
}
line(pp[0], pp[4], color);
line(pp[1], pp[5], color);
line(pp[2], pp[6], color);
line(pp[3], pp[7], color);
// near
line(pp[0], pp[1], color);
line(pp[1], pp[2], color);
line(pp[2], pp[3], color);
line(pp[3], pp[0], color);
// x
line(pp[0], pp[2], color);
line(pp[1], pp[3], color);
// far
line(pp[4], pp[5], color);
line(pp[5], pp[6], color);
line(pp[6], pp[7], color);
line(pp[7], pp[4], color);
// x
line(pp[4], pp[6], color);
line(pp[5], pp[7], color);
const vec4 gridColor = color * 0.7f;
const int gridLines = 100;
// bottom
{
vec3 p1 = pp[0];
vec3 p2 = pp[1];
const vec3 s1 = (pp[4] - pp[0]) / float(gridLines);
const vec3 s2 = (pp[5] - pp[1]) / float(gridLines);
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
line(p1, p2, gridColor);
}
// top
{
vec3 p1 = pp[2];
vec3 p2 = pp[3];
const vec3 s1 = (pp[6] - pp[2]) / float(gridLines);
const vec3 s2 = (pp[7] - pp[3]) / float(gridLines);
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
line(p1, p2, gridColor);
}
// left
{
vec3 p1 = pp[0];
vec3 p2 = pp[3];
const vec3 s1 = (pp[4] - pp[0]) / float(gridLines);
const vec3 s2 = (pp[7] - pp[3]) / float(gridLines);
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
line(p1, p2, gridColor);
}
// right
{
vec3 p1 = pp[1];
vec3 p2 = pp[2];
const vec3 s1 = (pp[5] - pp[1]) / float(gridLines);
const vec3 s2 = (pp[6] - pp[2]) / float(gridLines);
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
line(p1, p2, gridColor);
}
}
void LineCanvas3D::render(lvk::IContext& ctx, const lvk::Framebuffer& desc, lvk::ICommandBuffer& buf, uint32_t numSamples)
{
LVK_PROFILER_FUNCTION();
if (lines_.empty()) {
return;
}
const uint32_t requiredSize = lines_.size() * sizeof(LineData);
if (currentBufferSize_[currentFrame_] < requiredSize) {
linesBuffer_[currentFrame_] = ctx.createBuffer(
{ .usage = lvk::BufferUsageBits_Storage, .storage = lvk::StorageType_HostVisible, .size = requiredSize, .data = lines_.data() });
currentBufferSize_[currentFrame_] = requiredSize;
} else {
ctx.upload(linesBuffer_[currentFrame_], lines_.data(), requiredSize);
}
if (pipeline_.empty() || pipelineSamples_ != numSamples) {
pipelineSamples_ = numSamples;
vert_ = ctx.createShaderModule({ codeVS, lvk::Stage_Vert, "Shader Module: imgui (vert)" });
frag_ = ctx.createShaderModule({ codeFS, lvk::Stage_Frag, "Shader Module: imgui (frag)" });
pipeline_ = ctx.createRenderPipeline(
{
.topology = lvk::Topology_Line,
.smVert = vert_,
.smFrag = frag_,
.color = { {
.format = ctx.getFormat(desc.color[0].texture),
.blendEnabled = true,
.srcRGBBlendFactor = lvk::BlendFactor_SrcAlpha,
.dstRGBBlendFactor = lvk::BlendFactor_OneMinusSrcAlpha,
} },
.depthFormat = desc.depthStencil.texture ? ctx.getFormat(desc.depthStencil.texture) : lvk::Format_Invalid,
.cullMode = lvk::CullMode_None,
.samplesCount = numSamples,
},
nullptr);
}
struct {
mat4 mvp;
uint64_t addr;
} pc{
.mvp = mvp_,
.addr = ctx.gpuAddress(linesBuffer_[currentFrame_]),
};
buf.cmdBindRenderPipeline(pipeline_);
buf.cmdPushConstants(pc);
buf.cmdDraw(lines_.size());
currentFrame_ = (currentFrame_ + 1) % LVK_ARRAY_NUM_ELEMENTS(linesBuffer_);
}

Some files were not shown because too many files have changed in this diff Show more