Compare commits

..

No commits in common. "b59d0b1a1019d45efe587f2e9e2a5c89501a2adb" and "ad9515d8d674e2ee1f636e5b668b94b7f1c3344a" have entirely different histories.

14 changed files with 158 additions and 713 deletions

99
Cargo.lock generated
View File

@ -18,12 +18,6 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.11" version = "0.8.11"
@ -186,12 +180,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.9.0" version = "1.9.0"
@ -328,15 +316,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@ -401,25 +380,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "foldhash" name = "foldhash"
version = "0.1.4" version = "0.1.4"
@ -587,20 +547,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "image"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.0" version = "2.7.0"
@ -769,16 +715,6 @@ dependencies = [
"paste", "paste",
] ]
[[package]]
name = "miniz_oxide"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]] [[package]]
name = "naga" name = "naga"
version = "23.1.0" version = "23.1.0"
@ -1173,19 +1109,6 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.7.4" version = "3.7.4"
@ -1331,7 +1254,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"glam", "glam",
"image",
"pollster", "pollster",
"rand", "rand",
"rand_distr", "rand_distr",
@ -1443,12 +1365,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -2402,18 +2318,3 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-jpeg"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
dependencies = [
"zune-core",
]

View File

@ -2,12 +2,11 @@
name = "raytracing3" name = "raytracing3"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
default-run = "minitracer" default-run = "raytracing3"
[dependencies] [dependencies]
bytemuck = { version = "1.21.0", features = ["derive"] } bytemuck = { version = "1.21.0", features = ["derive"] }
glam = { version = "0.29.2", features = ["bytemuck"] } glam = { version = "0.29.2", features = ["bytemuck"] }
image = { version = "0.25.5", default-features = false, features = ["png", "jpeg"] }
pollster = "0.4.0" pollster = "0.4.0"
rand = "0.8.5" rand = "0.8.5"
rand_distr = { version = "0.4.3", features = ["std_math"] } rand_distr = { version = "0.4.3", features = ["std_math"] }

View File

@ -1,5 +1,5 @@
# Ray tracing example # Ray tracing example
Basic GPU-side ray tracing example. `cargo run --bin envmap` to prepare the sky, then `cargo run` to see it moving! Basic GPU-side ray tracing example. `cargo run` to see it moving!
![Screenshot](screenshot.jpg) ![Screenshot](screenshot.jpg)

View File

