finished diffuse pathtracing
This commit is contained in:
parent
3ffffb9bf5
commit
b911f89e86
|
@ -310,6 +310,7 @@ name = "eruption"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cgmath",
|
||||
"lazy_static",
|
||||
"tobj",
|
||||
"vulkano",
|
||||
"vulkano-shaders",
|
||||
|
|
|
@ -14,4 +14,6 @@ winit = "0.28.3"
|
|||
|
||||
tobj = "3.2.5"
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
cgmath = "0.18.0"
|
|
@ -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
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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(state);
|
||||
return floatConstruct(HASH_FUNCTION());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<_>>()
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue