diff --git a/Makefile b/Makefile index 2d9af9f..ba0100b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SHADER_DIR := shaders BUILD_DIR := build CXX := g++ -CXXFLAGS := -g -std=c++20 -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-unused-function +CXXFLAGS := -g -std=c++23 -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-unused-function LDFLAGS := -lSDL3 GLSLC := glslc diff --git a/shaders/post.frag b/shaders/post.frag index c54e372..e994cff 100644 --- a/shaders/post.frag +++ b/shaders/post.frag @@ -39,11 +39,51 @@ float f(mat3 kernel) { } +float brightness(vec3 color) { + return dot(color, vec3(0.299, 0.587, 0.114)); +} + +float dither() { + if(length(texture(screen, uv).xyz) == 0.0) + return 0.0; + vec2 coord = uv * textureSize(screen, 0).xy; + + int x = int(mod(coord.x, 4.0)); + int y = int(mod(coord.y, 4.0)); + int index = x + y * 4; + + float threshold[16] = float[]( + 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, + 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, + 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, + 15.0/16.0,7.0/16.0, 13.0/16.0, 5.0/16.0 + ); + + vec3 color = texture(screen, uv).rgb; + + return brightness(color) < threshold[index] ? 0.0 : 1.0; +} + + void main() { const mat3 sobelx = mat3(-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0); const mat3 sobely = mat3(-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0); - out_color = texture(screen, uv) + 0.1 * ((t(uv.x, uv.y) > 0.01)? - vec4(abs(f(sobelx) + f(sobely)), 0.0, 0.0, 1.0) : vec4(0.0)); -} \ No newline at end of file + int time = int(fract(time_padded.x/25.0) * 30.0)/10; + switch(time) { + case 0: + out_color = texture(screen, uv); + break; + case 1: + out_color = vec4(brightness(texture(screen, uv).xyz) < 0.5? 0.0 : 1.0); + break; + case 2: + out_color = vec4(dither()); + break; + } + + out_color = out_color * 0.9 + + 0.1*((t(uv.x, uv.y) > 0.01)? + vec4(abs(f(sobelx) + f(sobely)), 0.0, 0.0, 1.0) : vec4(0.0)); +} diff --git a/shaders/ray.frag b/shaders/ray.frag index 01986e6..1860762 100644 --- a/shaders/ray.frag +++ b/shaders/ray.frag @@ -69,8 +69,6 @@ vec4 kernel(float max_depth) { return march(cam_pos.xyz, cam_ray, max_depth); } -float t(float x, float y) { return length(texture(screen, vec2(x, y)).xyz); } - float linearize_depth(vec2 uv) { float d = texture(depth_tex, uv).r; if (d >= 1.0) return 1000.0; @@ -80,31 +78,6 @@ float linearize_depth(vec2 uv) { return -view_pos.z; // view space, positive distance } -float brightness(vec3 color) { - return dot(color, vec3(0.299, 0.587, 0.114)); -} - -float dither() { - if(length(texture(screen, uv).xyz) == 0.0) - return 0.0; - vec2 coord = uv * textureSize(screen, 0).xy; - - int x = int(mod(coord.x, 4.0)); - int y = int(mod(coord.y, 4.0)); - int index = x + y * 4; - - float threshold[16] = float[]( - 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, - 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, - 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, - 15.0/16.0,7.0/16.0, 13.0/16.0, 5.0/16.0 - ); - - vec3 color = texture(screen, uv).rgb; - - return brightness(color) < threshold[index] ? 0.0 : 1.0; - -} void main() { float max_depth = linearize_depth(uv); @@ -112,18 +85,7 @@ void main() { if (hit.w > 0.0) { out_color = vec4(getnormal(hit.xyz), 1.0); } else { - int t = int(fract(time_padded.x/5.0) * 30.0)/10; - switch(t) { - case 0: - out_color = texture(screen, uv); - break; - case 1: - out_color = vec4(brightness(texture(screen, uv).xyz) < 0.5? 0.0 : 1.0); - break; - case 2: - out_color = vec4(dither()); - break; - } + out_color = texture(screen, uv); } } diff --git a/src/renderer.cpp b/src/renderer.cpp index 0967ef9..f15909e 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -248,7 +248,6 @@ void Renderer::add_gol_layer() { } } - gol(graph, frame); size_t src = ((frame >> 8) & 1) ^ 1; @@ -259,7 +258,7 @@ void Renderer::add_gol_layer() { if (graph[src][y * 256 + x]) { float s = 0.01f; - vec3 p = vec3(x + s * sinf((float)frame), height, y + cosf((float)frame)) * s * 3.0f; + vec3 p = vec3(-x - s * sinf((float)frame), height, -y - cosf((float)frame)) * s * 3.0f; vec3 v0 = p + vec3(-s, -s, 0); vec3 v1 = p + vec3( s, -s, 0); @@ -279,6 +278,7 @@ void Renderer::add_gol_layer() { } } } + if (vert_buff) SDL_ReleaseGPUBuffer(dev, vert_buff); SDL_GPUBufferCreateInfo info { diff --git a/src/sdf.cpp b/src/sdf.cpp new file mode 100644 index 0000000..c7d0521 --- /dev/null +++ b/src/sdf.cpp @@ -0,0 +1,499 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* converted form of the rayquake sdf parsing code I wrote with Caz, which was in Zig */ + +struct Expr; + +struct BinArgs { std::unique_ptr lhs, rhs; }; +struct SqrtArg { std::unique_ptr arg; }; +struct Constant { float val; }; +struct Input { unsigned idx; }; + +using ExprVariant = std::variant< + Constant, Input, + struct Add, struct Sub, struct Mul, struct Div, + struct Min, struct Max, struct Sqrt +>; + +struct Add { std::unique_ptr lhs, rhs; }; +struct Sub { std::unique_ptr lhs, rhs; }; +struct Mul { std::unique_ptr lhs, rhs; }; +struct Div { std::unique_ptr lhs, rhs; }; +struct Min { std::unique_ptr lhs, rhs; }; +struct Max { std::unique_ptr lhs, rhs; }; +struct Sqrt { std::unique_ptr arg; }; + +struct Expr { + std::variant v; + + template + Expr(T &&t) : v(std::forward(t)) {} + + Expr(Expr &&) = default; + Expr &operator=(Expr &&) = default; + Expr(const Expr &) = delete; + Expr &operator=(const Expr &) = delete; +}; + +static std::unique_ptr box(Expr e) { + return std::make_unique(std::move(e)); +} + + +Expr make_constant(float f) { return Expr{ Constant{ f } }; } +Expr make_input(unsigned i) { return Expr{ Input{ i } }; } + +Expr expr_add(Expr l, Expr r) { return Expr{ Add{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_sub(Expr l, Expr r) { return Expr{ Sub{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_mul(Expr l, Expr r) { return Expr{ Mul{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_div(Expr l, Expr r) { return Expr{ Div{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_min(Expr l, Expr r) { return Expr{ Min{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_max(Expr l, Expr r) { return Expr{ Max{ box(std::move(l)), box(std::move(r)) } }; } +Expr expr_sqrt(Expr a) { return Expr{ Sqrt{ box(std::move(a)) } }; } + + +/* deep copy */ +Expr clone(const Expr &e); + +template +std::unique_ptr clone_box(const std::unique_ptr &p) { + return box(clone(*p)); +} + +Expr clone(const Expr &e) { + return std::visit([](const auto &node) -> Expr { + using T = std::decay_t; + /* filter 0-ary */ + if constexpr (std::is_same_v || std::is_same_v) { + return Expr{ node }; + } else if constexpr (std::is_same_v) { + /* only unary */ + return expr_sqrt(clone(*node.arg)); + } else { + /* all binary nodes have lhs and rhs */ + using R = T; + return Expr{ R{ box(clone(*node.lhs)), box(clone(*node.rhs)) } }; + } + }, e.v); +} + +float expr_eval(const Expr &e, const float *vals) { + return std::visit([&](const auto &node) -> float { + using T = std::decay_t; + if constexpr (std::is_same_v) return node.val; + if constexpr (std::is_same_v) return vals[node.idx]; + if constexpr (std::is_same_v) return expr_eval(*node.lhs, vals) + expr_eval(*node.rhs, vals); + if constexpr (std::is_same_v) return expr_eval(*node.lhs, vals) - expr_eval(*node.rhs, vals); + if constexpr (std::is_same_v) return expr_eval(*node.lhs, vals) * expr_eval(*node.rhs, vals); + if constexpr (std::is_same_v) return expr_eval(*node.lhs, vals) / expr_eval(*node.rhs, vals); + if constexpr (std::is_same_v) return std::min(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals)); + if constexpr (std::is_same_v) return std::max(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals)); + if constexpr (std::is_same_v) return std::sqrt(expr_eval(*node.arg, vals)); + return 0.f; + }, e.v); +} + +template +struct overloads : Ts... { using Ts::operator()...; }; + +// symbolic differentiation +Expr expr_diff(const Expr &e, unsigned i) { + return std::visit([&](const auto &node) -> Expr { + using T = std::decay_t; + + /* Rules for diff: + * - Variable/Input: same? 1 : 0 + * - Constant: 0 + * - Addition/Subtraction: a' +/- b' + * - Mul: f'g + g'f + * - Div: (fg' - gf') / g^2 + * - Sqrt: (1/2)f' / sqrt(f) + * - Min/Max: Identity + */ + + if constexpr (std::is_same_v) + return make_constant(node.idx == i ? 1.f : 0.f); + + if constexpr (std::is_same_v) + return make_constant(0.f); + + if constexpr (std::is_same_v) + return expr_add(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i)); + + if constexpr (std::is_same_v) + return expr_sub(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i)); + + if constexpr (std::is_same_v) + return expr_add( + expr_mul(expr_diff(*node.lhs, i), clone(*node.rhs)), + expr_mul(expr_diff(*node.rhs, i), clone(*node.lhs)) + ); + + if constexpr (std::is_same_v) + return expr_div( + expr_add( + expr_mul(clone(*node.lhs), expr_diff(*node.rhs, i)), + expr_mul(expr_mul(clone(*node.rhs), expr_diff(*node.lhs, i)), + make_constant(-1.f)) + ), + expr_mul(clone(*node.rhs), clone(*node.rhs)) + ); + + if constexpr (std::is_same_v) + return expr_mul( + expr_diff(*node.arg, i), + expr_mul(make_constant(0.5f), + expr_div(make_constant(1.f), expr_sqrt(clone(*node.arg)))) + ); + + if constexpr (std::is_same_v || std::is_same_v) + return clone(e); + + return make_constant(0.f); + }, e.v); +} + + +struct Dual { float val, d; }; + + +/* Autodiff */ + +Dual expr_auto_diff(const Expr &e, const Dual *pos) { + return std::visit([&](const auto &node) -> Dual { + using T = std::decay_t; + + if constexpr (std::is_same_v) return pos[node.idx]; + if constexpr (std::is_same_v) return { node.val, 0.f }; + + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return { l.val + r.val, l.d + r.d }; + } + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return { l.val - r.val, l.d - r.d }; + } + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return { l.val * r.val, l.val * r.d + l.d * r.val }; + } + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return { l.val / r.val, (l.d * r.val - l.val * r.d) / (r.val * r.val) }; + } + if constexpr (std::is_same_v) { + auto v = expr_auto_diff(*node.arg, pos); + float sv = std::sqrt(v.val); + return { sv, v.d / (2.f * sv) }; + } + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return l.val < r.val ? l : r; + } + if constexpr (std::is_same_v) { + auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos); + return l.val > r.val ? l : r; + } + return { 0.f, 0.f }; + }, e.v); +} + + +void expr_ndiff(const Expr &e, const float *vals, float eps, float out[3]) { + for (int k = 0; k < 3; k++) { + float vp[3] = { vals[0], vals[1], vals[2] }; + float vm[3] = { vals[0], vals[1], vals[2] }; + vp[k] += eps; vm[k] -= eps; + out[k] = (expr_eval(e, vp) - expr_eval(e, vm)) / (2.f * eps); + } +} + +void expr_print(const Expr &e, std::ostream &os = std::cout) { + const auto visitor = overloads { + [&](const Input& node) { os << "x" << node.idx; }, + [&](const Constant& node) { os << node.val; }, + [&](const Sqrt& node) { os << "(sqrt "; expr_print(*node.arg, os); os << ")"; }, + [&](const auto& node) { + using T = std::decay_t; + if constexpr (std::is_same_v) { os << "x" << node.idx; return; } + if constexpr (std::is_same_v) { os << node.val; return; } + if constexpr (std::is_same_v) { + os << "(sqrt "; expr_print(*node.arg, os); os << ")"; return; + } + + const char *op = + std::is_same_v ? "+" : + std::is_same_v ? "-" : + std::is_same_v ? "*" : + std::is_same_v ? "/" : + std::is_same_v ? "min" : "max"; + + os << "(" << op << " "; + expr_print(*node.lhs, os); + os << " "; + expr_print(*node.rhs, os); + os << ")"; + } + }; + + std::visit(visitor, e.v); +} + +static size_t gen_glsl_step( + const Expr &e, + std::unordered_map &visited, + std::ostringstream &ss) +{ + auto it = visited.find(&e); + if (it != visited.end()) return it->second; + + size_t idx = visited.size(); + visited[&e] = idx; + + std::ostringstream tmp; + + const auto visitor = overloads { + [&](const Constant& node) -> std::string { + tmp << "vec2(" << node.val << ", 0.0)"; + return tmp.str(); + }, + [&](const Input& node)-> std::string { + switch (node.idx) { + case 0: return "vec2(v.x, vp.x)"; + case 1: return "vec2(v.y, vp.y)"; + case 2: return "vec2(v.z, vp.z)"; + default: return "vec2(0.0, 0.0)"; + } + }, + [&](const Sqrt& node) -> std::string { + size_t a = gen_glsl_step(*node.arg, visited, ss); + tmp << "nsqrt(_" << a << ")"; + return tmp.str(); + }, + [&](const auto& node) -> std::string { + /* binops */ + size_t l = gen_glsl_step(*node.lhs, visited, ss); + size_t r = gen_glsl_step(*node.rhs, visited, ss); + + using T = std::decay_t; + + const char *fn = + std::is_same_v ? nullptr : + std::is_same_v ? nullptr : + std::is_same_v ? "nmul" : + std::is_same_v ? "ndiv" : + std::is_same_v ? "nmin" : "nmax"; + + if constexpr (std::is_same_v) + tmp << "(_" << l << " + _" << r << ")"; + else if constexpr (std::is_same_v) + tmp << "(_" << l << " - _" << r << ")"; + else + tmp << fn << "(_" << l << ", _" << r << ")"; + + return tmp.str(); + } + }; + + std::string rhs = std::visit(visitor, e.v); + + ss << "vec2 _" << idx << " = " << rhs << ";\n"; + return idx; +} + +std::string gen_glsl(const Expr &e) { + std::unordered_map visited; + std::ostringstream ss; + size_t last = gen_glsl_step(e, visited, ss); + ss << "return _" << last << ";"; + return ss.str(); +} + + +static void skip_ws(const std::string &s, size_t &i) { + while (i < s.size() && std::isspace((unsigned char)s[i])) i++; +} + +static bool is_op_char(char c) { + return std::isalpha((unsigned char)c) || std::string("+-/*").find(c) != std::string::npos; +} + +static std::string parse_op_str(const std::string &s, size_t &i) { + size_t start = i; + while (i < s.size() && is_op_char(s[i])) i++; + return s.substr(start, i - start); +} + +static Expr parse_arg(const std::string &s, size_t &i, + std::unordered_map &vm); + +static Expr parse_iden(const std::string &s, size_t &i, + std::unordered_map &vm) { + size_t start = i; + while (i < s.size() && std::isalnum((unsigned char)s[i])) i++; + std::string name = s.substr(start, i - start); + auto [it, inserted] = vm.emplace(name, (unsigned)vm.size()); + return make_input(it->second); +} + +static Expr parse_num(const std::string &s, size_t &i) { + bool neg = false; + if (i < s.size() && s[i] == '-') { neg = true; ++i; } + float num = 0.f; + int frac_digits = 0; + size_t start = i; + bool saw_digit = false; + + + while (i < s.size() && std::isdigit((unsigned char)s[i])) { + saw_digit = true; + num = num * 10.f + (s[i] - '0'); + ++i; + } + + if (i < s.size() && s[i] == '.') { + ++i; + while (i < s.size() && std::isdigit((unsigned char)s[i])) { + saw_digit = true; + num = num * 10.f + (s[i] - '0'); + ++i; + ++frac_digits; + } + } + + if (!saw_digit) { + return make_constant(0.0f); + } + + if (frac_digits > 0) { + num /= std::pow(10.f, (float)frac_digits); + } + + if (neg) num = -num; + return make_constant(num); +} + + + +static Expr parse_expr(const std::string &s, size_t &i, + std::unordered_map &vm) { + skip_ws(s, i); + assert(s[i] == '('); i++; + skip_ws(s, i); + + std::string op = parse_op_str(s, i); + + Expr e = [&]() -> Expr { + if (op == "sqrt") return expr_sqrt(parse_arg(s, i, vm)); + Expr lhs = parse_arg(s, i, vm); + Expr rhs = parse_arg(s, i, vm); + if (op == "+") return expr_add(std::move(lhs), std::move(rhs)); + if (op == "-") return expr_sub(std::move(lhs), std::move(rhs)); + if (op == "*") return expr_mul(std::move(lhs), std::move(rhs)); + if (op == "/") return expr_div(std::move(lhs), std::move(rhs)); + if (op == "min") return expr_min(std::move(lhs), std::move(rhs)); + if (op == "max") return expr_max(std::move(lhs), std::move(rhs)); + std::cerr << "Unknown op: " << op << "\n"; std::exit(1); + }(); + + skip_ws(s, i); + assert(s[i] == ')'); i++; + return e; +} + +static Expr parse_arg(const std::string &s, size_t &i, + std::unordered_map &vm) { + skip_ws(s, i); + char c = s[i]; + if (c == '(') return parse_expr(s, i, vm); + if (std::isalpha((unsigned char)c)) return parse_iden(s, i, vm); + if (std::isdigit((unsigned char)c) || c == '-' || c == '.') + return parse_num(s, i); + std::cerr << "Unexpected char '" << c << "'\n"; std::exit(1); +} + +Expr expr_parse(const std::string &src) { + std::unordered_map vm; + size_t i = 0; + skip_ws(src, i); + return parse_expr(src, i, vm); +} + + +Expr sdf_length(Expr pos[3]) { + return expr_sqrt( + expr_add( + expr_mul(clone(pos[0]), clone(pos[0])), + expr_add( + expr_mul(clone(pos[1]), clone(pos[1])), + expr_mul(clone(pos[2]), clone(pos[2])) + ) + ) + ); +} + +Expr sdf_sphere(Expr pos[3], float radius) { + return expr_sub(sdf_length(pos), make_constant(radius)); +} + +Expr sdf_box(Expr p[3], float b[3]) { + Expr q[3] = { + expr_sub(expr_sqrt(expr_mul(clone(p[0]), clone(p[0]))), make_constant(b[0])), + expr_sub(expr_sqrt(expr_mul(clone(p[1]), clone(p[1]))), make_constant(b[1])), + expr_sub(expr_sqrt(expr_mul(clone(p[2]), clone(p[2]))), make_constant(b[2])), + }; + Expr inner[3] = { + expr_max(clone(q[0]), make_constant(0.f)), + expr_max(clone(q[1]), make_constant(0.f)), + expr_max(clone(q[2]), make_constant(0.f)), + }; + return expr_add( + sdf_length(inner), + expr_min( + expr_max(clone(q[0]), expr_max(clone(q[1]), clone(q[2]))), + make_constant(0.f) + ) + ); +} + +/* +int main() { + Expr x = make_input(0), y = make_input(1), z = make_input(2); + Expr x2 = expr_mul(clone(x), clone(x)); + Expr y2 = expr_mul(clone(y), clone(y)); + Expr z2 = expr_mul(clone(z), clone(z)); + Expr l = expr_sqrt(expr_add(expr_add(std::move(x2), std::move(y2)), std::move(z2))); + Expr dl = expr_diff(l, 0); + + expr_print(l); std::cout << "\n"; + expr_print(dl); std::cout << "\n"; + + float v[3] = { 0.5f, 0.3f, 0.2f }; + std::cout << expr_eval(l, v) << "\n"; + + std::string src = + "(min" + " (- y 1)" + " (max" + " (max x (+ -9 (* -1 x)))" + " (* -1 (- (sqrt (+ (+ (* x x) (* y y)) (* z z))) 15))" + " ))"; + + Expr parsed = expr_parse(src); + expr_print(parsed); + std::cout << "\n"; + + std::cout << "\nGLSL:\n" << gen_glsl(l) << "\n"; + + return 0; +} +*/