to rebase
This commit is contained in:
+28
-1
@@ -10,6 +10,7 @@ use crate::fractal::{
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::fractal::{compute_reference, compute_set_reference};
|
||||
use crate::lights::Light;
|
||||
use crate::view::{
|
||||
Big, DEFAULT_HALF_HEIGHT, ViewState, big_from_decimal_str, big_from_f64, big_to_decimal_str,
|
||||
precision_for,
|
||||
@@ -30,7 +31,7 @@ const INTERACT_SETTLE: f64 = 0.12;
|
||||
/// Palette names; index maps to `palette_id` in the shader.
|
||||
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"];
|
||||
const SHADOW_PALETTE_NAMES: &[&str] = &["Grayscale", "Red & Blue", "Custom lights"];
|
||||
/// Buddhabrot tonemap style names; index maps to `BuddhabrotUniforms::palette`.
|
||||
const BUDDHA_PALETTE_NAMES: &[&str] = &["Nebula", "Yellow", "Grayscale"];
|
||||
|
||||
@@ -273,6 +274,9 @@ pub struct FractalApp {
|
||||
// Use shadow coloring
|
||||
shadow: bool,
|
||||
|
||||
/// List of enabled lights in the world
|
||||
lights: Vec<Light>,
|
||||
|
||||
/// Render as a Buddhabrot (Monte-Carlo orbit-density histogram) instead of
|
||||
/// the ordinary escape-time set. Plain f32 view — no deep zoom, no
|
||||
/// perturbation/reference-orbit machinery (see `fractal::buddhabrot`).
|
||||
@@ -410,6 +414,7 @@ impl FractalApp {
|
||||
antialias: false,
|
||||
de_coloring: false,
|
||||
shadow: false,
|
||||
lights: vec![Light::default()],
|
||||
buddhabrot: false,
|
||||
buddha_r_cap: 50,
|
||||
buddha_g_cap: 500,
|
||||
@@ -1384,6 +1389,25 @@ impl FractalApp {
|
||||
}
|
||||
});
|
||||
}
|
||||
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)")
|
||||
.on_hover_text("Supersample each pixel for smoother edges (~4× slower).");
|
||||
if !self.shadow {
|
||||
@@ -1397,6 +1421,7 @@ impl FractalApp {
|
||||
}
|
||||
|
||||
ui.collapsing("Animation", |ui| {
|
||||
if !self.shadow {
|
||||
ui.checkbox(&mut self.anim.color, "Cycle colours")
|
||||
.on_hover_text("Scroll the palette offset over time.");
|
||||
if self.anim.color {
|
||||
@@ -1406,6 +1431,7 @@ impl FractalApp {
|
||||
.logarithmic(true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ui.checkbox(&mut self.anim.zoom, "Auto-zoom")
|
||||
.on_hover_text("Continuously zoom toward the current center.");
|
||||
@@ -1779,6 +1805,7 @@ impl FractalApp {
|
||||
rect,
|
||||
FractalCallback {
|
||||
uniforms,
|
||||
lights: self.lights.clone(),
|
||||
reference: Arc::clone(&self.reference),
|
||||
generation: self.generation,
|
||||
size_px,
|
||||
|
||||
+32
-1
@@ -13,6 +13,8 @@ use std::sync::Arc;
|
||||
|
||||
use eframe::egui_wgpu::{self, wgpu};
|
||||
|
||||
use crate::lights::{Light, MAX_LIGHT_COUNT};
|
||||
|
||||
/// Maximum reference-orbit length (points) the storage buffer can hold. Also
|
||||
/// bounds the iteration count. 128k points * 8 bytes = 1 MiB.
|
||||
pub const MAX_REF_POINTS: usize = 1 << 17;
|
||||
@@ -132,6 +134,7 @@ pub struct FractalRenderer {
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
ref_buffer: wgpu::Buffer,
|
||||
lights_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
target_format: wgpu::TextureFormat,
|
||||
/// Generation of the reference orbit currently uploaded to `ref_buffer`.
|
||||
@@ -174,6 +177,13 @@ impl FractalRenderer {
|
||||
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 {
|
||||
label: Some("fractal bind group layout"),
|
||||
entries: &[
|
||||
@@ -305,6 +315,16 @@ impl FractalRenderer {
|
||||
},
|
||||
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 =
|
||||
@@ -413,6 +433,7 @@ impl FractalRenderer {
|
||||
bind_group_layout,
|
||||
uniform_buffer,
|
||||
ref_buffer,
|
||||
lights_buffer,
|
||||
bind_group,
|
||||
target_format,
|
||||
uploaded_generation: u64::MAX,
|
||||
@@ -481,6 +502,10 @@ impl FractalRenderer {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&data_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: self.lights_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -785,6 +810,7 @@ pub fn encode_png_with_progress(
|
||||
/// colourise pass (see `prepare`).
|
||||
pub struct FractalCallback {
|
||||
pub uniforms: Uniforms,
|
||||
pub lights: Vec<Light>,
|
||||
pub reference: Arc<Vec<[f32; 2]>>,
|
||||
pub generation: u64,
|
||||
/// Widget size in physical pixels — the cache texture resolution.
|
||||
@@ -831,7 +857,8 @@ impl egui_wgpu::CallbackTrait for FractalCallback {
|
||||
let color_dirty = iter_dirty
|
||||
|| renderer.colored.as_ref().is_none_or(|c| {
|
||||
c.width != width || c.height != height || color_differs(&c.uniforms, &self.uniforms)
|
||||
});
|
||||
})
|
||||
|| true;
|
||||
|
||||
if !color_dirty {
|
||||
return Vec::new(); // cache still valid; paint() just blits it
|
||||
@@ -843,6 +870,10 @@ impl egui_wgpu::CallbackTrait for FractalCallback {
|
||||
0,
|
||||
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 iter_dirty {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
// and calls the wasm `main`, which boots eframe onto the page's <canvas>.
|
||||
|
||||
mod app;
|
||||
mod lights;
|
||||
mod fractal;
|
||||
mod view;
|
||||
|
||||
|
||||
@@ -31,8 +31,16 @@ struct Uniforms {
|
||||
shadow: u32,
|
||||
};
|
||||
|
||||
struct Light {
|
||||
azimuth: f32,
|
||||
altitude: f32,
|
||||
color: u32,
|
||||
_pad: u32
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
@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
|
||||
// in mandelbrot.wgsl.
|
||||
@@ -71,11 +79,10 @@ fn load(x: i32, y: i32) -> vec3<f32> {
|
||||
}
|
||||
|
||||
fn compute_light(normal: vec3<f32>, light: vec3<f32>) -> vec3<f32> {
|
||||
return vec3<f32>((dot(normal, normalize(light)) + 0.2) / 1.2);
|
||||
return vec3<f32>(max(0., dot(normal, normalize(light))));
|
||||
}
|
||||
|
||||
fn uncharted2tonemap(x: vec3<f32>) -> vec3<f32> {
|
||||
// Paramètres de la courbe de Hable
|
||||
let A = 0.15; // Shoulder strength
|
||||
let B = 0.50; // Linear strength
|
||||
let C = 0.10; // Linear angle
|
||||
@@ -84,20 +91,32 @@ fn uncharted2tonemap(x: vec3<f32>) -> vec3<f32> {
|
||||
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>) -> vec3<f32> {
|
||||
}
|
||||
|
||||
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(4.2));
|
||||
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
|
||||
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
if u.shadow != 0u {
|
||||
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 {
|
||||
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)));
|
||||
|
||||
@@ -105,11 +124,30 @@ fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
|
||||
var color: vec3<f32>;
|
||||
if u.shadow_palette_id == 0u {
|
||||
color = compute_light(normal,vec3<f32>(.5, .5, .5));
|
||||
} else {
|
||||
color = compute_light(normal,vec3<f32>(.5, .5, .5)) + vec3<f32>(0.58, 0.85, 1.) * 0.2;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user