Compare commits

..

5 Commits

Author SHA1 Message Date
Sven Vogel 4b42ffa474 fixed image urls 2023-12-10 19:16:26 +00:00
Sven Vogel 0a3e4a8281 added smooth shading 2023-04-24 23:36:24 +02:00
Sven Vogel 182f7f2ba1 Update 'README.md' 2023-04-20 13:18:02 +00:00
Sven Vogel 427ccccc1b added more example scene and oren-nayar diffuse shading 2023-04-20 15:09:52 +02:00
Sven Vogel 074074704a Add 'README.md' 2023-04-19 09:56:44 +00:00
23 changed files with 14897 additions and 303 deletions

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Eruption
Eruption is a vulkan based pathtracer. It is in an experimental state an may have performance issues
# Features
* flat and gouraud shading
* physically based rendering (PBR)
* Oren-Nayar diffuse BRDF
* Schlick approximation for fresnel equations
* Cosine weighted sampling
* Temporal anti aliasing
* Realtime denoising
* Reinhard jodie tonemapping
* Progressive sampling
# Screenshots
Cornellbox | Dark suzanne
:-------------------------:|:-------------------------:
![](https://git.montehaselino.de/servostar/eruption/raw/branch/main/screenshots/Screenshot_20230420_143057.png) | ![](https://git.montehaselino.de/servostar/eruption/raw/branch/main/screenshots/Screenshot_20230420_143346.png)
Scene with text | RGB lights
:-------------------------:|:-------------------------:
![](https://git.montehaselino.de/servostar/eruption/raw/branch/main/screenshots/Screenshot_20230420_145416.png) | ![](https://git.montehaselino.de/servostar/eruption/raw/branch/main/screenshots/Screenshot_20230420_145957.png)
![](https://git.montehaselino.de/servostar/eruption/raw/branch/main/screenshots/Screenshot_20230420_150915.png)

72
res/head.mtl Normal file
View File

@ -0,0 +1,72 @@
# Blender 3.4.1 MTL File: 'None'
# www.blender.org
newmtl glass
Ns 1000.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 glossy
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 green
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
newmtl light
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
newmtl red
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
newmtl white
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
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

7737
res/head.obj Normal file

File diff suppressed because it is too large Load Diff

42
res/light_box.mtl Normal file
View File

@ -0,0 +1,42 @@
# Blender 3.4.1 MTL File: 'None'
# www.blender.org
newmtl light
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

2047
res/light_box.obj Normal file

File diff suppressed because it is too large Load Diff

42
res/plane.mtl Normal file
View File

@ -0,0 +1,42 @@
# Blender 3.4.1 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

4568
res/plane.obj Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 KiB

View File

@ -24,7 +24,7 @@ void main() {
vec3 color = texture(image, uv).rgb; vec3 color = texture(image, uv).rgb;
color = smart_de_noise(image, uv, 5.0, 1.0, 0.400).rgb; color = smart_de_noise(image, uv, 5.0, 1.0, 0.200).rgb;
color = reinhard_jodie(color); color = reinhard_jodie(color);

View File

@ -1 +0,0 @@

View File

@ -17,8 +17,8 @@ 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, 1.0, 9.0), pos: Vector3::new(0.0, 0.0, 3.9),
fov: 90.0f32 fov: 120.0f32
} }
} }

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferError, 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;
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract}; use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract};
@ -36,10 +36,10 @@ lazy_static! {
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.0, roughness: 1.0,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}; };
} }
@ -50,88 +50,121 @@ fn add_default_materials() {
albedo: Padded::from([1.0, 1.0, 1.0]), albedo: Padded::from([1.0, 1.0, 1.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.0, roughness: 0.4,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
__padding: 0 });
material_collection.insert(String::from("mirror"), 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: 0.12,
specular: 1.0,
transmission: 0.0,
ior: 1.0,
metallic: true as u32,
});
material_collection.insert(String::from("gold"), cs::Material {
albedo: Padded::from([0.944, 0.776, 0.373]),
emission: Padded::from([0.0, 0.0, 0.0]),
specular_color: [0.0, 0.0, 0.0],
roughness: 0.4,
specular: 0.0,
transmission: 0.0,
ior: 1.0,
metallic: true as u32,
}); });
material_collection.insert(String::from("red"), cs::Material { material_collection.insert(String::from("red"), cs::Material {
albedo: Padded::from([1.0, 0.0, 0.0]), albedo: Padded::from([1.0, 0.0, 0.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.0, roughness: 0.4,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("green"), cs::Material { material_collection.insert(String::from("green"), cs::Material {
albedo: Padded::from([0.0, 1.0, 0.0]), albedo: Padded::from([0.0, 1.0, 0.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.0, roughness: 0.4,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("glass"), cs::Material { material_collection.insert(String::from("glass"), cs::Material {
albedo: Padded::from([1.0, 1.0, 1.0]), albedo: Padded::from([1.0, 1.0, 1.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.5, roughness: 0.0,
specular: 0.0,
transmission: 1.0, transmission: 1.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("light"), cs::Material { material_collection.insert(String::from("light"), cs::Material {
albedo: Padded::from([1.0, 1.0, 1.0]), albedo: Padded::from([1.0, 1.0, 1.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.5, roughness: 1.0,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("light_blue"), cs::Material { material_collection.insert(String::from("light_blue"), cs::Material {
albedo: Padded::from([0.3, 0.3, 1.0]), albedo: Padded::from([0.3, 0.3, 1.0]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.5, roughness: 1.0,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("light_red"), cs::Material { material_collection.insert(String::from("light_red"), cs::Material {
albedo: Padded::from([1.0, 0.3, 0.3]), albedo: Padded::from([1.0, 0.3, 0.3]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.5, roughness: 1.0,
specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
material_collection.insert(String::from("light_green"), cs::Material { material_collection.insert(String::from("light_green"), cs::Material {
albedo: Padded::from([0.3, 1.0, 0.3]), albedo: Padded::from([0.3, 1.0, 0.3]),
emission: 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], specular_color: [0.0, 0.0, 0.0],
roughness: 1.5, roughness: 1.0,
specular: 0.0,
transmission: 0.0,
ior: 0.0,
metallic: false as u32,
});
material_collection.insert(String::from("glossy"), 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: 0.0,
specular: 1.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
metallic: false as u32, metallic: false as u32,
__padding: 0
}); });
} }
@ -146,6 +179,7 @@ pub struct PathtracerPipeline {
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]>,
normal_buffer: Subbuffer<[[f32; 4]]>,
material_buffer: Subbuffer<[cs::Material]>, material_buffer: Subbuffer<[cs::Material]>,
camera: Camera, camera: Camera,
frames: f32 frames: f32
@ -177,9 +211,9 @@ impl PathtracerPipeline {
}, },
); );
let (vertices, indices, materials) = load_example_scene(); let (vertices, indices, normals, materials) = load_example_scene();
let (vertex_buffer, index_buffer, materials) = create_gpu_buffer(&vertices, &indices, &materials, &renderer.memory_allocator); let (vertex_buffer, index_buffer, normal_buffer, materials) = create_gpu_buffer(&vertices, &indices, &normals, &materials, &renderer.memory_allocator);
return PathtracerPipeline { return PathtracerPipeline {
compute_queue: compute_queue.clone(), compute_queue: compute_queue.clone(),
@ -191,6 +225,7 @@ impl PathtracerPipeline {
uniform_buffer: Arc::new(uniform_buffer), uniform_buffer: Arc::new(uniform_buffer),
vertex_buffer, vertex_buffer,
index_buffer, index_buffer,
normal_buffer,
material_buffer: materials, material_buffer: materials,
seconds: Instant::now(), seconds: Instant::now(),
camera: Camera::new(), camera: Camera::new(),
@ -224,6 +259,7 @@ impl PathtracerPipeline {
let 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.frames = 0.0;
} }
/// Builds the command for a dispatch. /// Builds the command for a dispatch.
@ -257,7 +293,8 @@ impl PathtracerPipeline {
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()) WriteDescriptorSet::buffer(5, self.material_buffer.clone()),
WriteDescriptorSet::buffer(6, self.normal_buffer.clone())
], ],
).unwrap(); ).unwrap();
@ -290,16 +327,18 @@ fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>,
).unwrap() ).unwrap()
} }
fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>, Vec<cs::Material>) { fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>, Vec<[f32; 4]>, 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/head.obj", &tobj::GPU_LOAD_OPTIONS).expect("unable to load scene from obj");
// allocate some host memory // 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![]; let mut shader_materials:Vec<cs::Material> = vec![];
let mut normals:Vec<[f32; 4]> = vec![];
for model in models.iter_mut() { for model in models.iter_mut() {
let offset = vertices.len() as u32; let vertex_offset = vertices.len() as u32;
let material = model.mesh.material_id.unwrap_or(0); let material = model.mesh.material_id.unwrap_or(0);
@ -316,20 +355,54 @@ fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>, Vec<cs::Material>) {
// fill the index buffer // 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 + vertex_offset);
}
// fill the normal buffer
for normal_index in (0..model.mesh.normals.len()).step_by(3) {
normals.push([
model.mesh.normals[normal_index],
model.mesh.normals[normal_index + 1],
model.mesh.normals[normal_index + 2],
0.0 // padding
]);
} }
} }
println!("loaded vertices: {}", vertices.len());
println!("loaded indices: {}", indices.len());
println!("loaded normals: {}", normals.len());
let material_collection = &MATERIAL_COLLECTIO.lock().unwrap(); let material_collection = &MATERIAL_COLLECTIO.lock().unwrap();
for material in materials.unwrap().iter() { for material in materials.unwrap().iter() {
shader_materials.push(*material_collection.get(&material.name).unwrap_or(&DEFAULT_MATERIAL)); shader_materials.push(*material_collection.get(&material.name).unwrap_or(&DEFAULT_MATERIAL));
} }
(vertices, indices, shader_materials) (vertices, indices, normals, shader_materials)
} }
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]>) { fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, normals: &Vec<[f32; 4]>, materials: &Vec<cs::Material>, memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[[f32; 4]]>, Subbuffer<[u32]>, Subbuffer<[[f32; 4]]>, Subbuffer<[cs::Material]>) {
let vertex_buffer = Buffer::from_iter( let vertex_buffer = create_subbuffer_from_host(memory_allocator, vertices)
.expect("Failed to create subbuffer for vertices");
let index_buffer = create_subbuffer_from_host(memory_allocator, indices)
.expect("Failed to create subbuffer for indices");
let normal_buffer = create_subbuffer_from_host(memory_allocator, normals)
.expect("Failed to create subbuffer for vertices");
let material_buffer = create_subbuffer_from_host(memory_allocator, materials)
.expect("Failed to create subbuffer for material");
(vertex_buffer, index_buffer, normal_buffer, material_buffer)
}
fn create_subbuffer_from_host<T, I>(memory_allocator: &StandardMemoryAllocator, host_data: &I) -> Result<Subbuffer<[T]>, BufferError> where
T: BufferContents,
I: IntoIterator<Item = T> + Clone,
I::IntoIter: ExactSizeIterator, {
Buffer::from_iter(
memory_allocator, memory_allocator,
BufferCreateInfo { BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER, usage: BufferUsage::STORAGE_BUFFER,
@ -339,34 +412,6 @@ fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, materials: &V
usage: MemoryUsage::Upload, usage: MemoryUsage::Upload,
..Default::default() ..Default::default()
}, },
vertices.clone(), host_data.clone(),
).unwrap(); )
let index_buffer = Buffer::from_iter(
memory_allocator,
BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
..Default::default()
},
indices.clone(),
).unwrap();
let material_buffer = Buffer::from_iter(
memory_allocator,
BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
..Default::default()
},
materials.clone(),
).unwrap();
(vertex_buffer, index_buffer, material_buffer)
} }

View File

@ -27,6 +27,32 @@ 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;
} }
// from: https://github.com/glslify/glsl-diffuse-oren-nayar/blob/master/index.glsl
vec3 oren_nayar_diffuse(in vec3 lightDirection, in vec3 viewDirection, in vec3 surfaceNormal, in float roughness, in vec3 albedo) {
float LdotV = dot(lightDirection, viewDirection);
float NdotL = dot(lightDirection, surfaceNormal);
float NdotV = dot(surfaceNormal, viewDirection);
float s = LdotV - NdotL * NdotV;
float t = mix(1.0, max(NdotL, NdotV), step(0.0, s));
float sigma2 = roughness * roughness;
vec3 A = 1.0 + sigma2 * (albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33));
float B = 0.45 * sigma2 / (sigma2 + 0.09);
return albedo * max(0.0, NdotL) * (A + B * s / t) / PI;
}
float schlick(in float cosTheta, in float R0) {
return R0 + (1.0 - R0) * pow(1.0 - cosTheta, 5.0);
}
float R0(in float ior) {
float a = (ior - 1.0);
float b = (ior + 1.0);
return (a * a) / (b * b);
}
vec3 trace_direct(in Ray ray) { vec3 trace_direct(in Ray ray) {
vec3 color = vec3(0); vec3 color = vec3(0);
vec3 throughput = vec3(1); vec3 throughput = vec3(1);
@ -42,12 +68,48 @@ vec3 trace_direct(in Ray ray) {
Material material = materials[hit.material_index]; Material material = materials[hit.material_index];
color += material.emission * 64.0 * throughput * pdf; color += material.emission * 512.0 * throughput * pdf;
throughput *= material.albedo; float R0 = R0(1.45);
float fresnel = schlick(abs(dot(ray.direction, hit.normal)), R0);
ray.origin = ray.origin + ray.direction * hit.depth; ray.origin = ray.origin + ray.direction * hit.depth;
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, 1.0);
// metalic
if (material.metallic) {
// reflection ray
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, material.roughness);
throughput *= material.albedo;
continue;
}
// transmission
if (material.transmission > random()) {
if (random() < fresnel) {
// reflection ray
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, material.roughness);
} else {
// refraction ray
ray.direction = generate_btdf_ray_direction(-hit.normal, ray.direction, material.roughness, material.ior);
}
continue;
}
// dielectric
if (random() < material.specular * fresnel) {
// reflection ray
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, material.roughness);
} else {
vec3 incident = ray.direction;
// diffuse ray
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, 1.0);
throughput *= oren_nayar_diffuse(ray.direction, incident, hit.normal, material.roughness, material.albedo);
}
} }
return color; return color;

