Compare commits

...

9 Commits

Author SHA1 Message Date
5e97ff8eca A nice texture 2024-12-24 02:07:51 +03:00
61d3a90c7c Load the shader in runtime 2024-12-24 02:07:34 +03:00
001d7051ad Remove params
It’s easier to just edit the shader
2024-12-24 02:07:09 +03:00
5d8a7e7a62 sRGB? 2024-12-24 02:05:32 +03:00
e45f41f2e9 More noise options? 2024-12-23 22:51:49 +03:00
f051b812f5 Layered noise 2024-12-23 22:35:31 +03:00
b4b1805ce2 Basic noise generator 2024-12-23 22:35:31 +03:00
cec72b0661 Fix spherical random 2024-12-23 22:24:56 +03:00
d390df6564 Make spheres a bit fuzzy 2024-12-23 21:06:36 +03:00
6 changed files with 442 additions and 1 deletions

View File

@ -11,6 +11,11 @@
"build_cmd": "cargo build", "build_cmd": "cargo build",
"name": "build", "name": "build",
"run_cmd": "cargo run" "run_cmd": "cargo run"
},
{
"build_cmd": "cargo build --bin envmap",
"name": "envmap",
"run_cmd": "cargo run --bin envmap"
} }
] ]
} }

View File

@ -2,6 +2,7 @@
name = "raytracing3" name = "raytracing3"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
default-run = "raytracing3"
[dependencies] [dependencies]
bytemuck = { version = "1.21.0", features = ["derive"] } bytemuck = { version = "1.21.0", features = ["derive"] }

130
src/bin/envmap/main.rs Normal file
View File

@ -0,0 +1,130 @@
use std::error::Error;
use glam::{vec2, vec3};
use perlin::{Pipeline, Vertex};
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Window, WindowAttributes},
};
mod perlin;
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 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 {
world: 10. * world_coord[k],
screen: screen_coord[k],
})
}
fn main() {
let event_loop = EventLoop::new().unwrap();
#[allow(deprecated)]
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(
&queue,
perlin::Params {
seed: 42,
layers: 8,
roughness: 0.9,
scale: 1.7,
},
);
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: wgpu::TextureFormat::Bgra8UnormSrgb,
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,
},
);
noiser.set_view(&queue, &make_viewport(physical_size.width, physical_size.height));
surface_configured = true;
}
WindowEvent::RedrawRequested => {
if !surface_configured {
return;
}
let output = surface.get_current_texture().unwrap();
let view = output.texture.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: &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,
});
noiser.render(&mut render_pass);
drop(render_pass);
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))
}

118
src/bin/envmap/perlin.rs Normal file
View File

@ -0,0 +1,118 @@
use std::{
fs,
mem::{offset_of, size_of},
};
use bytemuck::{bytes_of, Pod, Zeroable};
use glam::{Vec2, Vec3};
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Params {
pub seed: u32,
pub layers: u32,
pub roughness: f32,
pub scale: f32,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex {
pub world: Vec3,
pub screen: Vec2,
}
pub struct Pipeline {
view_buf: wgpu::Buffer,
params_buf: wgpu::Buffer,
pipeline: wgpu::RenderPipeline,
}
impl Pipeline {
pub fn new(device: &wgpu::Device) -> Self {
let view_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: (4 * size_of::<Vertex>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let params_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: size_of::<Params>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let shader = fs::read_to_string("src/bin/envmap/perlin.wgsl").unwrap();
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,
},
wgpu::VertexAttribute {
shader_location: 1,
offset: offset_of!(Vertex, world) as u64,
format: wgpu::VertexFormat::Float32x3,
},
],
}],
},
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: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
cache: None,
});
Self {
view_buf,
params_buf,
pipeline,
}
}
pub fn set_params(&mut self, queue: &wgpu::Queue, params: Params) {
queue.write_buffer(&self.params_buf, 0, bytes_of(&params));
}
pub fn set_view(&mut self, queue: &wgpu::Queue, vertices: &[Vertex; 4]) {
queue.write_buffer(&self.view_buf, 0, bytes_of(vertices));
}
pub fn render(&self, pass: &mut wgpu::RenderPass) {
pass.set_pipeline(&self.pipeline);
pass.set_vertex_buffer(0, self.view_buf.slice(..));
pass.draw(0..4, 0..1);
}
}

