Last active
March 24, 2021 06:25
-
-
Save seanbaxter/583d16b35dec99f4d6cb22e479a9b6c8 to your computer and use it in GitHub Desktop.
Circle glTF viewer. C++ shaders.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
// #include <imgui.h> | |
// #include <examples/imgui_impl_glfw.h> | |
// #include <examples/imgui_impl_opengl3.h> | |
#include <stb_image.h> | |
#include <gl3w/GL/gl3w.h> | |
#include <GLFW/glfw3.h> | |
#include <cmath> | |
#include <cstdio> | |
#include <cfloat> | |
#include <cstring> | |
template<typename type_t> | |
const char* enum_to_string(type_t e) { | |
static_assert(std::is_enum_v<type_t>); | |
switch(e) { | |
@meta for enum(type_t e2 : type_t) { | |
case e2: | |
return @enum_name(e2); | |
} | |
default: | |
return nullptr; | |
} | |
} | |
#define PRINT_ERROR() { \ | |
auto err = glGetError(); \ | |
if(err) {\ | |
printf("%s:%d: %d\n", __FILE__, __LINE__, err); \ | |
exit(1); \ | |
} \ | |
} | |
inline void print_matrix(const mat4& m) { | |
for(int row = 0; row < 4; ++row) | |
printf("%f %f %f %f\n", m[0][row], m[1][row], m[2][row], m[3][row]); | |
} | |
constexpr vec4 qmul(vec4 q, vec4 p) { | |
return vec4( | |
q.w * p.xyz + p.w * q.xyz + cross(q.xyz, p.xyz), | |
p.w * q.w - dot(p.xyz, q.xyz) | |
); | |
} | |
constexpr vec4 qinv(vec4 q) { | |
// Change the sign of the imaginary components. | |
q.xyz = -q.xyz; | |
return q; | |
} | |
// https://users.aalto.fi/~ssarkka/pub/quat.pdf | |
constexpr vec4 qlog(vec4 q) { | |
vec3 v = normalize(q.xzy); | |
return vec4(v * acosf(q.w), 0); | |
} | |
constexpr vec4 slerp(vec4 q0, vec4 q1, float t) { | |
float d = dot(q0, q1); | |
float theta_0 = acosf(d); | |
float theta = theta_0 * t; | |
vec4 q2 = normalize(q1 - q1 * d); | |
return q0 * cosf(theta) + q2 * sinf(theta); | |
} | |
inline mat4 make_perspective(float fov, float ar, float near, float far) { | |
float f = 1 / tan(fov / 2); | |
if(FLT_MAX == far) { | |
return mat4( | |
f / ar, 0, 0, 0, | |
0, f, 0, 0, | |
0, 0, -1, -1, | |
0, 0, -2 * near, 0 | |
); | |
} else { | |
float range = near - far; | |
return mat4( | |
f / ar, 0, 0, 0, | |
0, f, 0, 0, | |
0, 0, (far + near) / range, -1, | |
0, 0, 2 * far * near / range, 0 | |
); | |
} | |
} | |
inline mat4 make_lookat(vec3 eye, vec3 at, vec3 up) { | |
vec3 zaxis = normalize(eye - at); | |
vec3 xaxis = normalize(cross(up, zaxis)); | |
vec3 yaxis = cross(zaxis, xaxis); | |
return mat4( | |
xaxis.x, yaxis.x, zaxis.x, 0, | |
xaxis.y, yaxis.y, zaxis.y, 0, | |
xaxis.z, yaxis.z, zaxis.z, 0, | |
-dot(xaxis, eye), -dot(yaxis, eye), -dot(zaxis, eye), 1 | |
); | |
} | |
inline mat4 make_scale(vec3 scale) { | |
return mat4( | |
scale.x, 0, 0, 0, | |
0, scale.y, 0, 0, | |
0, 0, scale.z, 0, | |
0, 0, 0, 1 | |
); | |
} | |
inline mat4 make_translate(vec3 translate) { | |
return mat4( | |
1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
translate, 1 | |
); | |
} | |
inline mat4 make_rotateX(float angle) { | |
float s = sin(angle); | |
float c = cos(angle); | |
return mat4( | |
1, 0, 0, 0, | |
0, c, -s, 0, | |
0, s, c, 0, | |
0, 0, 0, 1 | |
); | |
} | |
inline mat4 make_rotateY(float angle) { | |
float s = sin(angle); | |
float c = cos(angle); | |
return mat4( | |
c, 0, -s, 0, | |
0, 1, 0, 0, | |
s, 0, c, 0, | |
0, 0, 0, 1 | |
); | |
} | |
inline mat4 make_rotateZ(float angle) { | |
float s = sin(angle); | |
float c = cos(angle); | |
return mat4( | |
c, -s, 0, 0, | |
s, c, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1 | |
); | |
} | |
inline mat4 make_rotate(vec3 angles) { | |
mat4 x = make_rotateX(angles.x); | |
mat4 y = make_rotateY(angles.y); | |
mat4 z = make_rotateZ(angles.z); | |
return z * y * x; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
GLuint load_texture(const char* path, int& width, int& height) { | |
int comp; | |
stbi_uc* data = stbi_load(path, &width, &height, &comp, STBI_rgb_alpha ); | |
printf("Loaded image %s %d %d %d\n", path, width, height, comp); | |
GLuint texture; | |
glCreateTextures(GL_TEXTURE_2D, 1, &texture); | |
glTextureStorage2D(texture, 1, GL_RGBA8, width, height); | |
glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, | |
GL_UNSIGNED_BYTE, data); | |
glGenerateTextureMipmap(texture); | |
// glTextureParameteri(image, GL_TEXTURE_WRAP_S, sampler.wrap_s); | |
// glTextureParameteri(image, GL_TEXTURE_WRAP_T, sampler.wrap_t); | |
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); | |
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
return texture; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
template<typename type_t> | |
void specialize_shader(GLuint shader, const char* name, const type_t& obj) { | |
const int count = @member_count(type_t); | |
GLuint indices[count]; | |
GLuint values[count] { }; | |
@meta for(int i = 0; i < count; ++i) { | |
indices[i] = i; | |
memcpy(values + i, &@member_value(obj, i), sizeof(@member_type(type_t, i))); | |
} | |
glSpecializeShader(shader, name, count, indices, values); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
struct camera_t { | |
vec3 origin = vec3(); | |
float pitch = 0; | |
float yaw = 0; | |
float log_distance = log(20.f); | |
// Perspective terms. | |
float fov = radians(20.f); | |
float near = .5; | |
float far = FLT_MAX; | |
void adjust(float pitch2, float yaw2, float d2); | |
vec3 get_eye() const noexcept; | |
mat4 get_view() const noexcept; | |
mat4 get_perspective(int width, int height) const noexcept; | |
mat4 get_xform(int width, int height) const noexcept; | |
}; | |
inline void camera_t::adjust(float pitch2, float yaw2, float d2) { | |
pitch = clamp(pitch + pitch2, -radians(80.f), radians(80.f)); | |
yaw = fmod(yaw + yaw2, 2 * M_PI); | |
log_distance += d2; | |
} | |
inline vec3 camera_t::get_eye() const noexcept { | |
float d = exp(log_distance); | |
return vec3( | |
sin(yaw) * cos(pitch) * d, | |
sin(pitch) * d, | |
cos(yaw) * cos(pitch) * d | |
); | |
} | |
inline mat4 camera_t::get_view() const noexcept { | |
return make_lookat(get_eye(), origin, vec3(0, 1, 0)); | |
} | |
inline mat4 camera_t::get_perspective(int width, int height) const noexcept { | |
float ar = (float)width / height; | |
return make_perspective(fov, ar, near, far); | |
} | |
inline mat4 camera_t::get_xform(int width, int height) const noexcept { | |
return get_perspective(width, height) * get_view(); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
class app_t { | |
public: | |
app_t(const char* name, int width = 800, int height = 600); | |
void loop(); | |
virtual void display() { } | |
virtual void pos_callback(int xpos, int ypos) { } | |
virtual void size_callback(int width, int height) { } | |
virtual void close_callback() { } | |
virtual void refresh_callback() { } | |
virtual void focus_callback(int focused) { } | |
virtual void framebuffer_callback(int width, int height); | |
virtual void scale_callback(float xscale, float yscale) { } | |
virtual void cursor_callback(double xpos, double ypos); | |
virtual void button_callback(int button, int action, int mods); | |
virtual void debug_callback(GLenum source, GLenum type, GLuint id, | |
GLenum severity, GLsizei length, const GLchar* message); | |
protected: | |
GLFWwindow* window = nullptr; | |
camera_t camera { }; | |
int captured = false; | |
double last_x, last_y; | |
private: | |
static void _pos_callback(GLFWwindow* window, int xpos, int ypos); | |
static void _size_callback(GLFWwindow* window, int width, int height); | |
static void _close_callback(GLFWwindow* window); | |
static void _refresh_callback(GLFWwindow* window); | |
static void _focus_callback(GLFWwindow* window, int focused); | |
static void _framebuffer_callback(GLFWwindow* window, int width, int height); | |
static void _scale_callback(GLFWwindow* window, float xscale, float yscale); | |
static void _cursor_callback(GLFWwindow* window, double xpos, double ypos); | |
static void _button_callback(GLFWwindow* window, int button, int action, | |
int mods); | |
static void _debug_callback(GLenum source, GLenum type, GLuint id, | |
GLenum severity, GLsizei length, const GLchar* message, | |
const void* user_param); | |
void register_callbacks(); | |
}; | |
app_t::app_t(const char* name, int width, int height) { | |
glfwWindowHint(GLFW_DOUBLEBUFFER, 1); | |
glfwWindowHint(GLFW_DEPTH_BITS, 24); | |
glfwWindowHint(GLFW_STENCIL_BITS, 8); | |
glfwWindowHint(GLFW_SAMPLES, 8); // HQ 4x multisample. | |
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); | |
window = glfwCreateWindow(width, height, name, nullptr, nullptr); | |
glfwMakeContextCurrent(window); | |
glfwSwapInterval(1); | |
register_callbacks(); | |
gl3wInit(); | |
glEnable(GL_DEBUG_OUTPUT); | |
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); | |
glDebugMessageCallback(_debug_callback, this); | |
glfwGetWindowSize(window, &width, &height); | |
glViewport(0, 0, width, height); | |
// IMGUI_CHECKVERSION(); | |
// ImGui::CreateContext(); | |
// // ImGuiIO& io = ImGui::GetIO(); (void)io; | |
// | |
// ImGui_ImplGlfw_InitForOpenGL(window, true); | |
// ImGui_ImplOpenGL3_Init("#version 460"); | |
} | |
void app_t::register_callbacks() { | |
glfwSetWindowUserPointer(window, this); | |
glfwSetWindowPosCallback(window, _pos_callback); | |
glfwSetWindowSizeCallback(window, _size_callback); | |
glfwSetWindowCloseCallback(window, _close_callback); | |
glfwSetWindowRefreshCallback(window, _refresh_callback); | |
glfwSetWindowFocusCallback(window, _focus_callback); | |
glfwSetFramebufferSizeCallback(window, _framebuffer_callback); | |
glfwSetCursorPosCallback(window, _cursor_callback); | |
glfwSetMouseButtonCallback(window, _button_callback); | |
// glfwSetWindowContentScaleCallback(window, _scale_callback); | |
} | |
void app_t::loop() { | |
while(!glfwWindowShouldClose(window)) { | |
display(); | |
glfwSwapBuffers(window); | |
glfwPollEvents(); | |
} | |
} | |
void app_t::framebuffer_callback(int width, int height) { | |
glViewport(0, 0, width, height); | |
} | |
void app_t::cursor_callback(double xpos, double ypos) { | |
if(captured) { | |
double dx = xpos - last_x; | |
double dy = ypos - last_y; | |
if(GLFW_PRESS == glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT)) { | |
camera.adjust(0, 0, dy / 100.f); | |
} else { | |
camera.adjust(-dy / 100.f, dx / 100.f, 0); | |
} | |
last_x = xpos; | |
last_y = ypos; | |
} | |
} | |
void app_t::button_callback(int button, int action, int mods) { | |
bool is_release = | |
GLFW_RELEASE == glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) && | |
GLFW_RELEASE == glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT); | |
if(!is_release && !captured) { | |
glfwGetCursorPos(window, &last_x, &last_y); | |
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); | |
captured = true; | |
} else if(is_release && captured) { | |
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); | |
captured = false; | |
} | |
} | |
void app_t::debug_callback(GLenum source, GLenum type, GLuint id, | |
GLenum severity, GLsizei length, const GLchar* message) { | |
printf("OpenGL: %s\n", message); | |
if(GL_DEBUG_SEVERITY_HIGH == severity || | |
GL_DEBUG_SEVERITY_MEDIUM == severity) | |
exit(1); | |
} | |
void app_t::_pos_callback(GLFWwindow* window, int xpos, int ypos) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->pos_callback(xpos, ypos); | |
} | |
void app_t::_size_callback(GLFWwindow* window, int width, int height) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->size_callback(width, height); | |
} | |
void app_t::_close_callback(GLFWwindow* window) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->close_callback(); | |
} | |
void app_t::_refresh_callback(GLFWwindow* window) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->refresh_callback(); | |
} | |
void app_t::_focus_callback(GLFWwindow* window, int focused) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->focus_callback(focused); | |
} | |
void app_t::_framebuffer_callback(GLFWwindow* window, int width, int height) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->framebuffer_callback(width, height); | |
} | |
void app_t::_scale_callback(GLFWwindow* window, float xscale, float yscale) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->scale_callback(xscale, yscale); | |
} | |
void app_t::_cursor_callback(GLFWwindow* window, double xpos, double ypos) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->cursor_callback(xpos, ypos); | |
} | |
void app_t::_button_callback(GLFWwindow* window, int button, int action, | |
int mods) { | |
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window)); | |
app->button_callback(button, action, mods); | |
} | |
void app_t::_debug_callback(GLenum source, GLenum type, GLuint id, | |
GLenum severity, GLsizei length, const GLchar* message, | |
const void* user_param) { | |
app_t* app = (app_t*)user_param; | |
app->debug_callback(source, type, id, severity, length, message); | |
} | |
//////////////////////////////////////////////////////////////////////////////// |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adapted from https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/master/src/shaders/brdf.glsl | |
#pragma once | |
#include <cmath> | |
// | |
// 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 | |
inline vec3 F_None(vec3 f0, vec3 f90, float VdotH) { | |
return f0; | |
} | |
// The following equation models the Fresnel reflectance term of the spec equation (aka F()) | |
// Implementation of fresnel from [4], Equation 15 | |
inline vec3 F_Schlick(vec3 f0, vec3 f90, float VdotH) { | |
return f0 + (f90 - f0) * pow(clamp(1 - VdotH, 0.0f, 1.0f), 5); | |
} | |
inline vec3 F_CookTorrance(vec3 f0, vec3 f90, float VdotH) { | |
vec3 f0_sqrt = sqrt(f0); | |
vec3 ior = (1 + f0_sqrt) / (1 - f0_sqrt); | |
vec3 c = vec3(VdotH); | |
vec3 g = sqrt(sq(ior) + c*c - 1); | |
return 0.5f * pow(g-c, vec3(2)) / | |
pow(g+c, vec3(2)) * (1 + pow(c*(g+c) - 1, 2) / pow(c*(g-c) + 1, 2)); | |
} | |
// 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) | |
inline float V_GGX(float NdotL, float NdotV, float alphaRoughness) { | |
float r2 = sq(alphaRoughness); | |
float GGXV = NdotL * sqrt(NdotV * NdotV * (1 - r2) + r2); | |
float GGXL = NdotV * sqrt(NdotL * NdotL * (1 - r2) + r2); | |
float GGX = GGXV + GGXL; | |
return (GGX > 0) ? 0.5f / GGX : 0; | |
} | |
// Anisotropic GGX visibility function, with height correlation. | |
// T: Tanget, B: Bi-tanget | |
inline float V_GGX_anisotropic(float NdotL, float NdotV, float BdotV, | |
float TdotV, float TdotL, float BdotL, float anisotropy, float at, float ab) { | |
float GGXV = NdotL * length(vec3(at * TdotV, ab * BdotV, NdotV)); | |
float GGXL = NdotV * length(vec3(at * TdotL, ab * BdotL, NdotL)); | |
float v = 0.5f / (GGXV + GGXL); | |
return clamp(v, 0.f, 1.f); | |
} | |
// https://github.com/google/filament/blob/master/shaders/src/brdf.fs#L136 | |
// https://github.com/google/filament/blob/master/libs/ibl/src/CubemapIBL.cpp#L179 | |
// Note: Google call it V_Ashikhmin and V_Neubelt | |
inline float V_Ashikhmin(float NdotL, float NdotV) { | |
return clamp(1 / (4 * (NdotL + NdotV - NdotL * NdotV)), 0.f, 1.f); | |
} | |
// https://github.com/google/filament/blob/master/shaders/src/brdf.fs#L131 | |
inline float V_Kelemen(float LdotH) { | |
// Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling" | |
return 0.25f / (LdotH * LdotH); | |
} | |
// 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. | |
inline float D_GGX(float NdotH, float alphaRoughness) { | |
float alphaRoughnessSq = alphaRoughness * alphaRoughness; | |
float f = (NdotH * NdotH) * (alphaRoughnessSq - 1) + 1; | |
return alphaRoughnessSq / (M_PIf32 * f * f); | |
} | |
// Anisotropic GGX NDF with a single anisotropy parameter controlling the normal orientation. | |
// See https://google.github.io/filament/Filament.html#materialsystem/anisotropicmodel | |
// T: Tanget, B: Bi-tanget | |
inline float D_GGX_anisotropic(float NdotH, float TdotH, float BdotH, | |
float anisotropy, float at, float ab) { | |
float a2 = at * ab; | |
vec3 f = vec3(ab * TdotH, at * BdotH, a2 * NdotH); | |
float w2 = a2 / dot(f, f); | |
return a2 * w2 * w2 / M_PIf32; | |
} | |
inline float D_Ashikhmin(float NdotH, float alphaRoughness) { | |
// Ashikhmin 2007, "Distribution-based BRDFs" | |
float a2 = alphaRoughness * alphaRoughness; | |
float cos2h = NdotH * NdotH; | |
float sin2h = 1.0 - cos2h; | |
float sin4h = sin2h * sin2h; | |
float cot2 = -cos2h / (a2 * sin2h); | |
return 1 / (M_PIf32 * (4 * a2 + 1) * sin4h) * (4 * exp(cot2) + sin4h); | |
} | |
//Sheen implementation------------------------------------------------------------------------------------- | |
// See https://github.com/sebavan/glTF/tree/KHR_materials_sheen/extensions/2.0/Khronos/KHR_materials_sheen | |
// Estevez and Kulla http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf | |
inline float D_Charlie(float sheenRoughness, float NdotH) { | |
sheenRoughness = max(sheenRoughness, 0.000001f); //clamp (0,1] | |
float alphaG = sheenRoughness * sheenRoughness; | |
float invR = 1 / alphaG; | |
float cos2h = NdotH * NdotH; | |
float sin2h = 1 - cos2h; | |
return (2 + invR) * pow(sin2h, invR * 0.5f) / (2 * M_PIf32); | |
} | |
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB | |
inline vec3 BRDF_lambertian(vec3 f0, vec3 f90, vec3 diffuseColor, float VdotH) { | |
// see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ | |
return (1 - F_Schlick(f0, f90, VdotH)) * (diffuseColor / M_PIf32); | |
} | |
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB | |
inline vec3 BRDF_specularGGX(vec3 f0, vec3 f90, float alphaRoughness, | |
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 F * Vis * D; | |
} | |
inline vec3 BRDF_specularAnisotropicGGX(vec3 f0, vec3 f90, | |
float alphaRoughness, float VdotH, float NdotL, float NdotV, float NdotH, | |
float BdotV, float TdotV, float TdotL, float BdotL, float TdotH, float BdotH, | |
float anisotropy) { | |
// Roughness along tangent and bitangent. | |
// Christopher Kulla and Alejandro Conty. 2017. Revisiting Physically Based Shading at Imageworks | |
float at = max(alphaRoughness * (1 + anisotropy), 0.00001f); | |
float ab = max(alphaRoughness * (1 - anisotropy), 0.00001f); | |
vec3 F = F_Schlick(f0, f90, VdotH); | |
float V = V_GGX_anisotropic(NdotL, NdotV, BdotV, TdotV, TdotL, BdotL, | |
anisotropy, at, ab); | |
float D = D_GGX_anisotropic(NdotH, TdotH, BdotH, anisotropy, at, ab); | |
return F * V * D; | |
} | |
// f_sheen | |
vec3 BRDF_specularSheen(vec3 sheenColor, float sheenIntensity, | |
float sheenRoughness, float NdotL, float NdotV, float NdotH) { | |
float sheenDistribution = D_Charlie(sheenRoughness, NdotH); | |
float sheenVisibility = V_Ashikhmin(NdotL, NdotV); | |
return sheenColor * sheenIntensity * sheenDistribution * sheenVisibility; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
const float GAMMA = 2.2; | |
const float INV_GAMMA = 1.0 / GAMMA; | |
// linear to sRGB approximation | |
// see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html | |
inline vec3 linearTosRGB(vec3 color) { | |
return pow(color, vec3(INV_GAMMA)); | |
} | |
// sRGB to linear approximation | |
// see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html | |
inline vec3 sRGBToLinear(vec3 srgbIn) { | |
return vec3(pow(srgbIn.xyz, vec3(GAMMA))); | |
} | |
inline vec4 sRGBToLinear(vec4 srgbIn) { | |
return vec4(sRGBToLinear(srgbIn.xyz), srgbIn.w); | |
} | |
// Uncharted 2 tone map | |
// see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ | |
inline vec3 toneMapUncharted2Impl(vec3 color) { | |
const float A = 0.15; | |
const float B = 0.50; | |
const float C = 0.10; | |
const float D = 0.20; | |
const float E = 0.02; | |
const float F = 0.30; | |
return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; | |
} | |
inline vec3 toneMapUncharted(vec3 color) { | |
const float W = 11.2; | |
color = toneMapUncharted2Impl(2 * color); | |
vec3 whiteScale = 1 / toneMapUncharted2Impl(W); | |
return linearTosRGB(color * whiteScale); | |
} | |
// Hejl Richard tone map | |
// see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ | |
inline vec3 toneMapHejlRichard(vec3 color) { | |
color = max(vec3(0.0), color - vec3(0.004)); | |
return (color * (6.2f * color + .5f)) / | |
(color * (6.2f * color + 1.7f) + 0.06f); | |
} | |
// ACES tone map | |
// see: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ | |
inline vec3 toneMapACES(vec3 color) { | |
const float A = 2.51; | |
const float B = 0.03; | |
const float C = 2.43; | |
const float D = 0.59; | |
const float E = 0.14; | |
return linearTosRGB(clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.f, 1.f)); | |
} | |
inline vec3 toneMap(vec3 color, float exposure) { | |
color *= exposure; | |
/* | |
#ifdef TONEMAP_UNCHARTED | |
return toneMapUncharted(color); | |
#endif | |
#ifdef TONEMAP_HEJLRICHARD | |
return toneMapHejlRichard(color); | |
#endif | |
#ifdef TONEMAP_ACES | |
return toneMapACES(color); | |
#endif | |
*/ | |
return linearTosRGB(color); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cgltf/cgltf.h> | |
#include <vector> | |
#include <iostream> | |
#include <type_traits> | |
#include <cassert> | |
#include <sys/stat.h> | |
#include <sys/mman.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include "appglfw.hxx" | |
#include "brdf.hxx" | |
#include "tonemapping.hxx" | |
// PBR metallic roughness | |
struct pbrMetallicRoughness_t { | |
// baseColorTexture | |
// metallicRoughnessTexture | |
vec4 baseColorFactor; | |
float metallicFactor; | |
float roughnessFactor; | |
}; | |
// KHR_materials_pbrSpecularGlossiness | |
struct KHR_materials_pbrSpecularGlossiness_t { | |
// diffuseTexture | |
// specularGlossinessTexture | |
vec4 diffuseFactor; | |
vec3 specularFactor; | |
float glossinessFactor; | |
}; | |
// KHR_materials_clearcoat | |
struct KHR_materials_clearcoat_t { | |
// clearcoatTexture | |
// clearcoatRoughnessTexture | |
// clearcoatNormalTexture | |
float clearcoatFactor; | |
float clearcoatRoughnessFactor; | |
}; | |
// KHR_materials_transmission | |
struct KHR_materials_transmission_t { | |
// transmissionTexture | |
float transmissionFactor; | |
float ior; | |
}; | |
struct material_uniform_t { | |
// Textures: | |
// normalTexture | |
// occlusionTexture | |
// emissiveTexture | |
vec3 emissiveFactor; | |
float alphaCutoff; | |
// Material models | |
pbrMetallicRoughness_t pbrMetallicRoughness; | |
KHR_materials_pbrSpecularGlossiness_t pbrSpecularGlossiness; | |
KHR_materials_clearcoat_t clearcoat; | |
KHR_materials_transmission_t transmission; | |
}; | |
enum light_type_t { | |
light_type_directional, | |
light_type_point, | |
light_type_spot, | |
}; | |
struct light_t { | |
vec3 direction; | |
float range; | |
vec3 color; | |
float intensity; | |
vec3 position; | |
float innerConeCos; | |
float outerConeCos; | |
light_type_t type; | |
vec2 padding; | |
}; | |
struct uniform_t { | |
// Transform model space into world space. | |
mat4 model_to_world; | |
// Transform world space into clip space. | |
mat4 view_projection; | |
// The normal matrix is just a rotation (and possibly a scale). It | |
// ignores translation, so encode it as a mat3. | |
mat3 normal; | |
vec3 camera; // Position of camera. | |
float normal_scale; | |
material_uniform_t material; | |
float exposure; | |
int light_count; | |
light_t lights[16]; | |
mat4 joint_matrices[75]; | |
// TODO: Make these mat4x3. We only use 3D matrix operations. | |
mat4 joint_normal_matrices[75]; | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
// Vertex attribute and sampler binding locations. | |
enum vattrib_index_t { | |
// Shader locations for vertex attributes. | |
vattrib_position, | |
vattrib_normal, | |
vattrib_tangent, | |
vattrib_binormal, | |
// Repeat these vertex attributes in cycles of three. We don't have to | |
// bind all of them. | |
vattrib_texcoord0, | |
vattrib_joints0, | |
vattrib_weights0, | |
vattrib_texcoord1, | |
vattrib_joints1, | |
vattrib_weights1, | |
}; | |
enum sampler_index_t { | |
// Core | |
sampler_normal, | |
sampler_occlusion, | |
sampler_emissive, | |
// pbrMetallicRoughness | |
sampler_baseColor, | |
sampler_metallicRoughness, | |
// pbrSpecularGlossiness | |
sampler_diffuse, | |
sampler_specularGlossiness, | |
// KHR_materials_clearcoat | |
sampler_clearcoat, | |
sampler_clearcoatRoughness, | |
sampler_clearcoatNormal, | |
// KHR_materials_transmission | |
sampler_transmission, | |
// Image-based lighting textures and LUTs. | |
// The Env samplers are cube maps. | |
sampler_GGXLut, | |
sampler_GGXEnv, | |
sampler_LambertianEnv, | |
sampler_CharlieLut, | |
sampler_CharlieEnv, | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
// Vertex and fragment shader feature sets. The spaceship generates | |
// relational operators so we can use these as keys in maps. | |
// Shader specialization constants | |
struct vert_features_t { | |
// Each flag indicates the availability of a vertex attribute. | |
// vattrib_position is always available. | |
bool normal; // vattrib_normal | |
bool tangent; // vattrib_tangent + vattrib_binormal | |
bool texcoord0; // vattrib_texcoord0 | |
bool texcoord1; // vattrib_texcoord1 | |
bool joints0; // vattrib_joints0 + vattrib_weights0 | |
bool joints1; // vattrib_joints1 + vattrib_weights1 | |
}; | |
[[spirv::constant(0)]] | |
vert_features_t vert_features; | |
struct frag_features_t { | |
// Incoming vertex properties. | |
bool normal; | |
bool tangents; | |
// Available texture maps. | |
bool normal_map; | |
bool emissive_map; | |
bool occlusion_map; | |
// Material properties. | |
bool metallicRoughness; | |
bool anisotropy; | |
bool ibl; | |
bool point_lights; | |
}; | |
[[spirv::constant(0)]] | |
frag_features_t frag_features; | |
template<int I, typename type_t = vec4> | |
[[using spirv: in, location(I)]] | |
type_t shader_in; | |
template<int I, typename type_t = vec4> | |
[[using spirv: out, location(I)]] | |
type_t shader_out; | |
template<int I, typename type_t = sampler2D> | |
[[using spirv: uniform, binding(I)]] | |
type_t shader_sampler; | |
[[using spirv: uniform, binding(0)]] | |
uniform_t uniforms; | |
inline mat4 skinning_matrix(ivec4 joints, vec4 weights, const mat4* matrices) { | |
mat4 skin = | |
weights.x * matrices[joints.x] + | |
weights.y * matrices[joints.y] + | |
weights.z * matrices[joints.z] + | |
weights.w * matrices[joints.w]; | |
return skin; | |
} | |
[[spirv::vert]] | |
void vert_main() { | |
// Always load the position attribute. | |
vec4 pos = shader_in<vattrib_position, vec4>; | |
// Apply skeletal animation. | |
/* | |
if(vert_features.joints0) { | |
// Compute the first 4 components of the skin matrix. | |
mat4 skin = skinning_matrix( | |
shader_in<vattrib_joints0, ivec4>, | |
shader_in<vattrib_weights0>, | |
uniforms.joint_matrices | |
); | |
if(vert_features.joints1) { | |
// Compute the next 4 components of the skin matrix. | |
skin += skinning_matrix( | |
shader_in<vattrib_joints1, ivec4>, | |
shader_in<vattrib_weights1>, | |
uniforms.joint_matrices | |
); | |
} | |
// Advance the position by the skin matrix. | |
pos = skin * pos; | |
} | |
*/ | |
// Transform the model vertex into world space. | |
pos = uniforms.model_to_world * pos; | |
// Pass the vertex position to the fragment shader. | |
shader_out<vattrib_position, vec3> = pos.xyz / pos.w; | |
// Pass the vertex normal to the fragment shader. | |
if(vert_features.normal) { | |
// Load the vertex normal attribute. | |
vec3 n = shader_in<vattrib_normal, vec3>; | |
// Apply morphing. | |
// Apply skinning. | |
// Rotate into normal space and send to the fragment shader. | |
// TODO: Must we normalize? Is the normal vector already normalized coming | |
// out of the map? | |
shader_out<vattrib_normal, vec3> = normalize(uniforms.normal * n); | |
} | |
// Pass through texcoords. | |
if(vert_features.texcoord0) | |
shader_out<vattrib_texcoord0, vec2> = shader_in<vattrib_texcoord0, vec2>; | |
if(vert_features.texcoord1) | |
shader_out<vattrib_texcoord1, vec2> = shader_in<vattrib_texcoord1, vec2>; | |
// Set the vertex positions. | |
glvert_Output.Position = uniforms.view_projection * pos; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
struct normal_info_t { | |
vec3 ng; | |
vec3 n; | |
vec3 t; | |
vec3 b; | |
}; | |
inline normal_info_t get_normal_info(vec3 pos, vec3 v, vec2 uv) { | |
// Get derivatives of texcoord wrt fragment coordinates. | |
vec3 uv_dx = vec3(glfrag_dFdx(uv), 0); | |
vec3 uv_dy = vec3(glfrag_dFdy(uv), 0); | |
vec3 t_ = (uv_dy.t * glfrag_dFdx(pos) - uv_dx.t * glfrag_dFdy(pos)) / | |
(uv_dx.s * uv_dy.t - uv_dy.s * uv_dx.t); | |
vec3 ng, t, b; | |
if(frag_features.tangents) { | |
ng = shader_in<vattrib_normal, vec3>; | |
t = shader_in<vattrib_tangent, vec3>; | |
b = shader_in<vattrib_binormal, vec3>; | |
} else { | |
if(frag_features.normal) { | |
ng = shader_in<vattrib_normal, vec3>; | |
} else { | |
ng = normalize(cross(glfrag_dFdx(pos), glfrag_dFdy(pos))); | |
} | |
// Compute tangent and binormal. | |
t = normalize(t_ - ng * dot(ng, t_)); | |
b = cross(ng, t); | |
} | |
// For back-facing surface, the tangential basis vectors are negated. | |
float facing = 2 * step(0.f, dot(v, ng)) - 1; | |
t *= facing; | |
b *= facing; | |
ng *= facing; | |
vec3 direction; | |
if(frag_features.anisotropy) { | |
} else { | |
direction = vec3(1, 0, 0); | |
} | |
t = mat3(t, b, ng) * direction; | |
b = normalize(cross(ng, t)); | |
vec3 n; | |
if(frag_features.normal_map) { | |
n = 2 * texture(shader_sampler<sampler_normal>, uv).rgb - 1; | |
n *= vec3(uniforms.normal_scale, uniforms.normal_scale, 1); | |
n = mat3(t, b, ng) * normalize(n); | |
} else { | |
n = ng; | |
} | |
normal_info_t info; | |
info.ng = ng; | |
info.t = t; | |
info.b = b; | |
info.n = n; | |
return info; | |
} | |
struct material_info_t { | |
vec3 f0; | |
float roughness; | |
vec3 albedo; | |
float alpha_roughness; | |
vec3 f90; | |
float metallic; | |
vec3 n; | |
vec3 base_color; | |
}; | |
inline void get_metallic_roughness(material_info_t& info, float f0, vec2 uv) { | |
info.metallic = 1; uniforms.material.pbrMetallicRoughness.metallicFactor; | |
info.roughness = 1; uniforms.material.pbrMetallicRoughness.roughnessFactor; | |
// Sample the metallic-roughness texture. This has g and b channels. | |
vec4 mr = texture(shader_sampler<sampler_metallicRoughness>, uv); | |
info.roughness *= mr.g; | |
info.metallic *= mr.b; | |
// TODO: Provide for specular override of f0. | |
info.albedo = mix(info.base_color * (1 - f0), 0, info.metallic); | |
info.f0 = mix(f0, info.base_color, info.metallic); | |
} | |
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#appendix-b-brdf-implementation | |
inline float get_range_attenuation(float range, float distance) { | |
// NOTE: Multiple returns cause validation error. | |
if(range <= 0) | |
return 1; | |
else | |
return clamp(1.f - pow(distance / range, 4), 1.f, 0.f) / | |
(distance * distance); | |
} | |
inline float get_spot_attenuation(vec3 point_to_light, vec3 direction, | |
float outer_cos, float inner_cos) { | |
float cos = dot(normalize(direction), -normalize(point_to_light)); | |
return | |
cos <= outer_cos ? 0 : | |
cos >= inner_cos ? 1 : | |
smoothstep(outer_cos, inner_cos, cos); | |
} | |
// Image-based lighting. | |
inline vec3 getIBLRadianceGGX(vec3 n, vec3 v, float roughness, vec3 color) { | |
int levels = textureQueryLevels(shader_sampler<sampler_GGXEnv, samplerCube>); | |
float NdotV = clamp(dot(n, v), 0.f, 1.f); | |
float lod = levels * clamp(roughness, 0.f, 1.f); | |
vec3 reflection = normalize(reflect(-v, n)); | |
vec2 brdfSamplePoint = clamp(vec2(NdotV, roughness), 0, 1); | |
vec2 brdf = texture( | |
shader_sampler<sampler_GGXLut>, | |
brdfSamplePoint | |
).rg; | |
vec3 specular = textureLod( | |
shader_sampler<sampler_GGXEnv, samplerCube>, | |
reflection, | |
lod | |
).rgb; | |
return specular * (color * brdf.x + brdf.y); | |
} | |
inline vec3 getIBLRadianceLambertian(vec3 n, vec3 color) { | |
vec3 diffuseLight = texture( | |
shader_sampler<sampler_LambertianEnv, samplerCube>, | |
n | |
).rgb; | |
return diffuseLight * color; | |
} | |
[[spirv::frag]] | |
void frag_main() { | |
vec3 pos = shader_in<vattrib_position, vec3>; | |
vec2 uv = shader_in<vattrib_texcoord0, vec2>; | |
// TODO: Break into getBaseColor(). | |
vec4 base_color = texture(shader_sampler<sampler_baseColor>, uv); | |
// if(frag_features.has_pbr_metallic_roughness) { | |
base_color = sRGBToLinear(base_color); | |
// } | |
vec3 v = normalize(uniforms.camera - pos); | |
// Get the position of the fragment. | |
normal_info_t normal = get_normal_info(pos, v, uv); | |
vec3 n = normal.n; | |
vec3 t = normal.t; | |
vec3 b = normal.b; | |
float NdotV = clamp(dot(n, v), 0.f, 1.f); | |
float TdotV = clamp(dot(t, v), 0.f, 1.f); | |
float BdotV = clamp(dot(b, v), 0.f, 1.f); | |
// The default index of refraction of 1.5 yields a dielectric normal incidence reflectance of 0.04. | |
float ior = 1.5; | |
float f0_ior = 0.04; | |
material_info_t material { }; | |
material.base_color = base_color.rgb; | |
if(frag_features.metallicRoughness) { | |
// Load metallic-roughness properties once. We'll need these for each light | |
// in the scene. | |
get_metallic_roughness(material, f0_ior, uv); | |
} | |
// Clamp the metallic-roughness parameters. | |
material.roughness = clamp(material.roughness, 0.f, 1.f); | |
material.metallic = clamp(material.metallic, 0.f, 1.f); | |
material.alpha_roughness = sq(material.roughness); | |
// Compute reflectance. | |
float reflectance = max(max(material.f0.r, material.f0.g), material.f0.b); | |
material.f90 = vec3(clamp(50 * reflectance, 0.f, 1.f)); | |
material.n = n; | |
vec3 f_diffuse(0); | |
vec3 f_specular(0); | |
if(frag_features.ibl) { | |
// Use image-based lighting. | |
f_specular += getIBLRadianceGGX(n, v, material.roughness, material.f0); | |
f_diffuse += getIBLRadianceLambertian(n, material.albedo); | |
} | |
// Specialization constant over point lighting. | |
if(frag_features.point_lights) { | |
// Dynamic uniform control flow over all lights. | |
for(int i = 0; i < uniforms.light_count; ++i) { | |
light_t light = uniforms.lights[i]; | |
vec3 point_to_light = -light.direction; | |
float attenuation = 1; | |
if(light_type_directional == light.type) { | |
point_to_light = -light.direction; | |
} else if(light_type_point == light.type) { | |
point_to_light = light.position - pos; | |
attenuation = get_range_attenuation(light.range, length(point_to_light)); | |
} else { | |
// light_type_spot | |
point_to_light = light.position - pos; | |
attenuation = get_range_attenuation(light.range, length(point_to_light)); | |
attenuation *= get_spot_attenuation(point_to_light, light.direction, | |
light.outerConeCos, light.innerConeCos); | |
} | |
vec3 intensity = attenuation * light.intensity * light.color; | |
vec3 l = normalize(point_to_light); | |
vec3 h = normalize(l + v); // Half-angle vector. | |
float NdotL = clamp(dot(n, l), 0.f, 1.f); | |
float NdotH = clamp(dot(n, h), 0.f, 1.f); | |
float LdotH = clamp(dot(l, h), 0.f, 1.f); | |
float VdotH = clamp(dot(v, h), 0.f, 1.f); | |
if(NdotL > 0 || NdotV > 0) { | |
// Calculation of analytical light | |
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB | |
f_diffuse += intensity * NdotL * BRDF_lambertian(material.f0, | |
material.f90, material.albedo, VdotH); | |
f_specular += intensity * NdotL * BRDF_specularGGX(material.f0, | |
material.f90, material.alpha_roughness, VdotH, NdotL, NdotV, | |
NdotH); | |
} | |
} | |
} | |
vec3 f_emissive(0); | |
if(frag_features.emissive_map) { | |
vec3 sample = texture(shader_sampler<sampler_emissive>, uv).rgb; | |
f_emissive = sRGBToLinear(sample); | |
} | |
vec3 color = f_diffuse + f_specular + f_emissive; | |
if(frag_features.occlusion_map) { | |
float ao = texture(shader_sampler<sampler_occlusion>, uv).r; | |
color = mix(color, color * ao, 1.f); | |
} | |
shader_out<0> = vec4(toneMap(color, uniforms.exposure), base_color.a); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
struct env_map_t { | |
// GL textures. | |
GLuint GGXLut; // specular/specular.ktx2 | |
GLuint GGXEnv; // images/lut_ggx.png | |
GLuint LambertianEnv; // lambertian/diffuse.ktx2 | |
GLuint CharlieLut; // images/lut_charlie.png | |
GLuint CharlieEnv; // charlie/sheen.ktx2 | |
}; | |
struct env_paths_t { | |
const char* GGXLut; | |
const char* GGXEnv; | |
const char* LambertianEnv; | |
const char* CharlieLut; | |
const char* CharlieEnv; | |
}; | |
GLuint create_hdr_cubemap(const char* path) { | |
const uint8_t ktx2_version[12] { | |
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A | |
}; | |
struct ktx2_header_t { | |
char identifier[12]; | |
uint32_t vkFormat; | |
uint32_t typeSize; | |
uint32_t width; | |
uint32_t height; | |
uint32_t pixelDepth; | |
uint32_t layerCount; | |
uint32_t faceCount; | |
uint32_t levelCount; | |
uint32_t supercompressionScheme; | |
// Index | |
uint32_t dfdByteOffset; | |
uint32_t dfdByteLength; | |
uint32_t kvdByteOffset; | |
uint32_t kvdByteLength; | |
uint64_t sgdByteOffset; | |
uint64_t sgdByteLength; | |
struct indexing_t { | |
uint64_t byteOffset; | |
uint64_t byteLength; | |
uint64_t uncompressedByteLength; | |
}; | |
indexing_t levels[1]; // levelCount elements. | |
}; | |
int fd = open(path, O_RDONLY); | |
if(-1 == fd) { | |
printf("cannot open file %s\n", path); | |
exit(1); | |
} | |
struct stat statbuf; | |
if(-1 == fstat(fd, &statbuf)) { | |
printf("cannot stat file %s\n", path); | |
exit(1); | |
} | |
if(statbuf.st_size < sizeof(ktx2_header_t)) { | |
printf("file %s is too small to be a ktx2 file", path); | |
exit(1); | |
} | |
const char* data = (const char*)mmap(nullptr, statbuf.st_size, PROT_READ, | |
MAP_PRIVATE, fd, 0); | |
const ktx2_header_t& header = *(const ktx2_header_t*)data; | |
if(memcmp(header.identifier, ktx2_version, 12)) { | |
printf("file %s has the wrong KTX2 identifier", path); | |
exit(1); | |
} | |
if(6 != header.faceCount) { | |
printf("file %s does not hold a cube map", path); | |
exit(1); | |
} | |
GLenum format, iformat; | |
switch(header.vkFormat) { | |
case 97: | |
format = GL_HALF_FLOAT; | |
iformat = GL_RGBA16F; | |
break; | |
case 109: | |
format = GL_FLOAT; | |
iformat = GL_RGBA32F; | |
break; | |
default: | |
printf("cube map in %s is not encoded in a supported format", path); | |
exit(1); | |
} | |
// Construct the cube map and all of its face-layers. | |
GLuint cubemap; | |
glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &cubemap); | |
glTextureStorage2D(cubemap, header.levelCount, iformat, header.width, | |
header.height); | |
// The file is encoded by levels. | |
for(int level = 0; level < header.levelCount; ++level) { | |
ktx2_header_t::indexing_t indexing = header.levels[level]; | |
// Find the mip map level dimensions. | |
uint32_t width = header.width >> level; | |
uint32_t height = header.height >> level; | |
// Upload all six levels at once. | |
const char* level_data = data + indexing.byteOffset; | |
glTextureSubImage3D(cubemap, level, 0, 0, 0, width, height, 6, GL_RGBA, | |
GL_HALF_FLOAT, level_data); | |
} | |
munmap((void*)data, statbuf.st_size); | |
close(fd); | |
return cubemap; | |
} | |
env_map_t load_env_map(env_paths_t paths) { | |
env_map_t map { }; | |
int width, height; | |
map.GGXLut = load_texture(paths.GGXLut, width, height); | |
map.GGXEnv = create_hdr_cubemap(paths.GGXEnv); | |
map.LambertianEnv = create_hdr_cubemap(paths.LambertianEnv); | |
map.CharlieLut = load_texture(paths.CharlieLut, width, height); | |
map.CharlieEnv = create_hdr_cubemap(paths.CharlieEnv); | |
return map; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
int find_node_index(const cgltf_data* data, const cgltf_node* node) { | |
return node - data->nodes; | |
} | |
int find_image_index(const cgltf_data* data, cgltf_image* image) { | |
return image - data->images; | |
} | |
int find_sampler_index(const cgltf_data* data, cgltf_sampler* sampler) { | |
return sampler - data->samplers; | |
} | |
int find_texture_index(const cgltf_data* data, cgltf_texture* texture) { | |
return texture - data->textures; | |
} | |
int find_material_index(const cgltf_data* data, cgltf_material* material) { | |
return material - data->materials; | |
} | |
int find_view_index(const cgltf_data* data, const cgltf_buffer_view* view) { | |
return view - data->buffer_views; | |
} | |
int find_buffer_index(const cgltf_data* data, const cgltf_buffer* buffer) { | |
return buffer - data->buffers; | |
} | |
struct texture_view_t { | |
// Index of the texture in the gltf stream. | |
int index = -1; | |
// Index of the TEXCOORD_n in the vertex attribute stream. | |
int texcoord = 0; | |
// The scalar multiplied applied to each normal vector of the normal | |
// texture. | |
// Also strength for occlusionTextureInfo. | |
float scale = 1.f; | |
explicit operator bool() const noexcept { return -1 != index; } | |
}; | |
struct material_textures_t { | |
// Core: | |
texture_view_t normal; | |
texture_view_t occlusion; | |
texture_view_t emissive; | |
// pbrMetallicRoughness | |
texture_view_t baseColor; | |
texture_view_t metallicRoughness; | |
// KHR_materials_pbrSpecularGlossiness | |
texture_view_t diffuse; | |
texture_view_t specularGlossiness; | |
// KHR_materials_clearcoat | |
texture_view_t clearcoat; | |
texture_view_t clearcoatRoughness; | |
texture_view_t clearcoatNormal; | |
// KHR_materials_transmission | |
texture_view_t transmission; | |
}; | |
struct material_t { | |
bool has_pbr_metallic_roughness; | |
bool has_pbr_specular_glossiness; | |
bool has_clearcoat; | |
bool has_transmission; | |
material_uniform_t uniform; | |
material_textures_t textures; | |
}; | |
// Binary search in the array of animation keyframe times. | |
// This searches the input accessor of the animation sampler. | |
std::pair<int, float> find_animation_interpolate( | |
const cgltf_animation_sampler* sampler, float t) { | |
// Check sampler->input->min and max. | |
cgltf_accessor* input = sampler->input; | |
assert(cgltf_component_type_r_32f == input->component_type); | |
assert(cgltf_type_scalar == input->type); | |
const cgltf_buffer_view* view = input->buffer_view; | |
const char* data = (const char*)view->buffer->data + view->offset; | |
const float* times = (const float*)data; | |
const float* lb = std::lower_bound(times, times + input->count, t); | |
int index = lb - times; | |
float weight = 0; | |
if(index < input->count) { | |
float t0 = times[index]; | |
float t1 = times[index + 1]; | |
weight = (t - t0) / (t1 - t0); | |
} else { | |
index = input->count - 1; | |
weight = 1; | |
} | |
return { index, weight }; | |
} | |
vec3 interpolate_lerp(const cgltf_animation_sampler* sampler, | |
std::pair<int, float> interpolant) { | |
cgltf_accessor* output = sampler->output; | |
assert(cgltf_component_type_r_32f == output->component_type); | |
assert(cgltf_type_vec3 == output->type); | |
const cgltf_buffer_view* view = output->buffer_view; | |
const char* data = (const char*)view->buffer->data + view->offset; | |
const vec3* vectors = (const vec3*)data; | |
vec3 a = vectors[interpolant.first]; | |
vec3 b = vectors[interpolant.first + 1]; | |
return a * (1 - interpolant.second) + b * interpolant.second; | |
} | |
vec4 interpolate_slerp(const cgltf_animation_sampler* sampler, | |
std::pair<int, float> interpolant) { | |
cgltf_accessor* output = sampler->output; | |
assert(cgltf_component_type_r_32f == output->component_type); | |
assert(cgltf_type_vec4 == output->type); | |
const cgltf_buffer_view* view = output->buffer_view; | |
const char* data = (const char*)view->buffer->data + view->offset; | |
const vec4* quats = (const vec4*)data; | |
vec4 a = quats[interpolant.first]; | |
vec4 b = quats[interpolant.first + 1]; | |
return slerp(a, b, interpolant.second); | |
} | |
struct animation_t { | |
enum path_t { | |
path_translation, | |
path_rotation, | |
path_scale, | |
path_weights, | |
}; | |
struct channel_t { | |
int sampler; | |
int node; | |
}; | |
std::vector<channel_t> channels; | |
struct sampler_t { | |
}; | |
}; | |
struct sampler_t { | |
GLenum mag_filter, min_filter; | |
GLenum wrap_s, wrap_t; | |
}; | |
struct texture_t { | |
int image; | |
int sampler; | |
}; | |
struct prim_t { | |
int offset; // byte offset into the buffer. | |
int count; | |
vec3 min, max; | |
int material; | |
// The VAO for rendering the primitive. | |
GLuint vao = 0; | |
GLenum elements_type = GL_NONE; | |
}; | |
struct mesh_t { | |
std::vector<prim_t> primitives; | |
}; | |
// Create array buffers for storing vertex data. | |
struct model_t { | |
model_t(const char* path); | |
~model_t(); | |
GLuint load_buffer(const cgltf_buffer* buffer); | |
GLuint load_image(const cgltf_image* image, const char* data_path); | |
sampler_t load_sampler(const cgltf_sampler* sampler); | |
texture_t load_texture(const cgltf_texture* texture); | |
material_t load_material(const cgltf_material* material); | |
texture_view_t load_texture_view(const cgltf_texture_view& view); | |
prim_t load_prim(const cgltf_primitive* prim); | |
mesh_t load_mesh(const cgltf_mesh* mesh); | |
void bind_texture(sampler_index_t sampler_index, texture_view_t view); | |
void bind_material(material_t& material); | |
void render_primitive(mesh_t& mesh, prim_t& prim); | |
std::vector<mesh_t> meshes; | |
std::vector<GLuint> buffers; | |
std::vector<GLuint> images; | |
std::vector<sampler_t> samplers; | |
std::vector<texture_t> textures; | |
std::vector<material_t> materials; | |
std::vector<light_t> lights; | |
cgltf_data* data = nullptr; | |
}; | |
model_t::model_t(const char* path) { | |
cgltf_options options { }; | |
cgltf_result result = cgltf_parse_file(&options, path, &data); | |
if(cgltf_result_success != result) { | |
std::cerr<< enum_to_string(result)<< "\n"; | |
exit(1); | |
} | |
result = cgltf_load_buffers(&options, data, path); | |
if(cgltf_result_success != result) { | |
std::cerr<< enum_to_string(result)<< "\n"; | |
exit(1); | |
} | |
// Load the buffers. | |
buffers.resize(data->buffers_count); | |
for(int i = 0; i < data->buffers_count; ++i) { | |
buffers[i] = load_buffer(data->buffers + i); | |
} | |
// Load the images. | |
images.resize(data->images_count); | |
for(int i = 0; i < data->images_count; ++i) { | |
images[i] = load_image(data->images + i, path); | |
} | |
// Load the textures. | |
textures.resize(data->textures_count); | |
for(int i = 0; i < data->textures_count; ++i) { | |
textures[i] = load_texture(data->textures + i); | |
} | |
// Load the samplers. These apply glTextureParameters to the textures. | |
samplers.resize(data->samplers_count); | |
for(int i = 0; i < data->samplers_count; ++i) { | |
samplers[i] = load_sampler(data->samplers + i); | |
} | |
// Load the materials. | |
materials.resize(data->materials_count); | |
for(int i = 0; i < data->materials_count; ++i) { | |
materials[i] = load_material(data->materials + i); | |
} | |
// Load the meshes. | |
for(int i = 0; i < data->meshes_count; ++i) { | |
meshes.push_back(load_mesh(data->meshes + i)); | |
} | |
} | |
model_t::~model_t() { | |
for(mesh_t& mesh : meshes) { | |
for(prim_t& prim : mesh.primitives) | |
glDeleteVertexArrays(1, &prim.vao); | |
} | |
glDeleteBuffers(buffers.size(), buffers.data()); | |
glDeleteTextures(images.size(), images.data()); | |
cgltf_free(data); | |
} | |
GLuint model_t::load_buffer(const cgltf_buffer* buffer) { | |
GLuint buffer2; | |
glCreateBuffers(1, &buffer2); | |
glNamedBufferStorage(buffer2, buffer->size, buffer->data, | |
GL_DYNAMIC_STORAGE_BIT); | |
PRINT_ERROR(); | |
printf("Loaded buffer with %zu bytes\n", buffer->size); | |
return buffer2; | |
} | |
GLuint model_t::load_image(const cgltf_image* image, const char* base) { | |
char path[260]; | |
const char* s0 = strrchr(base, '/'); | |
const char* s1 = strrchr(base, '\\'); | |
const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; | |
if(slash) { | |
size_t prefix = slash - base + 1; | |
strncpy(path, base, prefix); | |
strcpy(path + prefix, image->uri); | |
} else { | |
strcpy(path, image->uri); | |
} | |
int width, height; | |
return ::load_texture(path, width, height); | |
} | |
sampler_t model_t::load_sampler(const cgltf_sampler* sampler) { | |
sampler_t sampler2 { }; | |
sampler2.mag_filter = sampler->mag_filter ? sampler->mag_filter : | |
GL_LINEAR; | |
sampler2.min_filter = sampler->min_filter ? sampler->min_filter : | |
GL_LINEAR_MIPMAP_LINEAR; | |
sampler2.wrap_s = sampler->wrap_s; | |
sampler2.wrap_t = sampler->wrap_t; | |
return sampler2; | |
} | |
texture_t model_t::load_texture(const cgltf_texture* texture) { | |
return { | |
find_image_index(data, texture->image), | |
find_sampler_index(data, texture->sampler) | |
}; | |
} | |
material_t model_t::load_material(const cgltf_material* material) { | |
material_t material2 { }; | |
// Core terms. | |
material2.uniform.emissiveFactor = vec3( | |
material->emissive_factor[0], | |
material->emissive_factor[1], | |
material->emissive_factor[2] | |
); | |
material2.uniform.alphaCutoff = material->alpha_cutoff; | |
material2.textures.normal = load_texture_view(material->normal_texture); | |
material2.textures.occlusion = load_texture_view(material->occlusion_texture); | |
material2.textures.emissive = load_texture_view(material->emissive_texture); | |
if(material->has_pbr_metallic_roughness) { | |
material2.has_pbr_metallic_roughness = true; | |
material2.uniform.pbrMetallicRoughness.baseColorFactor = vec4( | |
material->pbr_metallic_roughness.base_color_factor[0], | |
material->pbr_metallic_roughness.base_color_factor[1], | |
material->pbr_metallic_roughness.base_color_factor[2], | |
material->pbr_metallic_roughness.base_color_factor[3] | |
); | |
material2.uniform.pbrMetallicRoughness.metallicFactor = | |
material->pbr_metallic_roughness.metallic_factor; | |
material2.uniform.pbrMetallicRoughness.roughnessFactor = | |
material->pbr_metallic_roughness.roughness_factor; | |
material2.textures.baseColor = load_texture_view( | |
material->pbr_metallic_roughness.base_color_texture); | |
material2.textures.metallicRoughness = load_texture_view( | |
material->pbr_metallic_roughness.metallic_roughness_texture | |
); | |
} | |
if(material->has_pbr_specular_glossiness) { | |
material2.has_pbr_specular_glossiness = true; | |
material2.uniform.pbrSpecularGlossiness.diffuseFactor = vec4( | |
material->pbr_specular_glossiness.diffuse_factor[0], | |
material->pbr_specular_glossiness.diffuse_factor[1], | |
material->pbr_specular_glossiness.diffuse_factor[2], | |
material->pbr_specular_glossiness.diffuse_factor[3] | |
); | |
material2.uniform.pbrSpecularGlossiness.specularFactor = vec3( | |
material->pbr_specular_glossiness.specular_factor[0], | |
material->pbr_specular_glossiness.specular_factor[1], | |
material->pbr_specular_glossiness.specular_factor[2] | |
); | |
material2.uniform.pbrSpecularGlossiness.glossinessFactor = | |
material->pbr_specular_glossiness.glossiness_factor; | |
material2.textures.diffuse = load_texture_view( | |
material->pbr_specular_glossiness.diffuse_texture | |
); | |
material2.textures.specularGlossiness = load_texture_view( | |
material->pbr_specular_glossiness.specular_glossiness_texture | |
); | |
} | |
if(material->has_clearcoat) { | |
material2.has_clearcoat = true; | |
material2.uniform.clearcoat.clearcoatFactor = | |
material->clearcoat.clearcoat_factor; | |
material2.uniform.clearcoat.clearcoatRoughnessFactor = | |
material->clearcoat.clearcoat_roughness_factor; | |
material2.textures.clearcoat = load_texture_view( | |
material->clearcoat.clearcoat_texture | |
); | |
material2.textures.clearcoatRoughness = load_texture_view( | |
material->clearcoat.clearcoat_roughness_texture | |
); | |
material2.textures.clearcoatNormal = load_texture_view( | |
material->clearcoat.clearcoat_normal_texture | |
); | |
} | |
if(material->has_transmission) { | |
material2.has_transmission = true; | |
material2.uniform.transmission.transmissionFactor = | |
material->transmission.transmission_factor; | |
material2.textures.transmission = load_texture_view( | |
material->transmission.transmission_texture | |
); | |
} | |
return material2; | |
} | |
texture_view_t model_t::load_texture_view(const cgltf_texture_view& view) { | |
texture_view_t view2; | |
view2.index = find_texture_index(data, view.texture); | |
view2.texcoord = view2.texcoord; | |
view2.scale = view2.scale; | |
return view2; | |
} | |
prim_t model_t::load_prim(const cgltf_primitive* prim) { | |
prim_t prim2 { }; | |
prim2.material = find_material_index(data, prim->material); | |
glCreateVertexArrays(1, &prim2.vao); | |
if(prim->indices) { | |
// Bind the index array. | |
const cgltf_accessor* accessor = prim->indices; | |
const cgltf_buffer_view* view = accessor->buffer_view; | |
prim2.offset = accessor->offset + view->offset; | |
prim2.count = accessor->count; | |
switch(accessor->component_type) { | |
case cgltf_component_type_r_8: | |
case cgltf_component_type_r_8u: | |
prim2.elements_type = GL_UNSIGNED_BYTE; | |
break; | |
case cgltf_component_type_r_16: | |
case cgltf_component_type_r_16u: | |
prim2.elements_type = GL_UNSIGNED_SHORT; | |
break; | |
case cgltf_component_type_r_32u: | |
prim2.elements_type = GL_UNSIGNED_INT; | |
break; | |
default: | |
break; | |
} | |
// Associate the buffer holding the indices. | |
GLuint buffer = buffers[find_buffer_index(data, view->buffer)]; | |
glVertexArrayElementBuffer(prim2.vao, buffer); | |
} | |
for(int a = 0; a < prim->attributes_count; ++a) { | |
const cgltf_attribute* attrib = prim->attributes + a; | |
const cgltf_accessor* accessor = attrib->data; | |
const cgltf_buffer_view* view = accessor->buffer_view; | |
// Get the attribute location. | |
vattrib_index_t attribindex; | |
switch(attrib->type) { | |
case cgltf_attribute_type_position: | |
attribindex = vattrib_position; | |
break; | |
case cgltf_attribute_type_normal: | |
attribindex = vattrib_normal; | |
break; | |
case cgltf_attribute_type_texcoord: | |
attribindex = (vattrib_index_t)(vattrib_texcoord0 + 3 * attrib->index); | |
break; | |
case cgltf_attribute_type_joints: | |
attribindex = (vattrib_index_t)(vattrib_joints0 + 3 * attrib->index); | |
break; | |
case cgltf_attribute_type_weights: | |
attribindex = (vattrib_index_t)(vattrib_weights0 + 3 * attrib->index); | |
break; | |
} | |
// Use one binding per attribute. | |
GLuint buffer = buffers[find_buffer_index(data, view->buffer)]; | |
glVertexArrayVertexBuffer(prim2.vao, attribindex, buffer, | |
view->offset + accessor->offset, accessor->stride); | |
// Enable the vertex attribute location. | |
glEnableVertexArrayAttrib(prim2.vao, attribindex); | |
// Get the attribute size and type. | |
GLenum type = GL_NONE; | |
int size = 0; | |
switch(accessor->type) { | |
case cgltf_type_scalar: size = 1; break; | |
case cgltf_type_vec2: size = 2; break; | |
case cgltf_type_vec3: size = 3; break; | |
case cgltf_type_vec4: size = 4; break; | |
default: break; | |
} | |
switch(accessor->component_type) { | |
case cgltf_component_type_r_8: type = GL_BYTE; break; | |
case cgltf_component_type_r_8u: type = GL_UNSIGNED_BYTE; break; | |
case cgltf_component_type_r_16: type = GL_SHORT; break; | |
case cgltf_component_type_r_16u: type = GL_UNSIGNED_SHORT; break; | |
case cgltf_component_type_r_32u: type = GL_UNSIGNED_INT; break; | |
case cgltf_component_type_r_32f: type = GL_FLOAT; break; | |
default: break; | |
} | |
// Associate the buffer view with the attribute location. | |
glVertexArrayAttribBinding(prim2.vao, attribindex, attribindex); | |
if(accessor->normalized || GL_FLOAT == type) { | |
glVertexArrayAttribFormat(prim2.vao, attribindex, size, type, | |
accessor->normalized, 0); | |
} else { | |
glVertexArrayAttribIFormat(prim2.vao, attribindex, size, type, 0); | |
} | |
} | |
return prim2; | |
} | |
mesh_t model_t::load_mesh(const cgltf_mesh* mesh) { | |
mesh_t mesh2; | |
mesh2.primitives.resize(mesh->primitives_count); | |
for(int i = 0; i < mesh->primitives_count; ++i) | |
mesh2.primitives[i] = load_prim(mesh->primitives + i); | |
return mesh2; | |
} | |
void model_t::bind_texture(sampler_index_t sampler_index, | |
texture_view_t view) { | |
texture_t texture = textures[view.index]; | |
GLuint image = images[texture.image]; | |
sampler_t sampler = samplers[texture.sampler]; | |
glTextureParameteri(image, GL_TEXTURE_WRAP_S, sampler.wrap_s); | |
glTextureParameteri(image, GL_TEXTURE_WRAP_T, sampler.wrap_t); | |
glTextureParameteri(image, GL_TEXTURE_MIN_FILTER, sampler.min_filter); | |
glTextureParameteri(image, GL_TEXTURE_MAG_FILTER, sampler.mag_filter); | |
static float aniso = 0; | |
if(!aniso) { | |
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); | |
printf("aniso = %f\n", aniso); | |
} | |
glTextureParameteri(image, GL_TEXTURE_MAX_ANISOTROPY, aniso); | |
glBindTextureUnit(sampler_index, image); | |
} | |
void model_t::bind_material(material_t& mat) { | |
material_textures_t& tex = mat.textures; | |
if(tex.normal) | |
bind_texture(sampler_normal, tex.normal); | |
if(tex.occlusion) | |
bind_texture(sampler_occlusion, tex.occlusion); | |
if(tex.emissive) | |
bind_texture(sampler_emissive, tex.emissive); | |
if(tex.baseColor) | |
bind_texture(sampler_baseColor, tex.baseColor); | |
if(tex.metallicRoughness) | |
bind_texture(sampler_metallicRoughness, tex.metallicRoughness); | |
if(tex.diffuse) | |
bind_texture(sampler_diffuse, tex.diffuse); | |
if(tex.specularGlossiness) | |
bind_texture(sampler_specularGlossiness, tex.specularGlossiness); | |
if(tex.clearcoat) | |
bind_texture(sampler_clearcoat, tex.clearcoat); | |
if(tex.clearcoatRoughness) | |
bind_texture(sampler_clearcoatRoughness, tex.clearcoatRoughness); | |
if(tex.clearcoatNormal) | |
bind_texture(sampler_clearcoatNormal, tex.clearcoatNormal); | |
if(tex.transmission) | |
bind_texture(sampler_transmission, tex.transmission); | |
} | |
void model_t::render_primitive(mesh_t& mesh, prim_t& prim) { | |
bind_material(materials[prim.material]); | |
glBindVertexArray(prim.vao); | |
glDrawElements(GL_TRIANGLES, prim.count, prim.elements_type, | |
(void*)prim.offset); | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
struct myapp_t : app_t { | |
myapp_t(const char* gltf_path, env_paths_t env_paths); | |
void display() override; | |
model_t model; | |
env_map_t env_map; | |
GLuint vs, fs, program; | |
uniform_t uniforms; | |
GLuint ubo; | |
}; | |
myapp_t::myapp_t(const char* gltf_path, env_paths_t env_paths) : | |
app_t("glTF viewer", 800, 600), | |
model(gltf_path) { | |
// Load the environment maps. | |
env_map = load_env_map(env_paths); | |
// Compile the shaders. | |
vs = glCreateShader(GL_VERTEX_SHADER); | |
fs = glCreateShader(GL_FRAGMENT_SHADER); | |
GLuint shaders[] { vs, fs }; | |
glShaderBinary(2, shaders, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, | |
__spirv_data, __spirv_size); | |
vert_features_t vert_features { }; | |
vert_features.normal = true; | |
vert_features.texcoord0 = true; | |
specialize_shader(vs, @spirv(vert_main), vert_features); | |
frag_features_t frag_features { }; | |
frag_features.normal = true; | |
frag_features.normal_map = true; | |
frag_features.emissive_map = true; | |
frag_features.occlusion_map = true; | |
frag_features.metallicRoughness = true; | |
frag_features.ibl = true; | |
frag_features.point_lights = false; | |
specialize_shader(fs, @spirv(frag_main), frag_features); | |
program = glCreateProgram(); | |
glAttachShader(program, vs); | |
glAttachShader(program, fs); | |
glLinkProgram(program); | |
// Load the uniforms. | |
glCreateBuffers(1, &ubo); | |
glNamedBufferStorage(ubo, sizeof(uniform_t), nullptr, | |
GL_DYNAMIC_STORAGE_BIT); | |
} | |
void myapp_t::display() { | |
const float bg[4] { 0 }; | |
glClearBufferfv(GL_COLOR, 0, bg); | |
glClear(GL_DEPTH_BUFFER_BIT); | |
glEnable(GL_DEPTH_TEST); | |
glDepthFunc(GL_LESS); | |
glEnable(GL_CULL_FACE); | |
glFrontFace(GL_CCW); | |
glUseProgram(program); | |
int width, height; | |
glfwGetWindowSize(window, &width, &height); | |
mat4 view = camera.get_view(); | |
mat4 projection = camera.get_perspective(width, height); | |
double int_part; | |
double speed = 10; | |
double angle = modf(glfwGetTime() / speed, &int_part) * (2 * M_PI); | |
mat4 model_to_world = make_rotateX(radians(-90.f)); | |
model_to_world = make_rotateY(angle) * model_to_world; | |
uniforms.model_to_world = model_to_world; | |
uniforms.view_projection = projection * view; | |
uniforms.normal = mat3(uniforms.model_to_world); | |
uniforms.camera = camera.get_eye(); | |
uniforms.normal_scale = 1; | |
uniforms.light_count = 0; | |
// TODO: Add a number of lights on random paths. | |
light_t light { }; | |
// light.direction = vec3(-.7399, -.6428, -.1983); | |
light.position = 2 * vec3(0, sin(angle), cos(angle)); | |
light.range = -1; | |
light.color = vec3(1, 1, 1); | |
light.intensity = 1; | |
light.innerConeCos = 0; | |
light.outerConeCos = cos(radians(45.f)); | |
light.type = light_type_point; | |
uniforms.light_count = 2; | |
uniforms.lights[0] = light; | |
// Bind the environment maps. | |
glBindTextureUnit(sampler_GGXLut, env_map.GGXLut); | |
glBindTextureUnit(sampler_GGXEnv, env_map.GGXEnv); | |
glBindTextureUnit(sampler_LambertianEnv, env_map.LambertianEnv); | |
glBindTextureUnit(sampler_CharlieLut, env_map.CharlieLut); | |
glBindTextureUnit(sampler_CharlieEnv, env_map.CharlieEnv); | |
for(mesh_t& mesh : model.meshes) { | |
for(prim_t& prim : mesh.primitives) { | |
// Set the material for this primitive. | |
uniforms.material = model.materials[prim.material].uniform; | |
uniforms.exposure = 1; | |
glNamedBufferSubData(ubo, 0, sizeof(uniform_t), &uniforms); | |
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo); | |
model.render_primitive(mesh, prim); | |
} | |
} | |
/* | |
// start the Dear ImGui frame | |
ImGui_ImplOpenGL3_NewFrame(); | |
ImGui_ImplGlfw_NewFrame(); | |
ImGui::NewFrame(); | |
static bool show; | |
ImGui::ShowDemoWindow(&show); | |
ImGui::EndFrame(); | |
ImGui::Render(); | |
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | |
*/ | |
} | |
int main(int argc, char** argv) { | |
glfwInit(); | |
const char* path = (2 == argc) ? | |
argv[1] : | |
"/home/sean/projects/cgltf/test/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"; | |
env_paths_t env_paths { | |
"/home/sean/projects/glTF-Sample-Viewer/assets/images/lut_ggx.png", | |
"/home/sean/projects/glTF-Sample-Viewer/assets/environments/helipad/ggx/specular.ktx2", | |
"/home/sean/projects/glTF-Sample-Viewer/assets/environments/helipad/lambertian/diffuse.ktx2", | |
"/home/sean/projects/glTF-Sample-Viewer/assets/images/lut_charlie.png", | |
"/home/sean/projects/glTF-Sample-Viewer/assets/environments/helipad/charlie/sheen.ktx2", | |
}; | |
myapp_t myapp(path, env_paths); | |
myapp.loop(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment