Compare commits
2 Commits
89c0bb5f7f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3768da01cc | |||
| abab81ce53 |
BIN
Pasted image.png
Normal file
BIN
Pasted image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 261 KiB |
37
README.md
Normal file
37
README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Wayland Shader Demo
|
||||
|
||||
A lightweight, interactive shader playground built with **C++**, **Wayland**, and **OpenGL ES 2.0**.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
### 🌌 Interactive Fractals
|
||||
* **Deep Zoom Mandelbrot**: Features an **Infinite Zoom** engine using Perturbation Theory (CPU computes 128-bit reference orbit, GPU renders delta). Zoom level up to $10^{34}$!
|
||||
* **Customizable**: Use the sidebar sliders to adjust the **Exponent** (create "Multibrots") and morph the set with **Julia** offsets ($C_{real}$, $C_{imag}$).
|
||||
* **Fractal Explorer**: Standard fractal shaders included.
|
||||
|
||||
### 🧪 Physics & Simulations
|
||||
* **Fluid Dynamics**: Real-time interactive fluid simulation (mouse drag to swirl).
|
||||
* **Reaction Diffusion**: "Living" texture simulation (Gray-Scott model).
|
||||
* **Double Pendulum**: Chaotic physics visualization.
|
||||
|
||||
### 🎨 Visuals
|
||||
* **Plasma & Tunnel**: Classic demoscene effects.
|
||||
* **Sidebar UI**: Custom immediate-mode GUI with embedded font rendering.
|
||||
|
||||
## Controls
|
||||
* **Left Click**: Select shader from sidebar.
|
||||
* **Scroll**: Zoom (Mandelbrot).
|
||||
* **Drag (Left Click)**: Pan view / Interact with Fluid / Adjust Sliders.
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
make
|
||||
./shader-demo
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
* Wayland Client & EGL
|
||||
* GLESv2
|
||||
* libxkbcommon
|
||||
270
main.cpp
270
main.cpp
@@ -113,6 +113,95 @@ const int SIDEBAR_WIDTH = 200;
|
||||
const int BUTTON_HEIGHT = 60;
|
||||
const int BUTTON_MARGIN = 10;
|
||||
|
||||
// Slider UI
|
||||
struct Slider {
|
||||
std::string name;
|
||||
float* value_ptr;
|
||||
float min_val;
|
||||
float max_val;
|
||||
bool is_dragging;
|
||||
};
|
||||
|
||||
// Mandelbrot Parameters
|
||||
float m_exponent = 2.0f;
|
||||
float m_z_real = 0.0f;
|
||||
float m_z_imag = 0.0f;
|
||||
float m_c_real = 0.0f;
|
||||
float m_c_imag = 0.0f;
|
||||
|
||||
std::vector<Slider> mandel_sliders = {
|
||||
{"Exp", &m_exponent, 1.0f, 10.0f, false},
|
||||
// {"Zr", &m_z_real, -1.0f, 1.0f, false}, // Removing these to save space/simplify logic for Perturbation
|
||||
// {"Zi", &m_z_imag, -1.0f, 1.0f, false},
|
||||
{"Cr", &m_c_real, -0.1f, 0.1f, false}, // Detail C offset
|
||||
{"Ci", &m_c_imag, -0.1f, 0.1f, false}
|
||||
};
|
||||
|
||||
// --- Deep Zoom (Perturbation) State ---
|
||||
// Using __float128 (Quad Precision) if available (GCC/Clang usually have it).
|
||||
// This gives ~34 decimal digits ($10^34$ zoom).
|
||||
// For true "Infinite", we'd need GMP or a custom BigFloat array class.
|
||||
// For this demo, __float128 is a massive upgrade over double ($10^15$) and is much faster/safer to implement here.
|
||||
typedef __float128 DeepFloat;
|
||||
|
||||
DeepFloat center_r = -0.5;
|
||||
DeepFloat center_i = 0.0;
|
||||
DeepFloat zoom_deep = 1.0;
|
||||
|
||||
// Reference Orbit Data (uploaded to texture)
|
||||
const int MAX_REF_ITER = 1024; // Texture width
|
||||
std::vector<float> ref_orbit_data(MAX_REF_ITER * 4); // RGBA per step
|
||||
GLuint ref_orbit_texture = 0;
|
||||
|
||||
void calc_reference_orbit() {
|
||||
DeepFloat zr = 0.0;
|
||||
DeepFloat zi = 0.0;
|
||||
DeepFloat cr = center_r;
|
||||
DeepFloat ci = center_i;
|
||||
|
||||
// Upload Z0
|
||||
ref_orbit_data[0] = (float)zr;
|
||||
ref_orbit_data[1] = (float)zi;
|
||||
ref_orbit_data[2] = 0.0f;
|
||||
ref_orbit_data[3] = 1.0f;
|
||||
|
||||
for (int i = 1; i < MAX_REF_ITER; i++) {
|
||||
DeepFloat zr2 = zr * zr;
|
||||
DeepFloat zi2 = zi * zi;
|
||||
DeepFloat two_zr_zi = 2.0 * zr * zi;
|
||||
|
||||
zr = zr2 - zi2 + cr;
|
||||
zi = two_zr_zi + ci;
|
||||
|
||||
// Encoding for GPU (Safety against 0..1 clamping)
|
||||
// Range [-4, 4] -> [0, 1]
|
||||
float r_encoded = ((float)zr + 4.0f) / 8.0f;
|
||||
float i_encoded = ((float)zi + 4.0f) / 8.0f;
|
||||
|
||||
ref_orbit_data[i * 4 + 0] = r_encoded;
|
||||
ref_orbit_data[i * 4 + 1] = i_encoded;
|
||||
ref_orbit_data[i * 4 + 2] = 0.0f;
|
||||
ref_orbit_data[i * 4 + 3] = 1.0f;
|
||||
|
||||
if ((zr2 + zi2) > 4.0) {
|
||||
// Continue filling
|
||||
}
|
||||
}
|
||||
|
||||
// Upload to Texture
|
||||
if (ref_orbit_texture == 0) {
|
||||
glGenTextures(1, &ref_orbit_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MAX_REF_ITER, 1, 0, GL_RGBA, GL_FLOAT, ref_orbit_data.data());
|
||||
}
|
||||
|
||||
// Forward declarations
|
||||
void render_frame();
|
||||
GLuint load_shader_src(const char *source, GLenum type);
|
||||
@@ -315,16 +404,7 @@ float view_x = -0.5f;
|
||||
float view_y = 0.0f;
|
||||
bool is_dragging = false;
|
||||
|
||||
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
|
||||
if (cursor_x > SIDEBAR_WIDTH) { // Only zoom in main area
|
||||
double val = wl_fixed_to_double(value);
|
||||
if (val < 0) {
|
||||
zoom_level *= 1.1f;
|
||||
} else if (val > 0) {
|
||||
zoom_level /= 1.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {
|
||||
double old_x = cursor_x;
|
||||
@@ -332,12 +412,40 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uin
|
||||
cursor_x = wl_fixed_to_double(sx);
|
||||
cursor_y = wl_fixed_to_double(sy);
|
||||
|
||||
// Slider Logic
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
for (auto& s : mandel_sliders) {
|
||||
if (s.is_dragging) {
|
||||
float track_x = 10;
|
||||
float track_w = SIDEBAR_WIDTH - 20;
|
||||
float normalized = (float)(cursor_x - track_x) / track_w;
|
||||
if (normalized < 0.0f) normalized = 0.0f;
|
||||
if (normalized > 1.0f) normalized = 1.0f;
|
||||
*s.value_ptr = s.min_val + normalized * (s.max_val - s.min_val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dragging) {
|
||||
float dx = (cursor_x - old_x) / height;
|
||||
float dy = (cursor_y - old_y) / height;
|
||||
// Pan logic
|
||||
view_x -= dx / zoom_level * 2.0;
|
||||
view_y += dy / zoom_level * 2.0;
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
// Deep Pan
|
||||
double dx = (cursor_x - old_x) / height;
|
||||
double dy = (cursor_y - old_y) / height;
|
||||
|
||||
// Adjust Center (Deep Float)
|
||||
// Zoom is zoom_deep
|
||||
DeepFloat scale = 1.0 / zoom_deep;
|
||||
center_r -= (DeepFloat)dx * scale * 2.0;
|
||||
center_i += (DeepFloat)dy * scale * 2.0;
|
||||
|
||||
} else {
|
||||
// Normal Pan
|
||||
float dx = (cursor_x - old_x) / height;
|
||||
float dy = (cursor_y - old_y) / height;
|
||||
view_x -= dx / zoom_level * 2.0;
|
||||
view_y += dy / zoom_level * 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,22 +453,39 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin
|
||||
if (button == 0x110) { // Left click
|
||||
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||||
if (cursor_x < SIDEBAR_WIDTH) {
|
||||
int clicked_index = -1;
|
||||
for (size_t i = 0; i < shaders.size(); ++i) {
|
||||
int y_start = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN);
|
||||
int y_end = y_start + BUTTON_HEIGHT;
|
||||
if (cursor_y >= y_start && cursor_y <= y_end && cursor_x >= BUTTON_MARGIN && cursor_x <= SIDEBAR_WIDTH - BUTTON_MARGIN) {
|
||||
clicked_index = i;
|
||||
break;
|
||||
// Check Sliders
|
||||
bool hit_slider = false;
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
float sy = 490 + 20;
|
||||
for (auto& s : mandel_sliders) {
|
||||
if (cursor_y >= sy && cursor_y < sy + 20) {
|
||||
s.is_dragging = true;
|
||||
hit_slider = true;
|
||||
}
|
||||
sy += 30;
|
||||
}
|
||||
}
|
||||
if (clicked_index != -1) {
|
||||
current_shader_index = clicked_index;
|
||||
std::cout << "Switched to shader: " << shaders[current_shader_index].name << std::endl;
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
zoom_level = 1.0f;
|
||||
view_x = -0.5f;
|
||||
view_y = 0.0f;
|
||||
|
||||
if (!hit_slider) {
|
||||
int clicked_index = -1;
|
||||
for (size_t i = 0; i < shaders.size(); ++i) {
|
||||
int y_start = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN);
|
||||
int y_end = y_start + BUTTON_HEIGHT;
|
||||
if (cursor_y >= y_start && cursor_y <= y_end && cursor_x >= BUTTON_MARGIN && cursor_x <= SIDEBAR_WIDTH - BUTTON_MARGIN) {
|
||||
clicked_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clicked_index != -1) {
|
||||
current_shader_index = clicked_index;
|
||||
for (auto& s : mandel_sliders) s.is_dragging = false;
|
||||
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
// Reset Deep State
|
||||
center_r = -0.5;
|
||||
center_i = 0.0;
|
||||
zoom_deep = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -368,6 +493,27 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin
|
||||
}
|
||||
} else {
|
||||
is_dragging = false;
|
||||
for (auto& s : mandel_sliders) s.is_dragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
|
||||
if (cursor_x > SIDEBAR_WIDTH) { // Only zoom in main area
|
||||
double val = wl_fixed_to_double(value);
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
// Deep Zoom
|
||||
if (val < 0) {
|
||||
zoom_deep *= 1.1;
|
||||
} else if (val > 0) {
|
||||
zoom_deep /= 1.1;
|
||||
}
|
||||
} else {
|
||||
if (val < 0) {
|
||||
zoom_level *= 1.1f;
|
||||
} else if (val > 0) {
|
||||
zoom_level /= 1.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -491,6 +637,26 @@ void render_frame() {
|
||||
draw_text(shaders[i].name, text_x, text_y, text_scale, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
// Render Sliders if Mandelbrot
|
||||
if (shaders[current_shader_index].name == "MANDEL") {
|
||||
float sy = 490 + 20;
|
||||
for (const auto& s : mandel_sliders) {
|
||||
// Track
|
||||
glUseProgram(sidebar_program);
|
||||
draw_quad(10, sy + 8, SIDEBAR_WIDTH - 20, 4, width, height, 0.3f, 0.3f, 0.3f);
|
||||
|
||||
// Knob
|
||||
float normalized = (*s.value_ptr - s.min_val) / (s.max_val - s.min_val);
|
||||
float kx = 10 + normalized * (SIDEBAR_WIDTH - 20);
|
||||
draw_quad(kx - 5, sy, 10, 20, width, height, 0.8f, 0.8f, 0.8f);
|
||||
|
||||
// Label
|
||||
draw_text(s.name, 10, sy - 8, 1.0f, 1.0f, 1.0f, 0.0f); // Yellowish
|
||||
|
||||
sy += 30;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Render Active Shader
|
||||
int view_w = width - SIDEBAR_WIDTH;
|
||||
int view_h = height;
|
||||
@@ -521,7 +687,51 @@ void render_frame() {
|
||||
if (zoomLoc != -1) glUniform1f(zoomLoc, zoom_level);
|
||||
|
||||
GLint centerLoc = glGetUniformLocation(active.program, "u_center");
|
||||
if (centerLoc != -1) glUniform2f(centerLoc, view_x, view_y);
|
||||
if (centerLoc != -1) {
|
||||
if (active.name == "MANDEL") {
|
||||
// Pass Deep Orbit Texture
|
||||
calc_reference_orbit();
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
||||
GLint refLoc = glGetUniformLocation(active.program, "u_ref_orbit");
|
||||
if (refLoc != -1) glUniform1i(refLoc, 1); // Unit 1
|
||||
|
||||
// Pass Zoom and Center?
|
||||
// We actually don't need Center if we are doing Delta?
|
||||
// We need Delta C for the pixel.
|
||||
// Wait, standard Delta approach:
|
||||
// dc = (uv) / zoom
|
||||
// z = 0 + 0 (if starting at 0)
|
||||
// But u_ref_orbit contains the center's orbit.
|
||||
|
||||
// We still need to pass zoom.
|
||||
glUniform1f(zoomLoc, (float)zoom_deep);
|
||||
// Center is implicit 0,0 relative to reference?
|
||||
// No, we still want to visualize.
|
||||
|
||||
// Actually, for Delta Shader:
|
||||
// C_pixel = C_ref + DeltaC
|
||||
// C_ref is used on CPU.
|
||||
// DeltaC = (FragCoord - CenterScreen) * PixelScale
|
||||
|
||||
// So we assume u_center is 0,0 in the shader logic because we are tracking C_ref on CPU.
|
||||
glUniform2f(centerLoc, 0.0f, 0.0f);
|
||||
|
||||
} else {
|
||||
glUniform2f(centerLoc, view_x, view_y);
|
||||
}
|
||||
}
|
||||
|
||||
// Mandelbrot Params
|
||||
GLint expLoc = glGetUniformLocation(active.program, "u_exponent");
|
||||
if (expLoc != -1) glUniform1f(expLoc, m_exponent);
|
||||
|
||||
GLint zStartLoc = glGetUniformLocation(active.program, "u_z_start");
|
||||
if (zStartLoc != -1) glUniform2f(zStartLoc, m_z_real, m_z_imag);
|
||||
|
||||
GLint cOffsetLoc = glGetUniformLocation(active.program, "u_c_offset");
|
||||
if (cOffsetLoc != -1) glUniform2f(cOffsetLoc, m_c_real, m_c_imag);
|
||||
|
||||
if (active.is_stateful) {
|
||||
// Ping-Pong rendering
|
||||
|
||||
BIN
shader-demo
BIN
shader-demo
Binary file not shown.
@@ -4,53 +4,71 @@ precision highp float;
|
||||
uniform float u_time;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_zoom;
|
||||
uniform vec2 u_center;
|
||||
uniform vec2 u_center; // Unused if using perturbation?
|
||||
|
||||
uniform sampler2D u_ref_orbit; // Stores Reference Z (Real=Luminance, Imag=Alpha)
|
||||
|
||||
// Sliders
|
||||
uniform float u_exponent; // Perturbation usually assumes Z^2.
|
||||
uniform vec2 u_z_start;
|
||||
uniform vec2 u_c_offset;
|
||||
|
||||
// Complex Math helpers
|
||||
vec2 complex_mul(vec2 a, vec2 b) {
|
||||
return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
|
||||
}
|
||||
|
||||
vec2 complex_sq(vec2 a) {
|
||||
return vec2(a.x*a.x - a.y*a.y, 2.0*a.x*a.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Current pixel coordinate in 0..1
|
||||
vec2 st = gl_FragCoord.xy / u_resolution.xy;
|
||||
st.x *= u_resolution.x / u_resolution.y; // Aspect ratio correction
|
||||
// Current pixel coordinate relative to screen center
|
||||
vec2 p = (gl_FragCoord.xy - u_resolution.xy * 0.5) / u_resolution.y;
|
||||
|
||||
// Map to Mandelbrot space using Zoom and Center
|
||||
// Default (Zoom 1): -2.5 to 1.5 X, -1.5 to 1.5 Y approx?
|
||||
// Let's say center is 0,0. Range -2..2.
|
||||
// st is 0..AspectRatio (approx 0..1.7)
|
||||
// Delta C (Difference from Reference Point)
|
||||
vec2 dc = p / u_zoom;
|
||||
|
||||
// Center the coords:
|
||||
vec2 c_uv = (gl_FragCoord.xy - u_resolution.xy * 0.5) / u_resolution.y;
|
||||
// Apply C Offset from sliders (as Delta C)
|
||||
dc += u_c_offset;
|
||||
|
||||
// Apply Zoom and Pan
|
||||
vec2 c = (c_uv / u_zoom) + u_center;
|
||||
// Initial Delta Z
|
||||
vec2 dz = vec2(0.0);
|
||||
|
||||
// Mandelbrot iteration
|
||||
vec2 z = vec2(0.0);
|
||||
float iter = 0.0;
|
||||
float max_iter = 100.0 + log(u_zoom) * 20.0; // Increase details with zoom
|
||||
float max_iter = 1000.0;
|
||||
|
||||
for (float i = 0.0; i < 500.0; i++) {
|
||||
if (i > max_iter) break;
|
||||
// Z = Z^2 + C
|
||||
float x = (z.x * z.x - z.y * z.y) + c.x;
|
||||
float y = (z.y * z.x + z.x * z.y) + c.y;
|
||||
for (float i = 0.0; i < 1000.0; i++) {
|
||||
// Fetch Reference Z from Texture
|
||||
vec4 ref = texture2D(u_ref_orbit, vec2((i + 0.5) / 1024.0, 0.5));
|
||||
|
||||
if ((x * x + y * y) > 4.0) {
|
||||
// Decode Range [0, 1] -> [-4, 4]
|
||||
// Z = Val * 8 - 4
|
||||
// Use R and G channels (was R and A)
|
||||
vec2 Zn = vec2(ref.r * 8.0 - 4.0, ref.g * 8.0 - 4.0);
|
||||
|
||||
// dz = 2*Zn*dz + dz^2 + dc
|
||||
vec2 two_Zn_dz = complex_mul(2.0 * Zn, dz);
|
||||
vec2 dz_sq = complex_sq(dz);
|
||||
|
||||
dz = two_Zn_dz + dz_sq + dc; // Perturbation formula
|
||||
|
||||
// Check Escape: |Zn + dz|^2 > 4
|
||||
vec2 current_z = Zn + dz;
|
||||
if (dot(current_z, current_z) > 4.0) {
|
||||
iter = i;
|
||||
break;
|
||||
}
|
||||
z.x = x;
|
||||
z.y = y;
|
||||
}
|
||||
|
||||
// Coloring
|
||||
float t = iter / max_iter;
|
||||
if (iter == 0.0) {
|
||||
|
||||
// Standard Black Interior
|
||||
if (iter >= 999.0) {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
} else {
|
||||
gl_FragColor = vec4(
|
||||
sqrt(t),
|
||||
t * t,
|
||||
sin(t * 3.1415),
|
||||
1.0
|
||||
);
|
||||
// Original "Generic" Palette
|
||||
gl_FragColor = vec4(sqrt(t), t*t, sin(t * 3.14), 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user