added smooth shading

This commit is contained in:
Sven Vogel 2023-04-24 23:36:24 +02:00
parent 182f7f2ba1
commit 0a3e4a8281
13 changed files with 8099 additions and 7046 deletions

View File

@ -2,12 +2,14 @@
Eruption is a vulkan based pathtracer. It is in an experimental state an may have performance issues Eruption is a vulkan based pathtracer. It is in an experimental state an may have performance issues
# Features # Features
* Oren-Nayar diffuse pathtracing * flat and gouraud shading
* Specular reflection with roughness * physically based rendering (PBR)
* Cosine weighted ray generation * Oren-Nayar diffuse BRDF
* Schlick approximation for fresnel equations
* Cosine weighted sampling
* Temporal anti aliasing * Temporal anti aliasing
* Realtime denoising * Realtime denoising
* Tonemapping * Reinhard jodie tonemapping
* Progressive sampling * Progressive sampling
# Screenshots # Screenshots

View File

@ -1,7 +1,17 @@
# Blender 3.4.1 MTL File: 'None' # Blender 3.4.1 MTL File: 'None'
# www.blender.org # www.blender.org
newmtl light 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 Ns 250.000000
Ka 1.000000 1.000000 1.000000 Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000 Kd 0.800000 0.800000 0.800000
@ -11,15 +21,45 @@ Ni 1.450000
d 1.000000 d 1.000000
illum 2 illum 2
newmtl light_blue newmtl green
Ns 250.000000 Ns 0.000000
Ka 1.000000 1.000000 1.000000 Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000 Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000 Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.450000 Ni 1.450000
d 1.000000 d 1.000000
illum 2 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 newmtl white
Ns 250.000000 Ns 250.000000

14473
res/head.obj

File diff suppressed because it is too large Load Diff

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, 0.0, 9.0), pos: Vector3::new(0.0, 0.0, 3.9),
fov: 65.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};
@ -50,29 +50,40 @@ 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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, metallic: false as u32,
}); });
material_collection.insert(String::from("glossy"), cs::Material { material_collection.insert(String::from("mirror"), 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: 0.0, roughness: 0.12,
specular: 0.8, specular: 1.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
metallic: false as u32, 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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
@ -83,7 +94,7 @@ fn add_default_materials() {
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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 1.0, ior: 1.0,
@ -94,7 +105,7 @@ 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.5, roughness: 0.0,
specular: 0.0, specular: 0.0,
transmission: 1.0, transmission: 1.0,
ior: 1.0, ior: 1.0,
@ -105,7 +116,7 @@ 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([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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
@ -116,7 +127,7 @@ fn add_default_materials() {
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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
@ -127,7 +138,7 @@ fn add_default_materials() {
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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
@ -138,12 +149,23 @@ fn add_default_materials() {
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, specular: 0.0,
transmission: 0.0, transmission: 0.0,
ior: 0.0, ior: 0.0,
metallic: false as u32, 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,
ior: 0.0,
metallic: false as u32,
});
} }
pub struct PathtracerPipeline { pub struct PathtracerPipeline {
@ -157,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
@ -188,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(),
@ -202,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(),
@ -235,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.
@ -268,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();
@ -301,7 +327,7 @@ 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/head.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
@ -309,8 +335,10 @@ fn load_example_scene() -> (Vec<[f32; 4]>, Vec<u32>, Vec<cs::Material>) {
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);
@ -327,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,
@ -350,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

@ -43,6 +43,16 @@ vec3 oren_nayar_diffuse(in vec3 lightDirection, in vec3 viewDirection, in vec3 s
return albedo * max(0.0, NdotL) * (A + B * s / t) / PI; 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);
@ -58,19 +68,44 @@ vec3 trace_direct(in Ray ray) {
Material material = materials[hit.material_index]; Material material = materials[hit.material_index];
color += material.emission * 256.0 * throughput * pdf; color += material.emission * 512.0 * throughput * pdf;
float fresnel = abs(dot(ray.direction, hit.normal)); float R0 = R0(1.45);
float fresnel = schlick(abs(dot(ray.direction, hit.normal)), R0);
if (random() * material.specular > fresnel) { ray.origin = ray.origin + ray.direction * hit.depth;
// 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 // reflection ray
ray.origin = ray.origin + ray.direction * hit.depth;
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, material.roughness); ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, material.roughness);
} else { } else {
vec3 incident = ray.direction; vec3 incident = ray.direction;
// diffuse ray // diffuse ray
ray.origin = ray.origin + ray.direction * hit.depth;
ray.direction = generate_brdf_ray_direction(hit.normal, ray.direction, 1.0); 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); throughput *= oren_nayar_diffuse(ray.direction, incident, hit.normal, material.roughness, material.albedo);

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

@ -39,18 +39,22 @@ struct Material {
bool metallic; bool metallic;
}; };
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
@ -83,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);
@ -93,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>> {