@ -1,11 +1,10 @@
use glam::{vec3, Vec3}; use glam::{vec3, Vec3};
use rand_distr::{Bernoulli, Distribution, Uniform}; use rand_distr::{Distribution, LogNormal, Uniform};
pub struct SphereParams { pub struct SphereParams {
pub radius: f32, pub radius: f32,
pub color: Vec3, pub color: Vec3,
pub alpha: f32, pub alpha: f32,
pub glossiness: f32,
pub origin: Vec3, pub origin: Vec3,
pub amplitudes: Vec3, pub amplitudes: Vec3,
pub frequencies: Vec3, pub frequencies: Vec3,
@ -16,11 +15,10 @@ pub struct SphereParamsDistribution {
pub drad: Uniform<f32>, pub drad: Uniform<f32>,
pub dpos: Uniform<f32>, pub dpos: Uniform<f32>,
pub dcol: Uniform<f32>, pub dcol: Uniform<f32>,
pub demit: Bernoulli, pub demit: LogNormal<f32>,
pub dampl: Uniform<f32>, pub dampl: Uniform<f32>,
pub dfreq: Uniform<f32>, pub dfreq: Uniform<f32>,
pub dphase: Uniform<f32>, pub dphase: Uniform<f32>,
pub dgloss: Uniform<f32>,
} }
impl Default for SphereParamsDistribution { impl Default for SphereParamsDistribution {
@ -29,11 +27,10 @@ impl Default for SphereParamsDistribution {
drad: Uniform::new(0.01, 0.10), drad: Uniform::new(0.01, 0.10),
dpos: Uniform::new(-1.0, 1.0), dpos: Uniform::new(-1.0, 1.0),
dcol: Uniform::new(0.0, 1.0), dcol: Uniform::new(0.0, 1.0),
demit: Bernoulli::new(0.1).unwrap(), demit: LogNormal::new(-0.8, 2.0).unwrap(),
dampl: Uniform::new(0.3, 0.8), dampl: Uniform::new(0.3, 0.8),
dfreq: Uniform::new(0.2, 1.5), dfreq: Uniform::new(0.2, 1.5),
dphase: Uniform::new(0., 2. * std::f32::consts::PI), dphase: Uniform::new(0., 2. * std::f32::consts::PI),
dgloss: Uniform::new(0., 1.),
} }
} }
} }
@ -44,8 +41,7 @@ impl SphereParamsDistribution {
origin: self.dpos.sample3(rgen), origin: self.dpos.sample3(rgen),
radius: self.drad.sample(rgen), radius: self.drad.sample(rgen),
color: self.dcol.sample3(rgen).normalize(), color: self.dcol.sample3(rgen).normalize(),
alpha: if self.demit.sample(rgen) { 10.0 } else { 0.0 }, alpha: self.demit.sample(rgen),
glossiness: self.dgloss.sample(rgen),
amplitudes: self.dampl.sample3(rgen), amplitudes: self.dampl.sample3(rgen),
frequencies: self.dfreq.sample3(rgen), frequencies: self.dfreq.sample3(rgen),
phases: self.dphase.sample3(rgen), phases: self.dphase.sample3(rgen),
@ -58,14 +54,12 @@ impl SphereParams {
let center = self.origin + self.amplitudes * (self.frequencies * time + self.phases).map(|x| x.sin()); let center = self.origin + self.amplitudes * (self.frequencies * time + self.phases).map(|x| x.sin());
let radius = self.radius; let radius = self.radius;
let emit_color = self.alpha * self.color; let emit_color = self.alpha * self.color;
let glossiness = self.glossiness;
let reflect_color = 0.6 * self.color + Vec3::splat(0.2); let reflect_color = 0.6 * self.color + Vec3::splat(0.2);
crate::Sphere { crate::Sphere {
center, center,
radius, radius,
emit_color, emit_color,
reflect_color, reflect_color,
glossiness,
} }
} }
} }

View File