View File

@ -1,22 +0,0 @@
#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,30 +1,32 @@
#ifndef __RANDOM_GLSL__ #ifndef __RANDOM_GLSL__
#define __RANDOM_GLSL__ #define __RANDOM_GLSL__
#define _ONE_AT_A_TIME_ // Gold Noise ©2015 dcerisano@standard3d.com
//#define _XOSHIRO_ // - based on the Golden Ratio
// - uniform normalized distribution
// - fastest static noise generator function (also runs at low precision)
// - use with indicated fractional seeding method.
#include "one-at-a-time.glsl" const float PHI = 1.61803398874989484820459; // Φ = Golden Ratio
#include "xoshiro.glsl"
// Construct a float with half-open range [0:1] using low 23 bits. // different for every pixel on the image
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0. vec2 xy;
float floatConstruct(in uint m) { // different for every iteration and based on time
const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask float seed;
const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32
m &= ieeeMantissa; // Keep only mantissa bits (fractional part) // based on https://www.shadertoy.com/view/ltB3zD
m |= ieeeOne; // Add fractional part to 1.0 float gold_noise(){
return fract(tan(distance(xy * PHI, xy) * seed)*xy.x);
return uintBitsToFloat(m) - 1.0;
} }
void init_random_state(in float seed) { void init_random_state(in float time) {
INIT_STATE_FUNCTION(seed); xy = vec2(gl_GlobalInvocationID.xy);
seed = time;
} }
float random() { float random() {
return floatConstruct(HASH_FUNCTION()); seed += 0.1;
return gold_noise();
} }
#endif #endif

