Setting up support multiple shaders. Adding menu settings.

This commit is contained in:
2025-05-04 06:26:13 -05:00
parent d93e6b6b90
commit 89f46b6f5a
3 changed files with 220 additions and 107 deletions

179
main.cpp
View File

@@ -43,17 +43,24 @@ std::vector<std::string> string_expand(const std::string& input, char delimiter)
using namespace JUI::UDimLiterals;
enum class Fractal {
MandelbrotSet, JuliaSet
};
class ReShaderProgram : public ReWindow::OpenGLWindow {
class FractalInspectorApp : public ReWindow::OpenGLWindow {
public:
JUI::Scene *scene;
JUI::UtilityBar* toolbar;
JUI::CommandLine *console;
JUI::Window* info_dialog;
JUI::TextRect* fps_label;
JUI::Window* mandelbrotset_dialog;
JUI::Window* juliaset_dialog;
JUI::Window* colorpicker_window;
JGL::Shader shader;
JGL::Shader mandelbrot_shader;
JGL::Shader julia_shader;
JGL::RenderTarget *canvas;
float u_time;
float u_scale = 2.f;
@@ -62,42 +69,35 @@ public:
Color4 u_rgb_2 = Colors::Reds::LightCoral;
Color4 u_rgb_3 = Colors::Yellow;
Color4 u_rgb_4 = Colors::Green;
std::string frag_name;
std::string vert_name;
Vector2 u_julia_set {0,0};
Vector2 u_julia_value {0,0};
ReShaderProgram();
Fractal current_fractal = Fractal::MandelbrotSet;
FractalInspectorApp();
/// Returns a full-path file name for a GLSL vertex shader, from a given prefix name.
static std::filesystem::path VertexShaderFilepathFromPrefixName(const std::string &name)
{
return "../shaders/" + name + ".vert.glsl";
}
/// Returns a full-path file name for a GLSL fragment shader, from a given prefix name.
static std::filesystem::path FragmentShaderFilepathFromPrefixName(const std::string &name)
{
return "../shaders/" + name + ".frag.glsl";
}
void LoadShader(const std::string& name)
{
frag_name = name;
vert_name = name;
shader = Shader(VertexShaderFilepathFromPrefixName(name), FragmentShaderFilepathFromPrefixName(name));
}
void LoadShaders() {
void LoadShader(const std::string& vertex_name, const std::string& fragment_name)
{
this->vert_name = vertex_name;
this->frag_name = fragment_name;
shader = Shader(VertexShaderFilepathFromPrefixName(vert_name), FragmentShaderFilepathFromPrefixName(frag_name));
}
// TODO: Come up with a decent way to bake the shader program sources into the executable, when compiling a release build!
void LoadShaderWithDefaultVertex(const std::string& name) {
this->frag_name = name;
shader = Shader(VertexShaderFilepathFromPrefixName(vert_name), FragmentShaderFilepathFromPrefixName(name));
mandelbrot_shader = Shader(VertexShaderFilepathFromPrefixName("test"), FragmentShaderFilepathFromPrefixName("mandelbrot"));
julia_shader = Shader(VertexShaderFilepathFromPrefixName("test" ), FragmentShaderFilepathFromPrefixName("julia"));
}
void ReloadShader() {
LoadShader(vert_name, frag_name);
LoadShaders();
}
using CmdArgs = std::vector<std::string>;
@@ -132,22 +132,6 @@ public:
if (cmd == "r" || cmd == "reload") {
ReloadShader();
} else if (cmd == "lf") {
if (tokens.size() > 1) {
std::string fragment = tokens[1];
// TODO: Check for file in question.
LoadShaderWithDefaultVertex(fragment);
return;
}
console->Log("Error: Command syntax is lf <fragment-shader-name>", Colors::Red);
} else if (cmd == "ls") {
if (tokens.size() > 1) {
std::string shader = tokens[1];
// TODO: Check for file in question.
LoadShader(shader);
return;
}
console->Log("Error: Command syntax is ls <shader-name>", Colors::Red);
} else if (cmd == "pos") {
return PositionCmd(tokens);
} else {
@@ -155,11 +139,49 @@ public:
}
}
void LoadJulia() {
juliaset_dialog->Open();
}
void UnloadJulia() {
juliaset_dialog->Close();
}
void CreateMenu() {
using namespace JUI::UDimLiterals;
toolbar = new JUI::UtilityBar(scene);
toolbar->AddSubmenu("File"); toolbar->AddSubmenu("Edit");
auto* fractals_list = toolbar->AddSubmenu("Fractals");
fractals_list->AddButton("Mandelbrot set", [this]{
UnloadJulia();
current_fractal = Fractal::MandelbrotSet;
});
fractals_list->AddButton("Julia set", [this]{
LoadJulia();
current_fractal = Fractal::JuliaSet;
});
auto* view = toolbar->AddSubmenu("View"); {
view->AddButton("Color Picker Dialog", [this] {
colorpicker_window->Toggle();
});
view->AddButton("Console", [this] {
console->Toggle();
});
view->AddButton("README", [this] {
info_dialog->Toggle();
});
view->AddSeparator();
view->AddButton("Reset Viewport");
view->AddButton("Reset Colors");
view->AddButton("Zoom In");
view->AddButton("Zoom Out");
}
auto* btn_toggle_console = toolbar->AddButton("Console");
btn_toggle_console->OnClickEvent += [this] (auto a, auto b) mutable {
@@ -176,6 +198,12 @@ public:
info_dialog->Toggle();
};
fps_label = new JUI::TextRect(toolbar);
fps_label->Position({100_percent - 200_px, 0_px});
fps_label->Size({200_px, 100_percent});
fps_label->SetTextColor(Colors::Black);
fps_label->SetContent("60 FPS");
auto* layout = new JUI::VerticalListLayout(info_dialog->ViewportInstance());
layout->Padding(0_px);
@@ -200,13 +228,13 @@ public:
line_item("This program is written in C++, from scratch.");
// TODO: Utilize for things later.
auto* wind = new JUI::Window(scene);
wind->SetTitle("Color-Pickers");
wind->MinSize({600, 150});
wind->Size({600_px, 150_px});
wind->Position({50_px, 50_px});
colorpicker_window = new JUI::Window(scene);
colorpicker_window->SetTitle("Color-Pickers");
colorpicker_window->MinSize({600, 150});
colorpicker_window->Size({600_px, 150_px});
colorpicker_window->Position({50_px, 50_px});
auto* col_layout = new JUI::HorizontalListLayout(wind->Content());
auto* col_layout = new JUI::HorizontalListLayout(colorpicker_window->Content());
//wind->Close();
JUI::ColorPicker* pp = new JUI::ColorPicker(col_layout);
@@ -252,11 +280,11 @@ public:
mandelbrotset_dialog = new JUI::Window(scene);
mandelbrotset_dialog->Position({20_px, 300_px});
mandelbrotset_dialog->Close();
auto* mandelbrotset_layout = new JUI::VerticalListLayout(mandelbrotset_dialog->ViewportInstance());
mandelbrotset_layout->LayoutOrder(JUI::LayoutOrder::V::TOP);
int idx = 999;
auto mb_line_item = [&] (const std::string& text, int size = 20, const Color4& color = Colors::White) {
@@ -297,6 +325,30 @@ public:
mb_line_item("Color-code the 'escape velocity' of the input numbers that do escape, and you get a result like the one you see.");
juliaset_dialog = new JUI::Window(scene);
juliaset_dialog->SetTitle("Julia Set Inputs");
juliaset_dialog->Position({20_px, 300_px});
juliaset_dialog->Size({900_px, 70_px});
auto* julia_x_slider = new JUI::Slider(juliaset_dialog->Content());
julia_x_slider->Size({900_px, 25_px});
julia_x_slider->Interval(1e-5f); julia_x_slider->Minimum(0); julia_x_slider->Maximum(1);
julia_x_slider->ValueChanged += [this] (double value) {
value = value * 2;
value = value - 1;
u_julia_set.x = value;
};
auto* julia_y_slider = new JUI::Slider(juliaset_dialog->Content());
julia_y_slider->Size({900_px, 25_px});
julia_y_slider->Position({0_px, 30_px});
julia_y_slider->Interval(1e-5f); julia_y_slider->Minimum(0); julia_y_slider->Maximum(1);
julia_y_slider->ValueChanged += [this] (double value) {
value = value * 2;
value = value - 1;
u_julia_set.y = value;
};
}
bool Open() override
@@ -324,7 +376,7 @@ public:
CreateMenu();
canvas = new RenderTarget(vec_size);
LoadShader("test", "mandelbrot");
LoadShaders();
console->Log(std::format("OpenGL Renderer: {}", GetGLRenderer()));
console->Log(std::format("OpenGL Vendor: {}", GetGLVendor()));
@@ -395,11 +447,33 @@ public:
u_time += elapsed;
u_julia_value = Vector2::Lerp(u_julia_value, u_julia_set, 0.001f);
UpdateShaderUniforms(elapsed);
int fps = 1.f / elapsed;
std::string fps_text = std::format("FPS: {}", fps);
fps_label->SetContent(fps_text);
}
void UpdateShaderUniforms(float elapsed) {
Vector2 u_res = Vector2(GetSize().x, GetSize().y);
// TODO: Why do we need to multiply X by 1.5 here?
Vector2 u_mouse = Vector2(GetMouseCoordinates().x*1.5f, GetSize().y-GetMouseCoordinates().y);
JGL::Shader shader;
if (current_fractal == Fractal::JuliaSet) {
shader = julia_shader;
shader.SetVector2("u_julia_set", u_julia_value);
}
if (current_fractal == Fractal::MandelbrotSet) {
shader = mandelbrot_shader;
}
shader.SetVector2("u_resolution", u_res);
shader.SetFloat("u_time", u_time);
shader.SetVector2("u_mouse", u_mouse);
@@ -410,6 +484,7 @@ public:
shader.SetFloat("u_scale", u_scale);
shader.SetVector2("u_translation", u_translation);
}
void Draw() {
Shader::UseDefault();
@@ -427,7 +502,13 @@ public:
scene->Draw();
JGL::J2D::Begin(canvas, true);
shader.Use();
if (current_fractal == Fractal::JuliaSet) {
julia_shader.Use();
}
if (current_fractal == Fractal::MandelbrotSet) {
mandelbrot_shader.Use();
}
JGL::J2D::FillRect(Colors::Black, {0, 0}, Vector2(GetSize().x, GetSize().y));
JGL::J2D::End();
@@ -473,7 +554,7 @@ protected:
private:
};
ReShaderProgram::ReShaderProgram(): ReWindow::OpenGLWindow("ReShader", 1800, 1000, 2, 1) {
FractalInspectorApp::FractalInspectorApp(): ReWindow::OpenGLWindow("ReShader", 1800, 1000, 2, 1) {
Shader::OnCompilationErrorMessage += [this](std::string type, std::string infoLog) {
auto log_lines = string_expand(infoLog, '\n');
console->Log(type, Colors::Red);
@@ -490,7 +571,7 @@ ReShaderProgram::ReShaderProgram(): ReWindow::OpenGLWindow("ReShader", 1800, 100
int main(int argc, char** argv) {
ReWindow::Logger::Debug.EnableConsole(false);
auto* program = new ReShaderProgram();
auto* program = new FractalInspectorApp();
program->Open();
program->SetFullscreen(false);

60
shaders/julia.frag.glsl Normal file
View File

@@ -0,0 +1,60 @@
#version 120
#ifdef GL_ES
precision highp float;
#endif
#define PI 3.14159265359
vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
return a + b*cos(6.28318*(c*t+d));
}
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform vec2 u_translation;
uniform float u_scale;
uniform vec3 u_rgb_1;
uniform vec3 u_rgb_2;
uniform vec3 u_rgb_3;
uniform vec3 u_rgb_4;
uniform vec2 u_julia_set;
#define N 256. // Number of iterations?
#define B 4. // What does B mean?
float iterate_julia(vec2 p, vec2 c)
{
vec2 z = p;
float i;
for (i = 0.; i < N; i++) {
z = mat2(z, -z.y, z.x) * z + c;
if (dot(z, z) > B*B) break;
}
if (p.y < 0.f) return i;
return i - log(log(dot(z, z)) / log(B)) / log(2.);
}
void main() {
vec2 R = u_resolution.xy;
vec2 uv = (u_scale * gl_FragCoord.xy - R - 1.) / R.y - u_translation;
float n = iterate_julia(uv, u_julia_set) / N;
vec3 col = pal(fract(n + 0.5), u_rgb_1, u_rgb_2, u_rgb_3, u_rgb_4);
//if (n == 1.) {n = 0; }
gl_FragColor = vec4(n == 1. ? vec3(0) : col, 1.);
}

View File

@@ -6,6 +6,11 @@ precision highp float;
#define PI 3.14159265359
vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
return a + b*cos(6.28318*(c*t+d));
}
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
@@ -21,88 +26,55 @@ uniform vec3 u_rgb_4;
#define N 256. // Number of iterations?
#define B 4. // What does B mean?
vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
return a + b*cos(6.28318*(c*t+d));
}
// The mandelbrot set is a set of complex numbers c for which the function:
// f(z) = z*z + c
// stays bounded between a certain range of values when iterated from z = 0.
#define MAX_ITERATIONS 100.
vec2 square_imaginary(vec2 number) {
return vec2(
pow(number.x, 2) - pow(number.y, 2),
2*number.x*number.y
);
}
float iterate_mandelbrot(vec2 coord) {
vec2 z = vec2(0,0);
for (int i = 0; i < MAX_ITERATIONS; i++) {
z = square_imaginary(z) + coord;
if (length(z) > 2) return i / MAX_ITERATIONS;
}
return MAX_ITERATIONS;
}
float iterate(vec2 p) {
float iterate_mandelbrot(vec2 p) {
// Initializes the complex number z to 0, and the constant c to the input coordinate p.
vec2 z = vec2(0), c = p;
float i;
float i; // Track iterations.
for (i=0.; i < N; i++ ) {
// This line performs the core Mandelbrot iteration: z = z^2 + c.
// It is done using a matrix multiplication to perform the complex number squaring.
// If z = x + iy, then z&2 = (i+iy)(x+iy) = x^2 - y ^ 2 + 2ixy.
// The matrix [x, -y; y, x] multiplied by [x, y] gives [x*x - y&y, y*x + x*y] = [Re(z^2) Im(z^2)].
// Then we add the constant complex number c (represented by the input vec2).
z = mat2(z, -z.y, z.x) * z + c;
// Check if the magnitude squared of z (distance from the origin squared) exceeds a certain bound (B*B).
// If it does, the point is likely to escape to infinity, so we break out of the loop.
if (dot(z, z) > B*B) break;
}
//return i;
// Compute the iteration smoothly, instead of in integers.
// The following section calculates a smooth iteration count for better visual results.
// This condition seems to handle a specific case, possibly related to symmetry.
// If the y-component of the input point p is negative, it returns the integer iteration count.
if (p.y < 0.f) return i;
return i - log(log(dot(z, z)) / log(B)) / log(2.);;
// If the point didn't escape within the maximum iterations or if p.y is non-negative,
// this line calculates a more precise, floating-point iteration count.
// It uses the logarithm of the magnitude squared of z to estimate how far "outside" the
// Mandelbrot set the point is. This allows for smoother color gradients in the fractal.
// - log(log(dot(z, z)) / log(B)) / log(2.) is a common formula for this smoothing.
return i - log(log(dot(z, z)) / log(B)) / log(2.);
// The commented-out line would simply return the integer number of iterations.
// return i;
}
void main() {
vec2 R = u_resolution.xy;
//float translate_h = .4;
//float translate_v = 0.0;
//float scale = 2.;
//vec2 translation = vec2(translate_h, translate_v);
vec2 uv = (u_scale * gl_FragCoord.xy - R - 1.) / R.y - u_translation;
//vec2 z = vec2(0), c = uv;
//float i;
//for (i = 0.; i < N; i++) {
// z = mat2(z, -z.y, z.x) * z + c;
// if (dot(z, z) > B*B) break;
//}
float n = iterate(uv) / N;
float n = iterate_mandelbrot(uv) / N;
vec3 col = pal(fract(n + 0.5), u_rgb_1, u_rgb_2, u_rgb_3, u_rgb_4);
//if (n == 1.) {n = 0; }
gl_FragColor = vec4(n == 1. ? vec3(0) : col, 1.);
//vec2 st = gl_FragCoord.xy/u_resolution.xy;
//vec3 color = vec3(0.0);
//vec3 pct = vec3(st.x);
// pct.r = smoothstep(0.0,1.0, st.x);
// pct.g = sin(st.x*PI);
// pct.b = pow(st.x,0.5);
//color = mix(colorA, colorB, pct);
// Plot transition lines for each channel
// color = mix(color,vec3(1.0,0.0,0.0),plot(st,pct.r));
// color = mix(color,vec3(0.0,1.0,0.0),plot(st,pct.g));
// color = mix(color,vec3(0.0,0.0,1.0),plot(st,pct.b));
// gl_FragColor = vec4(color,1.0);
}