@ -1,70 +1,78 @@
use std::error::Error; use std::error::Error;
use glam::{mat3, vec2, vec3, Mat3, Vec3}; use glam::{vec2, vec3};
use image::buffer::ConvertBuffer; use perlin::{Pipeline, Vertex};
use raytracing3::perlin::{self, Pipeline, Vertex}; use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Window, WindowAttributes},
};
const EXTENT: u32 = 1024; mod perlin;
fn make_viewport(m: Mat3) -> [Vertex; 4] { fn make_viewport(w: u32, h: u32) -> [Vertex; 4] {
let w = w as f32;
let h = h as f32;
let (w, h) = (1.0f32.max(w / h), 1.0f32.max(h / w));
let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)]; let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)];
let world_coord = screen_coord.map(|s| m * vec3(s.x, s.y, 1.)); let world_coord = [vec3(-w, -h, 0.), vec3(w, -h, 0.), vec3(-w, h, 0.), vec3(w, h, 0.)];
[0, 1, 2, 3].map(|k| Vertex { [0, 1, 2, 3].map(|k| Vertex {
dir: world_coord[k], world: 10. * world_coord[k],
screen: screen_coord[k], screen: screen_coord[k],
}) })
} }
fn main() { fn main() {
let (device, queue) = pollster::block_on(init_gpu()).unwrap(); let event_loop = EventLoop::new().unwrap();
let format = wgpu::TextureFormat::Rgba8UnormSrgb; #[allow(deprecated)]
let mut noiser = Pipeline::new(&device, format); let window = &event_loop
.create_window(WindowAttributes::new().with_title("Noise generation test"))
.unwrap();
let (device, queue, surface) = pollster::block_on(init_gpu(window)).unwrap();
let mut noiser = Pipeline::new(&device);
noiser.set_params( noiser.set_params(
&queue, &queue,
perlin::Params { perlin::Params {
origin: vec3(0., 0., -30.), seed: 42,
radius: 30., layers: 8,
roughness: 0.9,
scale: 1.7,
}, },
); );
let faces = [
mat3(-Vec3::Z, -Vec3::Y, Vec3::X), let mut surface_configured = false;
mat3(Vec3::Z, -Vec3::Y, -Vec3::X), #[allow(deprecated)]
mat3(Vec3::X, Vec3::Z, Vec3::Y), event_loop
mat3(Vec3::X, -Vec3::Z, -Vec3::Y), .run(move |event, control_flow| match event {
mat3(Vec3::X, -Vec3::Y, Vec3::Z), Event::WindowEvent { ref event, window_id } if window_id == window.id() => match event {
mat3(-Vec3::X, -Vec3::Y, -Vec3::Z), WindowEvent::CloseRequested => control_flow.exit(),
]; WindowEvent::Resized(physical_size) => {
let output = device.create_texture(&wgpu::TextureDescriptor { surface.configure(
label: None, &device,
size: wgpu::Extent3d { &wgpu::SurfaceConfiguration {
width: EXTENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
height: EXTENT, format: wgpu::TextureFormat::Bgra8UnormSrgb,
depth_or_array_layers: 6, width: physical_size.width,
height: physical_size.height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
desired_maximum_frame_latency: 2,
}, },
mip_level_count: 1, );
sample_count: 1, noiser.set_view(&queue, &make_viewport(physical_size.width, physical_size.height));
dimension: wgpu::TextureDimension::D2, surface_configured = true;
format, }
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, WindowEvent::RedrawRequested => {
view_formats: &[], if !surface_configured {
}); return;
let buffers = faces.map(|_| { }
device.create_buffer(&wgpu::BufferDescriptor { let output = surface.get_current_texture().unwrap();
label: None, let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
size: (4 * EXTENT * EXTENT) as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
})
});
for (face, m) in faces.iter().enumerate() {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let buffer = &buffers[face];
let view = output.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
base_array_layer: face as u32,
..Default::default()
});
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None, label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
@ -79,61 +87,29 @@ fn main() {
occlusion_query_set: None, occlusion_query_set: None,
timestamp_writes: None, timestamp_writes: None,
}); });
noiser.set_view(&queue, &make_viewport(*m));
noiser.render(&mut render_pass); noiser.render(&mut render_pass);
drop(render_pass); drop(render_pass);
encoder.copy_texture_to_buffer( queue.submit(std::iter::once(encoder.finish()));
wgpu::ImageCopyTexture { output.present();
texture: &output,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face as u32,
},
aspect: wgpu::TextureAspect::All,
},
wgpu::ImageCopyBuffer {
buffer: &buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * EXTENT),
rows_per_image: Some(EXTENT),
},
},
wgpu::Extent3d {
width: EXTENT,
height: EXTENT,
depth_or_array_layers: 1,
},
);
queue.submit([encoder.finish()]);
}
for buf in &buffers {
buf.slice(..).map_async(wgpu::MapMode::Read, |res| res.unwrap());
}
device.poll(wgpu::Maintain::Wait);
std::thread::scope(|s| {
for (face, buf) in buffers.iter().enumerate() {
s.spawn(move || {
let img =
image::RgbaImage::from_raw(EXTENT, EXTENT, buf.slice(..).get_mapped_range().to_vec()).unwrap();
let img: image::RgbImage = img.convert();
img.save(format!("textures/env{face}.jpeg")).unwrap();
});
} }
_ => {}
},
_ => {}
}) })
.unwrap();
} }
async fn init_gpu() -> Result<(wgpu::Device, wgpu::Queue), Box<dyn Error>> { async fn init_gpu(wnd: &Window) -> Result<(wgpu::Device, wgpu::Queue, wgpu::Surface), Box<dyn Error>> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY, backends: wgpu::Backends::PRIMARY,
..Default::default() ..Default::default()
}); });
let surface = instance.create_surface(wnd)?;
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(), power_preference: wgpu::PowerPreference::default(),
compatible_surface: None, compatible_surface: Some(&surface),
force_fallback_adapter: false, force_fallback_adapter: false,
}) })
.await .await
@ -150,5 +126,5 @@ async fn init_gpu() -> Result<(wgpu::Device, wgpu::Queue), Box<dyn Error>> {
) )
.await .await
.unwrap(); .unwrap();
Ok((device, queue)) Ok((device, queue, surface))
} }

