feat: allow bigger render distance in 3d view
This commit is contained in:
+28
-3
@@ -503,6 +503,10 @@ pub struct FractalApp {
|
|||||||
// Use shadow coloring
|
// Use shadow coloring
|
||||||
// Use 3D raymarching rendering
|
// Use 3D raymarching rendering
|
||||||
rendering_mode: u32,
|
rendering_mode: u32,
|
||||||
|
/// Per-axis scale of the 3D view's height-field texture relative to the
|
||||||
|
/// widget: higher shows sharper, more distant terrain but costs GPU time
|
||||||
|
/// and memory.
|
||||||
|
render_scale_3d: f32,
|
||||||
|
|
||||||
/// List of enabled lights in the world
|
/// List of enabled lights in the world
|
||||||
lights: Vec<Light>,
|
lights: Vec<Light>,
|
||||||
@@ -716,6 +720,7 @@ impl FractalApp {
|
|||||||
worker: crate::worker::RefWorker::spawn(),
|
worker: crate::worker::RefWorker::spawn(),
|
||||||
pending: false,
|
pending: false,
|
||||||
export_scale: 2.0,
|
export_scale: 2.0,
|
||||||
|
render_scale_3d: 2.0,
|
||||||
last_size_px: egui::vec2(1280.0, 720.0),
|
last_size_px: egui::vec2(1280.0, 720.0),
|
||||||
last_interact_time: -1.0e9,
|
last_interact_time: -1.0e9,
|
||||||
export_requested: false,
|
export_requested: false,
|
||||||
@@ -1360,7 +1365,10 @@ impl FractalApp {
|
|||||||
/// then zoom the 2D view about the matching fractal-texture pixel.
|
/// then zoom the 2D view about the matching fractal-texture pixel.
|
||||||
fn zoom_3d_at(&mut self, off: egui::Vec2, rect: egui::Rect, height_px: f64, factor: f64) {
|
fn zoom_3d_at(&mut self, off: egui::Vec2, rect: egui::Rect, height_px: f64, factor: f64) {
|
||||||
let ndc = (off / rect.size()) * 2.;
|
let ndc = (off / rect.size()) * 2.;
|
||||||
let camera_ndc_pos = self.camera.orthographic(self.anim.camera_state).inverse()
|
let camera_ndc_pos = self
|
||||||
|
.camera
|
||||||
|
.orthographic(self.anim.camera_state, self.render_scale_3d)
|
||||||
|
.inverse()
|
||||||
* Vec4::new(ndc.x, ndc.y, 0., 1.);
|
* Vec4::new(ndc.x, ndc.y, 0., 1.);
|
||||||
let view_direction = self.camera.direction(self.anim.camera_state);
|
let view_direction = self.camera.direction(self.anim.camera_state);
|
||||||
|
|
||||||
@@ -1402,7 +1410,7 @@ impl FractalApp {
|
|||||||
morph_w: self.ref_morph.map_or(0.0, |(_, w)| w),
|
morph_w: self.ref_morph.map_or(0.0, |(_, w)| w),
|
||||||
camera_inv_proj: self
|
camera_inv_proj: self
|
||||||
.camera
|
.camera
|
||||||
.orthographic(self.anim.camera_state)
|
.orthographic(self.anim.camera_state, self.render_scale_3d)
|
||||||
.inverse()
|
.inverse()
|
||||||
.to_cols_array(),
|
.to_cols_array(),
|
||||||
screen_dim: self.screen_dim,
|
screen_dim: self.screen_dim,
|
||||||
@@ -2188,6 +2196,22 @@ impl FractalApp {
|
|||||||
ui.radio_value(&mut self.rendering_mode, 1, "Shadow");
|
ui.radio_value(&mut self.rendering_mode, 1, "Shadow");
|
||||||
ui.radio_value(&mut self.rendering_mode, 2, "3D");
|
ui.radio_value(&mut self.rendering_mode, 2, "3D");
|
||||||
});
|
});
|
||||||
|
if self.rendering_mode == 2 {
|
||||||
|
let old_scale = self.render_scale_3d;
|
||||||
|
ui.add(egui::Slider::new(&mut self.render_scale_3d, 1.0..=4.0).text("3D render scale"))
|
||||||
|
.on_hover_text(
|
||||||
|
"Resolution multiplier of the 3D height field. Higher shows more \
|
||||||
|
distant detail but costs GPU time and memory.",
|
||||||
|
);
|
||||||
|
// The 3D camera zooms in by the render scale (`Camera::orthographic`):
|
||||||
|
// zoom the view out by the same ratio so the fractal keeps its
|
||||||
|
// on-screen size and the extra texels become surrounding terrain.
|
||||||
|
if self.render_scale_3d != old_scale {
|
||||||
|
let factor = (self.render_scale_3d / old_scale) as f64;
|
||||||
|
self.view
|
||||||
|
.zoom_at_pixel(0.0, 0.0, self.last_size_px.y.max(1.0) as f64, factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.add_space(4.);
|
ui.add_space(4.);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -2832,7 +2856,8 @@ impl FractalApp {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if self.effective_rendering_mode() == 2 {
|
if self.effective_rendering_mode() == 2 {
|
||||||
size_px = [size_px[0] * 2, size_px[1] * 2];
|
let s = self.render_scale_3d;
|
||||||
|
size_px = size_px.map(|v| ((v as f32 * s).round() as u32).max(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.screen_dim = [rect.width(), rect.height()];
|
self.screen_dim = [rect.width(), rect.height()];
|
||||||
|
|||||||
+6
-2
@@ -53,11 +53,15 @@ impl Camera {
|
|||||||
self.pitch = pitch.clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
self.pitch = pitch.clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn orthographic(&self, t: f32) -> glam::Mat4 {
|
/// `render_scale` is the 3D texture's per-axis scale (see
|
||||||
|
/// `FractalApp::render_scale_3d`): the full 3D camera (`t = 1`) zooms in
|
||||||
|
/// by the same factor, so one texel still covers one screen pixel and the
|
||||||
|
/// extra texels become terrain beyond the screen edges.
|
||||||
|
pub fn orthographic(&self, t: f32, render_scale: f32) -> glam::Mat4 {
|
||||||
let yaw = self.yaw * t;
|
let yaw = self.yaw * t;
|
||||||
let pitch = self.pitch * t;
|
let pitch = self.pitch * t;
|
||||||
|
|
||||||
let zoom = 0.5 * (1. + t);
|
let zoom = 0.5 * (1. + t * (render_scale - 1.));
|
||||||
// Orbit pivot: the center of the fractal texture, which the raymarcher's
|
// Orbit pivot: the center of the fractal texture, which the raymarcher's
|
||||||
// `sdf` lays out over world x ∈ [0, aspect], y ∈ [0, 1] on the z = 0 plane.
|
// `sdf` lays out over world x ∈ [0, aspect], y ∈ [0, 1] on the z = 0 plane.
|
||||||
let view = glam::Mat4::from_translation(Vec3::new(0.5 * self.aspect_ratio, 0.5, 0.))
|
let view = glam::Mat4::from_translation(Vec3::new(0.5 * self.aspect_ratio, 0.5, 0.))
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ const DATA_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
|
|||||||
|
|
||||||
/// Cap on the interactive cache's pixel count (the texture is scaled down,
|
/// Cap on the interactive cache's pixel count (the texture is scaled down,
|
||||||
/// aspect kept, above it). Each pixel costs 36 bytes across the data, AA and
|
/// aspect kept, above it). Each pixel costs 36 bytes across the data, AA and
|
||||||
/// colour textures, and the 3D view renders at 2× per axis, so a HiDPI screen
|
/// colour textures, and the 3D view renders at a configurable multiple per axis
|
||||||
|
/// (default 2×), so a HiDPI screen
|
||||||
/// in 3D would otherwise want 0.5 GB+. Browsers cap WebGPU memory well below
|
/// in 3D would otherwise want 0.5 GB+. Browsers cap WebGPU memory well below
|
||||||
/// what native gets, so the web budget is ~4K (≈300 MB); native, ~8K.
|
/// what native gets, so the web budget is ~4K (≈300 MB); native, ~8K.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -1411,7 +1412,7 @@ impl egui_wgpu::CallbackTrait for FractalCallback {
|
|||||||
|
|
||||||
// Clamp to the device's texture-size limit, keeping the aspect ratio
|
// Clamp to the device's texture-size limit, keeping the aspect ratio
|
||||||
// (the iterate pass maps pixels through NDC, so the view is unchanged;
|
// (the iterate pass maps pixels through NDC, so the view is unchanged;
|
||||||
// the blit just upsamples). The 2× 3D supersample on a large/HiDPI
|
// the blit just upsamples). The 3D supersample (default 2×) on a large/HiDPI
|
||||||
// screen can otherwise exceed it.
|
// screen can otherwise exceed it.
|
||||||
// Also cap the total pixel count (`MAX_CACHE_PIXELS`), same way.
|
// Also cap the total pixel count (`MAX_CACHE_PIXELS`), same way.
|
||||||
let max_dim = device.limits().max_texture_dimension_2d;
|
let max_dim = device.limits().max_texture_dimension_2d;
|
||||||
|
|||||||
Reference in New Issue
Block a user