aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/bvh-node.hpp130
-rw-r--r--include/material.hpp14
-rw-r--r--include/scene.hpp12
-rw-r--r--include/sphere.hpp5
-rw-r--r--shaders/compute.comp334
-rw-r--r--shaders/tonemapping.comp12
-rw-r--r--src/main.cpp13
-rw-r--r--src/scene.cpp172
-rw-r--r--src/vec3.cpp7
9 files changed, 359 insertions, 340 deletions
diff --git a/include/bvh-node.hpp b/include/bvh-node.hpp
index 698977a..5a46273 100644
--- a/include/bvh-node.hpp
+++ b/include/bvh-node.hpp
@@ -8,16 +8,12 @@
#include "sphere.hpp"
#include "triangle.hpp"
-class bvh_node {
- public:
- bvh_node() = default;
- bvh_node(sphere& sphere, int32_t sphere_id): bounding_box(sphere.bounding_box), primitive_id(sphere_id){}
+struct bvh_node {
+ aabb bounding_box;
- aabb bounding_box;
-
- int32_t next_id = -1;
- int32_t primitive_id = -1;
- uint32_t padding[2];
+ int32_t next_id = -1;
+ int32_t primitive_id = -1;
+ uint32_t padding[2];
};
struct packed_bvh_node {
@@ -34,54 +30,21 @@ struct temp_node : public bvh_node {
class bvh {
public:
- // bvh(std::vector<sphere>& spheres, std::vector<bvh_node>& nodes)
- // :spheres(spheres), nodes(nodes) {
- // nodes = std::vector<bvh_node>(2 * spheres.size() - 1);
- // temp_nodes = std::vector<temp_node>(2 * spheres.size() - 1);
- // leafs = std::vector<temp_node>(spheres.size());
-
- // for (uint32_t id = 0; id < spheres.size(); id++) {
- // leafs[id].bounding_box = spheres[id].bounding_box;
- // leafs[id].primitive_id = id;
- // }
-
- // temp_nodes[0] = temp_node();
-
- // temp_nodes[0].bounding_box = compute_bounds(0, spheres.size());
- // subdivide(0, 0, spheres.size());
-
- // set_depth_first_order();
-
- // for (uint32_t id = 0; id < temp_nodes.size(); id++) {
- // auto old_node = temp_nodes[id];
- // auto &node = nodes[old_node.df_id];
-
- // node.primitive_id = old_node.primitive_id;
- // node.bounding_box = old_node.bounding_box;
+ bvh(std::vector<sphere>& spheres, std::vector<packed_bvh_node>& packed_nodes)
+ :spheres(spheres) {
+ packed_nodes.resize(2 * spheres.size() - 1);
+ temp_nodes = std::vector<temp_node>(2 * spheres.size() - 1);
+ leafs = std::vector<temp_node>(spheres.size());
- // if (old_node.next_id != -1) {
- // node.next_id = temp_nodes[old_node.next_id].df_id;
- // }
- // }
-
- // // exporter();
- // }
-
- bvh(std::vector<triangle>& triangles, std::vector<packed_bvh_node>& packed_nodes)
- :triangles(triangles) {
- packed_nodes.resize(2 * triangles.size() - 1);
- temp_nodes = std::vector<temp_node>(2 * triangles.size() - 1);
- leafs = std::vector<temp_node>(triangles.size());
-
- for (uint32_t id = 0; id < triangles.size(); id++) {
- leafs[id].bounding_box = aabb(triangles[id]);
+ for (uint32_t id = 0; id < spheres.size(); id++) {
+ leafs[id].bounding_box = aabb(spheres[id].center, spheres[id].radius);
leafs[id].primitive_id = id;
}
temp_nodes[0] = temp_node();
- temp_nodes[0].bounding_box = compute_bounds(0, triangles.size());
- subdivide(0, 0, triangles.size());
+ temp_nodes[0].bounding_box = compute_bounds(0, spheres.size());
+ subdivide(0, 0, spheres.size());
set_depth_first_order();
@@ -107,6 +70,46 @@ public:
// exporter();
}
+ // bvh(std::vector<triangle>& triangles, std::vector<packed_bvh_node>& packed_nodes)
+ // :triangles(triangles) {
+ // packed_nodes.resize(2 * triangles.size() - 1);
+ // temp_nodes = std::vector<temp_node>(2 * triangles.size() - 1);
+ // leafs = std::vector<temp_node>(triangles.size());
+
+ // for (uint32_t id = 0; id < triangles.size(); id++) {
+ // leafs[id].bounding_box = aabb(triangles[id]);
+ // leafs[id].primitive_id = id;
+ // }
+
+ // temp_nodes[0] = temp_node();
+
+ // temp_nodes[0].bounding_box = compute_bounds(0, triangles.size());
+ // subdivide(0, 0, triangles.size());
+
+ // set_depth_first_order();
+
+ // for (uint32_t id = 0; id < temp_nodes.size(); id++) {
+ // auto old_node = temp_nodes[id];
+ // auto &node = packed_nodes[old_node.df_id];
+
+ // node.primitive_id = old_node.primitive_id;
+
+ // node.min[0] = old_node.bounding_box.minimum.v[0];
+ // node.min[1] = old_node.bounding_box.minimum.v[1];
+ // node.min[2] = old_node.bounding_box.minimum.v[2];
+
+ // node.max[0] = old_node.bounding_box.maximum.v[0];
+ // node.max[1] = old_node.bounding_box.maximum.v[1];
+ // node.max[2] = old_node.bounding_box.maximum.v[2];
+
+ // if (old_node.next_id != -1) {
+ // node.next_id = temp_nodes[old_node.next_id].df_id;
+ // }
+ // }
+
+ // // exporter();
+ // }
+
//-------------------------
// BVH exporter for graphviz
//-------------------------
@@ -163,8 +166,8 @@ public:
std::vector<temp_node> temp_nodes;
std::vector<temp_node> leafs;
- // std::vector<sphere>& spheres;
- std::vector<triangle>& triangles;
+ std::vector<sphere>& spheres;
+ // std::vector<triangle>& triangles;
uint32_t next_node_id = 0;
@@ -176,7 +179,6 @@ private:
aabb global_box = aabb();
for (uint32_t id = begin; id < end; id++) {
- // global_box.union_with(spheres[leaf.primitive_id].bounding_box);
global_box.union_with(leafs[id].bounding_box);
}
@@ -205,12 +207,12 @@ private:
if (count <= 4) {
// Subdivide the node by splitting the set in two equal parts
std::sort(leafs.begin() + begin, leafs.begin() + end,
- // [&](const bvh_node& a, const bvh_node& b) {
- // return spheres[a.primitive_id].center[split_axis] < spheres[b.primitive_id].center[split_axis];
- // }
[&](const bvh_node& a, const bvh_node& b) {
- return triangles[a.primitive_id].center()[split_axis] < triangles[b.primitive_id].center()[split_axis];
+ return spheres[a.primitive_id].center[split_axis] < spheres[b.primitive_id].center[split_axis];
}
+ // [&](const bvh_node& a, const bvh_node& b) {
+ // return triangles[a.primitive_id].center()[split_axis] < triangles[b.primitive_id].center()[split_axis];
+ // }
);
temp_nodes[left_id].bounding_box = compute_bounds(begin, begin + (count / 2));
@@ -231,14 +233,18 @@ private:
aabb centroid_bounds;
for (size_t i = begin; i < end; i++) {
- auto primitive = triangles[leafs[i].primitive_id];
- centroid_bounds.union_with(primitive.center());
+ // auto primitive = triangles[leafs[i].primitive_id];
+ // centroid_bounds.union_with(primitive.center());
+ auto primitive = spheres[leafs[i].primitive_id];
+ centroid_bounds.union_with(primitive.center);
}
float split_axis_size = centroid_bounds.maximum[split_axis] - centroid_bounds.minimum[split_axis];
for (size_t i = begin; i < end; i++) {
- auto primitive = triangles[leafs[i].primitive_id];
- auto normalized_offset = (primitive.center()[split_axis] - centroid_bounds.minimum[split_axis]) / split_axis_size;
+ // auto primitive = triangles[leafs[i].primitive_id];
+ // auto normalized_offset = (primitive.center()[split_axis] - centroid_bounds.minimum[split_axis]) / split_axis_size;
+ auto primitive = spheres[leafs[i].primitive_id];
+ auto normalized_offset = (primitive.center[split_axis] - centroid_bounds.minimum[split_axis]) / split_axis_size;
uint32_t bucket_index = normalized_offset * buckets_count;
if (bucket_index == buckets_count) {
bucket_index -= 1;
diff --git a/include/material.hpp b/include/material.hpp
index ee4281b..4853279 100644
--- a/include/material.hpp
+++ b/include/material.hpp
@@ -3,20 +3,12 @@
#include "vec3.hpp"
-enum MATERIAL_TYPE: uint32_t {
- LAMBERTIAN = 0,
- METAL = 1,
- DIELECTRIC = 2,
- EMISSIVE = 3,
-};
-
struct material {
color albedo;
color emissive;
- float fuzz;
- float ior;
- uint32_t type;
- uint32_t padding;
+ float metalness = 0.f;
+ float roughness = 1.f;
+ uint32_t padding[2];
};
#endif // !__MATERIAL_HPP_
diff --git a/include/scene.hpp b/include/scene.hpp
index 3c69425..4eed567 100644
--- a/include/scene.hpp
+++ b/include/scene.hpp
@@ -14,18 +14,16 @@ class scene {
// uint32_t max_bounce = 50;
uint32_t max_bounce = 3;
+ uint32_t min_bounce = 1;
- // Output image resolution
uint32_t width;
uint32_t height;
uint32_t sample_index = 0;
- // uint32_t spheres_count = 0;
- uint32_t triangles_count = 0;
uint32_t enable_dof = false;
- uint32_t debug_bvh = false;
- uint32_t downscale_factor = 4;
+ uint32_t debug_bvh = false;
+ int32_t downscale_factor = 4;
metadata(camera cam, uint32_t width, uint32_t height)
: cam(cam), width(width), height(height) {}
@@ -39,8 +37,8 @@ public:
metadata meta;
// Geometry & BVH
- // std::vector<sphere> spheres;
- std::vector<triangle> triangles;
+ std::vector<sphere> spheres;
+ // std::vector<triangle> triangles;
std::vector<packed_bvh_node> packed_nodes;
};
diff --git a/include/sphere.hpp b/include/sphere.hpp
index 28bb0bc..3a29207 100644
--- a/include/sphere.hpp
+++ b/include/sphere.hpp
@@ -7,16 +7,13 @@
struct sphere {
sphere() = default;
sphere(point3 center, material material, float radius)
- : center(center), radius(radius), mat(material),
- bounding_box(center, radius)
+ : center(center), radius(radius), mat(material)
{}
point3 center;
material mat;
- aabb bounding_box;
-
float radius;
float padding[3];
};
diff --git a/shaders/compute.comp b/shaders/compute.comp
index fa69504..7454589 100644
--- a/shaders/compute.comp
+++ b/shaders/compute.comp
@@ -1,26 +1,35 @@
+// ----------------------------------------------------------------------
+// Most of the BRDF stuff in this code comes from github.com/boksajak/referencePT
+// This repo is used it Ray Tracing gems 2, reference path tracer chaper
+// ----------------------------------------------------------------------
+
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_nonuniform_qualifier : require
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+// Specifies minimal reflectance for dielectrics (when metalness is zero)
+// Nothing has lower reflectance than 2%, but we use 4% to have consistent results with UE4, Frostbite, et al.
+#define MIN_DIELECTRICS_F0 0.04f
+
struct ray {
vec3 origin;
vec3 direction;
+ float min_t;
+ float max_t;
};
const float PI = 3.141592653589793;
-const uint LAMBERTIAN_MATERIAL = 0;
-const uint METAL_MATERIAL = 1;
-const uint DIELECTRIC_MATERIAL = 2;
-const uint EMISSIVE_MATERIAL = 3;
+
+const uint DIFFUSE = 1;
+const uint SPECULAR = 2;
struct material {
vec4 albedo;
vec4 emissive;
- float fuzz;
- float ior;
- uint type;
- uint padding;
+ float metalness;
+ float roughness;
+ uint[2] padding;
};
struct hit_info {
@@ -28,7 +37,6 @@ struct hit_info {
vec3 point;
vec3 normal;
material mat;
- bool front_face;
};
struct sphere {
@@ -74,20 +82,19 @@ layout(buffer_reference) readonly buffer scene_metadata {
camera cam;
uint max_bounce;
+ uint min_bounce;
uint width;
uint height;
uint sample_index;
- // uint spheres_count;
- uint triangles_count;
- bool enable_dof;
- bool debug_bvh;
- uint downscale_factor;
+ uint enable_dof;
+ uint debug_bvh;
+ int downscale_factor;
};
layout(buffer_reference) readonly buffer primitives_array {
- // sphere[] spheres;
- triangle triangles[];
+ sphere[] spheres;
+ // triangle triangles[];
};
layout(buffer_reference) readonly buffer nodes_array {
@@ -105,10 +112,6 @@ layout(push_constant) uniform buffers {
uint padding[2];
} bufs;
-vec3 at(ray r, float t) {
- return r.origin + r.direction * t;
-}
-
uint pcg_hash(inout uint seed)
{
uint state = seed;
@@ -122,15 +125,7 @@ float rand(inout uint seed)
return float(pcg_hash(seed)) / 4294967296.0;
}
-vec3 unit_vec(inout uint seed) {
- float z = rand(seed) * 2.0 - 1.0;
- float a = 2.0 * PI * rand(seed);
- float r = sqrt(1.0 - z * z);
-
- return vec3(cos(a) * r, sin(a) * r, z);
-}
-
-vec3 diffuse_reflection_cosine_weighted(inout uint seed) {
+vec3 sample_hemisphere(inout uint seed) {
float r = rand(seed);
float radius = sqrt(r);
float theta = 2.0 * PI * rand(seed);
@@ -146,7 +141,36 @@ vec2 disk_vec(inout uint seed) {
return vec2(cos(a) * r, sin(a) * r);
}
-bool hit_aabb(vec3 minimum, vec3 maximum, ray r, float min_t, float max_t) {
+// Calculates rotation quaternion from axis vector to the vector (0, 0, 1)
+// Input vector must be normalized!
+vec4 rotation_to_z_axis(vec3 axis) {
+ // Handle special case when axis is exact or near opposite of (0, 0, 1)
+ if (axis.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f);
+
+ return normalize(vec4(axis.y, -axis.x, 0.0f, 1.0f + axis.z));
+}
+
+vec4 invert_rotation(vec4 q) {
+ return vec4(-q.xyz, q.w);
+}
+
+// Optimized point rotation using quaternion
+// Source: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
+vec3 rotate_point(vec4 q, vec3 v) {
+ const vec3 q_axis = q.xyz;
+ return 2.0f * dot(q_axis, v) * q_axis + (q.w * q.w - dot(q_axis, q_axis)) * v + 2.0f * q.w * cross(q_axis, v);
+}
+
+vec3 at(ray r, float t) {
+ return r.origin + r.direction * t;
+}
+
+float luminance(vec3 rgb)
+{
+ return dot(rgb, vec3(0.2126f, 0.7152f, 0.0722f));
+}
+
+bool hit_aabb(vec3 minimum, vec3 maximum, ray r) {
const vec3 invdir = 1.0 / r.direction;
const vec3 f = (maximum.xyz - r.origin.xyz) * invdir;
const vec3 n = (minimum.xyz - r.origin.xyz) * invdir;
@@ -154,27 +178,27 @@ bool hit_aabb(vec3 minimum, vec3 maximum, ray r, float min_t, float max_t) {
const vec3 tmax = max(f, n);
const vec3 tmin = min(f, n);
- const float t1 = min(tmax.x, min(tmax.y, tmax.z));
- const float t0 = max(max(tmin.x, max(tmin.y, tmin.z)), 0.0f);
+ const float t1 = min(min(tmax.x, min(tmax.y, tmax.z)), r.max_t);
+ const float t0 = max(max(max(tmin.x, max(tmin.y, tmin.z)), r.min_t), 0.0f);
return t1 >= t0;
}
-uint hit_aabbs(ray r, float min_t, float max_t) {
+uint hit_aabbs(ray r) {
uint hit_count = 0;
int id = 0;
while(id != -1) {
if (bufs.bvh.nodes[id].primitive_id != -1) {
- triangle tri = bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id];
- vec3 minimum = min(tri.v1.position, min(tri.v2.position, tri.v3.position)).xyz;
- vec3 maximum = max(tri.v1.position, max(tri.v2.position, tri.v3.position)).xyz;
+ // triangle tri = bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id];
+ // vec3 minimum = min(tri.v1.position, min(tri.v2.position, tri.v3.position)).xyz;
+ // vec3 maximum = max(tri.v1.position, max(tri.v2.position, tri.v3.position)).xyz;
- if (hit_aabb(minimum, maximum, r, min_t, max_t)) {
- hit_count++;
- }
+ // if (hit_aabb(minimum, maximum, r)) {
+ // hit_count++;
+ // }
id = bufs.bvh.nodes[id].next_id;
- } else if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r, min_t, max_t)) {
+ } else if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r)) {
hit_count++;
id++;
} else {
@@ -185,7 +209,7 @@ uint hit_aabbs(ray r, float min_t, float max_t) {
return hit_count;
}
-bool hit_sphere(sphere s, ray r, float min_t, float max_t, out hit_info info) {
+bool hit_sphere(sphere s, ray r, out hit_info info) {
vec3 origin_center = r.origin - s.position.xyz;
float a = dot(r.direction, r.direction);
@@ -199,10 +223,10 @@ bool hit_sphere(sphere s, ray r, float min_t, float max_t, out hit_info info) {
float sqrt_discriminant = sqrt(discriminant);
float root = (-half_b - sqrt_discriminant) / a;
- if (root < min_t || root > max_t) {
+ if (root < r.min_t || root > r.max_t) {
root = (-half_b + sqrt_discriminant) / a;
- if (root < min_t || root > max_t) {
+ if (root < r.min_t || root > r.max_t) {
return false;
}
}
@@ -211,14 +235,14 @@ bool hit_sphere(sphere s, ray r, float min_t, float max_t, out hit_info info) {
info.point = at(r, info.t);
vec3 outward_normal = (info.point - s.position.xyz) / s.radius;
- info.front_face = dot(outward_normal, r.direction) < 0.0;
- info.normal = info.front_face ? outward_normal : -outward_normal;
+ bool front_face = dot(outward_normal, r.direction) < 0.0;
+ info.normal = front_face ? outward_normal : -outward_normal;
info.mat = s.mat;
return true;
}
-bool hit_triangle(triangle tri, ray r, float min_t, float max_t, out hit_info info) {
+bool hit_triangle(triangle tri, ray r, out hit_info info) {
vec3 v2v1 = tri.v2.position.xyz - tri.v1.position.xyz;
vec3 v3v1 = tri.v3.position.xyz - tri.v1.position.xyz;
@@ -230,175 +254,183 @@ bool hit_triangle(triangle tri, ray r, float min_t, float max_t, out hit_info in
float v = d * dot(q, v2v1);
float t = d * dot(-normal, originv1);
- if (u < 0.0 || u > 1.0 || v < 0.0 || u + v > 1.0 || t < min_t || t > max_t) {
+ if (u < 0.0 || u > 1.0 || v < 0.0 || u + v > 1.0 || t < r.min_t || t > r.max_t) {
return false;
}
info.t = t;
- info.point = at(r, info.t);
-
- // vec3 outward_normal = normal;
- // info.front_face = dot(outward_normal, r.direction) < 0.0;
info.normal = normal;
- // info.mat = material(vec4(u, v, 0.0, 0.0), vec4(0.0), 1.0, 1.5, LAMBERTIAN_MATERIAL, 0);
- info.mat = material(vec4(0.55), vec4(0.0), 1.0, 1.5, LAMBERTIAN_MATERIAL, 0);
return true;
}
-bool hit_node(ray r, float min_t, float max_t, out hit_info info) {
+bool hit_node(ray r, out hit_info info) {
hit_info temp_info;
- float closest_t = max_t;
bool hit = false;
int id = 0;
while(id != -1) {
// Leafs
if (bufs.bvh.nodes[id].primitive_id != -1) {
- // if (hit_sphere(spheres[bufs.bvh.nodes[id].primitive_id], r, min_t, closest_t, temp_info)) {
- // info = temp_info;
- // closest_t = temp_info.t;
- // hit = true;
- // }
- if (hit_triangle(bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id], r, min_t, closest_t, temp_info)) {
+ if (hit_sphere(bufs.geometry.spheres[bufs.bvh.nodes[id].primitive_id], r, temp_info)) {
info = temp_info;
- closest_t = temp_info.t;
+ r.max_t = temp_info.t;
hit = true;
}
+ // if (hit_triangle(bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id], r, temp_info)) {
+ // info = temp_info;
+ // r.max_t = temp_info.t;
+ // hit = true;
+ // }
id = bufs.bvh.nodes[id].next_id;
- } else { // Internal nodes
- if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r, min_t, closest_t)) {
+ } else if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r)) {
id++;
- } else {
- id = bufs.bvh.nodes[id].next_id;
- }
+ } else {
+ id = bufs.bvh.nodes[id].next_id;
}
}
+ info.point = at(r, info.t);
return hit;
}
-bool lambertian_scattering(material mat, ray incident, inout uint seed, hit_info info, out vec4 attenuation, out ray scattered) {
- attenuation = mat.albedo;
- scattered = ray(info.point, normalize(info.normal + unit_vec(seed)));
-
- return true;
+vec3 albedo_to_specular_F0(vec3 albedo, float metalness) {
+ return mix(vec3(MIN_DIELECTRICS_F0), albedo, metalness);
}
-bool metal_scattering(material mat, ray incident, inout uint seed, hit_info info, out vec4 attenuation, out ray scattered) {
- vec3 reflected = reflect(normalize(incident.direction), info.normal);
-
- attenuation = mat.albedo;
- scattered = ray(info.point, reflected + mat.fuzz * unit_vec(seed));
-
- return (dot(scattered.direction, info.normal) > 0.0);
+vec3 albedo_to_diffuse_reflectance(vec3 albedo, float metalness) {
+ return albedo * (1.0f - metalness);
}
-float reflectance(float cosine, float eta) {
- // Use Schlick's approximation for reflectance.
- float r0 = (1 - eta) / (1 + eta);
- r0 = r0 * r0;
- return r0 + (1 - r0) * pow((1 - cosine), 5);
+// Schlick's approximation to Fresnel term
+// f90 should be 1.0, except for the trick used by Schuler (see 'shadowedF90' function)
+vec3 eval_fresnel_schlick(vec3 f0, float f90, float NdotS) {
+ return f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f);
+}
+vec3 eval_fresnel(vec3 f0, float f90, float NdotS) {
+ // Default is Schlick's approximation
+ return eval_fresnel_schlick(f0, f90, NdotS);
}
-bool dielectric_scattering(material mat, ray incident, inout uint seed, hit_info info, out vec4 attenuation, out ray scattered) {
- attenuation = vec4(1.0);
-
- vec3 unit_dir = normalize(incident.direction);
- float cos_theta = min(dot(-unit_dir, info.normal), 1.0);
- float sin_theta = sqrt(1.0 - cos_theta*cos_theta);
+// Attenuates F90 for very low F0 values
+// Source: "An efficient and Physically Plausible Real-Time Shading Model" in ShaderX7 by Schuler
+// Also see section "Overbright highlights" in Hoffman's 2010 "Crafting Physically Motivated Shading Models for Game Development" for discussion
+// IMPORTANT: Note that when F0 is calculated using metalness, it's value is never less than MIN_DIELECTRICS_F0, and therefore,
+// this adjustment has no effect. To be effective, F0 must be authored separately, or calculated in different way. See main text for discussion.
+float shadowed_F90(vec3 F0) {
+ // This scaler value is somewhat arbitrary, Schuler used 60 in his article. In here, we derive it from MIN_DIELECTRICS_F0 so
+ // that it takes effect for any reflectance lower than least reflective dielectrics
+ //const float t = 60.0f;
+ const float t = (1.f / MIN_DIELECTRICS_F0);
+ return min(1.f, t * luminance(F0));
+}
- float eta = info.front_face ? 1.0 / mat.ior : mat.ior;
- vec3 direction;
+float get_brdf_probability(material mat, vec3 view, vec3 shadingNormal) {
+ // Evaluate Fresnel term using the shading normal
+ // Note: we use the shading normal instead of the microfacet normal (half-vector) for Fresnel term here. That's suboptimal for rough surfaces at grazing angles, but half-vector is yet unknown at this point
+ vec3 specular_F0 = vec3(luminance(albedo_to_specular_F0(mat.albedo.rgb, mat.metalness)));
+ float diffuse_reflectance = luminance(albedo_to_diffuse_reflectance(mat.albedo.rgb, mat.metalness));
+ float fresnel = clamp(luminance(eval_fresnel(specular_F0, shadowed_F90(specular_F0), max(0.0f, dot(view, shadingNormal)))), 0.0, 1.0);
- if (eta * sin_theta > 1.0 || reflectance(cos_theta, eta) > rand(seed)) {
- direction = reflect(unit_dir, info.normal);
- } else {
- direction = refract(unit_dir, info.normal, eta);
- }
+ // Approximate relative contribution of BRDFs using the Fresnel term
+ float specular = fresnel;
+ float diffuse = diffuse_reflectance * (1.0f - fresnel); //< If diffuse term is weighted by Fresnel, apply it here as well
- scattered = ray(info.point, direction);
+ // Return probability of selecting specular BRDF over diffuse BRDF
+ float p = (specular / max(0.0001f, (specular + diffuse)));
- return true;
+ // Clamp probability to avoid undersampling of less prominent BRDF
+ return clamp(p, 0.1f, 0.9f);
}
-vec4 ray_color(ray r, uint seed) {
+vec3 ray_color(ray r, uint seed) {
hit_info info;
- uint bounces = 0;
+ vec3 radiance = vec3(0.0);
+ vec3 throughput = vec3(1.0);
- vec4 final_color = vec4(0.0);
- vec4 throughput = vec4(1.0);
- ray current_ray = r;
- while (hit_node(current_ray, 0.001, 1e15, info) && bounces < bufs.scene.max_bounce) {
- ray scattered;
- vec4 attenuation = vec4(0.0);
- // if ((info.mat.type == LAMBERTIAN_MATERIAL && lambertian_scattering(info.mat, current_ray, seed, info, attenuation, scattered)) ||
- // (info.mat.type == METAL_MATERIAL && metal_scattering(info.mat, current_ray, seed, info, attenuation, scattered)) ||
- // (info.mat.type == DIELECTRIC_MATERIAL && dielectric_scattering(info.mat, current_ray, seed, info, attenuation, scattered)) ||
- // info.mat.type == EMISSIVE_MATERIAL) {
- lambertian_scattering(info.mat, current_ray, seed, info, attenuation, scattered);
- // final_color += info.mat.emissive * throughput;
- throughput *= attenuation;
+ for (uint bounce = 0; bounce < bufs.scene.max_bounce; bounce++) {
+ if (!hit_node(r, info))
+ return throughput * vec3(1.0);
- float NdotR = dot(info.normal, scattered.direction);
- float PDF = NdotR / PI;
- vec4 BRDF = attenuation / PI;
- throughput = (throughput * NdotR / PDF) * BRDF;
- current_ray = scattered;
+ // uint brdf_type;
+ // if (info.mat.metalness == 1.f && info.mat.metalness == 0.f) {
+ // brdf_type = SPECULAR;
+ // } else {
+ // float brdf_probability = get_brdf_probability(info.mat, -r.direction, );
+ // if (rand(seed) < brdf_probability) {
+ // brdf_type = SPECULAR;
+ // } else {
+ // brdf_type = DIFFUSE;
+ // }
+ // }
- // Russian Roulette
- // As the throughput gets smaller, the ray is more likely to get terminated early.
- // Survivors have their value boosted to make up for fewer samples being in the average.
- // {
- // float p = max(throughput.r, max(throughput.g, throughput.b));
- // if (rand(seed) > p) {
- // break;
- // }
+ // Move to tangent space
+ vec4 rotation_to_z = rotation_to_z_axis(info.normal);
+ vec3 view_local = rotate_point(rotation_to_z, -r.direction);
+ vec3 normal_local = vec3(0.0, 0.0, 1.0);
+ vec3 ray_dir_local = sample_hemisphere(seed);
- // // Add the energy we 'lose' by randomly terminating paths
- // throughput *= 1.0 / p;
- // }
- // }
- bounces++;
- }
+ float NdotL = dot(normal_local, ray_dir_local);
+ float pdf = NdotL / PI;
+ vec3 brdf = albedo_to_diffuse_reflectance(info.mat.albedo.xyz, info.mat.metalness) / PI;
+ throughput *= brdf / pdf;
- return throughput;
- // return final_color;
-}
+ // Move to global space
+ vec4 rotation_from_z = invert_rotation(rotation_to_z);
+ vec3 ray_dir = rotate_point(rotation_from_z, ray_dir_local);
-void main() {
- uint x = gl_GlobalInvocationID.x * bufs.scene.downscale_factor;
- uint y = gl_GlobalInvocationID.y * bufs.scene.downscale_factor;
+ r = ray(info.point, normalize(info.normal + ray_dir), 0.001f, 1e15);
- vec2 uv = vec2(float(x) / (bufs.scene.width - 1), 1.0 - (float(y) / (bufs.scene.height - 1)));
+ // Russian Roulette
+ // As the throughput gets smaller, the ray is more likely to get terminated early.
+ // Survivors have their value boosted to make up for fewer samples being in the average.
+ if (bounce > bufs.scene.min_bounce) {
+ float probability = min(0.95f, luminance(throughput));
+ if (probability < rand(seed)) break;
+ throughput /= probability;
+ }
+ }
+
+ return radiance;
+}
- uint seed = uint(x * uint(1973) + y * uint(9277) + bufs.scene.sample_index * uint(26699)) | uint(1);
+ray generate_camera_ray(ivec2 coords, uint seed) {
+ vec2 scene_size = vec2(bufs.scene.width - 1, bufs.scene.height - 1);
+ vec2 uv = vec2(coords.x / scene_size.x, 1.0 - coords.y / scene_size.y);
vec2 rand_disk = disk_vec(seed);
- vec2 jittered_uvs = uv + vec2(rand(seed) / (bufs.scene.width - 1), rand(seed) / (bufs.scene.height - 1));
+ vec2 jittered_uvs = uv + vec2(rand(seed), rand(seed)) / scene_size;
vec2 lens_disk = vec2(0.0);
- if (bufs.scene.enable_dof) {
+
+ if (bufs.scene.enable_dof == 1) {
lens_disk = rand_disk * bufs.scene.cam.lens_radius;
}
vec3 offset = lens_disk.x * bufs.scene.cam.right.xyz + lens_disk.y * bufs.scene.cam.up.xyz;
- vec4 proj_plane_pos = bufs.scene.cam.first_pixel + jittered_uvs.x * bufs.scene.cam.horizontal + jittered_uvs.y * bufs.scene.cam.vertical;
+ vec3 proj_plane_pos = bufs.scene.cam.first_pixel.xyz + jittered_uvs.x * bufs.scene.cam.horizontal.xyz + jittered_uvs.y * bufs.scene.cam.vertical.xyz;
+
+ return ray(bufs.scene.cam.position.xyz + offset, proj_plane_pos.xyz - bufs.scene.cam.position.xyz - offset, 0.001f, 1e15);
+}
+
+void main() {
+ ivec2 coords = ivec2(gl_GlobalInvocationID.xy) * bufs.scene.downscale_factor;
- ray r = ray(bufs.scene.cam.position.xyz + offset, proj_plane_pos.xyz - bufs.scene.cam.position.xyz - offset);
- vec4 out_color = vec4(0.0);
+ uint seed = uint(coords.x * uint(1973) + coords.y * uint(9277) + bufs.scene.sample_index * uint(26699)) | uint(1);
+ ray r = generate_camera_ray(coords, seed);
- if (bufs.scene.debug_bvh) {
- out_color = hit_aabbs(r, 0.001, 1e15) * vec4(0.001, 0.0, 0.0, 1.0);
+ vec3 out_color = vec3(0.0);
+ if (bufs.scene.debug_bvh == 1) {
+ out_color = hit_aabbs(r) * vec3(0.001, 0.0, 0.0);
} else {
out_color = ray_color(r, seed);
}
float spp_scale = (1.0 / bufs.scene.sample_index);
- vec4 acc_color = imageLoad(images[nonuniformEXT(bufs.accumulation_image_index)], ivec2(x, y));
- out_color = vec4(mix(acc_color, out_color, spp_scale));
+ vec3 acc_color = imageLoad(images[nonuniformEXT(bufs.accumulation_image_index)], coords).xyz;
+ out_color = mix(acc_color, out_color, spp_scale);
for (uint i = 0; i < bufs.scene.downscale_factor; i++) {
for (uint j = 0; j < bufs.scene.downscale_factor; j++) {
- imageStore(images[nonuniformEXT(bufs.output_image_index)], ivec2(x + i, y + j), out_color);
+ imageStore(images[nonuniformEXT(bufs.output_image_index)], coords + ivec2(i, j), vec4(out_color, 1.0));
}
}
}
diff --git a/shaders/tonemapping.comp b/shaders/tonemapping.comp
index 94c7479..660327d 100644
--- a/shaders/tonemapping.comp
+++ b/shaders/tonemapping.comp
@@ -5,7 +5,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(buffer_reference) readonly buffer scene_metadata {
uint[39] unused;
- uint downscale_factor;
+ int downscale_factor;
};
layout(set = 0, binding = 2, rgba32f) uniform image2D images[];
@@ -17,14 +17,14 @@ layout(push_constant) uniform constants {
} consts;
void main() {
- uint x = gl_GlobalInvocationID.x * consts.scene.downscale_factor;
- uint y = gl_GlobalInvocationID.y * consts.scene.downscale_factor;
+ ivec2 coords = ivec2(gl_GlobalInvocationID.xy) * consts.scene.downscale_factor;
+
+ vec3 color = imageLoad(images[nonuniformEXT(consts.accumulation_image_index)], coords).rgb;
+ vec3 out_color = sqrt(color);
- vec4 color = imageLoad(images[nonuniformEXT(consts.accumulation_image_index)], ivec2(x, y));
- vec4 out_color = sqrt(color);
for (uint i = 0; i < consts.scene.downscale_factor; i++) {
for (uint j = 0; j < consts.scene.downscale_factor; j++) {
- imageStore(images[nonuniformEXT(consts.output_image_index)], ivec2(x + i, y + j), out_color);
+ imageStore(images[nonuniformEXT(consts.output_image_index)], coords + ivec2(i, j), vec4(out_color, 1.0));
}
}
}
diff --git a/src/main.cpp b/src/main.cpp
index 37ce060..9d3297d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -46,9 +46,6 @@ int main() {
const float aspect_ratio = 16.0 / 9.0;
uint32_t width = 400;
uint32_t height = width / aspect_ratio;
- const uint32_t samples_per_pixel = 500;
-
- std::cerr << sizeof(vec3) << std::endl;
window wnd { width , height };
@@ -65,12 +62,13 @@ int main() {
auto cam = camera(position, target, v_fov, (float)width / (float)height, aperture, focus_distance);
auto main_scene = scene(cam, width, height);
auto can_render = true;
- vkrenderer renderer { wnd, sizeof(main_scene.meta), sizeof(main_scene.triangles[1]) * main_scene.triangles.size(), sizeof(main_scene.packed_nodes[0]) * main_scene.packed_nodes.size() };
- // vkrenderer renderer { wnd, sizeof(main_scene.meta), sizeof(main_scene.spheres[0]) * main_scene.spheres.size(), sizeof(main_scene.nodes[0]) * main_scene.nodes.size() };
+ // vkrenderer renderer { wnd, sizeof(main_scene.meta), sizeof(main_scene.triangles[0]) * main_scene.triangles.size(), sizeof(main_scene.packed_nodes[0]) * main_scene.packed_nodes.size() };
+ vkrenderer renderer { wnd, sizeof(main_scene.meta), sizeof(main_scene.spheres[0]) * main_scene.spheres.size(), sizeof(main_scene.packed_nodes[0]) * main_scene.packed_nodes.size() };
// Upload scene content
std::memcpy(renderer.scene_buffer_ptr(), &main_scene.meta, sizeof(main_scene.meta));
- renderer.update_geometry_buffer(main_scene.triangles.data());
+ // renderer.update_geometry_buffer(main_scene.triangles.data());
+ renderer.update_geometry_buffer(main_scene.spheres.data());
renderer.update_bvh_buffer(main_scene.packed_nodes.data());
ImGuiIO& io = ImGui::GetIO();
@@ -218,7 +216,8 @@ int main() {
reset_accumulation = true;
}
- ImGui::SliderInt("bounces", (int32_t*)&((scene*) renderer.scene_buffer_ptr())->meta.max_bounce, 1, 250);
+ ImGui::SliderInt("max bounces", (int32_t*)&((scene*) renderer.scene_buffer_ptr())->meta.max_bounce, 1, 250);
+ ImGui::SliderInt("min bounces", (int32_t*)&((scene*) renderer.scene_buffer_ptr())->meta.min_bounce, 1, ((scene*) renderer.scene_buffer_ptr())->meta.max_bounce);
if (ImGui::SliderInt("downscale factor", (int32_t*)&((scene*) renderer.scene_buffer_ptr())->meta.downscale_factor, 1, 32)) {
reset_accumulation = true;
}
diff --git a/src/scene.cpp b/src/scene.cpp
index 15cea11..cf22415 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -6,66 +6,60 @@
#include "utils.hpp"
#include "gltf.hpp"
-// Return the number of instantiated spheres
-// void scene::random_scene() {
-// material ground_material = { { 0.5, 0.5, 0.5 } };
-// ground_material.type = MATERIAL_TYPE::LAMBERTIAN;
-// spheres.emplace_back(vec3 { 0, -1000, 0 }, ground_material, 1000);
-//
-// int32_t spheres_count = 11;
-// for (int a = -spheres_count; a < spheres_count; a++) {
-// for (int b = -spheres_count; b < spheres_count; b++) {
-// auto choose_mat = randd();
-// point3 center(a + 0.9 * randd(), 0.2, b + 0.9 * randd());
-//
-// if ((center - point3{ 4, 0.2, 0 }).length() > 0.9) {
-// material sphere_material = {};
-//
-// if (choose_mat < 0.8) {
-// // diffuse
-// sphere_material.albedo = color::random() * color::random();
-// sphere_material.type = MATERIAL_TYPE::LAMBERTIAN;
-// } else if (choose_mat < 0.95) {
-// // metal
-// sphere_material.albedo = color::random(0.5, 1);
-// sphere_material.fuzz = randd(0, 0.5);
-// sphere_material.type = MATERIAL_TYPE::METAL;
-// } else {
-// // glass
-// sphere_material.ior = 1.5;
-// sphere_material.type = MATERIAL_TYPE::DIELECTRIC;
-// }
-//
-// spheres.emplace_back(center, sphere_material, 0.2);
-// }
-// }
-// }
-//
-// material material1 = {};
-// material1.ior = 1.5;
-// material1.type = MATERIAL_TYPE::DIELECTRIC;
-// spheres.emplace_back(vec3 { 0, 1, 0 }, material1, 1.0);
-//
-// material material2 = {};
-// material2.albedo = color{ 0.4, 0.2, 0.1 };
-// material2.type = MATERIAL_TYPE::LAMBERTIAN;
-// spheres.emplace_back(vec3 { -4, 1, 0 }, material2, 1.0);
-//
-// material material3 = {};
-// material3.albedo = color{ 0.7, 0.6, 0.5 };
-// material3.fuzz = 0.0;
-// material3.type = MATERIAL_TYPE::METAL;
-// spheres.emplace_back(vec3 { 4, 1, 0 }, material3, 1.0);
-//
-// material material4 = {};
-// material4.albedo = color{ 0.0, 0.0, 0.0 };
-// material4.emissive = color{ 1.0, 1.0, 1.0 } * 1000.0;
-// material4.fuzz = 0.0;
-// material4.type = MATERIAL_TYPE::EMISSIVE;
-// spheres.emplace_back(vec3 { 2, 1.5, 2 }, material4, 0.5);
-//
-// spheres_count = spheres.size();
-// }
+void scene::random_scene() {
+ material ground_material = { { 0.5, 0.5, 0.5 } };
+ spheres.emplace_back(vec3 { 0, -1000, 0 }, ground_material, 1000);
+
+ int32_t spheres_count = 11;
+ for (int a = -spheres_count; a < spheres_count; a++) {
+ for (int b = -spheres_count; b < spheres_count; b++) {
+ auto choose_mat = randd();
+ point3 center(a + 0.9 * randd(), 0.2, b + 0.9 * randd());
+
+ if ((center - point3{ 4, 0.2, 0 }).length() > 0.9) {
+ material sphere_material = {};
+
+ if (choose_mat < 0.8) {
+ // diffuse
+ sphere_material.albedo = color::random() * color::random();
+ } else if (choose_mat < 0.95) {
+ // metal
+ sphere_material.albedo = color::random(0.5, 1);
+ sphere_material.metalness = randd(0, 0.5);
+ sphere_material.roughness = 0.f;
+ } else {
+ // glass
+ // sphere_material.ior = 1.5;
+ sphere_material.metalness = 1.f;
+ sphere_material.roughness = 0.f;
+ }
+
+ spheres.emplace_back(center, sphere_material, 0.2);
+ }
+ }
+ }
+
+ material material1 = {};
+ // material1.ior = 1.5;
+ spheres.emplace_back(vec3 { 0, 1, 0 }, material1, 1.0);
+
+ material material2 = {};
+ material2.albedo = color{ 0.4, 0.2, 0.1 };
+ spheres.emplace_back(vec3 { -4, 1, 0 }, material2, 1.0);
+
+ material material3 = {};
+ material3.albedo = color{ 0.7, 0.6, 0.5 };
+ material3.roughness = 0.0;
+ spheres.emplace_back(vec3 { 4, 1, 0 }, material3, 1.0);
+
+ material material4 = {};
+ material4.albedo = color{ 0.0, 0.0, 0.0 };
+ material4.emissive = color{ 1.0, 1.0, 1.0 } * 1000.0;
+ material4.roughness = 0.0;
+ spheres.emplace_back(vec3 { 2, 1.5, 2 }, material4, 0.5);
+
+ spheres_count = spheres.size();
+}
scene::scene(camera cam, uint32_t width, uint32_t height)
:meta(cam, width, height){
@@ -74,43 +68,43 @@ scene::scene(camera cam, uint32_t width, uint32_t height)
// auto bistro_interior_filename = std::string("BistroInterior.gltf");
// auto root_node = gltf::load(bistro_interior_path, bistro_interior_filename);
- auto sponza_path = std::filesystem::path("../models/sponza");
- auto sponza_filename = std::string("Sponza.gltf");
- auto root_node = gltf::load(sponza_path, sponza_filename);
+ // auto sponza_path = std::filesystem::path("../models/sponza");
+ // auto sponza_filename = std::string("Sponza.gltf");
+ // auto root_node = gltf::load(sponza_path, sponza_filename);
- std::queue<node> nodes_to_load {};
- nodes_to_load.push(root_node);
+ // std::queue<node> nodes_to_load {};
+ // nodes_to_load.push(root_node);
- while(nodes_to_load.size() != 0) {
- auto &node = nodes_to_load.front();
+ // while(nodes_to_load.size() != 0) {
+ // auto &node = nodes_to_load.front();
- if (node.mesh) {
- auto &mesh = node.mesh.value();
- for (auto& mesh_part: mesh.parts) {
- for (size_t i = 0; i < mesh_part.indices.size(); i += 3) {
- auto i1 = mesh_part.indices[i];
- auto i2 = mesh_part.indices[i + 1];
- auto i3 = mesh_part.indices[i + 2];
+ // if (node.mesh) {
+ // auto &mesh = node.mesh.value();
+ // for (auto& mesh_part: mesh.parts) {
+ // for (size_t i = 0; i < mesh_part.indices.size(); i += 3) {
+ // auto i1 = mesh_part.indices[i];
+ // auto i2 = mesh_part.indices[i + 1];
+ // auto i3 = mesh_part.indices[i + 2];
- auto v1 = vertex { .position = mesh_part.positions[i1] };
- auto v2 = vertex { .position = mesh_part.positions[i2] };
- auto v3 = vertex { .position = mesh_part.positions[i3] };
+ // auto v1 = vertex { .position = mesh_part.positions[i1] };
+ // auto v2 = vertex { .position = mesh_part.positions[i2] };
+ // auto v3 = vertex { .position = mesh_part.positions[i3] };
- triangles.emplace_back(v1, v2, v3);
- }
- }
- }
+ // triangles.emplace_back(v1, v2, v3);
+ // }
+ // }
+ // }
- for (auto &node: node.children) {
- nodes_to_load.push(node);
- }
+ // for (auto &node: node.children) {
+ // nodes_to_load.push(node);
+ // }
- nodes_to_load.pop();
- }
+ // nodes_to_load.pop();
+ // }
- // random_scene();
+ random_scene();
- bvh builder(triangles, packed_nodes);
- // bvh builder(spheres, nodes);
+ // bvh builder(triangles, packed_nodes);
+ bvh builder(spheres, packed_nodes);
// builder.exporter();
}
diff --git a/src/vec3.cpp b/src/vec3.cpp
index d075473..1e4a183 100644
--- a/src/vec3.cpp
+++ b/src/vec3.cpp
@@ -54,15 +54,16 @@ float vec3::operator[](int axis) {
}
float vec3::length() const {
- return std::sqrt((_mm_dp_ps(v, v, 0b10001111))[0]);
+ __m128 t = _mm_dp_ps(v, v, 0b11110001);
+ return std::sqrt(t[0]);
}
float vec3::length_sq() const {
- return (_mm_dp_ps(v, v, 0b10001111))[0];
+ return (_mm_dp_ps(v, v, 0b11110001))[0];
}
float vec3::dot(const vec3& vec) const {
- return (_mm_dp_ps(v, vec.v, 0b10001111))[0];
+ return (_mm_dp_ps(v, vec.v, 0b11110001))[0];
}
vec3 vec3::cross(const vec3& vec) const {