View File

@ -9,26 +9,27 @@ use glam::{Vec2, Vec3};
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct Params { pub struct Params {
pub origin: Vec3, pub seed: u32,
pub radius: f32, pub layers: u32,
pub roughness: f32,
pub scale: f32,
} }
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct Vertex { pub struct Vertex {
pub dir: Vec3, pub world: Vec3,
pub screen: Vec2, pub screen: Vec2,
} }
pub struct Pipeline { pub struct Pipeline {
view_buf: wgpu::Buffer, view_buf: wgpu::Buffer,
params_buf: wgpu::Buffer, params_buf: wgpu::Buffer,
bindings: wgpu::BindGroup,
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
} }
impl Pipeline { impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { pub fn new(device: &wgpu::Device) -> Self {
let view_buf = device.create_buffer(&wgpu::BufferDescriptor { let view_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: (4 * size_of::<Vertex>()) as u64, size: (4 * size_of::<Vertex>()) as u64,
@ -42,7 +43,7 @@ impl Pipeline {
mapped_at_creation: false, mapped_at_creation: false,
}); });
let shader = fs::read_to_string("shaders/perlin.wgsl").unwrap(); let shader = fs::read_to_string("src/bin/envmap/perlin.wgsl").unwrap();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None, label: None,
source: wgpu::ShaderSource::Wgsl(shader.into()), source: wgpu::ShaderSource::Wgsl(shader.into()),
@ -65,7 +66,7 @@ impl Pipeline {
}, },
wgpu::VertexAttribute { wgpu::VertexAttribute {
shader_location: 1, shader_location: 1,
offset: offset_of!(Vertex, dir) as u64, offset: offset_of!(Vertex, world) as u64,
format: wgpu::VertexFormat::Float32x3, format: wgpu::VertexFormat::Float32x3,
}, },
], ],
@ -86,7 +87,7 @@ impl Pipeline {
entry_point: None, entry_point: None,
compilation_options: wgpu::PipelineCompilationOptions::default(), compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format, format: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState::REPLACE), blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
})], })],
@ -94,18 +95,9 @@ impl Pipeline {
multiview: None, multiview: None,
cache: None, cache: None,
}); });
let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &pipeline.get_bind_group_layout(0),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: params_buf.as_entire_binding(),
}],
});
Self { Self {
view_buf, view_buf,
params_buf, params_buf,
bindings,
pipeline, pipeline,
} }
} }
@ -121,7 +113,6 @@ impl Pipeline {
pub fn render(&self, pass: &mut wgpu::RenderPass) { pub fn render(&self, pass: &mut wgpu::RenderPass) {
pass.set_pipeline(&self.pipeline); pass.set_pipeline(&self.pipeline);
pass.set_vertex_buffer(0, self.view_buf.slice(..)); pass.set_vertex_buffer(0, self.view_buf.slice(..));
pass.set_bind_group(0, &self.bindings, &[]);
pass.draw(0..4, 0..1); pass.draw(0..4, 0..1);
} }
} }

View File

@ -5,31 +5,24 @@ struct Params {
scale: f32, scale: f32,
} }
struct LookParams {
origin: vec3f,
radius: f32,
}
struct Vertex { struct Vertex {
@location(0) screen: vec2f, @location(0) screen: vec2f,
@location(1) dir: vec3f, @location(1) world: vec3f,
} }
struct Varying { struct Varying {
@location(0) dir: vec3f, @location(0) world: vec3f,
@builtin(position) screen: vec4f, @builtin(position) screen: vec4f,
} }
@group(0) @binding(0) var<uniform> params: LookParams;
@vertex @vertex
fn on_vertex(in: Vertex) -> Varying { fn on_vertex(in: Vertex) -> Varying {
return Varying(in.dir, vec4(in.screen, 0.0, 1.0)); return Varying(in.world, 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 {
let point = params.origin + params.radius * normalize(in.dir); let point = 30. * (normalize(in.world + vec3(0., 0., 30.)) - vec3(0., 0., 1.));
let sharp_area = perlin_noise(Params(1, 3, 0.9, 2.0), 0.1 * point); let sharp_area = perlin_noise(Params(1, 3, 0.9, 2.0), 0.1 * point);
let sharp_base = perlin_noise(Params(1, 6, 0.9, 2.0), 0.1 * point); let sharp_base = perlin_noise(Params(1, 6, 0.9, 2.0), 0.1 * point);
let cloud_base = perlin_noise(Params(2, 8, 0.6, 2.0), 0.1 * point); let cloud_base = perlin_noise(Params(2, 8, 0.6, 2.0), 0.1 * point);

View File

@ -1,272 +0,0 @@
use std::error::Error;
use glam::{vec2, vec3};
use image::ImageReader;
use present::Presenter;
use trace::{Tracer, TracerData, TracerEnv, Vertex};
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Window, WindowAttributes},
};
mod anim;
mod present;
mod trace;
pub use trace::Sphere;
fn make_viewport(w: u32, h: u32) -> [Vertex; 4] {
let w = w as f32;
let h = h as f32;
let (w, h) = (1.0f32.max(w / h), 1.0f32.max(h / w));
let r = 1.0f32;
let screen_coord = [vec2(-h, -w), vec2(h, -w), vec2(-h, w), vec2(h, w)];
let eye = vec3(-r, 0.0, 0.0);
let world_coord = [
vec3(0.0, -1.0, -1.0),
vec3(0.0, 1.0, -1.0),
vec3(0.0, -1.0, 1.0),
vec3(0.0, 1.0, 1.0),
];
[0, 1, 2, 3].map(|k| Vertex {
eye,
world: world_coord[k],
screen: screen_coord[k],
})
}
const N_SPHERES: u32 = 100;
fn main() {
let event_loop = EventLoop::new().unwrap();
#[allow(deprecated)]
let window = &event_loop
.create_window(WindowAttributes::new().with_title("Ray tracing reflection test"))
.unwrap();
let (device, queue, surface) = pollster::block_on(init_gpu(window)).unwrap();
let envmap = load_envmap(&device, &queue);
queue.submit([]);
let output_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let hdr_format = wgpu::TextureFormat::Rgba16Float;
let mut tracer = Tracer::new(&device, hdr_format);
let sphere_params: Vec<_> = {
let mut rng = rand_pcg::Pcg32::new(42, 0);
let distr = anim::SphereParamsDistribution::default();
(0..N_SPHERES).map(|_| distr.make_params(&mut rng)).collect()
};
let tracer_env = TracerEnv::new(&device, &tracer, &envmap);
let presenter = Presenter::new(&device, output_format);
let mut frame = 0;
let mut time = 0.0;
let mut surface_configured = false;
#[allow(deprecated)]
event_loop
.run(move |event, control_flow| match event {
Event::WindowEvent { ref event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface.configure(
&device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
format: output_format,
width: physical_size.width,
height: physical_size.height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
desired_maximum_frame_latency: 2,
},
);
tracer.set_view(&queue, &make_viewport(physical_size.width, physical_size.height));
surface_configured = true;
}
WindowEvent::RedrawRequested => {
window.request_redraw();
if !surface_configured {
return;
}
time += 1. / 60.;
frame += 1;
let spheres: Vec<_> = sphere_params.iter().map(|p| p.to_sphere(time)).collect();
let data = TracerData::new(&device, &tracer, &spheres);
tracer.set_params(
&queue,
trace::Params {
max_reflections: 3,
min_strength: 0.1,
sphere_count: N_SPHERES,
seed: frame,
},
);
let output = surface.get_current_texture().unwrap();
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let size = output.texture.size();
let hdr = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: 2 * size.width,
height: 2 * size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: hdr_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let hdr = hdr.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &hdr,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
tracer.render(&mut render_pass, &data, &tracer_env);
}
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
presenter.render(&device, &mut render_pass, &hdr);
}
queue.submit(std::iter::once(encoder.finish()));
output.present();
}
_ => {}
},
_ => {}
})
.unwrap();
}
async fn init_gpu(wnd: &Window) -> Result<(wgpu::Device, wgpu::Queue, wgpu::Surface), Box<dyn Error>> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
let surface = instance.create_surface(wnd)?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: Default::default(),
},
None,
)
.await
.unwrap();
Ok((device, queue, surface))
}
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}.jpeg"))
.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

