Compare commits

...

9 Commits

Author SHA1 Message Date
fe998585b4 Add non-zero aperture 2024-12-30 17:00:17 +03:00
44ce47612a Split view matrix from viewport size 2024-12-30 16:51:29 +03:00
b26800c070 Make emission non-uniform
Surface emission would be uniform so, let’s pretend it’s volume emission
2024-12-30 16:46:35 +03:00
96b5ac2200 Split camera location and viewport 2024-12-30 16:46:25 +03:00
750dc8d744 Remove useless alpha channel 2024-12-30 16:24:25 +03:00
0767eec826 Simplify varyings 2024-12-30 04:07:44 +03:00
50144c7b02 Move camera location into push constants 2024-12-30 04:05:49 +03:00
12159dd0b5 Use From 2024-12-30 03:32:35 +03:00
676fb20ad4 Move camera more! 2024-12-30 03:27:27 +03:00
4 changed files with 189 additions and 84 deletions

View File

@ -45,6 +45,10 @@ impl SphereParams {
glossiness, glossiness,
} }
} }
pub fn deriv(&self, time: f32) -> Vec3 {
self.frequencies * self.amplitudes * (self.frequencies * time + self.phases).map(|x| x.cos())
}
} }
trait VecDistribution { trait VecDistribution {

View File

@ -1,9 +1,9 @@
use std::error::Error; use std::error::Error;
use glam::{mat3, uvec2, vec2, vec3, Vec3}; use glam::{mat3, uvec2, vec3, Vec3};
use image::ImageReader; use image::ImageReader;
use present::Presenter; use present::Presenter;
use trace::{Tracer, TracerData, TracerEnv, Vertex}; use trace::{Tracer, TracerData, TracerEnv};
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
@ -16,24 +16,28 @@ mod trace;
pub use trace::Sphere; pub use trace::Sphere;
fn make_viewport(eye: Vec3, w: u32, h: u32) -> [Vertex; 4] { struct CamLoc {
eye: Vec3,
forward: Vec3,
right: Vec3,
}
fn make_viewport(w: u32, h: u32) -> trace::Viewport {
let size = uvec2(w, h).as_vec2(); let size = uvec2(w, h).as_vec2();
let size = size.normalize(); let size = size.normalize();
let (w, h, d) = (size.x, size.y, 1.); trace::Viewport {
let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)]; corner: vec3(size.x, size.y, 1.),
}
}
let up = Vec3::Z; fn convert_location(cam: CamLoc) -> trace::CameraLocation {
let fwd = -eye.normalize(); let fwd = cam.forward.normalize();
let right = eye.cross(up).normalize(); let up = cam.right.cross(fwd).normalize();
let up = eye.cross(right).normalize(); let right = up.cross(fwd).normalize();
let m = mat3(fwd, right, up); trace::CameraLocation {
eye: cam.eye,
let world_coord = [vec3(d, -w, -h), vec3(d, w, -h), vec3(d, -w, h), vec3(d, w, h)]; view: mat3(right, up, fwd),
[0, 1, 2, 3].map(|k| Vertex { }
eye,
world: eye + m * (world_coord[k]),
screen: screen_coord[k],
})
} }
const N_SPHERES: u32 = 100; const N_SPHERES: u32 = 100;
@ -53,7 +57,7 @@ fn main() {
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 mut tracer = Tracer::new(&device, hdr_format); let tracer = Tracer::new(&device, hdr_format);
let mut rng = rand_pcg::Pcg32::new(42, 0); let mut rng = rand_pcg::Pcg32::new(42, 0);
let sphere_params: Vec<_> = { let sphere_params: Vec<_> = {
let distr = anim::distr(); let distr = anim::distr();
@ -61,6 +65,14 @@ fn main() {
}; };
let camera_params = { let camera_params = {
let mut p = anim::distr()(&mut rng); let mut p = anim::distr()(&mut rng);
p.amplitudes *= 2.0;
p.frequencies *= 0.1;
p
};
let target_params = {
let mut p = anim::distr()(&mut rng);
p.origin = Vec3::splat(0.0);
p.amplitudes *= 0.5;
p.frequencies *= 0.1; p.frequencies *= 0.1;
p p
}; };
@ -131,8 +143,11 @@ fn main() {
for _ in 0..RAYS_PER_PIXEL { for _ in 0..RAYS_PER_PIXEL {
frame += 1; frame += 1;
let time = frame as f32 / (60. * RAYS_PER_PIXEL as f32); let time = frame as f32 / (60. * RAYS_PER_PIXEL as f32);
let camera_pos = camera_params.to_sphere(time).center; let eye = camera_params.to_sphere(time).center;
tracer.set_view(&queue, &make_viewport(camera_pos, size.width, size.height)); let right = camera_params.deriv(time);
let forward = target_params.to_sphere(time).center - eye;
let viewport = make_viewport(size.width, size.height);
let location = convert_location(CamLoc { eye, forward, right });
let spheres: Vec<_> = sphere_params.iter().map(|p| p.to_sphere(time)).collect(); let spheres: Vec<_> = sphere_params.iter().map(|p| p.to_sphere(time)).collect();
let data = TracerData::new(&device, &tracer, &spheres); let data = TracerData::new(&device, &tracer, &spheres);
tracer.render( tracer.render(
@ -145,6 +160,12 @@ fn main() {
sphere_count: N_SPHERES, sphere_count: N_SPHERES,
seed: frame, seed: frame,
}, },
viewport,
trace::Aperture {
radius: 0.001,
focal_distance: std::f32::INFINITY,
},
location,
); );
} }
} }
@ -202,7 +223,7 @@ async fn init_gpu(wnd: &Window) -> Result<(wgpu::Device, wgpu::Queue, wgpu::Surf
label: None, label: None,
required_features: wgpu::Features::PUSH_CONSTANTS, required_features: wgpu::Features::PUSH_CONSTANTS,
required_limits: wgpu::Limits { required_limits: wgpu::Limits {
max_push_constant_size: 32, max_push_constant_size: 128,
..Default::default() ..Default::default()
}, },
memory_hints: Default::default(), memory_hints: Default::default(),

View File

@ -1,7 +1,7 @@
use std::mem::{self, offset_of}; use std::mem;
use bytemuck::{bytes_of, cast_slice, Pod, Zeroable}; use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
use glam::{Vec2, Vec3}; use glam::{vec2, Mat3, Vec2, Vec3};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, Pod, Zeroable)]
@ -13,6 +13,57 @@ pub struct Params {
pub seed: u32, pub seed: u32,
} }
#[derive(Debug, Clone, Copy)]
pub struct Viewport {
pub corner: Vec3,
}
pub struct Aperture {
pub radius: f32,
pub focal_distance: f32, // from 0 (exclusive) to +∞ (inclusive)
}
#[derive(Debug, Clone, Copy)]
pub struct CameraLocation {
pub eye: Vec3,
pub view: Mat3,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct ParamsData {
params: Params,
camera: CameraData,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct CameraData {
u: Vec3,
width: f32,
v: Vec3,
height: f32,
w: Vec3,
aperture: f32,
eye: Vec3,
antifocal: f32,
}
impl From<(Viewport, CameraLocation, Aperture)> for CameraData {
fn from(value: (Viewport, CameraLocation, Aperture)) -> Self {
CameraData {
u: value.1.view.x_axis,
v: value.1.view.y_axis,
w: value.1.view.z_axis,
eye: value.1.eye,
width: value.0.corner.x / value.0.corner.z,
height: value.0.corner.y / value.0.corner.z,
aperture: value.2.radius,
antifocal: 1. / value.2.focal_distance,
}
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Sphere { pub struct Sphere {
pub center: Vec3, pub center: Vec3,
@ -24,9 +75,7 @@ pub struct Sphere {
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct Vertex { struct Vertex {
pub eye: Vec3,
pub world: Vec3,
pub screen: Vec2, pub screen: Vec2,
} }
@ -41,6 +90,19 @@ struct SphereData {
glossiness: f32, glossiness: f32,
} }
impl From<&Sphere> for SphereData {
fn from(s: &Sphere) -> Self {
SphereData {
center: s.center,
radius: s.radius,
emit_color: s.emit_color,
pad1: 0.0,
reflect_color: s.reflect_color,
glossiness: s.glossiness,
}
}
}
pub struct Tracer { pub struct Tracer {
view_buf: wgpu::Buffer, view_buf: wgpu::Buffer,
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
@ -58,11 +120,11 @@ static SHADER: &str = include_str!("trace.wgsl");
impl Tracer { impl Tracer {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let view_buf = device.create_buffer(&wgpu::BufferDescriptor { let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)];
let view_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None, label: None,
size: (4 * mem::size_of::<Vertex>()) as u64, contents: cast_slice(&screen_coord),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::VERTEX,
mapped_at_creation: false,
}); });
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None, label: None,
@ -107,7 +169,7 @@ impl Tracer {
bind_group_layouts: &[&spheres_bgl, &envmap_bgl], bind_group_layouts: &[&spheres_bgl, &envmap_bgl],
push_constant_ranges: &[wgpu::PushConstantRange { push_constant_ranges: &[wgpu::PushConstantRange {
stages: wgpu::ShaderStages::FRAGMENT, stages: wgpu::ShaderStages::FRAGMENT,
range: 0..16, range: 0..mem::size_of::<ParamsData>() as u32,
}], }],
}); });
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@ -120,23 +182,11 @@ impl Tracer {
buffers: &[wgpu::VertexBufferLayout { buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::<Vertex>() as u64, array_stride: size_of::<Vertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[ attributes: &[wgpu::VertexAttribute {
wgpu::VertexAttribute { shader_location: 0,
shader_location: 0, offset: 0,
offset: offset_of!(Vertex, eye) as u64, format: wgpu::VertexFormat::Float32x2,
format: wgpu::VertexFormat::Float32x3, }],
},
wgpu::VertexAttribute {
shader_location: 1,
offset: offset_of!(Vertex, world) as u64,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
shader_location: 2,
offset: offset_of!(Vertex, screen) as u64,
format: wgpu::VertexFormat::Float32x2,
},
],
}], }],
}, },
primitive: wgpu::PrimitiveState { primitive: wgpu::PrimitiveState {
@ -176,13 +226,25 @@ impl Tracer {
Self { view_buf, pipeline } Self { view_buf, pipeline }
} }
pub fn set_view(&mut self, queue: &wgpu::Queue, vertices: &[Vertex; 4]) { pub fn render(
queue.write_buffer(&self.view_buf, 0, bytes_of(vertices)); &self,
} pass: &mut wgpu::RenderPass,
data: &TracerData,
pub fn render(&self, pass: &mut wgpu::RenderPass, data: &TracerData, env: &TracerEnv, params: Params) { env: &TracerEnv,
params: Params,
viewport: Viewport,
aperture: Aperture,
camera: CameraLocation,
) {
pass.set_pipeline(&self.pipeline); pass.set_pipeline(&self.pipeline);
pass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 0, bytes_of(&params)); pass.set_push_constants(
wgpu::ShaderStages::FRAGMENT,
0,
bytes_of(&ParamsData {
params,
camera: (viewport, camera, aperture).into(),
}),
);
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.set_bind_group(1, &env.bindings, &[]);
@ -192,17 +254,7 @@ impl Tracer {
impl TracerData { impl TracerData {
pub fn new(device: &wgpu::Device, tracer: &Tracer, spheres: &[Sphere]) -> Self { pub fn new(device: &wgpu::Device, tracer: &Tracer, spheres: &[Sphere]) -> Self {
let spheres: Vec<_> = spheres let spheres: Vec<_> = spheres.iter().map(SphereData::from).collect();
.iter()
.map(|s| SphereData {
center: s.center,
radius: s.radius,
emit_color: s.emit_color,
pad1: 0.0,
reflect_color: s.reflect_color,
glossiness: s.glossiness,
})
.collect();
let spheres_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let spheres_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None, label: None,
contents: cast_slice(&spheres), contents: cast_slice(&spheres),

View File

@ -3,6 +3,15 @@ struct Params {
min_strength: f32, min_strength: f32,
sphere_count: i32, sphere_count: i32,
seed: u32, seed: u32,
right: vec3f,
width: f32,
up: vec3f,
height: f32,
forward: vec3f,
aperture: f32,
eye: vec3f,
antifocal: f32,
} }
struct Sphere { struct Sphere {
@ -14,14 +23,11 @@ struct Sphere {
} }
struct Vertex { struct Vertex {
@location(0) eye: vec3f, @location(0) screen: vec2f,
@location(1) world: vec3f,
@location(2) screen: vec2f,
} }
struct Varying { struct Varying {
@location(0) eye: vec3f, @location(0) dir: vec3f,
@location(1) world: vec3f,
@builtin(position) screen: vec4f, @builtin(position) screen: vec4f,
} }
@ -32,12 +38,13 @@ var<push_constant> params: Params;
@vertex @vertex
fn on_vertex(in: Vertex) -> Varying { fn on_vertex(in: Vertex) -> Varying {
return Varying(in.eye, in.world, vec4(in.screen, 0.0, 1.0)); let m = mat3x3(params.width * params.right, params.height * params.up, params.forward);
return Varying(m * vec3(in.screen, 1.0), vec4(in.screen, 0.0, 1.0));
} }
@fragment @fragment
fn on_fragment(in: Varying) -> @location(0) vec4f { fn on_fragment(in: Varying) -> @location(0) vec4f {
return trace_fragment(in); return vec4(trace_fragment(in), 1.);
} }
fn sqr(v: vec3f) -> f32 { fn sqr(v: vec3f) -> f32 {
@ -63,15 +70,19 @@ fn to_sphere(center: vec3f, radius: f32, t: ptr<function, f32>) -> bool {
return true; return true;
} }
fn trace_fragment(in: Varying) -> vec4f { fn trace_fragment(in: Varying) -> vec3f {
seed(in.screen); seed(in.screen);
var result = vec4(0.0, 0.0, 0.0, 0.0); var result = vec3(0.);
var color = vec3(1.0, 1.0, 1.0); var color = vec3(1.);
pos = in.eye; let view_mtx = mat3x3(params.right, params.up, params.forward);
let aperture_offset_rel = params.aperture * rand_disc();
let aperture_offset_abs = view_mtx * vec3(aperture_offset_rel, 0.);
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.world), dpdy(in.world)); let off_w = mat2x3(dpdx(in.dir), dpdy(in.dir));
ray = normalize(in.world + off_w * off_px - in.eye); let dir = in.dir + off_w * off_px;
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++) {
var sphere = -1; var sphere = -1;
@ -85,17 +96,24 @@ fn trace_fragment(in: Varying) -> vec4f {
} }
if (sphere == -1) { if (sphere == -1) {
let env = textureSampleLevel(env_texture, env_sampler, ray, 0.0); let env = textureSampleLevel(env_texture, env_sampler, ray, 0.0);
result += vec4(3.0 * color * env.xyz, 0.0); result += 3.0 * color * env.xyz;
break; break;
} }
let s = spheres[sphere]; let s = spheres[sphere];
pos += t * ray; pos += t * ray;
let normal = (pos - s.center) / s.radius; let normal = (pos - s.center) / s.radius;
result += vec4(color * s.emit_color, 0.0); if (all(s.emit_color == vec3(0.))) {
color *= s.reflect_color; color *= s.reflect_color;
let diffuse = normal + rand_sphere(); let diffuse = normal + rand_sphere();
let specular = reflect(ray, normal); let specular = reflect(ray, normal);
ray = normalize(mix(diffuse, specular, s.glossiness)); ray = normalize(mix(diffuse, specular, s.glossiness));
} else {
let d = dot(-ray, normal);
let strength = d * d; // it would be 1 for surface emission, but this models volume emission
result += color * s.emit_color * strength;
color *= (1. - strength);
pos += 1e-3 * ray;
}
if (length(color) < params.min_strength) { if (length(color) < params.min_strength) {
break; break;
} }
@ -128,6 +146,16 @@ fn rand_float() -> f32 {
return f32(rand_next()) / 0x1p32; return f32(rand_next()) / 0x1p32;
} }
fn rand_disc() -> vec2f {
for (var k = 0; k < 16; k++) {
let v = vec2f(rand_float(), rand_float()) - 0.5;
if (length(v) <= 0.5) {
return 2. * v;
}
}
return vec2f(0.0); // safeguard
}
fn rand_sphere() -> vec3f { fn rand_sphere() -> vec3f {
for (var k = 0; k < 16; k++) { for (var k = 0; k < 16; k++) {
let v = vec3f(rand_float(), rand_float(), rand_float()) - 0.5; let v = vec3f(rand_float(), rand_float(), rand_float()) - 0.5;