View File

@ -1,36 +0,0 @@
#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

@ -41,9 +41,20 @@ vec3 cosine_weighted_hemisphere(in float radius) {
} }
vec3 generate_brdf_ray_direction(in vec3 normal, in vec3 incident, in float roughness) { 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 merged_normal = mix(reflect(incident, normal), normal, roughness * roughness);
vec3 hemisphere = cosine_weighted_hemisphere(roughness); vec3 hemisphere = cosine_weighted_hemisphere(roughness * roughness);
vec3 u, v, w;
construct_orthonormal_basis(merged_normal, u, v, w);
return u * hemisphere.x + v * hemisphere.y + w * hemisphere.z;
}
vec3 generate_btdf_ray_direction(in vec3 normal, in vec3 incident, in float roughness, in float ior) {
vec3 merged_normal = mix(refract(incident, normal, ior), normal, roughness * roughness);
vec3 hemisphere = cosine_weighted_hemisphere(roughness * roughness);
vec3 u, v, w; vec3 u, v, w;
construct_orthonormal_basis(merged_normal, u, v, w); construct_orthonormal_basis(merged_normal, u, v, w);

View File

@ -30,30 +30,31 @@ struct Material {
vec3 specular_color; vec3 specular_color;
// roughtness of the microfacets // roughtness of the microfacets
float roughness; float roughness;
float specular;
// index of refraction (exclusive to metalic) // index of refraction (exclusive to metalic)
float ior; float ior;
// how transmissive the surface is (exclusive to metalic) // how transmissive the surface is (exclusive to metalic)
float transmission; float transmission;
// whether the surface is metalic or not (exclusive to ior and transmission) // whether the surface is metalic or not (exclusive to ior and transmission)
bool metallic; 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) readonly buffer VertexBuffer {
vec4 vertices[]; vec4 vertices[];
}; };
layout(set = 0, binding = 4) buffer IndexBuffer { layout(set = 0, binding = 4) readonly buffer IndexBuffer {
uint indices[]; uint indices[];
}; };
layout(set = 0, binding = 5) buffer MaterialBuffer { layout(set = 0, binding = 5) readonly buffer MaterialBuffer {
Material materials[]; Material materials[];
}; };
layout(set = 0, binding = 6) readonly buffer NormalBuffer {
vec4 normals[];
};
// from: https://iquilezles.org/articles/intersectors/ with a view modifications // 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 // triangle edges
@ -74,7 +75,7 @@ vec3 intersect_triangle(in Ray ray, in vec3 v0, in vec3 v1, in vec3 v2, out vec3
// test if the intersection lies outside of the triangle by checking the bounds of the barycentric coordinates // test if the intersection lies outside of the triangle by checking the bounds of the barycentric coordinates
// also perform backface culling // also perform backface culling
if(u < 0.0 || v < 0.0 || (u + v) > 1.0 || d > 0.0) if(u < 0.0 || v < 0.0 || (u + v) > 1.0)
t = -1.0; t = -1.0;
return vec3(t, u, v); return vec3(t, u, v);
@ -86,9 +87,13 @@ Hit intersect_scene(in Ray ray) {
hit.intersected = false; 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; uint index_0 = indices[i];
vec3 v1 = vertices[indices[i + 1]].xyz; uint index_1 = indices[i + 1];
vec3 v2 = vertices[indices[i + 2]].xyz; uint index_2 = indices[i + 2];
vec3 v0 = vertices[index_0].xyz;
vec3 v1 = vertices[index_1].xyz;
vec3 v2 = vertices[index_2].xyz;
vec3 normal; vec3 normal;
vec3 result = intersect_triangle(ray, v0, v1, v2, normal); vec3 result = intersect_triangle(ray, v0, v1, v2, normal);
@ -96,7 +101,12 @@ Hit intersect_scene(in Ray ray) {
if (result.x > ray.near && result.x < hit.depth) { if (result.x > ray.near && result.x < hit.depth) {
hit.barycentric = result.yz; hit.barycentric = result.yz;
hit.depth = result.x; hit.depth = result.x;
hit.normal = normalize(normal);
vec2 smoother_barycentric = smoothstep(vec2(0.0), vec2(1.0), result.yz);
// gouraud shading: interpolate between vertex normals with barycentric coordinates
hit.normal = mix(mix(normals[index_0], normals[index_1], smoother_barycentric.x), normals[index_2], smoother_barycentric.y).xyz;
// flat shading only: use raw triangle normals: hit.normal = normalize(normal);
hit.material_index = uint(vertices[indices[i]].a); hit.material_index = uint(vertices[indices[i]].a);
hit.intersected = true; hit.intersected = true;
} }

View File

@ -2,6 +2,7 @@ mod device;
pub(crate) mod textured_quad; pub(crate) mod textured_quad;
use std::sync::Arc; use std::sync::Arc;
use std::thread;
use std::time::Instant; use std::time::Instant;
use vulkano::device::{Device}; use vulkano::device::{Device};
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage}; use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
@ -19,6 +20,7 @@ use vulkano::pipeline::graphics::viewport::Viewport;
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass}; use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass};
use vulkano::sync::{FlushError, GpuFuture}; use vulkano::sync::{FlushError, GpuFuture};
use winit::event::{Event, VirtualKeyCode, WindowEvent}; use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::platform::run_return::EventLoopExtRunReturn;
use crate::shader::composite::TextureDrawPipeline; use crate::shader::composite::TextureDrawPipeline;
use crate::shader::pathtracing::PathtracerPipeline; use crate::shader::pathtracing::PathtracerPipeline;
@ -55,7 +57,7 @@ pub fn init() {
// //
// This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform
// winit window and a cross-platform Vulkan surface that represents the surface of the window. // winit window and a cross-platform Vulkan surface that represents the surface of the window.
let event_loop = EventLoop::new(); let mut event_loop = EventLoop::new();
let surface = WindowBuilder::new() let surface = WindowBuilder::new()
.build_vk_surface(&event_loop, instance.clone()) .build_vk_surface(&event_loop, instance.clone())
.unwrap(); .unwrap();
@ -108,7 +110,7 @@ pub fn init() {
// //
// Since we need to draw to multiple images, we are going to create a different framebuffer for // Since we need to draw to multiple images, we are going to create a different framebuffer for
// each image. // each image.
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport, &mut pathtracer); let mut framebuffers = window_size_dependent_setup(&images, &render_pass, &mut viewport, &mut pathtracer);
// Initialization is finally finished! // Initialization is finally finished!
@ -133,173 +135,161 @@ pub fn init() {
let texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass); let texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass);
let mut now_keys = [false; 255];
let mut start_frame = Instant::now(); let mut start_frame = Instant::now();
event_loop.run(move |event, _, control_flow| { loop {
match event { let (running, resized) = handle_events(&mut event_loop);
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
}
Event::WindowEvent {
event: WindowEvent::Resized(_),
..
} => {
recreate_swapchain = true;
},
Event::WindowEvent {
// Note this deeply nested pattern match
event: WindowEvent::KeyboardInput {
input:winit::event::KeyboardInput {
// Which serves to filter out only events we actually want
virtual_keycode:Some(keycode),
state,
..
},
..
},
..
} => {
// It also binds these handy variable names!
match state {
winit::event::ElementState::Pressed => {
// VirtualKeycode is an enum with a defined representation
now_keys[keycode as usize] = true;
},
winit::event::ElementState::Released => {
now_keys[keycode as usize] = false;
}
}
},
Event::RedrawEventsCleared => {
let elapsed = (Instant::now() - start_frame).as_secs_f32();
// Do not draw the frame when the screen dimensions are zero. On Windows, this can if !running {
// occur when minimizing the application. break;
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap(); }
let dimensions = window.inner_size();
if dimensions.width == 0 || dimensions.height == 0 { if resized || recreate_swapchain {
let (new_swapchain, new_images) =
match swapchain.recreate(SwapchainCreateInfo {
//image_extent: dimensions.into(),
..swapchain.create_info()
}) {
Ok(r) => r,
// This error tends to happen when the user is manually resizing the
// window. Simply restarting the loop is the easiest way to fix this
// issue.
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("failed to recreate swapchain: {}", e),
};
swapchain = new_swapchain;
// Because framebuffers contains a reference to the old swapchain, we need to
// recreate framebuffers as well.
framebuffers = window_size_dependent_setup(
&new_images,
&render_pass,
&mut viewport,
&mut pathtracer
);
recreate_swapchain = false;
}
let elapsed = (Instant::now() - start_frame).as_secs_f32();
// Do not draw the frame when the screen dimensions are zero. On Windows, this can
// occur when minimizing the application.
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
let dimensions = window.inner_size();
if dimensions.width == 0 || dimensions.height == 0 {
return;
}
// It is important to call this function from time to time, otherwise resources
// will keep accumulating and you will eventually reach an out of memory error.
// Calling this function polls various fences in order to determine what the GPU
// has already processed, and frees the resources that are no longer needed.
previous_frame_end.as_mut().unwrap().cleanup_finished();
// Before we can draw on the output, we have to *acquire* an image from the
// swapchain. If no image is available (which happens if you submit draw commands
// too quickly), then the function will block. This operation returns the index of
// the image that we are allowed to draw upon.
//
// This function can block if no image is available. The parameter is an optional
// timeout after which the function call will return an error.
let (image_index, suboptimal, acquire_future) =
match acquire_next_image(swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("failed to acquire next image: {}", e),
};
// It is important to call this function from time to time, otherwise resources // `acquire_next_image` can be successful, but suboptimal. This means that the
// will keep accumulating and you will eventually reach an out of memory error. // swapchain image will still work, but it may not display correctly. With some
// Calling this function polls various fences in order to determine what the GPU // drivers this can be when the window resizes, but it may not cause the swapchain
// has already processed, and frees the resources that are no longer needed. // to become out of date.
previous_frame_end.as_mut().unwrap().cleanup_finished(); if suboptimal {
recreate_swapchain = true;
}
// Whenever the window resizes we need to recreate everything dependent on the let future = pathtracer.compute();
// window size. In this example that includes the swapchain, the framebuffers and
// the dynamic state viewport.
if recreate_swapchain {
// Use the new dimensions of the window.
let (new_swapchain, new_images) = let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
match swapchain.recreate(SwapchainCreateInfo {
image_extent: dimensions.into(),
..swapchain.create_info()
}) {
Ok(r) => r,
// This error tends to happen when the user is manually resizing the
// window. Simply restarting the loop is the easiest way to fix this
// issue.
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("failed to recreate swapchain: {}", e),
};
swapchain = new_swapchain; let future = previous_frame_end
.take()
.unwrap()
.join(acquire_future)
.join(future)
.then_execute(queue.clone(), command_buffer)
.unwrap()
// The color output is now expected to contain our triangle. But in order to
// show it on the screen, we have to *present* the image by calling
// `then_swapchain_present`.
//
// This function does not actually present the image immediately. Instead it
// submits a present command at the end of the queue. This means that it will
// only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
.then_swapchain_present(
queue.clone(),
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
)
.then_signal_fence_and_flush();
// Because framebuffers contains a reference to the old swapchain, we need to match future {
// recreate framebuffers as well. Ok(future) => {
framebuffers = window_size_dependent_setup( previous_frame_end = Some(future.boxed());
&new_images,
render_pass.clone(),
&mut viewport,
&mut pathtracer
);
recreate_swapchain = false;
}
// Before we can draw on the output, we have to *acquire* an image from the
// swapchain. If no image is available (which happens if you submit draw commands
// too quickly), then the function will block. This operation returns the index of
// the image that we are allowed to draw upon.
//
// This function can block if no image is available. The parameter is an optional
// timeout after which the function call will return an error.
let (image_index, suboptimal, acquire_future) =
match acquire_next_image(swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
recreate_swapchain = true;
return;
}
Err(e) => panic!("failed to acquire next image: {}", e),
};
// `acquire_next_image` can be successful, but suboptimal. This means that the
// swapchain image will still work, but it may not display correctly. With some
// drivers this can be when the window resizes, but it may not cause the swapchain
// to become out of date.
if suboptimal {
recreate_swapchain = true;
}
let future = pathtracer.compute();
let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
let future = previous_frame_end
.take()
.unwrap()
.join(acquire_future)
.join(future)
.then_execute(queue.clone(), command_buffer)
.unwrap()
// The color output is now expected to contain our triangle. But in order to
// show it on the screen, we have to *present* the image by calling
// `then_swapchain_present`.
//
// This function does not actually present the image immediately. Instead it
// submits a present command at the end of the queue. This means that it will
// only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
.then_swapchain_present(
queue.clone(),
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
)
.then_signal_fence_and_flush();
match future {
Ok(future) => {
previous_frame_end = Some(future.boxed());
}
Err(FlushError::OutOfDate) => {
recreate_swapchain = true;
previous_frame_end = Some(sync::now(device.clone()).boxed());
}
Err(e) => {
panic!("failed to flush future: {}", e);
// previous_frame_end = Some(sync::now(device.clone()).boxed());
}
}
start_frame = Instant::now();
} }
Err(FlushError::OutOfDate) => {
recreate_swapchain = true;
previous_frame_end = Some(sync::now(device.clone()).boxed());
}
Err(e) => {
panic!("failed to flush future: {}", e);
// previous_frame_end = Some(sync::now(device.clone()).boxed());
}
}
start_frame = Instant::now();
}
}
/// Handles events and returns a `bool` indicating if we should quit.
fn handle_events(
event_loop: &mut EventLoop<()>,
) -> (bool, bool) {
let mut is_running = true;
let mut resize = false;
event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match &event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => is_running = false,
WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => {
resize = true;
}
_ => (),
},
Event::MainEventsCleared => *control_flow = ControlFlow::Exit,
_ => (), _ => (),
} }
// Pass event for the app to handle our inputs.
// app.handle_input(renderer.window_size(), &event);
}); });
(is_running, resize)
} }
/// This function is called once during initialization, then again whenever the window is resized. /// This function is called once during initialization, then again whenever the window is resized.
fn window_size_dependent_setup( 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, pathtracer: &mut PathtracerPipeline,
) -> Vec<Arc<Framebuffer>> { ) -> Vec<Arc<Framebuffer>> {