Compare commits

...

7 Commits

Author SHA1 Message Date
c39a871bf8 Move Sun to visibility 2025-03-29 11:02:57 +03:00
84b8e9c9a5 Remove glare 2025-03-29 10:45:16 +03:00
ae6b1f6b0d Fixed scenery 2025-03-29 00:53:47 +03:00
9a9a88360a Make Sun customizable! 2025-03-29 00:53:36 +03:00
c1b3e862d6 Remove load_envmap
Maybe I’ll need it later though...
2025-03-29 00:06:20 +03:00
c93ab42458 Don’t load envmap
It starts much faster now!
2025-03-29 00:04:13 +03:00
b637590308 Ditch envmap in favor of clear blue sky
It’s photometric now!
2025-03-29 00:00:04 +03:00
5 changed files with 50 additions and 184 deletions

View File

@ -2,7 +2,7 @@ use std::error::Error;
use glam::uvec2; use glam::uvec2;
use raytracing3::present::{self, Presenter}; use raytracing3::present::{self, Presenter};
use raytracing3::scene::{load_envmap, Renderer, SceneParams}; use raytracing3::scene::{Renderer, SceneParams};
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
@ -21,13 +21,12 @@ fn main() {
.unwrap(); .unwrap();
let (device, queue, surface) = pollster::block_on(init_gpu(window)).unwrap(); let (device, queue, surface) = pollster::block_on(init_gpu(window)).unwrap();
let envmap = load_envmap(&device, &queue);
queue.submit([]); queue.submit([]);
let output_format = wgpu::TextureFormat::Bgra8UnormSrgb; let output_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let hdr_format = wgpu::TextureFormat::Rgba16Float; let hdr_format = wgpu::TextureFormat::Rgba16Float;
let scene = SceneParams::new(N_SPHERES); let scene = SceneParams::new(N_SPHERES);
let renderer = Renderer::new(&device, envmap); let renderer = Renderer::new(&device);
let presenter = Presenter::new(&device, output_format); let presenter = Presenter::new(&device, output_format);
let mut frame = 0; let mut frame = 0;

View File

@ -8,7 +8,7 @@ use std::{fs, io};
use glam::{uvec2, UVec2}; use glam::{uvec2, UVec2};
use image::buffer::ConvertBuffer; use image::buffer::ConvertBuffer;
use raytracing3::present::{self, Presenter}; use raytracing3::present::{self, Presenter};
use raytracing3::scene::{load_envmap, Renderer, SceneParams}; use raytracing3::scene::{Renderer, SceneParams};
const SIZE: UVec2 = uvec2(1920, 1080); const SIZE: UVec2 = uvec2(1920, 1080);
const FRAME_RATE: u32 = 60; const FRAME_RATE: u32 = 60;
@ -82,7 +82,6 @@ fn do_work(img_sender: async_channel::Sender<Frame>, start_frame: u32, stop_fram
let img_sender = Arc::new(img_sender); let img_sender = Arc::new(img_sender);
let (device, queue) = pollster::block_on(init_gpu()).unwrap(); let (device, queue) = pollster::block_on(init_gpu()).unwrap();
let envmap = load_envmap(&device, &queue);
queue.submit([]); queue.submit([]);
let texsize = wgpu::Extent3d { let texsize = wgpu::Extent3d {
@ -95,7 +94,7 @@ fn do_work(img_sender: async_channel::Sender<Frame>, start_frame: u32, stop_fram
let hdr_format = wgpu::TextureFormat::Rgba16Float; let hdr_format = wgpu::TextureFormat::Rgba16Float;
let scene = SceneParams::new(N_SPHERES); let scene = SceneParams::new(N_SPHERES);
let renderer = Renderer::new(&device, envmap); let renderer = Renderer::new(&device);
let presenter = Presenter::new(&device, output_format); let presenter = Presenter::new(&device, output_format);
println!("Rendering..."); println!("Rendering...");

View File

@ -1,9 +1,10 @@
use crate::anim::{self, SphereParams}; use crate::anim::{self, SphereParams};
use crate::trace::{self, Tracer, TracerData, TracerEnv}; use crate::trace::{self, Tracer, TracerData};
use crate::Sphere;
use glam::{mat3, uvec2, vec3, UVec2, Vec3}; use glam::{mat3, uvec2, vec3, UVec2, Vec3};
use image::ImageReader;
use std::f32::consts::PI; use std::f32::consts::PI;
#[derive(Debug, Clone, Copy)]
struct CamLoc { struct CamLoc {
eye: Vec3, eye: Vec3,
forward: Vec3, forward: Vec3,
@ -33,47 +34,39 @@ const CAMERA_LAG: f32 = 0.03;
pub struct Renderer { pub struct Renderer {
tracer: Tracer, tracer: Tracer,
env: TracerEnv,
} }
impl Renderer { impl Renderer {
pub fn new(device: &wgpu::Device, env: wgpu::TextureView) -> Self { pub fn new(device: &wgpu::Device) -> Self {
let hdr_format = wgpu::TextureFormat::Rgba16Float; let hdr_format = wgpu::TextureFormat::Rgba16Float;
let tracer = Tracer::new(&device, hdr_format); let tracer = Tracer::new(&device, hdr_format);
let env = TracerEnv::new(&device, &tracer, &env); Self { tracer }
Self { tracer, env }
} }
} }
pub struct SceneParams { pub struct SceneParams {
pub spheres: Vec<SphereParams>, spheres: Vec<Sphere>,
pub camera: SphereParams, camera: CamLoc,
pub target: SphereParams,
} }
impl SceneParams { impl SceneParams {
pub fn new(n_spheres: u32) -> Self { pub fn new(n_spheres: u32) -> Self {
let mut rng = rand_pcg::Pcg32::new(42, 0); let mut rng = rand_pcg::Pcg32::new(42, 0);
let spheres: Vec<_> = { const R: f32 = 100.;
let distr = anim::distr(); let sphere = Sphere {
(0..n_spheres).map(|_| distr(&mut rng)).collect() center: vec3(0., 0., -R),
radius: R,
emit_color: vec3(0., 0., 0.),
reflect_color: Vec3::splat(0.3),
glossiness: 0.,
}; };
let camera = { let spheres = vec![sphere];
let mut p = anim::distr()(&mut rng); let camera = CamLoc {
p.amplitudes *= 2.0; eye: vec3(-1., -1., 1.),
p.frequencies *= 0.1; forward: vec3(1., 1., 0.).normalize(),
p right: vec3(1., -1., 0.).normalize(),
}; };
let target = { Self { spheres, camera }
let mut p = spheres[0];
p.phases -= 2. * PI * CAMERA_LAG * p.frequencies;
p
};
Self {
spheres,
camera,
target,
}
} }
} }
@ -103,18 +96,12 @@ impl Renderer {
time: f32, time: f32,
seed: u32, seed: u32,
) { ) {
let target = scene.target.to_sphere(time).center;
let eye = scene.camera.to_sphere(time).center;
let right = scene.camera.deriv(time);
let forward = target - eye;
let viewport = make_viewport(size.x, size.y); let viewport = make_viewport(size.x, size.y);
let location = convert_location(CamLoc { eye, forward, right }); let location = convert_location(scene.camera);
let spheres: Vec<_> = scene.spheres.iter().map(|p| p.to_sphere(time)).collect(); let data = TracerData::new(&device, &self.tracer, &scene.spheres);
let data = TracerData::new(&device, &self.tracer, &spheres);
self.tracer.render( self.tracer.render(
&mut render_pass.0, &mut render_pass.0,
&data, &data,
&self.env,
trace::Params { trace::Params {
max_reflections: 3, max_reflections: 3,
min_strength: 0.1, min_strength: 0.1,
@ -125,82 +112,8 @@ impl Renderer {
trace::Aperture { trace::Aperture {
radius: 0.0003, radius: 0.0003,
focal_distance: std::f32::INFINITY, focal_distance: std::f32::INFINITY,
glare_strength: 0.1,
glare_radius: 0.1,
}, },
location, location,
); );
} }
} }
pub fn load_envmap(device: &wgpu::Device, queue: &wgpu::Queue) -> wgpu::TextureView {
let imgs = std::thread::scope(|s| {
[0, 1, 2, 3, 4, 5]
.map(|face| {
s.spawn(move || {
let img = ImageReader::open(format!("textures/env{face}.webp"))
.unwrap()
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
img.to_rgba8()
})
})
.map(|t| t.join().unwrap())
});
let size = imgs[0].width();
for img in &imgs {
assert!(img.width() == size);
assert!(img.height() == size);
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: size,
height: size,
depth_or_array_layers: 6,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
for (face, img) in imgs.iter().enumerate() {
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face as u32,
},
aspect: wgpu::TextureAspect::All,
},
img.as_raw(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * size),
rows_per_image: Some(size),
},
wgpu::Extent3d {
width: size,
height: size,
depth_or_array_layers: 1,
},
);
}
texture.create_view(&wgpu::TextureViewDescriptor {
label: None,
format: None,
dimension: Some(wgpu::TextureViewDimension::Cube),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
})
}

View File

@ -22,8 +22,6 @@ pub struct Viewport {
pub struct Aperture { pub struct Aperture {
pub radius: f32, pub radius: f32,
pub focal_distance: f32, // from 0 (exclusive) to +∞ (inclusive) pub focal_distance: f32, // from 0 (exclusive) to +∞ (inclusive)
pub glare_strength: f32,
pub glare_radius: f32, // at distance 1
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -50,8 +48,6 @@ struct CameraData {
aperture: f32, aperture: f32,
eye: Vec3, eye: Vec3,
antifocal: f32, antifocal: f32,
glare_strength: f32,
glare_radius: f32,
} }
impl From<(Viewport, CameraLocation, Aperture)> for CameraData { impl From<(Viewport, CameraLocation, Aperture)> for CameraData {
@ -65,8 +61,6 @@ impl From<(Viewport, CameraLocation, Aperture)> for CameraData {
height: value.0.corner.y / value.0.corner.z, height: value.0.corner.y / value.0.corner.z,
aperture: value.2.radius, aperture: value.2.radius,
antifocal: 1. / value.2.focal_distance, antifocal: 1. / value.2.focal_distance,
glare_strength: value.2.glare_strength,
glare_radius: value.2.glare_radius,
} }
} }
} }
@ -119,10 +113,6 @@ pub struct TracerData {
bindings: wgpu::BindGroup, bindings: wgpu::BindGroup,
} }
pub struct TracerEnv {
bindings: wgpu::BindGroup,
}
static SHADER: &str = include_str!("trace.wgsl"); static SHADER: &str = include_str!("trace.wgsl");
impl Tracer { impl Tracer {
@ -150,30 +140,9 @@ impl Tracer {
count: None, count: None,
}], }],
}); });
let envmap_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None, label: None,
bind_group_layouts: &[&spheres_bgl, &envmap_bgl], bind_group_layouts: &[&spheres_bgl],
push_constant_ranges: &[wgpu::PushConstantRange { push_constant_ranges: &[wgpu::PushConstantRange {
stages: wgpu::ShaderStages::FRAGMENT, stages: wgpu::ShaderStages::FRAGMENT,
range: 0..mem::size_of::<ParamsData>() as u32, range: 0..mem::size_of::<ParamsData>() as u32,
@ -258,7 +227,6 @@ impl Tracer {
&self, &self,
pass: &mut wgpu::RenderPass, pass: &mut wgpu::RenderPass,
data: &TracerData, data: &TracerData,
env: &TracerEnv,
params: Params, params: Params,
viewport: Viewport, viewport: Viewport,
aperture: Aperture, aperture: Aperture,
@ -275,7 +243,6 @@ impl Tracer {
); );
pass.set_vertex_buffer(0, self.view_buf.slice(..)); pass.set_vertex_buffer(0, self.view_buf.slice(..));
pass.set_bind_group(0, &data.bindings, &[]); pass.set_bind_group(0, &data.bindings, &[]);
pass.set_bind_group(1, &env.bindings, &[]);
pass.draw(0..4, 0..1); pass.draw(0..4, 0..1);
} }
} }
@ -299,24 +266,3 @@ impl TracerData {
Self { bindings } Self { bindings }
} }
} }
impl TracerEnv {
pub fn new(device: &wgpu::Device, tracer: &Tracer, view: &wgpu::TextureView) -> Self {
let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &tracer.pipeline.get_bind_group_layout(1),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(view),
},
],
});
Self { bindings }
}
}

