From 2890cf45c1ad62bf8496f15dfb4796f9c71c02eb Mon Sep 17 00:00:00 2001 From: Hunter Kvalevog Date: Thu, 20 Nov 2025 15:37:28 -0600 Subject: shaders --- gl-vfog/CMakeLists.txt | 9 - gl-vfog/fs.glsl | 111 ------- gl-vfog/geometry.hh | 108 ------- gl-vfog/main.cc | 281 ------------------ gl-vfog/vs.glsl | 12 - shaders/CMakeLists.txt | 9 + shaders/main.cc | 784 +++++++++++++++++++++++++++++++++++++++++++++++++ shaders/polygon.glsl | 64 ++++ shaders/vert_cube.glsl | 31 ++ shaders/vert_quad.glsl | 23 ++ shaders/vfog.glsl | 131 +++++++++ 11 files changed, 1042 insertions(+), 521 deletions(-) delete mode 100644 gl-vfog/CMakeLists.txt delete mode 100644 gl-vfog/fs.glsl delete mode 100644 gl-vfog/geometry.hh delete mode 100644 gl-vfog/main.cc delete mode 100644 gl-vfog/vs.glsl create mode 100644 shaders/CMakeLists.txt create mode 100644 shaders/main.cc create mode 100644 shaders/polygon.glsl create mode 100644 shaders/vert_cube.glsl create mode 100644 shaders/vert_quad.glsl create mode 100644 shaders/vfog.glsl diff --git a/gl-vfog/CMakeLists.txt b/gl-vfog/CMakeLists.txt deleted file mode 100644 index e085e04..0000000 --- a/gl-vfog/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.15 FATAL_ERROR) -project(gl-vfog CXX) - -set(DEMO_NEEDS_DEAR_IMGUI TRUE) -set(DEMO_NEEDS_SDL3 TRUE) -include("${CMAKE_CURRENT_LIST_DIR}/../common/c_cpp/CMakeLists.txt") - -add_executable(gl-vfog main.cc) -target_link_libraries(gl-vfog PRIVATE common) \ No newline at end of file diff --git a/gl-vfog/fs.glsl b/gl-vfog/fs.glsl deleted file mode 100644 index 12f32b9..0000000 --- a/gl-vfog/fs.glsl +++ /dev/null @@ -1,111 +0,0 @@ -#version 330 core - -in vec3 f_p; - -out vec4 f_color; - -// ----------------------------------------------------------------------------- -// Uniforms -// ----------------------------------------------------------------------------- -uniform vec3 u_cam; -uniform float u_time; -uniform int u_mode; -uniform float u_sigma; -uniform int u_steps; - -// ----------------------------------------------------------------------------- -// Ray-AABB intersection -// ref: https://tavianator.com/2022/ray_box_boundary.html -// ----------------------------------------------------------------------------- -bool intersect(vec3 ro, vec3 rd, vec3 bbmin, vec3 bbmax, out float t0, out float t1) -{ - vec3 inv = 1.0f / rd; - vec3 ta = (bbmin - ro) * inv; - vec3 tb = (bbmax - ro) * inv; - vec3 tmin = min(ta, tb); - vec3 tmax = max(ta, tb); - t0 = max(max(tmin.x, tmin.y), tmin.z); - t1 = min(min(tmax.x, tmax.y), tmax.z); - return t1 >= max(t0, 0.0f); -} - -// ----------------------------------------------------------------------------- -// Mode 0: Basic volumetric fog -// ----------------------------------------------------------------------------- -float sample_basic(vec3 pos) -{ - return 1.0; -} - -// ----------------------------------------------------------------------------- -// Mode 1: Fade along y axis -// ----------------------------------------------------------------------------- - -float sample_vfade(vec3 pos) -{ - return 1.0f - pos.y; -} - -// ----------------------------------------------------------------------------- -// Mode 2: Some waves -// ----------------------------------------------------------------------------- - -float sample_waves(vec3 pos) -{ - // Fractal sum - // ref: https://thebookofshaders.com/13/ - float h = 0.75f; // baseline - - // Big waves - h += 0.08f * sin(pos.x * 2.0f + u_time); - h += 0.08f * cos(pos.z * 2.0f + u_time * -1.1f); - - // Medium waves - h += 0.05f * sin(pos.x * 5.0f + u_time * 1.7f); - h += 0.05f * cos(pos.z * 4.0f + u_time * -2.0f); - - // Small but choppy waves - h += 0.03f * sin(pos.x * 12.0f + u_time * 3.3f); - h += 0.03f * cos(pos.z * 11.0f + u_time * -2.5f); - - return float(pos.y < h); -} - -void main() -{ - vec3 bbmin = vec3(0.0f, 0.0f, 0.0f); - vec3 bbmax = vec3(1.0f, 1.0f, 1.0f); - - vec3 ro = u_cam; - vec3 rd = normalize(f_p - ro); - - float t0 = 0.0f; - float t1 = 0.0f; - if (!intersect(ro, rd, bbmin, bbmax, t0, t1)) { - f_color = vec4(0.0f, 0.0f, 0.0f, 0.0f); - return; - } - - // Clamp entry point in front of camera - float t_enter = max(t0, 0.0f); - float t_exit = t1; - float t_len = max(0.0f, t_exit - t_enter); - - float density = 0; - for (int i = 0; i < u_steps; ++i) { - float t = mix(t_enter, t_exit, (float(i) + 0.5f) / float(u_steps)); - vec3 p = ro + rd * t; - switch (u_mode) { - case 0: { density += sample_basic(p); } break; - case 1: { density += sample_vfade(p); } break; - case 2: { density += sample_waves(p); } break; - } - } - density /= float(u_steps); - - // Beer-Lambert attenuation - // ref: https://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law - float a = 1.0f - exp(-u_sigma * t_len * density); - - f_color = vec4(vec3(1.0f), a); -} diff --git a/gl-vfog/geometry.hh b/gl-vfog/geometry.hh deleted file mode 100644 index c48248a..0000000 --- a/gl-vfog/geometry.hh +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef _GEOMETRY_HH_ -#define _GEOMETRY_HH_ - -#include - -static inline float Radians(float degrees) -{ - return degrees * M_PI / 180.0f; -} - -class Vec3 -{ -public: - float x; - float y; - float z; -public: - Vec3(float x, float y, float z) - : x(x), y(y), z(z) { } - - inline Vec3 operator+(const Vec3& rhs) const { return Vec3(x + rhs.x, y + rhs.y, z + rhs.z); } - inline Vec3 operator-(const Vec3& rhs) const { return Vec3(x - rhs.x, y - rhs.y, z - rhs.z); } - - inline float* Base() { return &x; } - - inline float Length() { return std::sqrtf(x * x + y * y + z * z); } - - inline Vec3 Normalize() - { - float l = Length(); - if (l < 0.0f) { l = 1.0f; } - return Vec3(x / l, y / l, z / l); - } - - inline float Dot(const Vec3& rhs) - { - return x * rhs.x + y * rhs.y + z * rhs.z; - } - - inline Vec3 Cross(const Vec3& rhs) - { - return Vec3( - y * rhs.z - z * rhs.y, - z * rhs.x - x * rhs.z, - x * rhs.y - y * rhs.x - ); - } -}; - -class Mat4x4 -{ -private: - float m[4 * 4]; -public: - inline float& operator[](std::size_t s) { return m[s]; } - inline float operator[](std::size_t s) const { return m[s]; } - - inline float* Base() { return m; } - - inline Mat4x4 operator*(const Mat4x4& rhs) - { - Mat4x4 r = { }; - for (int col = 0; col < 4; ++col) { - for (int row = 0; row < 4; ++row) { - r[col * 4 + row] = - m[0 * 4 + row] * rhs[col * 4 + 0] + - m[1 * 4 + row] * rhs[col * 4 + 1] + - m[2 * 4 + row] * rhs[col * 4 + 2] + - m[3 * 4 + row] * rhs[col * 4 + 3]; - } - } - return r; - } -public: - static Mat4x4 LookAt(const Vec3& eye, const Vec3& at, const Vec3& up) - { - Vec3 f = (at - eye).Normalize(); - Vec3 s = f.Cross(up).Normalize(); - Vec3 u = s.Cross(f); - Vec3 t = Vec3(-s.Dot(eye), -u.Dot(eye), f.Dot(eye)); - - Mat4x4 m; - m[ 0] = s.x; m[ 1] = u.x; m[ 2] = -f.x; m[ 3] = 0.0f; - m[ 4] = s.y; m[ 5] = u.y; m[ 6] = -f.y; m[ 7] = 0.0f; - m[ 8] = s.z; m[ 9] = u.z; m[10] = -f.z; m[11] = 0.0f; - m[12] = t.x; m[13] = t.y; m[14] = t.z; m[15] = 1.0f; - - return m; - } - - static Mat4x4 Perspective(float fov_deg, float aspect, float z_near, float z_far) - { - const float fov_cot = 1.0f / std::tanf(Radians(fov_deg) / 2.0f); - - Mat4x4 m = { }; - - m[0*4+0] = fov_cot / aspect; - m[1*4+1] = fov_cot; - m[2*4+3] = -1.0f; - - m[2*4+2] = (z_far + z_near) / (z_near - z_far); - m[3*4+2] = (2.0f * z_near * z_far) / (z_near - z_far); - - return m; - } -}; - -#endif // _GEOMETRY_HH_ diff --git a/gl-vfog/main.cc b/gl-vfog/main.cc deleted file mode 100644 index 679d4ec..0000000 --- a/gl-vfog/main.cc +++ /dev/null @@ -1,281 +0,0 @@ -// OpenGL volumetric fog demo -// -// SPDX-License-Identifier: 0BSD -// -// Changelog: -// - 11/13/25: Initial release -// - -#define GLAD_GL_IMPLEMENTATION -#include "../common/c_cpp/thirdparty/glad33/glad33.h" - -#include -#include -#include - -#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM -#include "imgui.h" -#include "imgui_impl_opengl3.cpp" -#include "imgui_impl_sdl3.cpp" - -#include "geometry.hh" - -// Helper: Report errors via glGetError() after every OpenGL function call. -// macOS does not support glDebugMessageCallback -static void assert_gl_error(GLenum error, const char* expr, int line) -{ - if (error != GL_NO_ERROR) { - const char* error_string; - switch (error) { -#define BIND_ERROR(name) case name: { error_string = #name; }; break; - BIND_ERROR(GL_INVALID_ENUM); - BIND_ERROR(GL_INVALID_VALUE); - BIND_ERROR(GL_INVALID_OPERATION); - BIND_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); - BIND_ERROR(GL_OUT_OF_MEMORY); -#undef BIND_ERROR - default: SDL_assert(0 && "Invalid GL_ERROR enum"); - } - SDL_Log("[Line #%u] %s caused %s\n", line, expr, error_string); - } -} -#define GL(expr) \ - expr; \ - for (GLenum _glcode; (_glcode = glGetError()) != GL_NO_ERROR; ) { \ - assert_gl_error(_glcode, #expr, __LINE__); \ - } - -int main(int argc, char* argv[]) -{ - (void)argc; - (void)argv; - - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { - fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); - return EXIT_FAILURE; - } - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); - - SDL_Window* wnd = SDL_CreateWindow("gl-vfog", 1024, 768, SDL_WINDOW_OPENGL); - if (!wnd) { - fprintf(stderr, "Failed to create window: %s\n", SDL_GetError()); - return EXIT_FAILURE; - } - - if (!SDL_GL_CreateContext(wnd)) { - fprintf(stderr, "Failed to create OpenGL context: %s\n", SDL_GetError()); - return EXIT_FAILURE; - } - SDL_GL_SetSwapInterval(1); - - if (!gladLoadGL(SDL_GL_GetProcAddress)) { - fprintf(stderr, "Failed to load OpenGL functions\n"); - return EXIT_FAILURE; - } - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGui_ImplSDL3_Init(wnd, 0, 0); - ImGui_ImplOpenGL3_Init(); - ImGui::GetIO().IniFilename = NULL; - - // - // 0 1 - // +------------+ - // /| /| - // 4 / | 5 / | - // +------------+ | - // | | | | - // | | 3 | | 2 - // | +---------|--+ - // | / | / - // 7 |/ 6 |/ - // +------------+ - // - - const float vdata[] = { - 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - }; - - const uint16_t idata[] = { - 1, 0, 3, - 1, 3, 2, - 5, 1, 2, - 5, 2, 6, - 4, 5, 6, - 4, 6, 7, - 0, 4, 7, - 0, 7, 3, - 0, 1, 5, - 0, 5, 4, - 7, 6, 2, - 7, 2, 3, - }; - - GLuint vao; - GL(glGenVertexArrays(1, &vao)) - GL(glBindVertexArray(vao)); - - GLuint vbo; - GL(glGenBuffers(1, &vbo)); - GL(glBindBuffer(GL_ARRAY_BUFFER, vbo)); - GL(glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STATIC_DRAW)); - - GLuint ibo; - GL(glGenBuffers(1, &ibo)); - GL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)); - GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idata), idata, GL_STATIC_DRAW)); - - GL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, NULL)); - GL(glEnableVertexAttribArray(0)); - - struct { - GLenum type; - const char* path; - } shaders[] = { - { .type = GL_VERTEX_SHADER, .path = "vs.glsl" }, - { .type = GL_FRAGMENT_SHADER, .path = "fs.glsl" }, - }; - - GLuint prog = GL(glCreateProgram()); - for (size_t i = 0; i < sizeof(shaders) / sizeof(*shaders); ++i) { - const char* src = (const char*)SDL_LoadFile(shaders[i].path, NULL); - if (!src) { - fprintf(stderr, "Failed to load shader %s: %s\n", shaders[i].path, SDL_GetError()); - return EXIT_FAILURE; - } - - GLuint shader = GL(glCreateShader(shaders[i].type)); - GL(glShaderSource(shader, 1, &src, NULL)); - GL(glCompileShader(shader)); - GLint compile_status = 0; - GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status)); - if (compile_status != GL_TRUE) { - char error[1024] = { 0 }; - GL(glGetShaderInfoLog(shader, sizeof(error), NULL, error)); - SDL_Log("Error compiling shader %s: %s\n", shaders[i].path, error); - return EXIT_FAILURE; - } - GL(glAttachShader(prog, shader)); - } - GL(glLinkProgram(prog)); - - GLint prog_status; - GL(glGetProgramiv(prog, GL_LINK_STATUS, &prog_status)); - if (prog_status != GL_TRUE) { - char error[1024] = { 0 }; - GL(glGetProgramInfoLog(prog, sizeof(error), NULL, error)); - SDL_Log("Error linking program: %s\n", error); - return EXIT_FAILURE; - } - - GL(glUseProgram(prog)); - - GLuint u_view = GL(glGetUniformLocation(prog, "u_view")); - GLuint u_cam = GL(glGetUniformLocation(prog, "u_cam")); - GLuint u_time = GL(glGetUniformLocation(prog, "u_time")); - GLuint u_mode = GL(glGetUniformLocation(prog, "u_mode")); - GLuint u_sigma = GL(glGetUniformLocation(prog, "u_sigma")); - GLuint u_steps = GL(glGetUniformLocation(prog, "u_steps")); - - bool running = true; - while (running) { - SDL_Event evt = { 0 }; - while (SDL_PollEvent(&evt)) { - ImGui_ImplSDL3_ProcessEvent(&evt); - switch (evt.type) { - case SDL_EVENT_QUIT: { - running = false; - } break; - } - } - - int wnd_x = 0; - int wnd_y = 0; - SDL_GetWindowSizeInPixels(wnd, &wnd_x, &wnd_y); - - ImGui_ImplSDL3_NewFrame(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui::NewFrame(); - - // Controls - static int c_mode = 0; - static float c_sigma = 4.0f; - static float c_zoom = 1.5f; - static int c_steps = 64; - ImGui::SetNextWindowPos(ImVec2(15, 15)); - ImGui::SetNextWindowSize(ImVec2(150, 768 - 30)); - if (ImGui::Begin("Controls", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) { - ImGui::SeparatorText("Controls"); - ImGui::DragFloat("Zoom", &c_zoom, 0.05f, 0.1f, 3.0f); - ImGui::SeparatorText("Settings"); - const char* modes[] = { "Basic", "Fade", "Waves", }; - if (ImGui::BeginCombo("Mode", modes[c_mode])) { - for (int i = 0; i < IM_ARRAYSIZE(modes); ++i) { - if (ImGui::Selectable(modes[i], i == c_mode)) { - c_mode = i; - } - } - ImGui::EndCombo(); - } - ImGui::DragInt("Steps", &c_steps); - ImGui::DragFloat("Sigma", &c_sigma, 0.05); - ImGui::End(); - } - - GL(glViewport(0, 0, wnd_x, wnd_y)); - GL(glEnable(GL_CULL_FACE)); - GL(glCullFace(GL_FRONT)); - GL(glEnable(GL_DEPTH_TEST)); - GL(glEnable(GL_BLEND)); - GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - GL(glClearColor(0.1f, 0.1f, 0.1f, 1.0f)); - GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - - const float t = SDL_GetTicks() / 1e3f; - const float camt = t * 0.33f; - - Vec3 eye = Vec3(0.0f, 1.5f, 0.0f); - Vec3 tgt = Vec3(0.5f, 0.5f, 0.5f); - eye.x = tgt.x + SDL_sinf(camt) * c_zoom; - eye.z = tgt.z + SDL_cosf(camt) * c_zoom; - Vec3 up = Vec3(0.0f, 1.0f, 0.0f); - - Mat4x4 look = Mat4x4::LookAt(eye, tgt, up); - Mat4x4 proj = Mat4x4::Perspective(70.0f, (float)wnd_x / (float)wnd_y, 0.1f, 10.0f); - Mat4x4 view = proj * look; - - GL(glUniformMatrix4fv(u_view, 1, GL_FALSE, view.Base())) - GL(glUniform3fv(u_cam, 1, eye.Base())); - GL(glUniform1f(u_time, t)); - GL(glUniform1i(u_mode, c_mode)); - GL(glUniform1f(u_sigma, c_sigma)); - GL(glUniform1i(u_steps, c_steps)); - - GL(glDrawElements(GL_TRIANGLES, 3 * 12, GL_UNSIGNED_SHORT, NULL)); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - if (!SDL_GL_SwapWindow(wnd)) { - fprintf(stderr, "Failed to present window: %s", SDL_GetError()); - return EXIT_FAILURE; - } - } - - SDL_DestroyWindow(wnd); - - return EXIT_SUCCESS; -} diff --git a/gl-vfog/vs.glsl b/gl-vfog/vs.glsl deleted file mode 100644 index bf8c8eb..0000000 --- a/gl-vfog/vs.glsl +++ /dev/null @@ -1,12 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 v_p; - -out vec3 f_p; - -uniform mat4 u_view; - -void main() { - f_p = v_p; - gl_Position = u_view * vec4(v_p, 1.0f); -} \ No newline at end of file diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt new file mode 100644 index 0000000..86b6db1 --- /dev/null +++ b/shaders/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) +project(shaders CXX) + +set(DEMO_NEEDS_DEAR_IMGUI TRUE) +set(DEMO_NEEDS_SDL3 TRUE) +include("${CMAKE_CURRENT_LIST_DIR}/../common/c_cpp/CMakeLists.txt") + +add_executable(shaders main.cc) +target_link_libraries(shaders PRIVATE common) diff --git a/shaders/main.cc b/shaders/main.cc new file mode 100644 index 0000000..ffc6434 --- /dev/null +++ b/shaders/main.cc @@ -0,0 +1,784 @@ +// -------------------------------------------------------------------------------- +// OpenGL shader sandbox +// +// SPDX-License-Identifier: 0BSD +// -------------------------------------------------------------------------------- + +#define GLAD_GL_IMPLEMENTATION +#include "../common/c_cpp/thirdparty/glad33/glad33.h" + +#define SDL_MAIN_USE_CALLBACKS +#include +#include +#include + +#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#include "imgui.h" +#include "imgui_impl_opengl3.cpp" +#include "imgui_impl_sdl3.cpp" + +#include +#include + +// -------------------------------------------------------------------------------- +// Geometry +// -------------------------------------------------------------------------------- + +template +static inline T Min(T lhs, T rhs) +{ + return (lhs < rhs) ? lhs : rhs; +} + +template +static inline T Max(T lhs, T rhs) +{ + return (lhs > rhs) ? lhs : rhs; +} + +template +static inline T Clamp(T val, T vmin, T vmax) +{ + return Min(Max(val, vmin), vmax); +} + +static inline float Radians(float degrees) +{ + return degrees * M_PI / 180.0f; +} + +class Vec2 +{ +public: + float x = 0.0f; + float y = 0.0f; +public: + Vec2() = default; + Vec2(float x, float y) : x(x), y(y) { } +}; + +class Vec3 +{ +public: + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +public: + Vec3() = default; + Vec3(float x, float y, float z) : x(x), y(y), z(z) { } + + inline Vec3 operator+(const Vec3& rhs) const + { + return Vec3(x + rhs.x, y + rhs.y, z + rhs.z); + } + + inline Vec3 operator-(const Vec3& rhs) const + { + return Vec3(x - rhs.x, y - rhs.y, z - rhs.z); + } + + inline Vec3 operator*(float scalar) const + { + return Vec3(x * scalar, y * scalar, z * scalar); + } + + inline float Length() const + { + return std::sqrtf(x * x + y * y + z * z); + } + + inline Vec3 Normalize() const + { + float l = Length(); + if (l == 0.0f) { l = 1.0f; } + return Vec3(x / l, y / l, z / l); + } + + inline float Dot(const Vec3& rhs) const + { + return x * rhs.x + y * rhs.y + z * rhs.z; + } + + inline Vec3 Cross(const Vec3& rhs) const + { + return Vec3( + y * rhs.z - z * rhs.y, + z * rhs.x - x * rhs.z, + x * rhs.y - y * rhs.x + ); + } +public: + static Vec3 FromEuler(Vec2 angles) + { + const float rx = Radians(angles.x); + const float ry = Radians(angles.y); + const float x = cosf(ry) * sinf(rx); + const float y = sinf(ry); + const float z = cosf(ry) * cosf(rx); + return Vec3(x, y, z); + } +}; + +class Vec4 +{ +public: + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; +public: + Vec4() = default; + Vec4(float x, float y, float z) : x(x), y(y), z(z), w(1.0f) { } + Vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } +}; + +class Mat4x4 +{ +private: + float m[4 * 4]; +public: + inline float& operator[](std::size_t s) { return m[s]; } + inline float operator[](std::size_t s) const { return m[s]; } + + inline float* Base() { return m; } + + inline Mat4x4 operator*(const Mat4x4& rhs) + { + Mat4x4 r = { }; + for (int col = 0; col < 4; ++col) { + for (int row = 0; row < 4; ++row) { + r[col * 4 + row] = + m[0 * 4 + row] * rhs[col * 4 + 0] + + m[1 * 4 + row] * rhs[col * 4 + 1] + + m[2 * 4 + row] * rhs[col * 4 + 2] + + m[3 * 4 + row] * rhs[col * 4 + 3]; + } + } + return r; + } +public: + static Mat4x4 LookAt(const Vec3& eye, const Vec3& at, const Vec3& up) + { + Vec3 f = (at - eye).Normalize(); + Vec3 s = f.Cross(up).Normalize(); + Vec3 u = s.Cross(f); + Vec3 t = Vec3(-s.Dot(eye), -u.Dot(eye), f.Dot(eye)); + + Mat4x4 m; + m[ 0] = s.x; m[ 1] = u.x; m[ 2] = -f.x; m[ 3] = 0.0f; + m[ 4] = s.y; m[ 5] = u.y; m[ 6] = -f.y; m[ 7] = 0.0f; + m[ 8] = s.z; m[ 9] = u.z; m[10] = -f.z; m[11] = 0.0f; + m[12] = t.x; m[13] = t.y; m[14] = t.z; m[15] = 1.0f; + + return m; + } + + static Mat4x4 Perspective(float fov_deg, float aspect, float z_near, float z_far) + { + const float fov_cot = 1.0f / std::tanf(Radians(fov_deg) / 2.0f); + + Mat4x4 m = { }; + + m[0*4+0] = fov_cot / aspect; + m[1*4+1] = fov_cot; + m[2*4+3] = -1.0f; + + m[2*4+2] = (z_far + z_near) / (z_near - z_far); + m[3*4+2] = (2.0f * z_near * z_far) / (z_near - z_far); + + return m; + } +}; + +// -------------------------------------------------------------------------------- +// Shader metadata parser +// -------------------------------------------------------------------------------- + +enum : int +{ + SHADER_MODEL_TYPE_INVALID = 0, + SHADER_MODEL_TYPE_QUAD, + SHADER_MODEL_TYPE_CUBE, +}; + +enum : int +{ + SHADER_UNIFORM_TYPE_INVALID = 0, + SHADER_UNIFORM_TYPE_INT, + SHADER_UNIFORM_TYPE_FLOAT, + SHADER_UNIFORM_TYPE_COLOR, +}; + +struct ShaderUniform +{ + char name[32]; + int type; + int val_i1; + float val_fl; + Vec4 val_v4; + int min_i; + int max_i; +}; + +struct ShaderMetadata +{ + int model; + ImVector uniforms; +}; + +static bool ReadToken(const char** src, const char** out, int* len) +{ + while (isspace(**src)) { + ++*src; + } + if (!**src) { + return false; + } + + *len = 0; + *out = *src; + while (**src && !isspace(**src)) { + ++*src; + ++*len; + } + + return true; +} + +static bool TokenEq(const char* tok, int len, const char* str) +{ + return (int)strlen(str) == len && !strncmp(tok, str, len); +} + +static int TokenInt(const char* tok, int len) +{ + char tmp[64] = { }; + strncpy(tmp, tok, Min((int)sizeof(tmp), len)); + return atoi(tmp); +} + +static float TokenFloat(const char* tok, int len) +{ + char tmp[64] = { }; + strncpy(tmp, tok, Min((int)sizeof(tmp), len)); + return strtof(tmp, NULL); +} + +static void ParseShaderMetadata(ShaderMetadata* sm, const char* src) +{ + memset(sm, 0, sizeof(*sm)); + + while ((src = strchr(src, '@'))) { + // @model + if (!strncmp(src, "@model", 6)) { + src += 6; + + const char* tok; + int len; + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@model: Missing parameter"); + continue; + } + + if (TokenEq(tok, len, "quad")) { + sm->model = SHADER_MODEL_TYPE_QUAD; + } else if (TokenEq(tok, len, "cube")) { + sm->model = SHADER_MODEL_TYPE_CUBE; + } else { + SDL_Log("@model: Invalid parameter: %.*s", len, tok); + } + + continue; + } + + // @uniform + else if (!strncmp(src, "@uniform", 8)) { + src += 8; + + ShaderUniform u = { }; + + const char* tok; + int len; + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: Missing parameter"); + continue; + } + const char* type_tok = tok; + int type_len = len; + + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: Missing parameter"); + continue; + } + strncpy(u.name, tok, Min((int)sizeof(u.name), len)); + + // i min max + if (TokenEq(type_tok, type_len, "int")) { + u.type = SHADER_UNIFORM_TYPE_INT; + + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_i1 = TokenInt(tok, len); + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.min_i = TokenInt(tok, len); + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.max_i = TokenInt(tok, len); + } + // f + else if (TokenEq(type_tok, type_len, "float")) { + u.type = SHADER_UNIFORM_TYPE_FLOAT; + + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_fl = TokenFloat(tok, len); + } + // f f f f + else if (TokenEq(type_tok, type_len, "color")) { + u.type = SHADER_UNIFORM_TYPE_COLOR; + + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_v4.x = TokenFloat(tok, len); + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_v4.y = TokenFloat(tok, len); + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_v4.z = TokenFloat(tok, len); + if (!ReadToken(&src, &tok, &len)) { + SDL_Log("@uniform: No value"); + continue; + } + u.val_v4.w = TokenFloat(tok, len); + } else { + SDL_Log("@uniform: Unsupported type: %.*s", type_len, type_tok); + continue; + } + + sm->uniforms.push_back(u); + + continue; + } + } +} + +// -------------------------------------------------------------------------------- +// Application +// -------------------------------------------------------------------------------- + +static struct +{ + SDL_Window* wnd; + ShaderMetadata sm; + bool ui_hidden; + + GLuint shader; + + float cam_dist = 3.5f; + Vec2 cam_ang = Vec2(0, 0); + + GLuint vao; + GLuint vbo; + GLuint ibo; + int ibo_len; +} G = { }; + +static inline bool Is3D() +{ + return G.sm.model == SHADER_MODEL_TYPE_CUBE; +} + +// Helper: Report errors via glGetError() after every OpenGL function call. +// macOS does not support glDebugMessageCallback +static void AssertGL(GLenum error, const char* expr, int line) +{ + if (error != GL_NO_ERROR) { + const char* error_string; + switch (error) { +#define BIND_ERROR(name) case name: { error_string = #name; }; break; + BIND_ERROR(GL_INVALID_ENUM); + BIND_ERROR(GL_INVALID_VALUE); + BIND_ERROR(GL_INVALID_OPERATION); + BIND_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); + BIND_ERROR(GL_OUT_OF_MEMORY); +#undef BIND_ERROR + default: SDL_assert(0 && "Invalid GL_ERROR enum"); + } + SDL_Log("[Line #%u] %s caused %s\n", line, expr, error_string); + } +} +#define GL(expr) \ + expr; \ + for (GLenum _glcode; (_glcode = glGetError()) != GL_NO_ERROR; ) { \ + AssertGL(_glcode, #expr, __LINE__); \ + } + +SDL_AppResult SDLCALL SDL_AppInit(void**, int argc, char* argv[]) +{ + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + const char* path = (argc > 1) ? argv[1] : NULL; + if (!path) { + SDL_Log("No shader file supplied"); + return SDL_APP_FAILURE; + } + + char* src = (char*)SDL_LoadFile(path, NULL); + if (!src) { + SDL_Log("Failed to load %s: %s", path, SDL_GetError()); + return SDL_APP_FAILURE; + } + + ParseShaderMetadata(&G.sm, src); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); + + char title[64]; snprintf(title, sizeof(title), "shaders: %s", path); + Uint32 wflags = SDL_WINDOW_OPENGL | + SDL_WINDOW_HIGH_PIXEL_DENSITY | + SDL_WINDOW_RESIZABLE; + G.wnd = SDL_CreateWindow(title, 1024, 768, wflags); + if (!G.wnd) { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_GL_CreateContext(G.wnd)) { + SDL_Log("Failed to create OpenGL context: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_GL_SetSwapInterval(1); + + if (!gladLoadGL(SDL_GL_GetProcAddress)) { + SDL_Log("Failed to load OpenGL functions"); + return SDL_APP_FAILURE; + } + + GL(glGenVertexArrays(1, &G.vao)); + GL(glBindVertexArray(G.vao)); + + GL(glGenBuffers(1, &G.vbo)); + GL(glBindBuffer(GL_ARRAY_BUFFER, G.vbo)); + + GL(glGenBuffers(1, &G.ibo)); + GL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, G.ibo)); + + switch (G.sm.model) { + case SHADER_MODEL_TYPE_QUAD: { + // + // 0 1 + // +------------+ + // | | + // | | + // | | + // | | + // | 3 | 2 + // +------------+ + // + struct V { Vec2 p; Vec2 t; }; + const V vdata[] = { + { Vec2(-1.0f, 1.0f), Vec2(0, 1) }, + { Vec2( 1.0f, 1.0f), Vec2(1, 1) }, + { Vec2( 1.0f, -1.0f), Vec2(1, 0) }, + { Vec2(-1.0f, -1.0f), Vec2(0, 0) }, + }; + const uint16_t idata[] = { + 0, 1, 2, + 0, 2, 3, + }; + GL(glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STATIC_DRAW)); + GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idata), idata, GL_STATIC_DRAW)); + GL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, p))); + GL(glEnableVertexAttribArray(0)); + GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, t))); + GL(glEnableVertexAttribArray(1)); + G.ibo_len = sizeof(idata) / sizeof(*idata); + } break; + case SHADER_MODEL_TYPE_CUBE: { + // + // +------------+ + // /| /| + // / | / | + // +------------+ | + // | | | | + // | | | | + // | +---------|--+ + // | / | / + // |/ |/ + // +------------+ + // + struct V { Vec3 p; Vec2 t; Vec3 n; }; + const V vdata[] = { + // Front + { Vec3(-1, 1, 1), Vec2(0, 1), Vec3(0, 0, 1) }, + { Vec3( 1, 1, 1), Vec2(1, 1), Vec3(0, 0, 1) }, + { Vec3( 1, -1, 1), Vec2(1, 0), Vec3(0, 0, 1) }, + { Vec3(-1, -1, 1), Vec2(0, 0), Vec3(0, 0, 1) }, + // Right + { Vec3( 1, 1, 1), Vec2(0, 1), Vec3(1, 0, 0) }, + { Vec3( 1, 1, -1), Vec2(1, 1), Vec3(1, 0, 0) }, + { Vec3( 1, -1, -1), Vec2(1, 0), Vec3(1, 0, 0) }, + { Vec3( 1, -1, 1), Vec2(0, 0), Vec3(1, 0, 0) }, + // Back + { Vec3( 1, 1, -1), Vec2(0, 1), Vec3(0, 0, -1) }, + { Vec3(-1, 1, -1), Vec2(1, 1), Vec3(0, 0, -1) }, + { Vec3(-1, -1, -1), Vec2(1, 0), Vec3(0, 0, -1) }, + { Vec3( 1, -1, -1), Vec2(0, 0), Vec3(0, 0, -1) }, + // Left + { Vec3(-1, 1, -1), Vec2(0, 1), Vec3(-1, 0, 0) }, + { Vec3(-1, 1, 1), Vec2(1, 1), Vec3(-1, 0, 0) }, + { Vec3(-1, -1, 1), Vec2(1, 0), Vec3(-1, 0, 0) }, + { Vec3(-1, -1, -1), Vec2(0, 0), Vec3(-1, 0, 0) }, + // Top + { Vec3(-1, 1, -1), Vec2(0, 1), Vec3(0, 1, 0) }, + { Vec3( 1, 1, -1), Vec2(1, 1), Vec3(0, 1, 0) }, + { Vec3( 1, 1, 1), Vec2(1, 0), Vec3(0, 1, 0) }, + { Vec3(-1, 1, 1), Vec2(0, 0), Vec3(0, 1, 0) }, + // Bottom + { Vec3(-1, -1, 1), Vec2(0, 1), Vec3(0, -1, 0) }, + { Vec3( 1, -1, 1), Vec2(1, 1), Vec3(0, -1, 0) }, + { Vec3( 1, -1, -1), Vec2(1, 0), Vec3(0, -1, 0) }, + { Vec3(-1, -1, -1), Vec2(0, 0), Vec3(0, -1, 0) }, + }; + const uint16_t idata[] = { + 0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11, + 12, 13, 14, + 12, 14, 15, + 16, 17, 18, + 16, 18, 19, + 20, 21, 22, + 20, 22, 23, + }; + GL(glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STATIC_DRAW)); + GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idata), idata, GL_STATIC_DRAW)); + GL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, p))); + GL(glEnableVertexAttribArray(0)); + GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, t))); + GL(glEnableVertexAttribArray(1)); + GL(glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, n))); + GL(glEnableVertexAttribArray(2)); + G.ibo_len = sizeof(idata) / sizeof(*idata); + } break; + default: return SDL_APP_FAILURE; + } + + const char* vs_table[] = { + NULL, + "vert_quad.glsl", + "vert_cube.glsl", + }; + char* vs_src = (char*)SDL_LoadFile(vs_table[G.sm.model], NULL); + if (!vs_src) { + SDL_Log("Failed to load vertex shader %s: %s", vs_table[G.sm.model], SDL_GetError()); + return SDL_APP_FAILURE; + } + char* fs_src = src; + + struct { + GLenum type; + const char* src; + } shaders[] = { + { .type = GL_VERTEX_SHADER, .src = vs_src }, + { .type = GL_FRAGMENT_SHADER, .src = fs_src }, + }; + G.shader = GL(glCreateProgram()); + for (size_t i = 0; i < sizeof(shaders) / sizeof(*shaders); ++i) { + GLuint shader = GL(glCreateShader(shaders[i].type)); + GL(glShaderSource(shader, 1, &shaders[i].src, NULL)); + GL(glCompileShader(shader)); + GLint compile_status = 0; + GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status)); + if (compile_status != GL_TRUE) { + char error[1024] = { 0 }; + GL(glGetShaderInfoLog(shader, sizeof(error), NULL, error)); + SDL_Log("Error compiling shader %s", error); + return SDL_APP_FAILURE; + } + GL(glAttachShader(G.shader, shader)); + } + GL(glLinkProgram(G.shader)); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui_ImplSDL3_Init(G.wnd, 0, 0); + ImGui_ImplOpenGL3_Init(); + ImGui::GetIO().IniFilename = NULL; + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDLCALL SDL_AppIterate(void*) +{ + int wnd_x = 0; + int wnd_y = 0; + SDL_GetWindowSizeInPixels(G.wnd, &wnd_x, &wnd_y); + + GL(glViewport(0, 0, wnd_x, wnd_y)); + GL(glEnable(GL_CULL_FACE)); + GL(glCullFace(GL_FRONT)); + GL(glEnable(GL_DEPTH_TEST)); + GL(glEnable(GL_BLEND)); + GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + GL(glClearColor(0.1f, 0.1f, 0.1f, 1.0f)); + GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + + GL(glUseProgram(G.shader)); + GL(glBindVertexArray(G.vao)); + + GLint u_id = -1; + if ((u_id = glGetUniformLocation(G.shader, "u_res")) != -1) { + GL(glUniform2f(u_id, (float)wnd_x, (float)wnd_y)); + } + if ((u_id = glGetUniformLocation(G.shader, "u_time")) != -1) { + GL(glUniform1f(u_id, SDL_GetTicks() / 1e3f)); + } + + if (Is3D()) { + const Vec3 cam = Vec3::FromEuler(G.cam_ang) * G.cam_dist; + const Vec3 tgt = Vec3(0, 0, 0); + const float fov = 75.0f; + + Mat4x4 m_look = Mat4x4::LookAt(cam, tgt, Vec3(0, 1, 0)); + Mat4x4 m_proj = Mat4x4::Perspective(fov, (float)wnd_x / (float)wnd_y, 0.1f, 100.0f); + Mat4x4 m_view = m_proj * m_look; + + if ((u_id = glGetUniformLocation(G.shader, "u_vmat")) == -1) { + SDL_Log("Warning: u_vmat uniform not found"); + } else { + GL(glUniformMatrix4fv(u_id, 1, GL_FALSE, m_view.Base())); + } + + if ((u_id = glGetUniformLocation(G.shader, "u_cam")) != -1) { + GL(glUniform3f(u_id, cam.x, cam.y, cam.z)); + } + } + + for (auto& u : G.sm.uniforms) { + if ((u_id = glGetUniformLocation(G.shader, u.name)) == -1) { + SDL_Log("Warning: Couldn't find a uniform with the name %s", u.name); + continue; + } + switch (u.type) { + case SHADER_UNIFORM_TYPE_INT: { + GL(glUniform1i(u_id, u.val_i1)); + } break; + case SHADER_UNIFORM_TYPE_FLOAT: { + GL(glUniform1f(u_id, u.val_fl)); + } break; + case SHADER_UNIFORM_TYPE_COLOR: { + GL(glUniform4f(u_id, u.val_v4.x, u.val_v4.y, u.val_v4.z, u.val_v4.w)); + } break; + } + } + + GL(glDrawElements(GL_TRIANGLES, G.ibo_len, GL_UNSIGNED_SHORT, NULL)); + + ImGui_ImplSDL3_NewFrame(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); + + int wnd_height = 0; + SDL_GetWindowSize(G.wnd, NULL, &wnd_height); + if (!G.ui_hidden) { + ImGui::SetNextWindowPos(ImVec2(15.0f, 15.0f), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(200.0f, wnd_height - 30.0f), ImGuiCond_Once); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize; + if (ImGui::Begin("Test", NULL, flags)) { + ImGui::SeparatorText("Controls"); + ImGui::Text("Tab: Toggle UI"); + if (G.sm.uniforms.size() > 0) { + ImGui::SeparatorText("Uniforms"); + for (auto& u : G.sm.uniforms) { + switch (u.type) { + case SHADER_UNIFORM_TYPE_INT: { + ImGui::SliderInt(u.name, &u.val_i1, u.min_i, u.max_i); + } break; + case SHADER_UNIFORM_TYPE_FLOAT: { + ImGui::DragFloat(u.name, &u.val_fl, 0.01f); + } break; + case SHADER_UNIFORM_TYPE_COLOR: { + ImGui::ColorEdit4(u.name, &u.val_v4.x); + } break; + } + } + } + } + ImGui::End(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + if (!SDL_GL_SwapWindow(G.wnd)) { + SDL_Log("Failed to present window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDLCALL SDL_AppEvent(void*, SDL_Event* event) +{ + ImGui_ImplSDL3_ProcessEvent(event); + switch (event->type) { + case SDL_EVENT_KEY_DOWN: { + if (event->key.key == SDLK_TAB) { + G.ui_hidden = !G.ui_hidden; + } + } break; + case SDL_EVENT_MOUSE_MOTION: { + if (Is3D() && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_MASK(SDL_BUTTON_LEFT))) { + const float sens = 0.4f; + G.cam_ang.x -= event->motion.xrel * sens; + G.cam_ang.y += event->motion.yrel * sens; + G.cam_ang.y = Clamp(G.cam_ang.y, -89.0f, 89.0f); + } + } break; + case SDL_EVENT_MOUSE_WHEEL: { + if (Is3D()) { + G.cam_dist -= event->wheel.y * 0.1f; + G.cam_dist = Max(G.cam_dist, 1.0f); + } + } break; + case SDL_EVENT_QUIT: { + return SDL_APP_SUCCESS; + } break; + } + return SDL_APP_CONTINUE; +} + +void SDLCALL SDL_AppQuit(void*, SDL_AppResult) +{ + SDL_DestroyWindow(G.wnd); +} diff --git a/shaders/polygon.glsl b/shaders/polygon.glsl new file mode 100644 index 0000000..e33aaf7 --- /dev/null +++ b/shaders/polygon.glsl @@ -0,0 +1,64 @@ +#version 330 core + +// -------------------------------------------------------------------------------- +// Regular polygon shader +// +// ref: https://thndl.com/square-shaped-shaders.html +// ref: https://thebookofshaders.com/07/ +// +// @model quad +// @uniform int u_n 3 3 12 +// @uniform color u_bg 0 0 0 1 +// @uniform color u_fg 1 1 1 1 +// +// SPDX-License-Identifier: 0BSD +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Uniforms +// -------------------------------------------------------------------------------- +uniform float u_time; +uniform vec2 u_res; +uniform int u_n; +uniform vec4 u_bg; +uniform vec4 u_fg; + +// -------------------------------------------------------------------------------- +// Vertex outputs +// -------------------------------------------------------------------------------- +in vec2 v_p; +in vec2 v_t; + +// -------------------------------------------------------------------------------- +// Fragment outputs +// -------------------------------------------------------------------------------- +out vec4 f_c; + +// -------------------------------------------------------------------------------- +// Entry point +// -------------------------------------------------------------------------------- + +#define PI 3.14159265359 + +void main() +{ + // Centered with square aspect ratio + vec2 st = gl_FragCoord.xy / u_res.xy; + float aspect = u_res.x / u_res.y; + if (aspect > 1.0f) { st.x = (st.x - 0.5f) * aspect + 0.5f; } + else { st.y = (st.y - 0.5f) / aspect + 0.5f; } + + // Remap [0, 1] to [-1, 1] + st = st * 2.0f - 1.0f; + + // Angle of pixel from center + rotation + float a = atan(st.y, st.x) + u_time * 0.2f; + + // Angle of each sector from center + float r = (PI * 2.0f) / float(u_n); + + // Polar polygon edge test. cos(theta) = dot product. + float d = cos(floor(0.5f + a / r) * r - a) * length(st); + + f_c = vec4(vec3(mix(u_bg, u_fg, 1.0f - step(0.5f, d))), 1.0f); +} diff --git a/shaders/vert_cube.glsl b/shaders/vert_cube.glsl new file mode 100644 index 0000000..629b5cd --- /dev/null +++ b/shaders/vert_cube.glsl @@ -0,0 +1,31 @@ +#version 330 core + +// -------------------------------------------------------------------------------- +// Uniforms +// -------------------------------------------------------------------------------- +uniform mat4 u_vmat; + +// -------------------------------------------------------------------------------- +// Vertex inputs +// -------------------------------------------------------------------------------- +layout (location = 0) in vec3 v_in_p; +layout (location = 1) in vec2 v_in_t; +layout (location = 2) in vec3 v_in_n; + +// -------------------------------------------------------------------------------- +// Vertex outputs +// -------------------------------------------------------------------------------- +out vec3 v_p; +out vec2 v_t; +out vec3 v_n; + +// -------------------------------------------------------------------------------- +// Main +// -------------------------------------------------------------------------------- +void main() +{ + v_p = v_in_p; + v_t = v_in_t; + v_n = v_in_n; + gl_Position = u_vmat * vec4(v_p, 1.0f); +} diff --git a/shaders/vert_quad.glsl b/shaders/vert_quad.glsl new file mode 100644 index 0000000..611ae61 --- /dev/null +++ b/shaders/vert_quad.glsl @@ -0,0 +1,23 @@ +#version 330 core + +// -------------------------------------------------------------------------------- +// Vertex inputs +// -------------------------------------------------------------------------------- +layout (location = 0) in vec2 v_in_p; +layout (location = 1) in vec2 v_in_t; + +// -------------------------------------------------------------------------------- +// Vertex outputs +// -------------------------------------------------------------------------------- +out vec2 v_p; +out vec2 v_t; + +// -------------------------------------------------------------------------------- +// Main +// -------------------------------------------------------------------------------- +void main() +{ + v_p = v_in_p; + v_t = v_in_t; + gl_Position = vec4(v_p.x, v_p.y, -1.0f, 1.0f); +} diff --git a/shaders/vfog.glsl b/shaders/vfog.glsl new file mode 100644 index 0000000..f638b27 --- /dev/null +++ b/shaders/vfog.glsl @@ -0,0 +1,131 @@ +#version 330 core + +// -------------------------------------------------------------------------------- +// Volumetric fog +// +// @model cube +// @uniform int u_mode 0 0 2 +// @uniform int u_steps 8 1 64 +// @uniform float u_sigma 4.0f +// +// SPDX-License-Identifier: 0BSD +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Uniforms +// -------------------------------------------------------------------------------- +uniform float u_time; +uniform vec3 u_cam; +uniform int u_mode; +uniform int u_steps; +uniform float u_sigma; + +// -------------------------------------------------------------------------------- +// Vertex outputs +// -------------------------------------------------------------------------------- +in vec3 v_p; +in vec2 v_t; +in vec3 v_n; + +// -------------------------------------------------------------------------------- +// Fragment outputs +// -------------------------------------------------------------------------------- +out vec4 f_c; + +// ----------------------------------------------------------------------------- +// Ray-AABB intersection +// ref: https://tavianator.com/2022/ray_box_boundary.html +// ----------------------------------------------------------------------------- +bool intersect(vec3 ro, vec3 rd, vec3 bbmin, vec3 bbmax, out float t0, out float t1) +{ + vec3 inv = 1.0f / rd; + vec3 ta = (bbmin - ro) * inv; + vec3 tb = (bbmax - ro) * inv; + vec3 tmin = min(ta, tb); + vec3 tmax = max(ta, tb); + t0 = max(max(tmin.x, tmin.y), tmin.z); + t1 = min(min(tmax.x, tmax.y), tmax.z); + return t1 >= max(t0, 0.0f); +} + +// ----------------------------------------------------------------------------- +// Mode 0: Basic volumetric fog +// ----------------------------------------------------------------------------- +float sample_basic(vec3 pos) +{ + return 1.0; +} + +// ----------------------------------------------------------------------------- +// Mode 1: Fade along y axis +// ----------------------------------------------------------------------------- +float sample_vfade(vec3 pos) +{ + return 1.0f - pos.y; +} + +// ----------------------------------------------------------------------------- +// Mode 2: Some waves +// ----------------------------------------------------------------------------- +float sample_waves(vec3 pos) +{ + // Fractal sum + // ref: https://thebookofshaders.com/13/ + float h = 0.75f; // baseline + + // Big waves + h += 0.08f * sin(pos.x * 2.0f + u_time); + h += 0.08f * cos(pos.z * 2.0f + u_time * -1.1f); + + // Medium waves + h += 0.05f * sin(pos.x * 5.0f + u_time * 1.7f); + h += 0.05f * cos(pos.z * 4.0f + u_time * -2.0f); + + // Small but choppy waves + h += 0.03f * sin(pos.x * 12.0f + u_time * 3.3f); + h += 0.03f * cos(pos.z * 11.0f + u_time * -2.5f); + + return float(pos.y < h); +} + +// -------------------------------------------------------------------------------- +// Entry point +// -------------------------------------------------------------------------------- +void main() +{ + vec3 bbmin = vec3(-1.0f); + vec3 bbmax = vec3( 1.0f); + + vec3 ro = u_cam; + vec3 rd = normalize(v_p - ro); + + float t0 = 0.0f; + float t1 = 0.0f; + if (!intersect(ro, rd, bbmin, bbmax, t0, t1)) { + f_c = vec4(0.0f, 0.0f, 0.0f, 0.0f); + return; + } + + // Clamp entry point in front of camera + float t_enter = max(t0, 0.0f); + float t_exit = t1; + float t_len = max(0.0f, t_exit - t_enter); + + float density = 0; + for (int i = 0; i < u_steps; ++i) { + float t = mix(t_enter, t_exit, (float(i) + 0.5f) / float(u_steps)); + vec3 p = ro + rd * t; + switch (u_mode) { + case 0: { density += sample_basic(p); } break; + case 1: { density += sample_vfade(p); } break; + case 2: { density += sample_waves(p); } break; + } + } + density /= float(u_steps); + + // Beer-Lambert attenuation + // ref: https://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law + float a = 1.0f - exp(-u_sigma * t_len * density); + + f_c = vec4(vec3(1.0f), a); +} \ No newline at end of file -- cgit v1.2.3