finished diffuse pathtracing

This commit is contained in:
Sven Vogel 2023-04-17 20:02:22 +02:00
parent 3ffffb9bf5
commit b911f89e86
17 changed files with 7883 additions and 158 deletions

1
Cargo.lock generated
View File

@ -310,6 +310,7 @@ name = "eruption"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
"lazy_static",
"tobj", "tobj",
"vulkano", "vulkano",
"vulkano-shaders", "vulkano-shaders",

View File

@ -14,4 +14,6 @@ winit = "0.28.3"
tobj = "3.2.5" tobj = "3.2.5"
lazy_static = "1.4.0"
cgmath = "0.18.0" cgmath = "0.18.0"

View File

@ -3,7 +3,7 @@
newmtl glass newmtl glass
Ns 1000.000000 Ns 1000.000000
Ka 1.000000 1.000000 1.000000 Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000 Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
@ -13,7 +13,7 @@ illum 2
newmtl green newmtl green
Ns 250.000000 Ns 250.000000
Ka 1.000000 1.000000 1.000000 Ka 0.000000 0.000000 0.000000
Kd 0.006232 0.800000 0.014503 Kd 0.006232 0.800000 0.014503
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
@ -33,7 +33,7 @@ illum 2
newmtl red newmtl red
Ns 250.000000 Ns 250.000000
Ka 1.000000 1.000000 1.000000 Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.006232 0.009300 Kd 0.800000 0.006232 0.009300
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
@ -43,7 +43,7 @@ illum 2
newmtl white newmtl white
Ns 250.000000 Ns 250.000000
Ka 1.000000 1.000000 1.000000 Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000 Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000

42
res/monkey_disk.mtl Normal file
View File

@ -0,0 +1,42 @@
# Blender 3.5.0 MTL File: 'None'
# www.blender.org
newmtl light_blue
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl light_green
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl light_red
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl white
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2

7363
res/monkey_disk.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Copyright (c) 2018-2019 Michele Morrone
// All rights reserved.
//
// https://michelemorrone.eu - https://BrutPitt.com
//
// me@michelemorrone.eu - brutpitt@gmail.com
// twitter: @BrutPitt - github: BrutPitt
//
// https://github.com/BrutPitt/glslSmartDeNoise/
//
// This software is distributed under the terms of the BSD 2-Clause license
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define INV_SQRT_OF_2PI 0.39894228040143267793994605993439 // 1.0/SQRT_OF_2PI
#define INV_PI 0.31830988618379067153776752674503
// smartDeNoise - parameters
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// sampler2D tex - sampler image / texture
// vec2 uv - actual fragment coord
// float sigma > 0 - sigma Standard Deviation
// float kSigma >= 0 - sigma coefficient
// kSigma * sigma --> radius of the circular kernel
// float threshold - edge sharpening threshold
vec4 smart_de_noise(in sampler2D tex, vec2 uv, float sigma, float kSigma, float threshold)
{
float radius = round(kSigma*sigma);
float radQ = radius * radius;
float invSigmaQx2 = .5 / (sigma * sigma); // 1.0 / (sigma^2 * 2.0)
float invSigmaQx2PI = INV_PI * invSigmaQx2; // 1/(2 * PI * sigma^2)
float invThresholdSqx2 = .5 / (threshold * threshold); // 1.0 / (sigma^2 * 2.0)
float invThresholdSqrt2PI = INV_SQRT_OF_2PI / threshold; // 1.0 / (sqrt(2*PI) * sigma^2)
vec4 centrPx = texture(tex,uv);
float zBuff = 0.0;
vec4 aBuff = vec4(0.0);
vec2 size = vec2(textureSize(tex, 0));
vec2 d;
for (d.x=-radius; d.x <= radius; d.x++) {
float pt = sqrt(radQ-d.x*d.x); // pt = yRadius: have circular trend
for (d.y=-pt; d.y <= pt; d.y++) {
float blurFactor = exp( -dot(d , d) * invSigmaQx2 ) * invSigmaQx2PI;
vec4 walkPx = texture(tex,uv+d/size);
vec4 dC = walkPx-centrPx;
float deltaFactor = exp( -dot(dC, dC) * invThresholdSqx2) * invThresholdSqrt2PI * blurFactor;
zBuff += deltaFactor;
aBuff += deltaFactor*walkPx;
}
}
return aBuff/zBuff;
}

View File