@ -1,103 +0,0 @@
use std::mem::offset_of;
use bytemuck::{bytes_of, Pod, Zeroable};
use glam::{vec2, Vec2};
use wgpu::util::DeviceExt;
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct Vertex {
pub screen: Vec2,
}
pub struct Presenter {
view_buf: wgpu::Buffer,
sampler: wgpu::Sampler,
pipeline: wgpu::RenderPipeline,
}
static SHADER: &str = include_str!("present.wgsl");
impl Presenter {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let view_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytes_of(&[(0., 0.), (1., 0.), (0., 1.), (1., 1.)].map(|(x, y)| Vertex { screen: vec2(x, y) })),
usage: wgpu::BufferUsages::VERTEX,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(SHADER.into()),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: None,
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::<Vertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
offset: offset_of!(Vertex, screen) as u64,
format: wgpu::VertexFormat::Float32x2,
}],
}],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: None,
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
cache: None,
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
Self {
view_buf,
sampler,
pipeline,
}
}
pub fn render(&self, device: &wgpu::Device, pass: &mut wgpu::RenderPass, texture: &wgpu::TextureView) {
let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.pipeline.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(texture),
},
],
});
pass.set_pipeline(&self.pipeline);
pass.set_vertex_buffer(0, self.view_buf.slice(..));
pass.set_bind_group(0, &bindings, &[]);
pass.draw(0..4, 0..1);
}
}

View File

@ -1,25 +0,0 @@
struct Vertex {
@location(0) screen: vec2f,
}
struct Varying {
@location(0) tex: vec2f,
@builtin(position) v: vec4f,
}
@group(0) @binding(0) var smp: sampler;
@group(0) @binding(1) var tex: texture_2d<f32>;
@vertex
fn on_vertex(in: Vertex) -> Varying {
return Varying(in.screen, vec4(2.0 * in.screen - 1.0, 0.0, 1.0));
}
@fragment
fn on_fragment(in: Varying) -> @location(0) vec4f {
let hdr = textureSample(tex, smp, in.tex).xyz;
let luminosity = dot(hdr, vec3(0.2126, 0.7152, 0.0722));
let color = hdr / luminosity;
let luma = luminosity / (luminosity + 1.0);
return vec4(luma * color, 1.0);
}

View File

@ -1 +0,0 @@
pub mod perlin;

View File

