Compare commits
1
Commits
58eb443e56
...
e05c663e40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e05c663e40 |
+2
-1
@@ -26,7 +26,8 @@ web-sys = { version = "0.3.105", features = ["Window", "Location", "Url", "UrlSe
|
|||||||
# Release: optimize hard (fractal math is hot).
|
# Release: optimize hard (fractal math is hot).
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
codegen-units = 1
|
# codegen-units = 1
|
||||||
|
debug = true
|
||||||
|
|
||||||
# Dev: keep our own crate debuggable, but optimize dependencies (dashu, wgpu,
|
# Dev: keep our own crate debuggable, but optimize dependencies (dashu, wgpu,
|
||||||
# egui) so the explorer is actually interactive during development.
|
# egui) so the explorer is actually interactive during development.
|
||||||
|
|||||||
+18
-12
@@ -267,6 +267,8 @@ pub struct FractalApp {
|
|||||||
/// Distance-estimation shading: darkens toward the set boundary using the
|
/// Distance-estimation shading: darkens toward the set boundary using the
|
||||||
/// orbit derivative, giving crisp filaments at deep zoom instead of speckle.
|
/// orbit derivative, giving crisp filaments at deep zoom instead of speckle.
|
||||||
de_coloring: bool,
|
de_coloring: bool,
|
||||||
|
// Use shadow coloring
|
||||||
|
shadow: bool,
|
||||||
|
|
||||||
/// Render as a Buddhabrot (Monte-Carlo orbit-density histogram) instead of
|
/// Render as a Buddhabrot (Monte-Carlo orbit-density histogram) instead of
|
||||||
/// the ordinary escape-time set. Plain f32 view — no deep zoom, no
|
/// the ordinary escape-time set. Plain f32 view — no deep zoom, no
|
||||||
@@ -403,6 +405,7 @@ impl FractalApp {
|
|||||||
palette: 0,
|
palette: 0,
|
||||||
antialias: false,
|
antialias: false,
|
||||||
de_coloring: false,
|
de_coloring: false,
|
||||||
|
shadow: false,
|
||||||
buddhabrot: false,
|
buddhabrot: false,
|
||||||
buddha_r_cap: 50,
|
buddha_r_cap: 50,
|
||||||
buddha_g_cap: 500,
|
buddha_g_cap: 500,
|
||||||
@@ -808,8 +811,8 @@ impl FractalApp {
|
|||||||
dc_offset: self.dc_offset(),
|
dc_offset: self.dc_offset(),
|
||||||
phoenix_p: [self.phoenix_p.0 as f32, self.phoenix_p.1 as f32],
|
phoenix_p: [self.phoenix_p.0 as f32, self.phoenix_p.1 as f32],
|
||||||
lambda_l: [self.lambda_l.0 as f32, self.lambda_l.1 as f32],
|
lambda_l: [self.lambda_l.0 as f32, self.lambda_l.1 as f32],
|
||||||
de_coloring: self.de_coloring as u32,
|
de_coloring: (self.de_coloring | self.shadow) as u32,
|
||||||
_pad: [0],
|
shadow: self.shadow as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,11 +836,11 @@ impl FractalApp {
|
|||||||
r_cap: self.buddha_r_cap,
|
r_cap: self.buddha_r_cap,
|
||||||
g_cap: self.buddha_g_cap,
|
g_cap: self.buddha_g_cap,
|
||||||
b_cap: self.buddha_b_cap,
|
b_cap: self.buddha_b_cap,
|
||||||
seed: 0, // set by the callback's own dispatch counter
|
seed: 0, // set by the callback's own dispatch counter
|
||||||
samples_this_dispatch: 0, // set by the callback
|
samples_this_dispatch: 0, // set by the callback
|
||||||
exposure: self.buddha_exposure,
|
exposure: self.buddha_exposure,
|
||||||
width: 0, // set by the callback from size_px
|
width: 0, // set by the callback from size_px
|
||||||
height: 0, // set by the callback from size_px
|
height: 0, // set by the callback from size_px
|
||||||
total_samples: 0.0, // tracked by the renderer across frames
|
total_samples: 0.0, // tracked by the renderer across frames
|
||||||
palette: self.buddha_palette,
|
palette: self.buddha_palette,
|
||||||
_pad: [0; 3],
|
_pad: [0; 3],
|
||||||
@@ -1354,13 +1357,16 @@ impl FractalApp {
|
|||||||
.logarithmic(true),
|
.logarithmic(true),
|
||||||
);
|
);
|
||||||
ui.add(egui::Slider::new(&mut self.color_offset, 0.0..=1.0).text("color offset"));
|
ui.add(egui::Slider::new(&mut self.color_offset, 0.0..=1.0).text("color offset"));
|
||||||
egui::ComboBox::from_label("palette")
|
ui.checkbox(&mut self.shadow, "Shadow");
|
||||||
.selected_text(PALETTE_NAMES[self.palette as usize])
|
if !self.shadow {
|
||||||
.show_ui(ui, |ui| {
|
egui::ComboBox::from_label("palette")
|
||||||
for (i, name) in PALETTE_NAMES.iter().enumerate() {
|
.selected_text(PALETTE_NAMES[self.palette as usize])
|
||||||
ui.selectable_value(&mut self.palette, i as u32, *name);
|
.show_ui(ui, |ui| {
|
||||||
}
|
for (i, name) in PALETTE_NAMES.iter().enumerate() {
|
||||||
});
|
ui.selectable_value(&mut self.palette, i as u32, *name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
ui.checkbox(&mut self.antialias, "Antialiasing (2×2)")
|
ui.checkbox(&mut self.antialias, "Antialiasing (2×2)")
|
||||||
.on_hover_text("Supersample each pixel for smoother edges (~4× slower).");
|
.on_hover_text("Supersample each pixel for smoother edges (~4× slower).");
|
||||||
ui.checkbox(&mut self.de_coloring, "Distance shading")
|
ui.checkbox(&mut self.de_coloring, "Distance shading")
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ fn color_differs(a: &Uniforms, b: &Uniforms) -> bool {
|
|||||||
a.color_offset != b.color_offset
|
a.color_offset != b.color_offset
|
||||||
|| a.color_scale != b.color_scale
|
|| a.color_scale != b.color_scale
|
||||||
|| a.palette_id != b.palette_id
|
|| a.palette_id != b.palette_id
|
||||||
|
|| a.shadow != b.shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GPU-side view + coloring parameters. Layout must match `Uniforms` in the
|
/// GPU-side view + coloring parameters. Layout must match `Uniforms` in the
|
||||||
@@ -83,8 +84,8 @@ pub struct Uniforms {
|
|||||||
pub lambda_l: [f32; 2],
|
pub lambda_l: [f32; 2],
|
||||||
/// 0 = escape-time coloring, 1 = distance-estimation shading.
|
/// 0 = escape-time coloring, 1 = distance-estimation shading.
|
||||||
pub de_coloring: u32,
|
pub de_coloring: u32,
|
||||||
/// Padding to a 16-byte multiple (uniform buffer requirement).
|
// 0 = classic colors, 1 = shadows
|
||||||
pub _pad: [u32; 1],
|
pub shadow: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Offscreen textures for the two-pass render, recreated whenever the widget's
|
/// Offscreen textures for the two-pass render, recreated whenever the widget's
|
||||||
|
|||||||
+35
-14
@@ -25,7 +25,9 @@ struct Uniforms {
|
|||||||
power: u32,
|
power: u32,
|
||||||
dc_offset: vec2<f32>,
|
dc_offset: vec2<f32>,
|
||||||
phoenix_p: vec2<f32>,
|
phoenix_p: vec2<f32>,
|
||||||
|
lambda_l: vec2<f32>,
|
||||||
de_coloring: u32,
|
de_coloring: u32,
|
||||||
|
shadow: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||||
@@ -34,18 +36,18 @@ struct Uniforms {
|
|||||||
// Smooth cyclic palettes (Inigo Quilez cosine palettes). Must match the palette
|
// Smooth cyclic palettes (Inigo Quilez cosine palettes). Must match the palette
|
||||||
// in mandelbrot.wgsl.
|
// in mandelbrot.wgsl.
|
||||||
fn palette(id: u32, t: f32) -> vec3<f32> {
|
fn palette(id: u32, t: f32) -> vec3<f32> {
|
||||||
if (id == 4u) {
|
if id == 4u {
|
||||||
return vec3<f32>(t, t, t); // grayscale
|
return vec3<f32>(t, t, t); // grayscale
|
||||||
}
|
}
|
||||||
let a = vec3<f32>(0.5, 0.5, 0.5);
|
let a = vec3<f32>(0.5, 0.5, 0.5);
|
||||||
let b = vec3<f32>(0.5, 0.5, 0.5);
|
let b = vec3<f32>(0.5, 0.5, 0.5);
|
||||||
var c = vec3<f32>(1.0, 1.0, 1.0);
|
var c = vec3<f32>(1.0, 1.0, 1.0);
|
||||||
var d = vec3<f32>(0.00, 0.10, 0.20); // 0: amber / blue
|
var d = vec3<f32>(0.00, 0.10, 0.20); // 0: amber / blue
|
||||||
if (id == 1u) {
|
if id == 1u {
|
||||||
d = vec3<f32>(0.00, 0.33, 0.67); // rainbow
|
d = vec3<f32>(0.00, 0.33, 0.67); // rainbow
|
||||||
} else if (id == 2u) {
|
} else if id == 2u {
|
||||||
d = vec3<f32>(0.30, 0.20, 0.20); // warm ember
|
d = vec3<f32>(0.30, 0.20, 0.20); // warm ember
|
||||||
} else if (id == 3u) {
|
} else if id == 3u {
|
||||||
c = vec3<f32>(1.0, 1.0, 0.5);
|
c = vec3<f32>(1.0, 1.0, 0.5);
|
||||||
d = vec3<f32>(0.80, 0.90, 0.30); // lime / magenta
|
d = vec3<f32>(0.80, 0.90, 0.30); // lime / magenta
|
||||||
}
|
}
|
||||||
@@ -62,17 +64,36 @@ fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4<f32> {
|
|||||||
return vec4<f32>(verts[idx], 0.0, 1.0);
|
return vec4<f32>(verts[idx], 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load(x: i32, y: i32) -> vec3<f32> {
|
||||||
|
let dist = textureLoad(data_tex, vec2<i32>(x, y), 0).g;
|
||||||
|
return vec3<f32>(f32(x), f32(y), dist);
|
||||||
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
||||||
let d = textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0);
|
if u.shadow != 0u {
|
||||||
let ci = d.r;
|
if textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0).b != 0. {
|
||||||
let de = d.g;
|
return vec4<f32>(0., 0.11, 0.54, 1.0);
|
||||||
let interior_frac = d.b;
|
} else {
|
||||||
|
let d = array<vec3<f32>, 3>(load(i32(pos.x), i32(pos.y)), load(i32(pos.x + 1), i32(pos.y)), load(i32(pos.x), i32(pos.y + 1)));
|
||||||
|
|
||||||
let t = fract(ci * u.color_scale + u.color_offset);
|
let normal = normalize(cross(d[1] - d[0], d[2] - d[0]));
|
||||||
var col = palette(u.palette_id, t) * de;
|
|
||||||
// Anti-alias the set boundary: fade toward black by the fraction of the
|
let color = (dot(normal, vec3<f32>(.5, .5, .5)) + 0.2) / 1.2;
|
||||||
// pixel's sub-samples that landed in the interior.
|
|
||||||
col = col * (1.0 - interior_frac);
|
return vec4<f32>(vec3<f32>(color), 1.0);
|
||||||
return vec4<f32>(col, 1.0);
|
}
|
||||||
|
} else {
|
||||||
|
let d = textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0);
|
||||||
|
let ci = d.r;
|
||||||
|
let de = d.g;
|
||||||
|
let interior_frac = d.b;
|
||||||
|
|
||||||
|
let t = fract(ci * u.color_scale + u.color_offset);
|
||||||
|
var col = palette(u.palette_id, t) * de;
|
||||||
|
// Anti-alias the set boundary: fade toward black by the fraction of the
|
||||||
|
// pixel's sub-samples that landed in the interior.
|
||||||
|
col = col * (1.0 - interior_frac);
|
||||||
|
return vec4<f32>(col, 1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ struct Uniforms {
|
|||||||
lambda_l: vec2<f32>,
|
lambda_l: vec2<f32>,
|
||||||
// 0 = escape-time coloring, 1 = distance-estimation shading.
|
// 0 = escape-time coloring, 1 = distance-estimation shading.
|
||||||
de_coloring: u32,
|
de_coloring: u32,
|
||||||
|
// 0 = classic colors, 1 = shadows
|
||||||
|
shadow: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const KIND_MANDELBROT: u32 = 0u;
|
const KIND_MANDELBROT: u32 = 0u;
|
||||||
@@ -326,7 +328,11 @@ fn iterate_sample(offset: vec2<f32>, px: f32) -> Sample {
|
|||||||
let zmag = sqrt(max(z2, 1.0));
|
let zmag = sqrt(max(z2, 1.0));
|
||||||
let dzmag = sqrt(max(dot(dz, dz), 1e-20));
|
let dzmag = sqrt(max(dot(dz, dz), 1e-20));
|
||||||
let d = zmag * log(zmag) / dzmag;
|
let d = zmag * log(zmag) / dzmag;
|
||||||
de = clamp(d / max(px, 1e-30), 0.0, 1.0);
|
var max_de = 1.;
|
||||||
|
if u.shadow != 0u {
|
||||||
|
max_de = 1000.;
|
||||||
|
}
|
||||||
|
de = clamp(d / max(px, 1e-30), 0.0, max_de);
|
||||||
}
|
}
|
||||||
return Sample(ci, de, true);
|
return Sample(ci, de, true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user