@ -1,20 +1,32 @@
#version 450 #version 450
layout(location = 0) in vec2 texture_coordinate; #include "denoise.glsl"
layout(location = 0) noperspective in vec2 texture_coordinate;
layout(set = 0, binding = 0) uniform sampler2D image; layout(set = 0, binding = 0) uniform sampler2D image;
layout(location = 0) out vec4 frag_color; layout(location = 0) out vec4 frag_color;
float luminance(in vec3 color) {
return dot(color, vec3(0.2126f, 0.7152f, 0.0722f));
}
vec3 reinhard_jodie(in vec3 v) {
float l = luminance(v);
vec3 tv = v / (1.0f + v);
return mix(v / (1.0f + l), tv, tv);
}
void main() { void main() {
vec2 uv = texture_coordinate; vec2 uv = texture_coordinate;
vec3 color = texture(image, uv).rgb; vec3 color = texture(image, uv).rgb;
// TODO: tonemapping color = smart_de_noise(image, uv, 5.0, 1.0, 0.400).rgb;
// TODO: denoising
// TODO: bloom color = reinhard_jodie(color);
frag_color = vec4(color, 1.0); frag_color = vec4(color, 1.0);
} }

View File

@ -1,11 +1,16 @@
#version 450 #version 450
// vertex position
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
// vertex texture coordinate
layout(location = 1) in vec2 texture; layout(location = 1) in vec2 texture;
layout(location = 0) out vec2 texture_coordinate; // no perspective required for filling the screen with a quad
layout(location = 0) noperspective out vec2 texture_coordinate;
void main() { void main() {
// pass texture coordinates straight to fragment shader
texture_coordinate = texture; texture_coordinate = texture;
// no perspective transform required
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }

View File

@ -17,7 +17,7 @@ impl Camera {
front: Vector3::new(0.0, 0.0, -1.0), front: Vector3::new(0.0, 0.0, -1.0),
left: Vector3::new(1.0, 0.0, 0.0), left: Vector3::new(1.0, 0.0, 0.0),
up: Vector3::new(0.0, -1.0, 0.0), up: Vector3::new(0.0, -1.0, 0.0),
pos: Vector3::new(0.0, 0.0, 9.0), pos: Vector3::new(0.0, 1.0, 9.0),
fov: 90.0f32 fov: 90.0f32
} }
} }

View File