@ -1,45 +1,66 @@
use std::error::Error; use std::error::Error;
use glam::{vec2, vec3}; use glam::{vec2, vec3};
use raytracing3::perlin::{self, Pipeline, Vertex}; use trace::{Tracer, TracerData, Vertex};
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::EventLoop, event_loop::EventLoop,
window::{Window, WindowAttributes}, window::{Window, WindowAttributes},
}; };
mod anim;
mod trace;
pub use trace::Sphere;
fn make_viewport(w: u32, h: u32) -> [Vertex; 4] { fn make_viewport(w: u32, h: u32) -> [Vertex; 4] {
let w = w as f32; let w = w as f32;
let h = h as f32; let h = h as f32;
let d = 3.;
let (w, h) = (1.0f32.max(w / h), 1.0f32.max(h / w)); let (w, h) = (1.0f32.max(w / h), 1.0f32.max(h / w));
let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)]; let r = 3.0f32;
let world_coord = [vec3(-w, -h, d), vec3(w, -h, d), vec3(-w, h, d), vec3(w, h, d)]; let screen_coord = [vec2(-h, -w), vec2(h, -w), vec2(-h, w), vec2(h, w)];
let eye = vec3(-r, 0.0, 0.0);
let world_coord = [
vec3(0.0, -1.0, -1.0),
vec3(0.0, 1.0, -1.0),
vec3(0.0, -1.0, 1.0),
vec3(0.0, 1.0, 1.0),
];
[0, 1, 2, 3].map(|k| Vertex { [0, 1, 2, 3].map(|k| Vertex {
dir: world_coord[k], eye,
world: world_coord[k],
screen: screen_coord[k], screen: screen_coord[k],
}) })
} }
const N_SPHERES: u32 = 100;
fn main() { fn main() {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
#[allow(deprecated)] #[allow(deprecated)]
let window = &event_loop let window = &event_loop
.create_window(WindowAttributes::new().with_title("Noise generation test")) .create_window(WindowAttributes::new().with_title("Ray tracing reflection test"))
.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 format = wgpu::TextureFormat::Bgra8UnormSrgb; let mut tracer = Tracer::new(&device);
let mut noiser = Pipeline::new(&device, format); tracer.set_params(
noiser.set_params(
&queue, &queue,
perlin::Params { trace::Params {
origin: vec3(0., 0., -30.), max_reflections: 3,
radius: 30., min_strength: 0.1,
sphere_count: N_SPHERES,
}, },
); );
let sphere_params: Vec<_> = {
let mut rng = rand_pcg::Pcg32::new(42, 0);
let distr = anim::SphereParamsDistribution::default();
(0..N_SPHERES).map(|_| distr.make_params(&mut rng)).collect()
};
let mut time = 0.0;
let mut surface_configured = false; let mut surface_configured = false;
#[allow(deprecated)] #[allow(deprecated)]
@ -52,7 +73,7 @@ fn main() {
&device, &device,
&wgpu::SurfaceConfiguration { &wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
format, format: wgpu::TextureFormat::Bgra8Unorm,
width: physical_size.width, width: physical_size.width,
height: physical_size.height, height: physical_size.height,
present_mode: wgpu::PresentMode::Fifo, present_mode: wgpu::PresentMode::Fifo,
@ -61,13 +82,18 @@ fn main() {
desired_maximum_frame_latency: 2, desired_maximum_frame_latency: 2,
}, },
); );
noiser.set_view(&queue, &make_viewport(physical_size.width, physical_size.height)); tracer.set_view(&queue, &make_viewport(physical_size.width, physical_size.height));
surface_configured = true; surface_configured = true;
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
window.request_redraw();
if !surface_configured { if !surface_configured {
return; return;
} }
time += 1. / 60.;
let spheres: Vec<_> = sphere_params.iter().map(|p| p.to_sphere(time)).collect();
let data = TracerData::new(&device, &tracer, &spheres);
let output = surface.get_current_texture().unwrap(); let output = surface.get_current_texture().unwrap();
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
@ -85,8 +111,7 @@ fn main() {
occlusion_query_set: None, occlusion_query_set: None,
timestamp_writes: None, timestamp_writes: None,
}); });
noiser.render(&mut render_pass); tracer.render(&mut render_pass, &data);
drop(render_pass); drop(render_pass);
queue.submit(std::iter::once(encoder.finish())); queue.submit(std::iter::once(encoder.finish()));
output.present(); output.present();

View File