148
src/bin/envmap/perlin.wgsl Normal file
View File

@ -0,0 +1,148 @@
struct Params {
seed: u32,
layers: u32,
roughness: f32,
scale: f32,
}
struct Vertex {
@location(0) screen: vec2f,
@location(1) world: vec3f,
}
struct Varying {
@location(0) world: vec3f,
@builtin(position) screen: vec4f,
}
@vertex
fn on_vertex(in: Vertex) -> Varying {
return Varying(in.world, vec4(in.screen, 0.0, 1.0));
}
@fragment
fn on_fragment(in: Varying) -> @location(0) vec4f {
let sharp_area = perlin_noise(Params(1, 3, 0.9, 2.0), 0.1 * in.world);
let sharp_base = perlin_noise(Params(1, 6, 0.9, 2.0), 0.1 * in.world);
let cloud_base = perlin_noise(Params(2, 8, 0.6, 2.0), 0.1 * in.world);
let sharp_detail = structured_noise(Params(11, 8, 3.0, 2.0), in.world);
let cloud_detail1 = perlin_noise(Params(12, 8, 0.7, 2.0), in.world);
let cloud_detail2 = perlin_noise(Params(13, 8, 0.7, 2.0), in.world);
let dust = sharp_noise(Params(21, 8, 0.9, 2.0), in.world);
let stars = sharp_noise(Params(22, 8, 2.7, 2.0), in.world);
let cloud = exp(5.0 * (cloud_base - 1.0));
let cloud1 = cloud * (2.0 + cloud_detail1);
let cloud2 = cloud * (2.0 + cloud_detail2);
let tint = clamp(sharp_area - 0.2, 0.0, 0.3) / 0.3 * max(0.0, sharp_base - 0.3) * max(0.0, cloud_base + 0.3) * max(0.0, sharp_detail);
return vec4(
// dust * vec3(0.0, 0.3, 0.0) +
// max(0.0, sin(stars - 1.5) * vec3(0.3, 0.2, 0.1) +
// max(0.0, stars - 2.0) * vec3(0.3, 0.5, 2.0) +
cloud1 * vec3(0.1, 0.2, 1.0) +
cloud2 * vec3(0.1, 0.3, 0.7) +
tint * vec3(4.0, 0.0, 0.4) +
max(0.0, tint - 0.1) * vec3(0.0, 4.0, 0.0),
1.0);
}
fn sharp_noise(params: Params, point: vec3f) -> f32 {
var result = 1.0;
var hscale = 1.0;
var seed = params.seed;
for (var layer = 0u; layer < params.layers; layer++) {
result *= pow(4.0 * abs(perlin_layer(seed, hscale * point)), params.roughness);
hscale *= params.scale;
seed = hash(seed);
}
return result;
}
fn structured_noise(params: Params, point: vec3f) -> f32 {
var result = 1.0;
var hscale = 1.0;
var seed = params.seed;
for (var layer = 0u; layer < params.layers; layer++) {
result *= pow(clamp(1. + perlin_layer(seed, hscale * point), 0., 1.), params.roughness);
hscale *= params.scale;
seed = hash(seed);
}
return result;
}
fn perlin_noise(params: Params, point: vec3f) -> f32 {
var result = 0.0;
var hscale = 1.0;
var vscale = 1.0;
var seed = params.seed;
for (var layer = 0u; layer < params.layers; layer++) {
result += vscale * perlin_layer(seed, hscale * point);
hscale *= params.scale;
vscale *= params.roughness;
seed = hash(seed);
}
return result;
}
fn perlin_layer(seed: u32, coords: vec3f) -> f32 {
let s = split(coords);
var ret = 0.0;
for (var i = 0u; i < 2; i++) {
for (var j = 0u; j < 2; j++) {
for (var k = 0u; k < 2; k++) {
ret += part(seed, s, vec3u(i, j, k));
}
}
}
return ret;
}
fn part(seed: u32, pos: Split, off: vec3u) -> f32 {
let base_vec = base(seed, pos.int + off);
let to_node = vec3f(off) - pos.frac;
let base_val = dot(base_vec, to_node);
let scale = smoothstep(vec3(0.0), vec3(1.0), 1.0 - abs(to_node));
return scale.x * scale.y * scale.z * base_val;
}
fn base(base_seed: u32, key: vec3u) -> vec3f {
var seed = hash(hash(hash(hash(base_seed) ^ key.x) ^ key.y) ^ key.z);
return rand_sphere(&seed);
}
struct Split {
int: vec3u,
frac: vec3f,
}
fn split(val: vec3f) -> Split {
let int = floor(val);
return Split(vec3u(vec3i(int)), val - int);
}
fn hash(key : u32) -> u32 {
var v = key;
v *= 0xb384af1bu;
v ^= v >> 15u;
return v;
}
fn rand(state: ptr<function, u32>) -> u32 {
*state = hash(*state);
return *state;
}
fn rand_float(state: ptr<function, u32>) -> f32 {
return f32(rand(state)) / 0x1p32;
}
fn rand_sphere(state: ptr<function, u32>) -> vec3f {
loop {
let v = vec3f(rand_float(state), rand_float(state), rand_float(state)) - 0.5;
let l = length(v);
if (length(v) <= 0.5) {
return v / l;
}
}
return vec3f(0.0); // unreachable
}