View File

@ -12,9 +12,6 @@ struct Params {
aperture: f32, aperture: f32,
eye: vec3f, eye: vec3f,
antifocal: f32, antifocal: f32,
glare_strength: f32,
glare_radius: f32,
} }
struct Sphere { struct Sphere {
@ -36,8 +33,6 @@ struct Varying {
var<push_constant> params: Params; var<push_constant> params: Params;
@group(0) @binding(1) var<storage, read> spheres: array<Sphere>; @group(0) @binding(1) var<storage, read> spheres: array<Sphere>;
@group(1) @binding(0) var env_sampler: sampler;
@group(1) @binding(1) var env_texture: texture_cube<f32>;
@vertex @vertex
fn on_vertex(in: Vertex) -> Varying { fn on_vertex(in: Vertex) -> Varying {
@ -84,13 +79,7 @@ fn trace_fragment(in: Varying) -> vec3f {
pos = params.eye + aperture_offset_abs; pos = params.eye + aperture_offset_abs;
let off_px = vec2(rand_float(), rand_float()) - .5; let off_px = vec2(rand_float(), rand_float()) - .5;
let off_w = mat2x3(dpdx(in.dir), dpdy(in.dir)); let off_w = mat2x3(dpdx(in.dir), dpdy(in.dir));
var dir = in.dir + off_w * off_px; let dir = in.dir + off_w * off_px;
if (rand_float() < params.glare_strength) {
let p = rand_float();
let d = params.glare_radius * pow(p, 2.);
let glare_off = d * rand_disc();
dir += view_mtx * vec3(glare_off, 0.);
}
ray = normalize(dir - params.antifocal * aperture_offset_abs); ray = normalize(dir - params.antifocal * aperture_offset_abs);
for (var k = 0; k < params.max_reflections; k++) { for (var k = 0; k < params.max_reflections; k++) {
@ -104,8 +93,28 @@ fn trace_fragment(in: Varying) -> vec3f {
} }
} }
if (sphere == -1) { if (sphere == -1) {
let env = textureSampleLevel(env_texture, env_sampler, ray, 0.0); let theta = dot(ray, normalize(vec3(1., 2., 1.)));
result += 3.0 * color * env.xyz; var env: vec3f; // in kilonits
const ILLUMINANCE_LUX = 1e5;
const ANGULAR_DIAMETER_DEG = 20.0; // Sun: 0.5°
const PI = 3.141592653589793;
const ANGULAR_DIAMETER_RAD = PI / 180.0 * ANGULAR_DIAMETER_DEG;
const THETA = 0.5 * ANGULAR_DIAMETER_RAD;
const COS_THETA = 1.0 - 0.5 * THETA * THETA; // approximately
const SOLID_ANGLE_SR = PI * THETA * THETA; // approximately
const LUMINANCE_NIT = ILLUMINANCE_LUX / SOLID_ANGLE_SR;
const LUMINANCE_KNIT = 1e-3 * LUMINANCE_NIT;
if (theta > COS_THETA) {
env = vec3(1.0, 0.9, 0.6) * LUMINANCE_KNIT;
} else {
env = mix(vec3(0.5, 1.0, 2.0), vec3(2.0, 3.0, 4.0), 0.5 * theta + 0.5);
}
result += color * env.xyz;
break; break;
} }
let s = spheres[sphere]; let s = spheres[sphere];