Compare commits

..
2 Commits
Author SHA1 Message Date
surv 58eb443e56 to rebase 2026-09-18 17:30:02 +02:00
surv 2cd06c4219 feat: add shadow coloring 2026-09-18 11:01:29 +02:00
7 changed files with 230 additions and 19 deletions
+47 -1
View File
@@ -10,6 +10,7 @@ use crate::fractal::{
}; };
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::fractal::{compute_reference, compute_set_reference}; use crate::fractal::{compute_reference, compute_set_reference};
use crate::lights::Light;
use crate::view::{ use crate::view::{
Big, DEFAULT_HALF_HEIGHT, ViewState, big_from_decimal_str, big_from_f64, big_to_decimal_str, Big, DEFAULT_HALF_HEIGHT, ViewState, big_from_decimal_str, big_from_f64, big_to_decimal_str,
precision_for, precision_for,
@@ -29,6 +30,8 @@ const INTERACT_DOWNSCALE: u32 = 2;
const INTERACT_SETTLE: f64 = 0.12; const INTERACT_SETTLE: f64 = 0.12;
/// Palette names; index maps to `palette_id` in the shader. /// Palette names; index maps to `palette_id` in the shader.
const PALETTE_NAMES: &[&str] = &["Amber", "Rainbow", "Ember", "Lime", "Grayscale"]; const PALETTE_NAMES: &[&str] = &["Amber", "Rainbow", "Ember", "Lime", "Grayscale"];
/// Shadow palette names; index maps to `palette_id` in the shader.
const SHADOW_PALETTE_NAMES: &[&str] = &["Grayscale", "Red & Blue", "Custom lights"];
/// Buddhabrot tonemap style names; index maps to `BuddhabrotUniforms::palette`. /// Buddhabrot tonemap style names; index maps to `BuddhabrotUniforms::palette`.
const BUDDHA_PALETTE_NAMES: &[&str] = &["Nebula", "Yellow", "Grayscale"]; const BUDDHA_PALETTE_NAMES: &[&str] = &["Nebula", "Yellow", "Grayscale"];
@@ -121,7 +124,7 @@ const SET_PRESETS: [&[SetPreset]; FractalKind::Lambda as usize + 1] = [
"-0.7436438870371587", "-0.7436438870371587",
"0.1318259042053", "0.1318259042053",
8.0e-8, 8.0e-8,
10000, 2000,
None, None,
), ),
], ],
@@ -262,6 +265,7 @@ pub struct FractalApp {
color_scale: f32, color_scale: f32,
color_offset: f32, color_offset: f32,
palette: u32, palette: u32,
shadow_palette: u32,
/// Supersample each pixel 2×2 for smoother edges (costs ~4× fragment work). /// Supersample each pixel 2×2 for smoother edges (costs ~4× fragment work).
antialias: bool, antialias: bool,
/// Distance-estimation shading: darkens toward the set boundary using the /// Distance-estimation shading: darkens toward the set boundary using the
@@ -270,6 +274,9 @@ pub struct FractalApp {
// Use shadow coloring // Use shadow coloring
shadow: bool, shadow: bool,
/// List of enabled lights in the world
lights: Vec<Light>,
/// 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
/// perturbation/reference-orbit machinery (see `fractal::buddhabrot`). /// perturbation/reference-orbit machinery (see `fractal::buddhabrot`).
@@ -403,9 +410,11 @@ impl FractalApp {
color_scale: 0.15, color_scale: 0.15,
color_offset: 0.0, color_offset: 0.0,
palette: 0, palette: 0,
shadow_palette: 0,
antialias: false, antialias: false,
de_coloring: false, de_coloring: false,
shadow: false, shadow: false,
lights: vec![Light::default()],
buddhabrot: false, buddhabrot: false,
buddha_r_cap: 50, buddha_r_cap: 50,
buddha_g_cap: 500, buddha_g_cap: 500,
@@ -579,6 +588,7 @@ impl FractalApp {
color_scale: self.color_scale, color_scale: self.color_scale,
color_offset: self.color_offset, color_offset: self.color_offset,
palette: self.palette, palette: self.palette,
shadow_palette: self.shadow_palette,
} }
} }
@@ -597,6 +607,8 @@ impl FractalApp {
self.color_scale = s.color_scale; self.color_scale = s.color_scale;
self.color_offset = s.color_offset; self.color_offset = s.color_offset;
self.palette = (s.palette as usize).min(PALETTE_NAMES.len() - 1) as u32; self.palette = (s.palette as usize).min(PALETTE_NAMES.len() - 1) as u32;
self.shadow_palette =
(s.shadow_palette as usize).min(SHADOW_PALETTE_NAMES.len() - 1) as u32;
// The link carries an explicit iteration count; honor it rather than // The link carries an explicit iteration count; honor it rather than
// letting the auto-scaler immediately overwrite it. // letting the auto-scaler immediately overwrite it.
self.auto_iterations = false; self.auto_iterations = false;
@@ -805,6 +817,7 @@ impl FractalApp {
bailout_sq: BAILOUT_SQ, bailout_sq: BAILOUT_SQ,
is_julia: matches!(self.mode, FractalMode::Julia) as u32, is_julia: matches!(self.mode, FractalMode::Julia) as u32,
palette_id: self.palette, palette_id: self.palette,
shadow_palette_id: self.shadow_palette,
aa_level: if self.antialias { 2 } else { 1 }, aa_level: if self.antialias { 2 } else { 1 },
kind: self.kind as u32, kind: self.kind as u32,
power: self.power, power: self.power,
@@ -813,6 +826,7 @@ impl FractalApp {
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 | self.shadow) as u32, de_coloring: (self.de_coloring | self.shadow) as u32,
shadow: self.shadow as u32, shadow: self.shadow as u32,
_pad: [0; _],
} }
} }
@@ -1366,9 +1380,37 @@ impl FractalApp {
ui.selectable_value(&mut self.palette, i as u32, *name); ui.selectable_value(&mut self.palette, i as u32, *name);
} }
}); });
} else {
egui::ComboBox::from_label("palette")
.selected_text(SHADOW_PALETTE_NAMES[self.shadow_palette as usize])
.show_ui(ui, |ui| {
for (i, name) in SHADOW_PALETTE_NAMES.iter().enumerate() {
ui.selectable_value(&mut self.shadow_palette, i as u32, *name);
} }
});
}
if self.shadow && self.shadow_palette as usize == SHADOW_PALETTE_NAMES.len() - 1 {
ui.horizontal(|ui| {
ui.label("lights:");
if ui.button("+").clicked() {
self.lights.push(Light::default());
}
});
egui::Grid::new("lights")
.striped(true)
.num_columns(1)
.show(ui, |ui| {
self.lights.retain_mut(|light| {
let delete = !light.widget(ui);
ui.end_row();
delete
});
});
}
ui.separator();
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).");
if !self.shadow {
ui.checkbox(&mut self.de_coloring, "Distance shading") ui.checkbox(&mut self.de_coloring, "Distance shading")
.on_hover_text( .on_hover_text(
"Shade by distance to the set boundary (from the orbit derivative) \ "Shade by distance to the set boundary (from the orbit derivative) \
@@ -1376,8 +1418,10 @@ impl FractalApp {
(Mandelbrot/Multibrot/Phoenix), approximate for the abs-based kinds \ (Mandelbrot/Multibrot/Phoenix), approximate for the abs-based kinds \
(Burning Ship/Tricorn/Celtic/Perpendicular/Buffalo).", (Burning Ship/Tricorn/Celtic/Perpendicular/Buffalo).",
); );
}
ui.collapsing("Animation", |ui| { ui.collapsing("Animation", |ui| {
if !self.shadow {
ui.checkbox(&mut self.anim.color, "Cycle colours") ui.checkbox(&mut self.anim.color, "Cycle colours")
.on_hover_text("Scroll the palette offset over time."); .on_hover_text("Scroll the palette offset over time.");
if self.anim.color { if self.anim.color {
@@ -1387,6 +1431,7 @@ impl FractalApp {
.logarithmic(true), .logarithmic(true),
); );
} }
}
ui.checkbox(&mut self.anim.zoom, "Auto-zoom") ui.checkbox(&mut self.anim.zoom, "Auto-zoom")
.on_hover_text("Continuously zoom toward the current center."); .on_hover_text("Continuously zoom toward the current center.");
@@ -1760,6 +1805,7 @@ impl FractalApp {
rect, rect,
FractalCallback { FractalCallback {
uniforms, uniforms,
lights: self.lights.clone(),
reference: Arc::clone(&self.reference), reference: Arc::clone(&self.reference),
generation: self.generation, generation: self.generation,
size_px, size_px,
+35 -1
View File
@@ -13,6 +13,8 @@ use std::sync::Arc;
use eframe::egui_wgpu::{self, wgpu}; use eframe::egui_wgpu::{self, wgpu};
use crate::lights::{Light, MAX_LIGHT_COUNT};
/// Maximum reference-orbit length (points) the storage buffer can hold. Also /// Maximum reference-orbit length (points) the storage buffer can hold. Also
/// bounds the iteration count. 128k points * 8 bytes = 1 MiB. /// bounds the iteration count. 128k points * 8 bytes = 1 MiB.
pub const MAX_REF_POINTS: usize = 1 << 17; pub const MAX_REF_POINTS: usize = 1 << 17;
@@ -46,6 +48,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_palette_id != b.shadow_palette_id
|| a.shadow != b.shadow || a.shadow != b.shadow
} }
@@ -65,12 +68,14 @@ pub struct Uniforms {
/// 0 = Mandelbrot, 1 = Julia. /// 0 = Mandelbrot, 1 = Julia.
pub is_julia: u32, pub is_julia: u32,
pub palette_id: u32, pub palette_id: u32,
pub shadow_palette_id: u32,
/// Supersampling factor per axis: 1 = off, 2 = 2×2 (4 samples). /// Supersampling factor per axis: 1 = off, 2 = 2×2 (4 samples).
pub aa_level: u32, pub aa_level: u32,
/// Iteration formula (`FractalKind::shader_id`). /// Iteration formula (`FractalKind::shader_id`).
pub kind: u32, pub kind: u32,
/// Exponent for the Multibrot kind. /// Exponent for the Multibrot kind.
pub power: u32, pub power: u32,
pub _pad: [u32; 1],
/// Complex offset of the view center from the reference center, so a stale /// Complex offset of the view center from the reference center, so a stale
/// or reused reference (computed at a slightly different center) still maps /// or reused reference (computed at a slightly different center) still maps
/// correctly. Added to every pixel's per-pixel offset. /// correctly. Added to every pixel's per-pixel offset.
@@ -129,6 +134,7 @@ pub struct FractalRenderer {
bind_group_layout: wgpu::BindGroupLayout, bind_group_layout: wgpu::BindGroupLayout,
uniform_buffer: wgpu::Buffer, uniform_buffer: wgpu::Buffer,
ref_buffer: wgpu::Buffer, ref_buffer: wgpu::Buffer,
lights_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
target_format: wgpu::TextureFormat, target_format: wgpu::TextureFormat,
/// Generation of the reference orbit currently uploaded to `ref_buffer`. /// Generation of the reference orbit currently uploaded to `ref_buffer`.
@@ -171,6 +177,13 @@ impl FractalRenderer {
mapped_at_creation: false, mapped_at_creation: false,
}); });
let lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("lights parameters"),
size: (MAX_LIGHT_COUNT * std::mem::size_of::<Light>()) as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("fractal bind group layout"), label: Some("fractal bind group layout"),
entries: &[ entries: &[
@@ -302,6 +315,16 @@ impl FractalRenderer {
}, },
count: None, count: None,
}, },
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
], ],
}); });
let colorize_pipeline_layout = let colorize_pipeline_layout =
@@ -410,6 +433,7 @@ impl FractalRenderer {
bind_group_layout, bind_group_layout,
uniform_buffer, uniform_buffer,
ref_buffer, ref_buffer,
lights_buffer,
bind_group, bind_group,
target_format, target_format,
uploaded_generation: u64::MAX, uploaded_generation: u64::MAX,
@@ -478,6 +502,10 @@ impl FractalRenderer {
binding: 1, binding: 1,
resource: wgpu::BindingResource::TextureView(&data_view), resource: wgpu::BindingResource::TextureView(&data_view),
}, },
wgpu::BindGroupEntry {
binding: 2,
resource: self.lights_buffer.as_entire_binding(),
},
], ],
}); });
@@ -782,6 +810,7 @@ pub fn encode_png_with_progress(
/// colourise pass (see `prepare`). /// colourise pass (see `prepare`).
pub struct FractalCallback { pub struct FractalCallback {
pub uniforms: Uniforms, pub uniforms: Uniforms,
pub lights: Vec<Light>,
pub reference: Arc<Vec<[f32; 2]>>, pub reference: Arc<Vec<[f32; 2]>>,
pub generation: u64, pub generation: u64,
/// Widget size in physical pixels — the cache texture resolution. /// Widget size in physical pixels — the cache texture resolution.
@@ -828,7 +857,8 @@ impl egui_wgpu::CallbackTrait for FractalCallback {
let color_dirty = iter_dirty let color_dirty = iter_dirty
|| renderer.colored.as_ref().is_none_or(|c| { || renderer.colored.as_ref().is_none_or(|c| {
c.width != width || c.height != height || color_differs(&c.uniforms, &self.uniforms) c.width != width || c.height != height || color_differs(&c.uniforms, &self.uniforms)
}); })
|| true;
if !color_dirty { if !color_dirty {
return Vec::new(); // cache still valid; paint() just blits it return Vec::new(); // cache still valid; paint() just blits it
@@ -840,6 +870,10 @@ impl egui_wgpu::CallbackTrait for FractalCallback {
0, 0,
bytemuck::bytes_of(&self.uniforms), bytemuck::bytes_of(&self.uniforms),
); );
let mut bytes = [0; size_of::<Light>() * MAX_LIGHT_COUNT];
bytes[..self.lights.len() * size_of::<Light>()]
.copy_from_slice(bytemuck::cast_slice(&self.lights));
queue.write_buffer(&renderer.lights_buffer, 0, &bytes);
if let Some(cache) = &renderer.cache { if let Some(cache) = &renderer.cache {
if iter_dirty { if iter_dirty {
+5
View File
@@ -29,6 +29,8 @@ pub struct ShareState {
pub color_offset: f32, pub color_offset: f32,
/// Palette index (`palette_id` in the shader). /// Palette index (`palette_id` in the shader).
pub palette: u32, pub palette: u32,
/// Shadow palette index (`shadow_palette_id` in the shader).
pub shadow_palette: u32,
} }
impl ShareState { impl ShareState {
@@ -110,6 +112,7 @@ impl ShareState {
color_scale: map.get("cs").and_then(|s| s.parse().ok()).unwrap_or(0.02), color_scale: map.get("cs").and_then(|s| s.parse().ok()).unwrap_or(0.02),
color_offset: map.get("co").and_then(|s| s.parse().ok()).unwrap_or(0.0), color_offset: map.get("co").and_then(|s| s.parse().ok()).unwrap_or(0.0),
palette: map.get("pal").and_then(|s| s.parse().ok()).unwrap_or(0), palette: map.get("pal").and_then(|s| s.parse().ok()).unwrap_or(0),
shadow_palette: map.get("spal").and_then(|s| s.parse().ok()).unwrap_or(0),
}) })
} }
} }
@@ -134,6 +137,7 @@ mod tests {
color_scale: 0.02, color_scale: 0.02,
color_offset: 0.25, color_offset: 0.25,
palette: 3, palette: 3,
shadow_palette: 1,
}; };
let d = ShareState::decode(&s.encode()).unwrap(); let d = ShareState::decode(&s.encode()).unwrap();
assert_eq!(d.julia, s.julia); assert_eq!(d.julia, s.julia);
@@ -146,6 +150,7 @@ mod tests {
assert_eq!(d.julia_c, s.julia_c); assert_eq!(d.julia_c, s.julia_c);
assert_eq!(d.phoenix_p, s.phoenix_p); assert_eq!(d.phoenix_p, s.phoenix_p);
assert_eq!(d.palette, s.palette); assert_eq!(d.palette, s.palette);
assert_eq!(d.shadow_palette, s.shadow_palette);
} }
#[test] #[test]
+55
View File
@@ -0,0 +1,55 @@
use std::f32::consts::PI;
use bytemuck::{Pod, Zeroable};
use egui::{Color32, Ui};
/// Maximum number of simultaneous lights.
pub const MAX_LIGHT_COUNT: usize = 16;
#[derive(Clone, Copy, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct Light {
pub azimuth: f32,
pub altitude: f32,
pub color: Color32,
pub _pad: u32,
}
impl Default for Light {
fn default() -> Self {
Self {
azimuth: PI / 4.,
altitude: PI / 4.,
color: Color32::WHITE,
_pad: 0,
}
}
}
impl Light {
pub fn widget(&mut self, ui: &mut Ui) -> bool {
let formater = |v, _| format!("{}°", ((v as f32 * 180. / PI) as u32));
ui.horizontal(|ui| {
let del = ui.button("-").clicked();
ui.label("color:");
ui.color_edit_button_srgba(&mut self.color);
ui.label("θ:");
ui.add(
egui::DragValue::new(&mut self.azimuth)
.range(0.0..=PI * 2.)
.custom_formatter(formater)
.speed(0.02),
);
ui.label("φ:");
ui.add(
egui::DragValue::new(&mut self.altitude)
.range(0.0..=PI / 2.)
.custom_formatter(formater)
.speed(0.02),
);
del
})
.inner
}
}
+1
View File
@@ -9,6 +9,7 @@
// and calls the wasm `main`, which boots eframe onto the page's <canvas>. // and calls the wasm `main`, which boots eframe onto the page's <canvas>.
mod app; mod app;
mod lights;
mod fractal; mod fractal;
mod view; mod view;
+72 -3
View File
@@ -20,6 +20,7 @@ struct Uniforms {
bailout_sq: f32, bailout_sq: f32,
is_julia: u32, is_julia: u32,
palette_id: u32, palette_id: u32,
shadow_palette_id: u32,
aa_level: u32, aa_level: u32,
kind: u32, kind: u32,
power: u32, power: u32,
@@ -30,8 +31,16 @@ struct Uniforms {
shadow: u32, shadow: u32,
}; };
struct Light {
azimuth: f32,
altitude: f32,
color: u32,
_pad: u32
};
@group(0) @binding(0) var<uniform> u: Uniforms; @group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var data_tex: texture_2d<f32>; @group(0) @binding(1) var data_tex: texture_2d<f32>;
@group(0) @binding(2) var<uniform> lights: array<Light, 16>;
// 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.
@@ -69,19 +78,79 @@ fn load(x: i32, y: i32) -> vec3<f32> {
return vec3<f32>(f32(x), f32(y), dist); return vec3<f32>(f32(x), f32(y), dist);
} }
fn compute_light(normal: vec3<f32>, light: vec3<f32>) -> vec3<f32> {
return vec3<f32>(max(0., dot(normal, normalize(light))));
}
fn uncharted2tonemap(x: vec3<f32>) -> vec3<f32> {
let A = 0.15; // Shoulder strength
let B = 0.50; // Linear strength
let C = 0.10; // Linear angle
let D = 0.20; // Toe strength
let E = 0.02; // Toe numerator / shoarder angle/etc.
let F = 0.30; // Toe denominator
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
fn filmic(color: vec3<f32>, white_point: f32) -> vec3<f32> {
let exposure_bias = 2.0;
let curr = uncharted2tonemap(color * exposure_bias);
// Valeur blanche maximale de référence
let white_scale = vec3(1.0) / uncharted2tonemap(vec3(white_point));
return curr * white_scale;
}
fn s(color: vec3<f32>, k: f32, c: f32) -> vec3<f32> {
return 1. / (1. + exp(-k * (color - c)));
}
fn contrast(color: vec3<f32>, k: f32, c: f32) -> vec3<f32> {
let color_c = s(color, k, c);
return (color_c - s(vec3<f32>(0), k, c)) / (s(vec3<f32>(1), k, c) - s(vec3<f32>(0), k, c));
}
@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> {
if u.shadow != 0u { if u.shadow != 0u {
if textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0).b != 0. { if textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0).b != 0. {
return vec4<f32>(0., 0.11, 0.54, 1.0); return vec4<f32>(0.1, 0.1, 0.1, 1.0);
} else { } 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 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 normal = normalize(cross(d[1] - d[0], d[2] - d[0])); let normal = normalize(cross(d[1] - d[0], d[2] - d[0]));
let color = (dot(normal, vec3<f32>(.5, .5, .5)) + 0.2) / 1.2; var color: vec3<f32>;
if u.shadow_palette_id == 0u {
color = compute_light(normal,vec3<f32>(.5, .5, .5)) + vec3<f32>(0.58, 0.85, 1.) * 0.2;
return vec4<f32>(vec3<f32>(color), 1.0); color = filmic(color, 2.5);
color = contrast(color, 4., 0.67);
} else if u.shadow_palette_id == 1u {
color = compute_light(normal, vec3<f32>(0., .5, .5)) * vec3<f32>(1., 0.5, 0.5) + compute_light(normal, vec3<f32>(0.5, 0., .5)) * vec3<f32>(0.5, 1., 1.);
color = filmic(color, 4.2);
} else {
color = vec3<f32>(0);
var light_count = 0;
for (var i = 0u ; i < 16; i++) {
let light_color = unpack4x8unorm(lights[i].color);
if any(light_color != vec4<f32>(0)) {
light_count += 1;
}
color += compute_light(normal, vec3<f32>(
cos(lights[i].azimuth) * cos(lights[i].altitude),
sin(lights[i].azimuth) * cos(lights[i].altitude),
sin(lights[i].altitude))) * light_color.xyz * light_color.a;
}
color = filmic(color, 1. + f32(light_count));
}
return vec4<f32>(color, 1.0);
} }
} else { } else {
let d = textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0); let d = textureLoad(data_tex, vec2<i32>(i32(pos.x), i32(pos.y)), 0);
+1
View File
@@ -21,6 +21,7 @@ struct Uniforms {
bailout_sq: f32, bailout_sq: f32,
is_julia: u32, is_julia: u32,
palette_id: u32, palette_id: u32,
shadow_palette_id: u32,
aa_level: u32, aa_level: u32,
// Iteration formula (see the KIND_* constants below). // Iteration formula (see the KIND_* constants below).
kind: u32, kind: u32,