Skip to content

Commit 2bd6862

Browse files
authored
Merge pull request #3 from MrMagnifico/pixel-similarity-heuristics
Pixel similarity heuristics
2 parents 14e179c + fa41e99 commit 2bd6862

File tree

15 files changed

+277
-101
lines changed

15 files changed

+277
-101
lines changed

.vscode/settings.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,5 @@
8888
"bitset": "cpp",
8989
"regex": "cpp",
9090
"typeindex": "cpp"
91-
},
92-
"cmake.buildEnvironment": {
93-
"embree_DIR": "C:/embree-4.3.1.x64.windows/lib/cmake/embree-4.3.1/",
94-
"TBB_DIR": "C:/Program Files (x86)/Intel/oneAPI/tbb/2021.12/lib/cmake/tbb/"
9591
}
9692
}

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ target_sources(CGFinProjLib
44

55
"${CMAKE_CURRENT_LIST_DIR}/ray_tracing/embree_interface.cpp"
66

7+
"${CMAKE_CURRENT_LIST_DIR}/rendering/neighbour_selection.cpp"
78
"${CMAKE_CURRENT_LIST_DIR}/rendering/render_utils.cpp"
89
"${CMAKE_CURRENT_LIST_DIR}/rendering/render.cpp"
910
"${CMAKE_CURRENT_LIST_DIR}/rendering/reservoir.cpp"

src/main.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ int main(int argc, char** argv) {
5959
camera.setCamera(config.cameras[0].lookAt, glm::radians(config.cameras[0].rotation), config.cameras[0].distanceFromLookAt);
6060

6161
SceneType sceneType = SceneType::CornellNightClub;
62-
std::optional<Ray> optDebugRay;
62+
std::optional<RayHit> optDebugRayHit;
6363
Scene scene = loadScenePrebuilt(sceneType, config.dataPath);
6464
EmbreeInterface embreeInterface(scene);
6565
std::shared_ptr<ReservoirGrid> previousFrameGrid;
@@ -71,16 +71,17 @@ int main(int argc, char** argv) {
7171
ViewMode viewMode = ViewMode::Rasterization;
7272
int selectedLightIdx = scene.lights.empty() ? -1 : 0;
7373

74-
UiManager uiManager(embreeInterface, camera, config, optDebugRay, previousFrameGrid, scene, sceneType, screen, viewMode, window,
74+
UiManager uiManager(embreeInterface, camera, config, optDebugRayHit, previousFrameGrid, scene, sceneType, screen, viewMode, window,
7575
selectedLightIdx);
7676

7777
window.registerKeyCallback([&](int key, int /* scancode */, int action, int /* mods */) {
7878
if (action == GLFW_PRESS) {
7979
switch (key) {
8080
// Shoot a ray. Produce a ray from camera to the far plane.
8181
case GLFW_KEY_R: {
82-
const auto tmp = window.getNormalizedCursorPos();
83-
optDebugRay = camera.generateRay(tmp * 2.0f - 1.0f);
82+
optDebugRayHit.emplace();
83+
const auto tmp = window.getNormalizedCursorPos();
84+
optDebugRayHit.value().ray = camera.generateRay(tmp * 2.0f - 1.0f);
8485
break;
8586
}
8687
case GLFW_KEY_A: {
@@ -131,13 +132,13 @@ int main(int argc, char** argv) {
131132
} else {
132133
drawSceneOpenGL(scene);
133134
}
134-
if (optDebugRay) {
135+
if (optDebugRayHit) {
135136
// Call getFinalColor for the debug ray. Ignore the result but tell the function that it should
136137
// draw the rays instead.
137138
enableDebugDraw = true;
138139
glDisable(GL_LIGHTING);
139140
glDepthFunc(GL_LEQUAL);
140-
(void) genCanonicalSamples(scene, embreeInterface, config.features, *optDebugRay);
141+
embreeInterface.closestHit(optDebugRayHit.value().ray, optDebugRayHit.value().hit);
141142
enableDebugDraw = false;
142143
}
143144
glPopAttrib();

src/ray_tracing/embree_interface.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ bool EmbreeInterface::closestHit(Ray& ray, HitInfo& hitInfo) const {
6666
RTCRayHit rayhit = constructEmbreeRay(ray);
6767
rtcIntersect1(m_scene, &rayhit);
6868

69-
// No intersection: return false
70-
if (rayhit.hit.geomID == RTC_INVALID_GEOMETRY_ID) { return false; };
69+
// No intersection: draw no hit debug ray and return false
70+
if (rayhit.hit.geomID == RTC_INVALID_GEOMETRY_ID) {
71+
drawRay(ray, CAMERA_RAY_NO_HIT_COLOR);
72+
return false;
73+
};
7174

7275
// Intersection: update hit info and user ray
7376
rtcInterpolate0(rtcGetGeometry(m_scene, rayhit.hit.geomID), rayhit.hit.primID, rayhit.hit.u, rayhit.hit.v,
@@ -77,9 +80,11 @@ bool EmbreeInterface::closestHit(Ray& ray, HitInfo& hitInfo) const {
7780
rtcInterpolate0(rtcGetGeometry(m_scene, rayhit.hit.geomID), rayhit.hit.primID, rayhit.hit.u, rayhit.hit.v,
7881
RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 1, glm::value_ptr(hitInfo.texCoord), 2);
7982
hitInfo.material = m_meshToMaterial.at(rayhit.hit.geomID);
83+
hitInfo.geometryId = rayhit.hit.geomID;
8084
ray.t = rayhit.ray.tfar;
8185

82-
// Debug draw normal and return true
86+
// Debug draw ray and normal, then return true
87+
drawRay(ray, CAMERA_RAY_HIT_COLOR);
8388
drawRay({ray.origin + (ray.t * ray.direction), hitInfo.normal, 1.0f}, hitInfo.material.kd);
8489
return true;
8590
}

src/ray_tracing/embree_interface.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class EmbreeInterface {
1919
bool closestHit(Ray& ray, HitInfo& hitInfo) const;
2020

2121
private:
22+
static constexpr glm::vec3 CAMERA_RAY_HIT_COLOR = {0.0f, 1.0f, 0.0f};
23+
static constexpr glm::vec3 CAMERA_RAY_NO_HIT_COLOR = {1.0f, 0.0f, 0.0f};
24+
2225
void initDevice();
2326
void initScene(const Scene& scene);
2427
void populateVertexDataBuffers(glm::vec3* positionBuffer, glm::vec3* normalBuffer, glm::vec2* texCoordBuffer,

src/rendering/neighbour_selection.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include "neighbour_selection.h"
2+
3+
#include <algorithm>
4+
#include <random>
5+
6+
7+
bool areSimilar(const RayHit& lhs, const RayHit& rhs, const Features& features) {
8+
// Same geometries
9+
if (features.neighbourSameGeometry && lhs.hit.geometryId != rhs.hit.geometryId) { return false; }
10+
11+
// Depth difference
12+
float depthFracDiff = std::abs(1.0f - (lhs.ray.t / rhs.ray.t));
13+
if (depthFracDiff > features.neighbourMaxDepthDifferenceFraction) { return false; }
14+
15+
// Normal angle difference
16+
float maxDiffCos = std::cos(features.neighbourMaxNormalAngleDifferenceRadians);
17+
float normalsDotProd = glm::dot(lhs.hit.normal, rhs.hit.normal);
18+
if (normalsDotProd < features.neighbourMaxNormalAngleDifferenceRadians) { return false; }
19+
20+
// We passed the similarity test!
21+
return true;
22+
}
23+
24+
std::vector<glm::ivec2> indicesRandom(int32_t x, int32_t y,
25+
const glm::ivec2& windowResolution, const Features& features) {
26+
// Define the range of possible values based on window dimensions and resample radius
27+
std::random_device rd;
28+
std::mt19937 gen(rd());
29+
int32_t resampleRadiusCast = static_cast<int32_t>(features.spatialResampleRadius);
30+
std::uniform_int_distribution<> distrX(std::max(0, x - resampleRadiusCast),
31+
std::min(windowResolution.x - 1, x + resampleRadiusCast));
32+
std::uniform_int_distribution<> distrY(std::max(0, y - resampleRadiusCast),
33+
std::min(windowResolution.y - 1, y + resampleRadiusCast));
34+
35+
// Create indices
36+
std::vector<glm::ivec2> indices;
37+
indices.reserve(features.numNeighboursToSample + 1); // Assign enough space for current pixel AND neighbours
38+
indices.push_back(glm::ivec2(x, y)); // Always include the pixel itself
39+
for (uint32_t candidateIdx = 0U; candidateIdx < features.numNeighboursToSample; candidateIdx++) {
40+
indices.push_back(glm::ivec2(distrX(gen), distrY(gen)));
41+
}
42+
return indices;
43+
}
44+
45+
std::vector<glm::ivec2> indicesSimilarity(int32_t x, int32_t y,
46+
const PrimaryHitGrid& primaryHits, const glm::ivec2& windowResolution, const Features& features) {
47+
// In extreme cases, all neighbours are similar or dissimilar. We reserve enough memory for either
48+
std::vector<glm::ivec2> similarIndices, dissimilarIndices;
49+
similarIndices.reserve(features.spatialResampleRadius * features.spatialResampleRadius * 4U);
50+
dissimilarIndices.reserve(features.spatialResampleRadius * features.spatialResampleRadius * 4U);
51+
52+
// Ensure that our traversal does not exceed screen bounds
53+
int32_t resampleRadiusCast = static_cast<int32_t>(features.spatialResampleRadius);
54+
glm::ivec2 minExtents(std::max(0, x - resampleRadiusCast), std::max(0, y - resampleRadiusCast));
55+
glm::ivec2 maxExtents(std::min(windowResolution.x - 1, x + resampleRadiusCast), std::min(windowResolution.y - 1, y + resampleRadiusCast));
56+
57+
// Categorise all pixels in neighbourhood
58+
const RayHit& canonical = primaryHits[y][x];
59+
for (int32_t neighbourY = minExtents.y; neighbourY <= maxExtents.y; neighbourY++) {
60+
for (int32_t neighbourX = minExtents.x; neighbourX <= maxExtents.x; neighbourX++) {
61+
// Skip canonical pixel
62+
if (neighbourY == y && neighbourX == x) { continue; }
63+
64+
// Categorise neighbour
65+
glm::ivec2 neighbourCoords(neighbourX, neighbourY);
66+
const RayHit& neighbour = primaryHits[neighbourY][neighbourX];
67+
if (areSimilar(canonical, neighbour, features)) { similarIndices.push_back(neighbourCoords); }
68+
else { dissimilarIndices.push_back(neighbourCoords); }
69+
}
70+
}
71+
72+
// Create indices
73+
std::vector<glm::ivec2> indices;
74+
indices.reserve(features.numNeighboursToSample + 1); // Assign enough space for current pixel AND neighbours
75+
indices.push_back(glm::ivec2(x, y)); // Always include the pixel itself
76+
std::random_device rd;
77+
std::mt19937 rng(rd());
78+
switch (features.neighbourSelectionStrategy) {
79+
case NeighbourSelectionStrategy::Similar: {
80+
if (similarIndices.size() < features.numNeighboursToSample) { // Not enough similar neighbours
81+
indices.insert(indices.end(), similarIndices.begin(), similarIndices.end()); // Place however many we can
82+
std::sample(dissimilarIndices.begin(), dissimilarIndices.end(), std::back_inserter(indices), // Make up for deficit from dissimilar neighbours
83+
features.numNeighboursToSample - similarIndices.size(), rng);
84+
} else { std::sample(similarIndices.begin(), similarIndices.end(), std::back_inserter(indices), features.numNeighboursToSample, rng); }
85+
} break;
86+
case NeighbourSelectionStrategy::Dissimilar: {
87+
if (dissimilarIndices.size() < features.numNeighboursToSample) { // Not enough dissimilar neighbours
88+
indices.insert(indices.end(), dissimilarIndices.begin(), dissimilarIndices.end()); // Place however many we can
89+
std::sample(similarIndices.begin(), similarIndices.end(), std::back_inserter(indices), // Make up for deficit from similar neighbours
90+
features.numNeighboursToSample - similarIndices.size(), rng);
91+
} else { std::sample(dissimilarIndices.begin(), dissimilarIndices.end(), std::back_inserter(indices), features.numNeighboursToSample, rng); }
92+
} break;
93+
case NeighbourSelectionStrategy::EqualSimilarDissimilar: {
94+
// Ensure there are sufficient quantities of similars and dissimilars to satisfy halfway split and make up for difference if that is not possible
95+
uint32_t similarsSampled = std::min((features.numNeighboursToSample / 2U) + 1U, static_cast<uint32_t>(similarIndices.size()));
96+
uint32_t desiredDissimilars = features.numNeighboursToSample - similarsSampled;
97+
if (desiredDissimilars > dissimilarIndices.size()) { similarsSampled += features.numNeighboursToSample - dissimilarIndices.size() - similarsSampled; }
98+
99+
std::sample(similarIndices.begin(), similarIndices.end(), std::back_inserter(indices), similarsSampled, rng);
100+
std::sample(dissimilarIndices.begin(), dissimilarIndices.end(), std::back_inserter(indices), features.numNeighboursToSample - similarsSampled, rng);
101+
} break;
102+
default: { throw std::runtime_error("indicesSimilarity called with unsupported neighbour selection strategy"); }
103+
}
104+
return indices;
105+
}
106+
107+
ResampleIndicesGrid generateResampleIndicesGrid(const PrimaryHitGrid& primaryHits,
108+
const glm::ivec2& windowResolution, const Features& features) {
109+
ResampleIndicesGrid resampleIndices(windowResolution.y, std::vector<std::vector<glm::ivec2>>(windowResolution.x));
110+
bool useRandom = features.neighbourSelectionStrategy == NeighbourSelectionStrategy::Random;
111+
#ifdef NDEBUG
112+
#pragma omp parallel for schedule(guided)
113+
#endif
114+
for (int y = 0; y < windowResolution.y; y++) {
115+
for (int x = 0; x != windowResolution.x; x++) {
116+
resampleIndices[y][x] = useRandom ?
117+
indicesRandom(x, y, windowResolution, features) :
118+
indicesSimilarity(x, y, primaryHits, windowResolution, features);
119+
}
120+
}
121+
return resampleIndices;
122+
}

src/rendering/neighbour_selection.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
#ifndef _NEIGHBOUR_SELECTION_H_
3+
#define _NEIGHBOUR_SELECTION_H_
4+
5+
#include <framework/disable_all_warnings.h>
6+
DISABLE_WARNINGS_PUSH()
7+
#include <glm/vec2.hpp>
8+
DISABLE_WARNINGS_POP()
9+
10+
#include <rendering/render_utils.h>
11+
#include <utils/common.h>
12+
13+
#include <vector>
14+
15+
16+
using ResampleIndicesGrid = std::vector<std::vector<std::vector<glm::ivec2>>>;
17+
18+
bool areSimilar(const RayHit& lhs, const RayHit& rhs, const Features& features);
19+
std::vector<glm::ivec2> indicesRandom(int32_t x, int32_t y,
20+
const glm::ivec2& windowResolution, const Features& features);
21+
std::vector<glm::ivec2> indicesSimilarity(int32_t x, int32_t y,
22+
const PrimaryHitGrid& primaryHits, const glm::ivec2& windowResolution, const Features& features);
23+
ResampleIndicesGrid generateResampleIndicesGrid(const PrimaryHitGrid& primaryHits,
24+
const glm::ivec2& windowResolution, const Features& features);
25+
26+
#endif // _NEIGHBOUR_SELECTION_H_

src/rendering/render.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
#include "render.h"
22

3+
#include <framework/disable_all_warnings.h>
4+
DISABLE_WARNINGS_PUSH()
5+
#include <cereal/archives/json.hpp>
6+
DISABLE_WARNINGS_POP()
7+
38
#ifdef NDEBUG
49
#include <omp.h>
510
#endif
611

712
#include <framework/trackball.h>
813

9-
#include <cereal/archives/json.hpp>
10-
1114
#include <post_processing/tone_mapping.h>
15+
#include <rendering/neighbour_selection.h>
1216
#include <rendering/render_utils.h>
1317
#include <rendering/screen.h>
1418
#include <scene/light.h>
@@ -26,7 +30,8 @@ ReservoirGrid renderReSTIR(std::shared_ptr<ReservoirGrid> previousFrameGrid,
2630
const EmbreeInterface& embreeInterface, Screen& screen,
2731
const Features& features) {
2832
std::cout << "===== Rendering with ReSTIR =====" << std::endl;
29-
ReservoirGrid reservoirGrid = genInitialSamples(scene, camera, embreeInterface, screen, features);
33+
PrimaryHitGrid primaryHits = genPrimaryRayHits(scene, camera, embreeInterface, screen, features);
34+
ReservoirGrid reservoirGrid = genInitialSamples(primaryHits, scene, embreeInterface, features, screen.resolution());
3035
if (features.temporalReuse && previousFrameGrid) { temporalReuse(reservoirGrid, *previousFrameGrid.get(), embreeInterface, screen, features); }
3136
if (features.spatialReuse) { spatialReuse(reservoirGrid, embreeInterface, screen, features); }
3237

@@ -57,15 +62,16 @@ ReservoirGrid renderReSTIR(std::shared_ptr<ReservoirGrid> previousFrameGrid,
5762
}
5863

5964
void renderRMIS(const Scene& scene, const Trackball& camera, const EmbreeInterface& embreeInterface, Screen& screen, const Features& features) {
65+
std::cout << "===== Rendering with R-MIS =====" << std::endl;
6066
glm::ivec2 windowResolution = screen.resolution();
6167
const uint32_t totalDistributions = features.numNeighboursToSample + 1U; // Original pixel and neighbours
62-
ResampleIndicesGrid resampleIndices = generateResampleIndicesGrid(windowResolution, features);
68+
PrimaryHitGrid primaryHits = genPrimaryRayHits(scene, camera, embreeInterface, screen, features);
69+
ResampleIndicesGrid resampleIndices = generateResampleIndicesGrid(primaryHits, windowResolution, features);
6370
PixelGrid finalPixelColors(windowResolution.y, std::vector<glm::vec3>(windowResolution.x, glm::vec3(0.0f)));
64-
std::cout << "===== Rendering with R-MIS =====" << std::endl;
6571

6672
for (uint32_t iteration = 0U; iteration < features.maxIterationsMIS; iteration++) {
6773
std::cout << "= Iteration " << iteration + 1 << std::endl;
68-
ReservoirGrid reservoirGrid = genInitialSamples(scene, camera, embreeInterface, screen, features);
74+
ReservoirGrid reservoirGrid = genInitialSamples(primaryHits, scene, embreeInterface, features, windowResolution);
6975
progressbar progressBarPixels(windowResolution.y);
7076
#ifdef NDEBUG
7177
#pragma omp parallel for schedule(guided)
@@ -114,8 +120,10 @@ void renderRMIS(const Scene& scene, const Trackball& camera, const EmbreeInterfa
114120

115121
void renderROMIS(const Scene& scene, const Trackball& camera, const EmbreeInterface& embreeInterface, Screen& screen, const Features& features) {
116122
// Used in both direct and progressive estimators
123+
std::cout << "===== Rendering with R-OMIS =====" << std::endl;
117124
glm::ivec2 windowResolution = screen.resolution();
118-
ResampleIndicesGrid resampleIndices = generateResampleIndicesGrid(windowResolution, features);
125+
PrimaryHitGrid primaryHits = genPrimaryRayHits(scene, camera, embreeInterface, screen, features);
126+
ResampleIndicesGrid resampleIndices = generateResampleIndicesGrid(primaryHits, windowResolution, features);
119127
const uint32_t totalDistributions = features.numNeighboursToSample + 1U; // Original pixel and neighbours
120128
MatrixGrid techniqueMatrices(windowResolution.y, std::vector<Eigen::MatrixXf>(windowResolution.x, Eigen::MatrixXf::Zero(totalDistributions, totalDistributions)));
121129
VectorGrid contributionVectorsRed(windowResolution.y, std::vector<Eigen::VectorXf>(windowResolution.x, Eigen::VectorXf::Zero(totalDistributions)));
@@ -130,10 +138,9 @@ void renderROMIS(const Scene& scene, const Trackball& camera, const EmbreeInterf
130138
const int32_t totalSamples = totalDistributions * features.numSamplesInReservoir; // All pixels generate the same number of samples and sample the same number of neighbours
131139
const int32_t fractionOfTotalSamples = features.numSamplesInReservoir / totalDistributions; // Redundant? Yes. Helps with code clarity? Also yes
132140

133-
std::cout << "===== Rendering with R-OMIS =====" << std::endl;
134141
for (uint32_t iteration = 0U; iteration < features.maxIterationsMIS; iteration++) {
135142
std::cout << "= Iteration " << iteration + 1 << std::endl;
136-
ReservoirGrid reservoirGrid = genInitialSamples(scene, camera, embreeInterface, screen, features);
143+
ReservoirGrid reservoirGrid = genInitialSamples(primaryHits, scene, embreeInterface, features, windowResolution);
137144
progressbar progressbarPixels(static_cast<int32_t>(windowResolution.y));
138145
#ifdef NDEBUG
139146
#pragma omp parallel for schedule(guided)

0 commit comments

Comments
 (0)