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"
dependencies = [
"cgmath",
"lazy_static",
"tobj",
"vulkano",
"vulkano-shaders",

View File

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

View File

@ -3,7 +3,7 @@
newmtl glass
Ns 1000.000000
Ka 1.000000 1.000000 1.000000
Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
@ -13,7 +13,7 @@ illum 2
newmtl green
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ka 0.000000 0.000000 0.000000
Kd 0.006232 0.800000 0.014503
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
@ -33,7 +33,7 @@ illum 2
newmtl red
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.006232 0.009300
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
@ -43,7 +43,7 @@ illum 2
newmtl white
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
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
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(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() {
vec2 uv = texture_coordinate;
vec3 color = texture(image, uv).rgb;
// TODO: tonemapping
// TODO: denoising
// TODO: bloom
color = smart_de_noise(image, uv, 5.0, 1.0, 0.400).rgb;
color = reinhard_jodie(color);
frag_color = vec4(color, 1.0);
}

View File

@ -1,11 +1,16 @@
#version 450
// vertex position
layout(location = 0) in vec2 position;
// vertex texture coordinate
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() {
// pass texture coordinates straight to fragment shader
texture_coordinate = texture;
// no perspective transform required
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),
left: Vector3::new(1.0, 0.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
}
}

View File

@ -1,7 +1,9 @@
mod camera;
use std::sync::Arc;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use lazy_static::lazy_static;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
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 {
compute_queue: Arc<Queue>,
compute_pipeline: Arc<ComputePipeline>,
@ -34,16 +142,19 @@ pub struct PathtracerPipeline {
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
memory_allocator: Arc<StandardMemoryAllocator>,
image: Arc<ImageView<StorageImage>>,
raw_image: Subbuffer<[[f32; 4]]>,
seconds: Instant,
uniform_buffer: Arc<SubbufferAllocator>,
vertex_buffer: Subbuffer<[[f32; 4]]>,
index_buffer: Subbuffer<[u32]>,
camera: Camera
material_buffer: Subbuffer<[cs::Material]>,
camera: Camera,
frames: f32
}
impl PathtracerPipeline {
pub fn new(renderer: &Renderer, compute_queue: &Arc<Queue>, size: [u32; 2]) -> Self {
add_default_materials();
let compute_pipeline = {
let shader = cs::load(compute_queue.device().clone()).unwrap();
@ -56,7 +167,7 @@ impl PathtracerPipeline {
).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(
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 {
compute_queue: compute_queue.clone(),
@ -77,12 +188,13 @@ impl PathtracerPipeline {
descriptor_set_allocator: renderer.descriptor_set_allocator.clone(),
memory_allocator: renderer.memory_allocator.clone(),
image,
raw_image: raw_image_buffer,
uniform_buffer: Arc::new(uniform_buffer),
vertex_buffer,
index_buffer,
material_buffer: materials,
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]) {
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.raw_image = raw_image_buffer;
}
/// Builds the command for a dispatch.
fn dispatch(
&self,
&mut self,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer,Arc<StandardCommandBufferAllocator>>
) {
self.frames += 1.0;
let camera = cs::Camera {
front: Padded::from(Into::<[f32; 3]>::into(self.camera.front)),
left: Padded::from(Into::<[f32; 3]>::into(self.camera.left)),
@ -140,17 +253,18 @@ impl PathtracerPipeline {
&self.descriptor_set_allocator,
desc_layout.clone(),
[
WriteDescriptorSet::buffer(0, self.raw_image.clone()),
WriteDescriptorSet::image_view(1, self.image.clone()),
WriteDescriptorSet::buffer(2, subbuffer),
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();
let push_constants = cs::PushConstants {
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
@ -166,58 +280,55 @@ impl PathtracerPipeline {
}
}
fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>, size: [u32; 2]) -> (Subbuffer<[[f32; 4]]>, Arc<ImageView<StorageImage>>) {
let raw_image = Buffer::from_iter(
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(
fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>, size: [u32; 2]) -> Arc<ImageView<StorageImage>> {
StorageImage::general_purpose_image_view(
memory_allocator,
queue.clone(),
size,
Format::R8G8B8A8_UNORM,
Format::R32G32B32A32_SFLOAT,
ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST,
).unwrap();
(raw_image, image)
).unwrap()
}
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");
// allocate some host memory
let mut vertices:Vec<[f32; 4]> = vec![];
let mut indices:Vec<u32> = vec![];
let mut shader_materials:Vec<cs::Material> = vec![];
for model in models.iter_mut() {
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) {
vertices.push([
model.mesh.positions[vertex_index],
model.mesh.positions[vertex_index + 1],
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() {
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(
memory_allocator,
BufferCreateInfo {
@ -225,7 +336,7 @@ fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, memory_alloca
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
usage: MemoryUsage::DeviceOnly,
..Default::default()
},
vertices.clone(),
@ -238,11 +349,24 @@ fn create_gpu_buffer(vertices: &Vec<[f32; 4]>, indices: &Vec<u32>, memory_alloca
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
usage: MemoryUsage::DeviceOnly,
..Default::default()
},
indices.clone(),
).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 "raytracing/raytracing.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(set = 0, binding = 0) buffer LinearImage { vec4 pixels[]; };
layout(set = 0, binding = 1, rgba8) uniform writeonly image2D image;
layout(set = 0, binding = 1, rgba32f) uniform image2D image;
layout(push_constant) uniform PushConstants {
vec2 resolution;
float seconds;
float frames;
} program_metadata;
vec2 get_viewport_coordinate() {
@ -26,82 +27,44 @@ uint get_pixel_index() {
return gl_GlobalInvocationID.y * uint(program_metadata.resolution.x) + gl_GlobalInvocationID.x;
}
vec3 project(in vec3 a, in vec3 b) {
return a * (dot(a, b) / dot(a, a));
}
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;
vec3 trace_direct(in Ray ray) {
vec3 color = vec3(0);
vec3 throughput = vec3(1);
for (uint depth = 0; depth < MAX_DEPTH; depth++) {
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() {
init_random_state(floatBitsToInt(program_metadata.seconds));
init_random_state(program_metadata.seconds);
vec2 uv = get_viewport_coordinate();
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
uint pixel_index = get_pixel_index();
vec4 previous = imageLoad(image, ivec2(gl_GlobalInvocationID.xy));
vec4 buffer_data = pixels[pixel_index] + vec4(color, 1);
// contribute to the raw pixel buffer
pixels[pixel_index] = buffer_data;
vec4 merged = mix(previous, color, 1.0 / program_metadata.frames);
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) {
state = (gl_GlobalInvocationID.x << 16) | gl_GlobalInvocationID.y;
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;
}
#include "one-at-a-time.glsl"
#include "xoshiro.glsl"
// 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.
float floatConstruct( uint m ) {
float floatConstruct(in uint m) {
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 |= ieeeOne; // Add fractional part to 1.0
float f = uintBitsToFloat(m); // Range [1:2]
return f - 1.0; // Range [0:1]
return uintBitsToFloat(m) - 1.0;
}
void init_random_state(in float seed) {
INIT_STATE_FUNCTION(seed);
}
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 {
vec3 nor;
vec2 uv;
// normal of the hit triangle
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;
// 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 {
@ -21,19 +50,31 @@ layout(set = 0, binding = 4) buffer IndexBuffer {
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) {
// triangle edges
vec3 v1v0 = v1 - v0;
vec3 v2v0 = v2 - v0;
vec3 rov0 = ray.origin - v0;
// normal
n = cross(v1v0, v2v0);
vec3 q = cross(rov0, ray.direction);
float d = 1.0 / dot(ray.direction, n);
float u = d * dot( -q, v2v0);
float v = d * dot( q, v1v0);
float t = d * dot( -n, rov0);
// barycentric coordinates
float u = d * dot(-q, v2v0);
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;
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 hit;
hit.depth = ray.far;
hit.intersected = false;
for (int i = 0; i < indices.length(); i += 3) {
vec3 v0 = vertices[indices[i]].xyz;
vec3 v1 = vertices[indices[i + 1]].xyz;
vec3 v2 = vertices[indices[i + 2]].xyz;
vec3 n;
vec3 result = intersect_triangle(ray, v0, v1, v2, n);
vec3 normal;
vec3 result = intersect_triangle(ray, v0, v1, v2, normal);
if (result.x > ray.near && result.x < hit.depth) {
hit.uv = result.yz;
hit.barycentric = result.yz;
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,
};
// 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
// them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools
// underneath and provides a safe interface for them.
@ -108,6 +101,15 @@ pub fn init() {
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!
// 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 texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass);
let mut pathtracer = PathtracerPipeline::new(&renderer, &queue, [512, 512]);
let mut now_keys = [false; 255];
@ -218,6 +219,7 @@ pub fn init() {
&new_images,
render_pass.clone(),
&mut viewport,
&mut pathtracer
);
recreate_swapchain = false;
@ -248,7 +250,7 @@ pub fn init() {
recreate_swapchain = true;
}
pathtracer.compute();
let future = pathtracer.compute();
let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
@ -256,6 +258,7 @@ pub fn init() {
.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
@ -298,10 +301,13 @@ fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>,
viewport: &mut Viewport,
pathtracer: &mut PathtracerPipeline,
) -> Vec<Arc<Framebuffer>> {
let dimensions = images[0].dimensions().width_height();
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
pathtracer.resize_image(dimensions);
images
.iter()
.map(|image| {
@ -312,8 +318,7 @@ fn window_size_dependent_setup(
attachments: vec![view],
..Default::default()
},
)
.unwrap()
).unwrap()
}).collect::<Vec<_>>()
}