@ -10,16 +10,15 @@ pub struct Params {
pub max_reflections: u32, pub max_reflections: u32,
pub min_strength: f32, pub min_strength: f32,
pub sphere_count: u32, pub sphere_count: u32,
pub seed: u32,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Sphere { pub struct Sphere {
pub center: Vec3, pub center: Vec3,
pub radius: f32, pub radius: f32,
pub emit_color: Vec3, pub emit_color: Vec3,
pub reflect_color: Vec3, pub reflect_color: Vec3,
pub glossiness: f32,
} }
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, Pod, Zeroable)]
@ -38,7 +37,7 @@ struct SphereData {
emit_color: Vec3, emit_color: Vec3,
pad1: f32, pad1: f32,
reflect_color: Vec3, reflect_color: Vec3,
glossiness: f32, pad2: f32,
} }
pub struct Tracer { pub struct Tracer {
@ -51,14 +50,10 @@ 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 {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { pub fn new(device: &wgpu::Device) -> Self {
let view_buf = device.create_buffer(&wgpu::BufferDescriptor { let view_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: (4 * mem::size_of::<Vertex>()) as u64, size: (4 * mem::size_of::<Vertex>()) as u64,
@ -119,7 +114,7 @@ impl Tracer {
entry_point: None, entry_point: None,
compilation_options: wgpu::PipelineCompilationOptions::default(), compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format, format: wgpu::TextureFormat::Bgra8Unorm,
blend: Some(wgpu::BlendState::REPLACE), blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
})], })],
@ -142,11 +137,10 @@ impl Tracer {
queue.write_buffer(&self.view_buf, 0, bytes_of(vertices)); queue.write_buffer(&self.view_buf, 0, bytes_of(vertices));
} }
pub fn render(&self, pass: &mut wgpu::RenderPass, data: &TracerData, env: &TracerEnv) { pub fn render(&self, pass: &mut wgpu::RenderPass, data: &TracerData) {
pass.set_pipeline(&self.pipeline); pass.set_pipeline(&self.pipeline);
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);
} }
} }
@ -161,7 +155,7 @@ impl TracerData {
emit_color: s.emit_color, emit_color: s.emit_color,
pad1: 0.0, pad1: 0.0,
reflect_color: s.reflect_color, reflect_color: s.reflect_color,
glossiness: s.glossiness, pad2: 0.0,
}) })
.collect(); .collect();
let spheres_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let spheres_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@ -186,24 +180,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

@ -2,7 +2,6 @@ struct Params {
max_reflections: i32, max_reflections: i32,
min_strength: f32, min_strength: f32,
sphere_count: i32, sphere_count: i32,
seed: u32,
} }
struct Sphere { struct Sphere {
@ -10,7 +9,6 @@ struct Sphere {
radius: f32, radius: f32,
emit_color: vec3f, emit_color: vec3f,
reflect_color: vec3f, reflect_color: vec3f,
glossiness: f32,
} }
struct Vertex { struct Vertex {
@ -27,8 +25,6 @@ struct Varying {
@group(0) @binding(0) var<uniform> params: Params; @group(0) @binding(0) var<uniform> 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 {
@ -82,24 +78,22 @@ fn trace_fragment(in: Varying) -> vec4f {
} }
} }
if (sphere == -1) { if (sphere == -1) {
let env = textureSampleLevel(env_texture, env_sampler, ray, 0.0);
result += vec4(3.0 * color * env.xyz, 0.0);
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); result += vec4(color * s.emit_color * -dot(normal, ray), 0.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, 0.8));
if (length(color) < params.min_strength) { if (length(color) < params.min_strength) {
break; break;
} }
} }
return result; return clamp(result, vec4(0.0), vec4(1.0));
} }
fn hash(key : u32) -> u32 { fn hash(key : u32) -> u32 {
@ -114,7 +108,7 @@ var<private> rand_state: u32;
fn seed(key: vec4f) { fn seed(key: vec4f) {
let x = bitcast<u32>(key.x); let x = bitcast<u32>(key.x);
let y = bitcast<u32>(key.y); let y = bitcast<u32>(key.y);
rand_state = hash(hash(hash(params.seed) ^ x) ^ y); rand_state = hash(hash(x) ^ y);
} }
fn rand_next() -> u32 { fn rand_next() -> u32 {
@ -127,12 +121,12 @@ fn rand_float() -> f32 {
} }
fn rand_sphere() -> vec3f { fn rand_sphere() -> vec3f {
for (var k = 0; k < 16; k++) { loop {
let v = vec3f(rand_float(), rand_float(), rand_float()) - 0.5; let v = vec3f(rand_float(), rand_float(), rand_float()) - 0.5;
let l = length(v); let l = length(v);
if (length(v) <= 0.5) { if (length(v) <= 0.5) {
return v / l; return v / l;
} }
} }
return vec3f(0.0); // safeguard return vec3f(0.0); // unreachable
} }