@ -1,7 +1,9 @@
mod camera; mod camera;
use std::sync::Arc; use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use lazy_static::lazy_static;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}; use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
@ -27,6 +29,112 @@ pub(crate) mod cs {
} }
} }
lazy_static! {
static ref MATERIAL_COLLECTIO: Mutex<HashMap<String, cs::Material>> = Mutex::new(HashMap::new());
static ref DEFAULT_MATERIAL: cs::Material = cs::Material {
albedo: Padded::from([0.0, 0.0, 0.0]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.0,
transmission: 0.0,
ior: 1.0,
metallic: false as u32,
__padding: 0
};
}
fn add_default_materials() {
let mut material_collection = MATERIAL_COLLECTIO.lock().unwrap();
material_collection.insert(String::from("white"), cs::Material {
albedo: Padded::from([1.0, 1.0, 1.0]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.0,
transmission: 0.0,
ior: 1.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("red"), cs::Material {
albedo: Padded::from([1.0, 0.0, 0.0]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.0,
transmission: 0.0,
ior: 1.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("green"), cs::Material {
albedo: Padded::from([0.0, 1.0, 0.0]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.0,
transmission: 0.0,
ior: 1.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("glass"), cs::Material {
albedo: Padded::from([1.0, 1.0, 1.0]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.5,
transmission: 1.0,
ior: 1.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("light"), cs::Material {
albedo: Padded::from([1.0, 1.0, 1.0]),
emission: Padded::from([1.0, 1.0, 1.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.5,
transmission: 0.0,
ior: 0.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("light_blue"), cs::Material {
albedo: Padded::from([0.3, 0.3, 1.0]),
emission: Padded::from([0.3, 0.3, 1.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.5,
transmission: 0.0,
ior: 0.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("light_red"), cs::Material {
albedo: Padded::from([1.0, 0.3, 0.3]),
emission: Padded::from([1.0, 0.3, 0.3]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.5,
transmission: 0.0,
ior: 0.0,
metallic: false as u32,
__padding: 0
});
material_collection.insert(String::from("light_green"), cs::Material {
albedo: Padded::from([0.3, 1.0, 0.3]),
emission: Padded::from([0.3, 1.0, 0.3]),
specular_color: [0.0, 0.0, 0.0],
roughness: 1.5,
transmission: 0.0,
ior: 0.0,
metallic: false as u32,
__padding: 0
});
}
pub struct PathtracerPipeline { pub struct PathtracerPipeline {
compute_queue: Arc<Queue>, compute_queue: Arc<Queue>,
compute_pipeline: Arc<ComputePipeline>, compute_pipeline: Arc<ComputePipeline>,
@ -34,16 +142,19 @@ pub struct PathtracerPipeline {
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>, descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
memory_allocator: Arc<StandardMemoryAllocator>, memory_allocator: Arc<StandardMemoryAllocator>,
image: Arc<ImageView<StorageImage>>, image: Arc<ImageView<StorageImage>>,
raw_image: Subbuffer<[[f32; 4]]>,
seconds: Instant, seconds: Instant,
uniform_buffer: Arc<SubbufferAllocator>, uniform_buffer: Arc<SubbufferAllocator>,
vertex_buffer: Subbuffer<[[f32; 4]]>, vertex_buffer: Subbuffer<[[f32; 4]]>,
index_buffer: Subbuffer<[u32]>, index_buffer: Subbuffer<[u32]>,
camera: Camera material_buffer: Subbuffer<[cs::Material]>,
camera: Camera,
frames: f32
} }
impl PathtracerPipeline { impl PathtracerPipeline {
pub fn new(renderer: &Renderer, compute_queue: &Arc<Queue>, size: [u32; 2]) -> Self { pub fn new(renderer: &Renderer, compute_queue: &Arc<Queue>, size: [u32; 2]) -> Self {
add_default_materials();
let compute_pipeline = { let compute_pipeline = {
let shader = cs::load(compute_queue.device().clone()).unwrap(); let shader = cs::load(compute_queue.device().clone()).unwrap();
@ -56,7 +167,7 @@ impl PathtracerPipeline {
).unwrap() ).unwrap()
}; };
let (raw_image_buffer, image) = create_image(&renderer.memory_allocator, compute_queue, size); let image = create_image(&renderer.memory_allocator, compute_queue, size);
let uniform_buffer = SubbufferAllocator::new( let uniform_buffer = SubbufferAllocator::new(
renderer.memory_allocator.clone(), renderer.memory_allocator.clone(),
@ -66,9 +177,9 @@ impl PathtracerPipeline {
}, },
); );
let (vertices, indices) = load_example_scene(); let (vertices, indices, materials) = load_example_scene();
let (vertex_buffer, index_buffer) = create_gpu_buffer(&vertices, &indices, &renderer.memory_allocator); let (vertex_buffer, index_buffer, materials) = create_gpu_buffer(&vertices, &indices, &materials, &renderer.memory_allocator);
return PathtracerPipeline { return PathtracerPipeline {
compute_queue: compute_queue.clone(), compute_queue: compute_queue.clone(),
@ -77,12 +188,13 @@ impl PathtracerPipeline {
descriptor_set_allocator: renderer.descriptor_set_allocator.clone(), descriptor_set_allocator: renderer.descriptor_set_allocator.clone(),
memory_allocator: renderer.memory_allocator.clone(), memory_allocator: renderer.memory_allocator.clone(),
image, image,
raw_image: raw_image_buffer,
uniform_buffer: Arc::new(uniform_buffer), uniform_buffer: Arc::new(uniform_buffer),
vertex_buffer, vertex_buffer,
index_buffer, index_buffer,
material_buffer: materials,
seconds: Instant::now(), seconds: Instant::now(),
camera: Camera::new() camera: Camera::new(),
frames: 0.0
}; };
} }
@ -109,17 +221,18 @@ impl PathtracerPipeline {
} }
pub fn resize_image(&mut self, size: [u32; 2]) { pub fn resize_image(&mut self, size: [u32; 2]) {
let (raw_image_buffer, image) = create_image(&self.memory_allocator, &self.compute_queue, size); let image = create_image(&self.memory_allocator, &self.compute_queue, size);
self.image = image; self.image = image;
self.raw_image = raw_image_buffer;
} }
/// Builds the command for a dispatch. /// Builds the command for a dispatch.
fn dispatch( fn dispatch(
&self, &mut self,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer,Arc<StandardCommandBufferAllocator>> builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer,Arc<StandardCommandBufferAllocator>>
) { ) {
self.frames += 1.0;
let camera = cs::Camera { let camera = cs::Camera {
front: Padded::from(Into::<[f32; 3]>::into(self.camera.front)), front: Padded::from(Into::<[f32; 3]>::into(self.camera.front)),
left: Padded::from(Into::<[f32; 3]>::into(self.camera.left)), left: Padded::from(Into::<[f32; 3]>::into(self.camera.left)),
@ -140,17 +253,18 @@ impl PathtracerPipeline {
&self.descriptor_set_allocator, &self.descriptor_set_allocator,
desc_layout.clone(), desc_layout.clone(),
[ [
WriteDescriptorSet::buffer(0, self.raw_image.clone()),
WriteDescriptorSet::image_view(1, self.image.clone()), WriteDescriptorSet::image_view(1, self.image.clone()),
WriteDescriptorSet::buffer(2, subbuffer), WriteDescriptorSet::buffer(2, subbuffer),
WriteDescriptorSet::buffer(3, self.vertex_buffer.clone()), WriteDescriptorSet::buffer(3, self.vertex_buffer.clone()),
WriteDescriptorSet::buffer(4, self.index_buffer.clone()) WriteDescriptorSet::buffer(4, self.index_buffer.clone()),
WriteDescriptorSet::buffer(5, self.material_buffer.clone())
], ],
).unwrap(); ).unwrap();
let push_constants = cs::PushConstants { let push_constants = cs::PushConstants {
resolution: [size[0] as f32, size[1] as f32], resolution: [size[0] as f32, size[1] as f32],
seconds: (Instant::now() - self.seconds).as_secs_f32() seconds: (Instant::now() - self.seconds).as_secs_f32(),
frames: self.frames
}; };
builder builder
@ -166,58 +280,55 @@ impl PathtracerPipeline {
} }
} }
fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>, size: [u32; 2]) -> (Subbuffer<[[f32; 4]]>, Arc<ImageView<StorageImage>>) { fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>, size: [u32; 2]) -> Arc<ImageView<StorageImage>> {
let raw_image = Buffer::from_iter( StorageImage::general_purpose_image_view(
memory_allocator,
BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
..Default::default()
},
vec![[0f32; 4]; (size[0] * size[1]) as usize],
).unwrap();
let image = StorageImage::general_purpose_image_view(
memory_allocator, memory_allocator,
queue.clone(), queue.clone(),
size, size,
Format::R8G8B8A8_UNORM, Format::R32G32B32A32_SFLOAT,
ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST, ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST,
).unwrap(); ).unwrap()
(raw_image, image)
} }
fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>) { fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>, Vec<cs::Material>) {
let (mut models, materials) = tobj::load_obj("res/example-scene.obj", &tobj::GPU_LOAD_OPTIONS).expect("unable to load scene from obj"); let (mut models, materials) = tobj::load_obj("res/example-scene.obj", &tobj::GPU_LOAD_OPTIONS).expect("unable to load scene from obj");
// allocate some host memory
let mut vertices:Vec<[f32; 4]> = vec![]; let mut vertices:Vec<[f32; 4]> = vec![];
let mut indices:Vec<u32> = vec![]; let mut indices:Vec<u32> = vec![];
let mut shader_materials:Vec<cs::Material> = vec![];
for model in models.iter_mut() { for model in models.iter_mut() {
let offset = vertices.len() as u32; let offset = vertices.len() as u32;
let material = model.mesh.material_id.unwrap_or(0);
// fill the vertex buffer
for vertex_index in (0..model.mesh.positions.len()).step_by(3) { for vertex_index in (0..model.mesh.positions.len()).step_by(3) {
vertices.push([ vertices.push([
model.mesh.positions[vertex_index], model.mesh.positions[vertex_index],
model.mesh.positions[vertex_index + 1], model.mesh.positions[vertex_index + 1],
model.mesh.positions[vertex_index + 2], model.mesh.positions[vertex_index + 2],
0.0 // padding will store material index to be used for this triangle
material as f32
]); ]);
} }
// fill the index buffer
for index in model.mesh.indices.iter() { for index in model.mesh.indices.iter() {
indices.push(*index + offset); indices.push(*index + offset);
} }
} }
(vertices, indices) let material_collection = &MATERIAL_COLLECTIO.lock().unwrap();
for material in materials.unwrap().iter() {
shader_materials.push(*material_collection.get(&material.name).unwrap_or(&DEFAULT_MATERIAL));
}
(vertices, indices, shader_materials)
} }
fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[[f32; 4]]>, Subbuffer<[u32]>) { fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, materials: &Vec<cs::Material>, memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[[f32; 4]]>, Subbuffer<[u32]>, Subbuffer<[cs::Material]>) {
let vertex_buffer = Buffer::from_iter( let vertex_buffer = Buffer::from_iter(
memory_allocator, memory_allocator,
BufferCreateInfo { BufferCreateInfo {
@ -225,7 +336,7 @@ fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, memory_alloca
..Default::default() ..Default::default()
}, },
AllocationCreateInfo { AllocationCreateInfo {
usage: MemoryUsage::Upload, usage: MemoryUsage::DeviceOnly,
..Default::default() ..Default::default()
}, },
vertices.clone(), vertices.clone(),
@ -238,11 +349,24 @@ fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, memory_alloca
..Default::default() ..Default::default()
}, },
AllocationCreateInfo { AllocationCreateInfo {
usage: MemoryUsage::Upload, usage: MemoryUsage::DeviceOnly,
..Default::default() ..Default::default()
}, },
indices.clone(), indices.clone(),
).unwrap(); ).unwrap();
(vertex_buffer, index_buffer) let material_buffer = Buffer::from_iter(
memory_allocator,
BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::DeviceOnly,
..Default::default()
},
materials.clone(),
).unwrap();
(vertex_buffer, index_buffer, material_buffer)
} }

View File

@ -3,17 +3,18 @@
#include "rand/random.glsl" #include "rand/random.glsl"
#include "raytracing/raytracing.glsl" #include "raytracing/raytracing.glsl"
#include "raytracing/camera.glsl" #include "raytracing/camera.glsl"
#include "bsdf.glsl" #include "raytracing/bsdf.glsl"
#define MAX_DEPTH 4
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer LinearImage { vec4 pixels[]; }; layout(set = 0, binding = 1, rgba32f) uniform image2D image;
layout(set = 0, binding = 1, rgba8) uniform writeonly image2D image;
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
vec2 resolution; vec2 resolution;
float seconds; float seconds;
float frames;
} program_metadata; } program_metadata;
vec2 get_viewport_coordinate() { vec2 get_viewport_coordinate() {
@ -26,82 +27,44 @@ uint get_pixel_index() {
return gl_GlobalInvocationID.y * uint(program_metadata.resolution.x) + gl_GlobalInvocationID.x; return gl_GlobalInvocationID.y * uint(program_metadata.resolution.x) + gl_GlobalInvocationID.x;
} }
vec3 project(in vec3 a, in vec3 b) { vec3 trace_direct(in Ray ray) {
return a * (dot(a, b) / dot(a, a)); vec3 color = vec3(0);
} vec3 throughput = vec3(1);
void construct_orthonormal_basis(in vec3 up, out vec3 u, out vec3 v, out vec3 w) {
u = normalize(up);
vec3 n2 = normalize(cross(up, vec3(0, 1.0, 1.0)));
vec3 n3 = cross(u, n2);
vec3 w2 = normalize(n2 - (project(u, n2)));
vec3 w3 = normalize(n3 - project(u, n3) - project(w2, n3));
v = w2;
w = w3;
}
vec3 cosine_weighted_hemisphere() {
float u1 = random();
float u2 = random();
float r = sqrt(u1);
float theta = 2 * 3.1415926535 * u2;
float x = r * cos(theta);
float y = r * sin(theta);
return vec3(x, y, sqrt(1.0 - u1));
}
vec3 generate_diffuse_ray_direction(in vec3 nor) {
vec3 hemisphere = cosine_weighted_hemisphere();
vec3 u, v, w;
construct_orthonormal_basis(nor, u, v, w);
return normalize(u * hemisphere.x + v * hemisphere.y + w * hemisphere.z);
}
vec3 trace_direct(in Ray direct_ray) {
Hit hit = intersect_scene(direct_ray);
if (hit.depth < direct_ray.far) {
Ray ray;
ray.origin = direct_ray.origin + direct_ray.direction * hit.depth;
ray.direction = generate_diffuse_ray_direction(hit.nor);
ray.near = 1e-3;
ray.far = 1.0;
for (uint depth = 0; depth < MAX_DEPTH; depth++) {
Hit hit = intersect_scene(ray); Hit hit = intersect_scene(ray);
if (hit.depth == 1.0) {
return vec3(1); if (!hit.intersected) {
break;
} }
return vec3(0); float pdf = abs(dot(ray.direction, hit.normal) / PI);
Material material = materials[hit.material_index];
color += material.emission * 64.0 * throughput * pdf;
throughput *= material.albedo;
ray.origin = ray.origin + ray.direction * hit.depth;
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, 1.0);
} }
return vec3(0); return color;
} }
void main() { void main() {
init_random_state(floatBitsToInt(program_metadata.seconds)); init_random_state(program_metadata.seconds);
vec2 uv = get_viewport_coordinate(); vec2 uv = get_viewport_coordinate();
Ray camera_ray = construct_camera_ray_pinhole(uv); Ray camera_ray = construct_camera_ray_pinhole(uv);
vec3 color = trace_direct(camera_ray); vec4 color = vec4(trace_direct(camera_ray), 1);
// index of the current pixel as array index vec4 previous = imageLoad(image, ivec2(gl_GlobalInvocationID.xy));
uint pixel_index = get_pixel_index();
vec4 buffer_data = pixels[pixel_index] + vec4(color, 1); vec4 merged = mix(previous, color, 1.0 / program_metadata.frames);
// contribute to the raw pixel buffer
pixels[pixel_index] = buffer_data;
imageStore(image, ivec2(gl_GlobalInvocationID.xy), vec4(buffer_data.rgb / buffer_data.a, 1)); imageStore(image, ivec2(gl_GlobalInvocationID.xy), merged);
} }

View File

@ -0,0 +1,22 @@
#ifdef _ONE_AT_A_TIME_
uint x;
void init_random_state_one_at_a_time(in float seed) {
x = ((gl_GlobalInvocationID.y << 16) | (gl_GlobalInvocationID.x)) + floatBitsToInt(seed);
}
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint one_at_a_time_hash() {
x += (x << 10u);
x ^= (x >> 6u);
x += (x << 3u);
x ^= (x >> 11u);
x += (x << 15u);
return x;
}
#define HASH_FUNCTION one_at_a_time_hash
#define INIT_STATE_FUNCTION init_random_state_one_at_a_time
#endif

View File

@ -1,36 +1,30 @@
#ifndef __RANDOM_GLSL__
#define __RANDOM_GLSL__
uint state; #define _ONE_AT_A_TIME_
//#define _XOSHIRO_
void init_random_state(uint seed) { #include "one-at-a-time.glsl"
state = (gl_GlobalInvocationID.x << 16) | gl_GlobalInvocationID.y; #include "xoshiro.glsl"
state += seed;
}
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint hash(in uint x) {
x += ( x << 10u );
x ^= ( x >> 6u );
x += ( x << 3u );
x ^= ( x >> 11u );
x += ( x << 15u );
return x;
}
// Construct a float with half-open range [0:1] using low 23 bits. // Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0. // All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
float floatConstruct( uint m ) { float floatConstruct(in uint m) {
const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32 const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32
m &= ieeeMantissa; // Keep only mantissa bits (fractional part) m &= ieeeMantissa; // Keep only mantissa bits (fractional part)
m |= ieeeOne; // Add fractional part to 1.0 m |= ieeeOne; // Add fractional part to 1.0
float f = uintBitsToFloat(m); // Range [1:2] return uintBitsToFloat(m) - 1.0;
return f - 1.0; // Range [0:1] }
void init_random_state(in float seed) {
INIT_STATE_FUNCTION(seed);
} }
float random() { float random() {
state = hash(state); return floatConstruct(HASH_FUNCTION());
return floatConstruct(state);
} }
#endif

View File

@ -0,0 +1,36 @@
#ifdef _XOSHIRO_
uint rol(in uint x, in uint k) {
return (x << k) | (x >> (32 - k));
}
uint XoshiroState[4];
void init_random_state_xhoshiro(in float seed) {
uint utime = floatBitsToUint(seed);
XoshiroState[0] = ((gl_GlobalInvocationID.x << 16) | gl_GlobalInvocationID.y) + utime;
XoshiroState[1] = XoshiroState[0] ^ utime;
XoshiroState[2] = 0x92abc32;
XoshiroState[3] = rol(utime, 16);
}
uint xoshiro_hash() {
uint result = rol(XoshiroState[1] * 5, 7) * 9;
uint t = XoshiroState[1] << 8;
XoshiroState[2] ^= XoshiroState[0];
XoshiroState[3] ^= XoshiroState[1];
XoshiroState[1] ^= XoshiroState[2];
XoshiroState[0] ^= XoshiroState[3];
XoshiroState[2] ^= t;
XoshiroState[3] = rol(XoshiroState[3], 22);
return result;
}
#define HASH_FUNCTION xoshiro_hash
#define INIT_STATE_FUNCTION init_random_state_xhoshiro
#endif

View File

@ -0,0 +1,52 @@
#include "rand/random.glsl"
const float PI = 3.1415926535;
// project vector b onto vector a
vec3 project(in vec3 a, in vec3 b) {
return a * (dot(a, b) / dot(a, a));
}
// construct a 3D coordinate system with the input up being the "upwards" facing vector
// which will be directly stored in w.
// Mathematically this function will create two non linear vectors of up and generate an orthonormal
// basis using gram-schmidt.
// This function assumes "up" being already normalized
void construct_orthonormal_basis(in vec3 up, out vec3 u, out vec3 v, out vec3 w) {
w = up;
vec3 n2 = normalize(cross(w, vec3(0, 1.0, 1.0))); // build perpendicular vector from w
vec3 n3 = cross(w, n2); // create 2nd vector perpendicular to w and n2
// gram schmidt
u = n2 - (project(w, n2));
v = n3 - project(w, n3) - project(u, n3);
}
// generate a normalized vector within the bounds of the hemisphere with radius of 1.
// Z-Coordinate will be "upwards".
// radius determines the maximum radius the output vector will have.
// NOTE: shrinkin radius will still result in the output to be normalized
vec3 cosine_weighted_hemisphere(in float radius) {
float u1 = random() * radius;
float u2 = random();
float r = sqrt(u1);
float theta = 2 * PI * u2;
float x = r * cos(theta);
float y = r * sin(theta);
return vec3(x, y, sqrt(1.0 - u1));
}
vec3 generate_brdf_ray_direction(in vec3 normal, in vec3 incident, in float roughness) {
vec3 merged_normal = mix(normal, reflect(incident, normal), 1.0 - roughness);
vec3 hemisphere = cosine_weighted_hemisphere(roughness);
vec3 u, v, w;
construct_orthonormal_basis(merged_normal, u, v, w);
return u * hemisphere.x + v * hemisphere.y + w * hemisphere.z;
}

View File

@ -8,9 +8,38 @@ struct Ray {
}; };
struct Hit { struct Hit {
vec3 nor; // normal of the hit triangle
vec2 uv; vec3 normal;
// barycentric coordinates of the hit point relative to the hit triangle vertices
vec2 barycentric;
// distance between the ray origin and the intersection
float depth; float depth;
// index of the material of the intersection relative to the material buffer
uint material_index;
// whether we hit something
bool intersected;
};
// Describes the properties of an objects surface and volume
struct Material {
// how much diffuse light is reflected by the surface
vec3 albedo;
// emitted light
vec3 emission;
// color of the specular reflection
vec3 specular_color;
// roughtness of the microfacets
float roughness;
// index of refraction (exclusive to metalic)
float ior;
// how transmissive the surface is (exclusive to metalic)
float transmission;
// whether the surface is metalic or not (exclusive to ior and transmission)
bool metallic;
// extra padding required for vulkano not properly padding the structs in the buffer
// the size of the useble data is 60 bytes, GLSL will add 4 additional bytes, to round up to 64.
// for compatibility I added the padding manually
bool __padding;
}; };
layout(set = 0, binding = 3) buffer VertexBuffer { layout(set = 0, binding = 3) buffer VertexBuffer {
@ -21,19 +50,31 @@ layout(set = 0, binding = 4) buffer IndexBuffer {
uint indices[]; uint indices[];
}; };
layout(set = 0, binding = 5) buffer MaterialBuffer {
Material materials[];
};
// from: https://iquilezles.org/articles/intersectors/ with a view modifications
vec3 intersect_triangle(in Ray ray, in vec3 v0, in vec3 v1, in vec3 v2, out vec3 n) { vec3 intersect_triangle(in Ray ray, in vec3 v0, in vec3 v1, in vec3 v2, out vec3 n) {
// triangle edges
vec3 v1v0 = v1 - v0; vec3 v1v0 = v1 - v0;
vec3 v2v0 = v2 - v0; vec3 v2v0 = v2 - v0;
vec3 rov0 = ray.origin - v0; vec3 rov0 = ray.origin - v0;
// normal
n = cross(v1v0, v2v0); n = cross(v1v0, v2v0);
vec3 q = cross(rov0, ray.direction); vec3 q = cross(rov0, ray.direction);
float d = 1.0 / dot(ray.direction, n); float d = 1.0 / dot(ray.direction, n);
float u = d * dot( -q, v2v0); // barycentric coordinates
float v = d * dot( q, v1v0); float u = d * dot(-q, v2v0);
float t = d * dot( -n, rov0); float v = d * dot(q, v1v0);
// intersection distance
float t = d * dot(-n, rov0);
if(u < 0.0 || v < 0.0 || (u+v) > 1.0 || d > 0.0) // test if the intersection lies outside of the triangle by checking the bounds of the barycentric coordinates
// also perform backface culling
if(u < 0.0 || v < 0.0 || (u + v) > 1.0 || d > 0.0)
t = -1.0; t = -1.0;
return vec3(t, u, v); return vec3(t, u, v);
@ -42,19 +83,22 @@ vec3 intersect_triangle(in Ray ray, in vec3 v0, in vec3 v1, in vec3 v2, out vec3
Hit intersect_scene(in Ray ray) { Hit intersect_scene(in Ray ray) {
Hit hit; Hit hit;
hit.depth = ray.far; hit.depth = ray.far;
hit.intersected = false;
for (int i = 0; i < indices.length(); i += 3) { for (int i = 0; i < indices.length(); i += 3) {
vec3 v0 = vertices[indices[i]].xyz; vec3 v0 = vertices[indices[i]].xyz;
vec3 v1 = vertices[indices[i + 1]].xyz; vec3 v1 = vertices[indices[i + 1]].xyz;
vec3 v2 = vertices[indices[i + 2]].xyz; vec3 v2 = vertices[indices[i + 2]].xyz;
vec3 n; vec3 normal;
vec3 result = intersect_triangle(ray, v0, v1, v2, n); vec3 result = intersect_triangle(ray, v0, v1, v2, normal);
if (result.x > ray.near && result.x < hit.depth) { if (result.x > ray.near && result.x < hit.depth) {
hit.uv = result.yz; hit.barycentric = result.yz;
hit.depth = result.x; hit.depth = result.x;
hit.nor = normalize(n); hit.normal = normalize(normal);
hit.material_index = uint(vertices[indices[i]].a);
hit.intersected = true;
} }
} }

View File

@ -85,13 +85,6 @@ pub fn init() {
depth_range: 0.0..1.0, depth_range: 0.0..1.0,
}; };
// The render pass we created above only describes the layout of our framebuffers. Before we
// can draw we also need to create the actual framebuffers.
//
// Since we need to draw to multiple images, we are going to create a different framebuffer for
// each image.
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport);
// Before we can start creating and recording command buffers, we need a way of allocating // Before we can start creating and recording command buffers, we need a way of allocating
// them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools // them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools
// underneath and provides a safe interface for them. // underneath and provides a safe interface for them.
@ -108,6 +101,15 @@ pub fn init() {
descriptor_set_allocator descriptor_set_allocator
}; };
let mut pathtracer = PathtracerPipeline::new(&renderer, &queue, [512, 512]);
// The render pass we created above only describes the layout of our framebuffers. Before we
// can draw we also need to create the actual framebuffers.
//
// Since we need to draw to multiple images, we are going to create a different framebuffer for
// each image.
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport, &mut pathtracer);
// Initialization is finally finished! // Initialization is finally finished!
// In some situations, the swapchain will become invalid by itself. This includes for example // In some situations, the swapchain will become invalid by itself. This includes for example
@ -130,7 +132,6 @@ pub fn init() {
let mut previous_frame_end = Some(sync::now(device.clone()).boxed()); let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
let texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass); let texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass);
let mut pathtracer = PathtracerPipeline::new(&renderer, &queue, [512, 512]);
let mut now_keys = [false; 255]; let mut now_keys = [false; 255];
@ -218,6 +219,7 @@ pub fn init() {
&new_images, &new_images,
render_pass.clone(), render_pass.clone(),
&mut viewport, &mut viewport,
&mut pathtracer
); );
recreate_swapchain = false; recreate_swapchain = false;
@ -248,7 +250,7 @@ pub fn init() {
recreate_swapchain = true; recreate_swapchain = true;
} }
pathtracer.compute(); let future = pathtracer.compute();
let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer); let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
@ -256,6 +258,7 @@ pub fn init() {
.take() .take()
.unwrap() .unwrap()
.join(acquire_future) .join(acquire_future)
.join(future)
.then_execute(queue.clone(), command_buffer) .then_execute(queue.clone(), command_buffer)
.unwrap() .unwrap()
// The color output is now expected to contain our triangle. But in order to // The color output is now expected to contain our triangle. But in order to
@ -298,10 +301,13 @@ fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
viewport: &mut Viewport, viewport: &mut Viewport,
pathtracer: &mut PathtracerPipeline,
) -> Vec<Arc<Framebuffer>> { ) -> Vec<Arc<Framebuffer>> {
let dimensions = images[0].dimensions().width_height(); let dimensions = images[0].dimensions().width_height();
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32]; viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
pathtracer.resize_image(dimensions);
images images
.iter() .iter()
.map(|image| { .map(|image| {
@ -312,8 +318,7 @@ fn window_size_dependent_setup(
attachments: vec![view], attachments: vec![view],
..Default::default() ..Default::default()
}, },
) ).unwrap()
.unwrap()
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
} }