View File

@ -60,6 +60,8 @@ fn to_sphere(center: vec3f, radius: f32, t: ptr<function, f32>) -> bool {
} }
fn trace_fragment(in: Varying) -> vec4f { fn trace_fragment(in: Varying) -> vec4f {
seed(in.screen);
var result = vec4(0.0, 0.0, 0.0, 0.0); var result = vec4(0.0, 0.0, 0.0, 0.0);
var color = vec3(1.0, 1.0, 1.0); var color = vec3(1.0, 1.0, 1.0);
pos = in.eye; pos = in.eye;
@ -83,7 +85,9 @@ fn trace_fragment(in: Varying) -> vec4f {
let normal = (pos - s.center) / s.radius; let normal = (pos - s.center) / s.radius;
result += vec4(color * s.emit_color * -dot(normal, ray), 0.0); result += vec4(color * s.emit_color * -dot(normal, ray), 0.0);
color *= s.reflect_color; color *= s.reflect_color;
ray = reflect(ray, normal); let diffuse = normal + rand_sphere();
let specular = reflect(ray, normal);
ray = normalize(mix(diffuse, specular, 0.8));
if (length(color) < params.min_strength) { if (length(color) < params.min_strength) {
break; break;
} }
@ -91,3 +95,38 @@ fn trace_fragment(in: Varying) -> vec4f {
return clamp(result, vec4(0.0), vec4(1.0)); return clamp(result, vec4(0.0), vec4(1.0));
} }
fn hash(key : u32) -> u32 {
var v = key;
v *= 0xb384af1bu;
v ^= v >> 15u;
return v;
}
var<private> rand_state: u32;
fn seed(key: vec4f) {
let x = bitcast<u32>(key.x);
let y = bitcast<u32>(key.y);
rand_state = hash(hash(x) ^ y);
}
fn rand_next() -> u32 {
rand_state = hash(rand_state);
return rand_state;
}
fn rand_float() -> f32 {
return f32(rand_next()) / 0x1p32;
}
fn rand_sphere() -> vec3f {
loop {
let v = vec3f(rand_float(), rand_float(), rand_float()) - 0.5;
let l = length(v);
if (length(v) <= 0.5) {
return v / l;
}
}
return vec3f(0.0); // unreachable
}