Skip to content

Commit c964d99

Browse files
favreaujeffamstutz
authored andcommitted
Add Edges as AOV frame channel visualization
1 parent 8255a98 commit c964d99

File tree

9 files changed

+131
-9
lines changed

9 files changed

+131
-9
lines changed

tsd/apps/tools/tsdRender.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,16 @@ static void setupRenderPipeline()
261261
aovPass->setAOVType(g_core->offline.aov.aovType);
262262
aovPass->setDepthRange(
263263
g_core->offline.aov.depthMin, g_core->offline.aov.depthMax);
264+
aovPass->setEdgeThreshold(g_core->offline.aov.edgeThreshold);
265+
aovPass->setEdgeInvert(g_core->offline.aov.edgeInvert);
264266

265267
// Enable necessary frame channels
266268
if (g_core->offline.aov.aovType == tsd::rendering::AOVType::ALBEDO) {
267269
arp->setEnableAlbedo(true);
268270
} else if (g_core->offline.aov.aovType == tsd::rendering::AOVType::NORMAL) {
269271
arp->setEnableNormals(true);
272+
} else if (g_core->offline.aov.aovType == tsd::rendering::AOVType::EDGES) {
273+
arp->setEnableIDs(true);
270274
}
271275
}
272276

tsd/src/tsd/app/Core.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,8 @@ void OfflineRenderSequenceConfig::saveSettings(tsd::core::DataNode &root)
755755
aovRoot["aovType"] = static_cast<int>(aov.aovType);
756756
aovRoot["depthMin"] = aov.depthMin;
757757
aovRoot["depthMax"] = aov.depthMax;
758+
aovRoot["edgeThreshold"] = aov.edgeThreshold;
759+
aovRoot["edgeInvert"] = aov.edgeInvert;
758760
}
759761

760762
void OfflineRenderSequenceConfig::loadSettings(tsd::core::DataNode &root)
@@ -798,6 +800,8 @@ void OfflineRenderSequenceConfig::loadSettings(tsd::core::DataNode &root)
798800
aov.aovType = static_cast<tsd::rendering::AOVType>(aovTypeInt);
799801
aovRoot["depthMin"].getValue(ANARI_FLOAT32, &aov.depthMin);
800802
aovRoot["depthMax"].getValue(ANARI_FLOAT32, &aov.depthMax);
803+
aovRoot["edgeThreshold"].getValue(ANARI_FLOAT32, &aov.edgeThreshold);
804+
aovRoot["edgeInvert"].getValue(ANARI_BOOL, &aov.edgeInvert);
801805
}
802806

803807
} // namespace tsd::app

tsd/src/tsd/app/Core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ struct OfflineRenderSequenceConfig
202202
tsd::rendering::AOVType aovType{tsd::rendering::AOVType::NONE};
203203
float depthMin{0.f};
204204
float depthMax{1.f};
205+
float edgeThreshold{0.5f};
206+
bool edgeInvert{false};
205207
} aov;
206208

207209
void saveSettings(tsd::core::DataNode &root);

tsd/src/tsd/app/renderAnimationSequence.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,16 @@ static OfflineRenderRig setupRig(tsd::app::Core &core)
102102
rig.pipeline->emplace_back<tsd::rendering::VisualizeAOVPass>();
103103
aovPass->setAOVType(config.aov.aovType);
104104
aovPass->setDepthRange(config.aov.depthMin, config.aov.depthMax);
105+
aovPass->setEdgeThreshold(config.aov.edgeThreshold);
106+
aovPass->setEdgeInvert(config.aov.edgeInvert);
105107

106108
// Enable necessary frame channels
107109
if (config.aov.aovType == tsd::rendering::AOVType::ALBEDO) {
108110
rig.anariPass->setEnableAlbedo(true);
109111
} else if (config.aov.aovType == tsd::rendering::AOVType::NORMAL) {
110112
rig.anariPass->setEnableNormals(true);
113+
} else if (config.aov.aovType == tsd::rendering::AOVType::EDGES) {
114+
rig.anariPass->setEnableIDs(true);
111115
}
112116
}
113117

tsd/src/tsd/rendering/pipeline/passes/VisualizeAOVPass.cpp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ namespace tsd::rendering {
1111

1212
// Thrust kernels /////////////////////////////////////////////////////////////
1313

14-
void computeDepthImage(RenderBuffers &b, float minDepth, float maxDepth, tsd::math::uint2 size)
14+
void computeDepthImage(
15+
RenderBuffers &b, float minDepth, float maxDepth, tsd::math::uint2 size)
1516
{
1617
detail::parallel_for(
1718
b.stream, 0u, uint32_t(size.x * size.y), [=] DEVICE_FCN(uint32_t i) {
1819
const float depth = b.depth[i];
1920
const float range = maxDepth - minDepth;
20-
const float v = range > 0.f ? std::clamp((depth - minDepth) / range, 0.f, 1.f) : 0.f;
21+
const float v = range > 0.f
22+
? std::clamp((depth - minDepth) / range, 0.f, 1.f)
23+
: 0.f;
2124
b.color[i] = helium::cvt_color_to_uint32({tsd::math::float3(v), 1.f});
2225
});
2326
}
@@ -42,6 +45,61 @@ void computeNormalImage(RenderBuffers &b, tsd::math::uint2 size)
4245
});
4346
}
4447

48+
void computeEdgesImage(
49+
RenderBuffers &b, float threshold, bool invert, tsd::math::uint2 size)
50+
{
51+
detail::parallel_for(
52+
b.stream, 0u, uint32_t(size.x * size.y), [=] DEVICE_FCN(uint32_t i) {
53+
uint32_t y = i / size.x;
54+
uint32_t x = i % size.x;
55+
56+
// Get center pixel object ID
57+
uint32_t centerID = b.objectId ? b.objectId[i] : ~0u;
58+
59+
// Only check for edges if center pixel is not background
60+
if (centerID == ~0u) {
61+
b.color[i] =
62+
helium::cvt_color_to_uint32({tsd::math::float3(0.f), 1.f});
63+
return;
64+
}
65+
66+
// Check if any neighbor has a different object ID (including
67+
// background)
68+
bool isEdge = false;
69+
70+
for (int dy = -1; dy <= 1 && !isEdge; ++dy) {
71+
for (int dx = -1; dx <= 1 && !isEdge; ++dx) {
72+
if (dx == 0 && dy == 0)
73+
continue; // Skip center pixel
74+
75+
int nx = static_cast<int>(x) + dx;
76+
int ny = static_cast<int>(y) + dy;
77+
78+
if (nx >= 0 && nx < static_cast<int>(size.x) && ny >= 0
79+
&& ny < static_cast<int>(size.y)) {
80+
size_t neighborIdx =
81+
static_cast<size_t>(nx) + static_cast<size_t>(ny) * size.x;
82+
uint32_t neighborID = b.objectId ? b.objectId[neighborIdx] : ~0u;
83+
84+
// Edge if neighbor has different ID (including background)
85+
if (centerID != neighborID) {
86+
isEdge = true;
87+
}
88+
}
89+
}
90+
}
91+
92+
float edgeValue = isEdge ? 1.f : 0.f;
93+
94+
if (invert) {
95+
edgeValue = 1.f - edgeValue;
96+
}
97+
98+
b.color[i] =
99+
helium::cvt_color_to_uint32({tsd::math::float3(edgeValue), 1.f});
100+
});
101+
}
102+
45103
// VisualizeAOVPass definitions ///////////////////////////////////////////////
46104

47105
VisualizeAOVPass::VisualizeAOVPass() = default;
@@ -60,6 +118,16 @@ void VisualizeAOVPass::setDepthRange(float minDepth, float maxDepth)
60118
m_maxDepth = maxDepth;
61119
}
62120

121+
void VisualizeAOVPass::setEdgeThreshold(float threshold)
122+
{
123+
m_edgeThreshold = threshold;
124+
}
125+
126+
void VisualizeAOVPass::setEdgeInvert(bool invert)
127+
{
128+
m_edgeInvert = invert;
129+
}
130+
63131
void VisualizeAOVPass::render(RenderBuffers &b, int stageId)
64132
{
65133
if (stageId == 0 || m_aovType == AOVType::NONE)
@@ -77,6 +145,9 @@ void VisualizeAOVPass::render(RenderBuffers &b, int stageId)
77145
case AOVType::NORMAL:
78146
computeNormalImage(b, size);
79147
break;
148+
case AOVType::EDGES:
149+
computeEdgesImage(b, m_edgeThreshold, m_edgeInvert, size);
150+
break;
80151
default:
81152
break;
82153
}

tsd/src/tsd/rendering/pipeline/passes/VisualizeAOVPass.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ enum class AOVType
1212
NONE,
1313
DEPTH,
1414
ALBEDO,
15-
NORMAL
15+
NORMAL,
16+
EDGES
1617
};
1718

1819
struct VisualizeAOVPass : public RenderPass
@@ -22,13 +23,17 @@ struct VisualizeAOVPass : public RenderPass
2223

2324
void setAOVType(AOVType type);
2425
void setDepthRange(float minDepth, float maxDepth);
26+
void setEdgeThreshold(float threshold);
27+
void setEdgeInvert(bool invert);
2528

2629
private:
2730
void render(RenderBuffers &b, int stageId) override;
2831

2932
AOVType m_aovType{AOVType::NONE};
3033
float m_minDepth{0.f};
3134
float m_maxDepth{1.f};
35+
float m_edgeThreshold{0.5f};
36+
bool m_edgeInvert{false};
3237
};
3338

3439
} // namespace tsd::rendering

tsd/src/tsd/ui/imgui/modals/AppSettingsDialog.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ void AppSettingsDialog::buildUI_offlineRenderSettings()
271271
ImGui::Separator();
272272
ImGui::Text("==== AOV Visualization ====");
273273

274-
const char *aovItems[] = {"none", "depth", "albedo", "normal"};
274+
const char *aovItems[] = {"none", "depth", "albedo", "normal", "edges"};
275275
int aovIdx = static_cast<int>(core->offline.aov.aovType);
276276
if (ImGui::Combo("AOV type", &aovIdx, aovItems, IM_ARRAYSIZE(aovItems))) {
277277
core->offline.aov.aovType = static_cast<tsd::rendering::AOVType>(aovIdx);
@@ -291,6 +291,13 @@ void AppSettingsDialog::buildUI_offlineRenderSettings()
291291
1e20f);
292292
ImGui::EndDisabled();
293293

294+
ImGui::BeginDisabled(
295+
core->offline.aov.aovType != tsd::rendering::AOVType::EDGES);
296+
ImGui::DragFloat(
297+
"edge threshold", &core->offline.aov.edgeThreshold, 0.01f, 0.f, 1.f);
298+
ImGui::Checkbox("invert edges", &core->offline.aov.edgeInvert);
299+
ImGui::EndDisabled();
300+
294301
ImGui::Unindent(tsd::ui::INDENT_AMOUNT);
295302
}
296303

tsd/src/tsd/ui/imgui/windows/Viewport.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,11 @@ void Viewport::buildUI()
8282
bool didPick = ui_picking(); // Needs to happen before ui_menubar
8383
ui_menubar();
8484

85-
if (m_anariPass && !didPick)
86-
m_anariPass->setEnableIDs(appCore()->getFirstSelected().valid());
85+
if (m_anariPass && !didPick) {
86+
bool needIDs = appCore()->getFirstSelected().valid()
87+
|| m_visualizeAOV == tsd::rendering::AOVType::EDGES;
88+
m_anariPass->setEnableIDs(needIDs);
89+
}
8790

8891
if (m_rIdx && (m_rIdx->isFlat() != appCore()->anari.useFlatRenderIndex())) {
8992
tsd::core::logWarning("instancing setting changed: resetting viewport");
@@ -270,6 +273,8 @@ void Viewport::saveSettings(tsd::core::DataNode &root)
270273
root["visualizeAOV"] = static_cast<int>(m_visualizeAOV);
271274
root["depthVisualMinimum"] = m_depthVisualMinimum;
272275
root["depthVisualMaximum"] = m_depthVisualMaximum;
276+
root["edgeThreshold"] = m_edgeThreshold;
277+
root["edgeInvert"] = m_edgeInvert;
273278
root["fov"] = m_fov;
274279
root["resolutionScale"] = m_resolutionScale;
275280
root["showAxes"] = m_showAxes;
@@ -326,6 +331,8 @@ void Viewport::loadSettings(tsd::core::DataNode &root)
326331
m_visualizeAOV = static_cast<tsd::rendering::AOVType>(aovType);
327332
root["depthVisualMinimum"].getValue(ANARI_FLOAT32, &m_depthVisualMinimum);
328333
root["depthVisualMaximum"].getValue(ANARI_FLOAT32, &m_depthVisualMaximum);
334+
root["edgeThreshold"].getValue(ANARI_FLOAT32, &m_edgeThreshold);
335+
root["edgeInvert"].getValue(ANARI_BOOL, &m_edgeInvert);
329336
root["fov"].getValue(ANARI_FLOAT32, &m_fov);
330337
root["resolutionScale"].getValue(ANARI_FLOAT32, &m_resolutionScale);
331338
root["showAxes"].getValue(ANARI_BOOL, &m_showAxes);
@@ -521,6 +528,8 @@ void Viewport::setupRenderPipeline()
521528
m_visualizeAOVPass =
522529
m_pipeline.emplace_back<tsd::rendering::VisualizeAOVPass>();
523530
m_visualizeAOVPass->setEnabled(false);
531+
m_visualizeAOVPass->setEdgeThreshold(m_edgeThreshold);
532+
m_visualizeAOVPass->setEdgeInvert(m_edgeInvert);
524533

525534
m_outlinePass = m_pipeline.emplace_back<tsd::rendering::OutlineRenderPass>();
526535

@@ -1120,7 +1129,8 @@ void Viewport::ui_menubar()
11201129

11211130
ImGui::Separator();
11221131

1123-
const char *aovItems[] = {"default", "depth", "albedo", "normal"};
1132+
const char *aovItems[] = {
1133+
"default", "depth", "albedo", "normal", "edges"};
11241134
if (int aov = int(m_visualizeAOV); ImGui::Combo(
11251135
"visualize AOV", &aov, aovItems, IM_ARRAYSIZE(aovItems))) {
11261136
if (aov != int(m_visualizeAOV)) {
@@ -1130,6 +1140,8 @@ void Viewport::ui_menubar()
11301140
m_visualizeAOV == tsd::rendering::AOVType::ALBEDO);
11311141
m_anariPass->setEnableNormals(
11321142
m_visualizeAOV == tsd::rendering::AOVType::NORMAL);
1143+
m_anariPass->setEnableIDs(
1144+
m_visualizeAOV == tsd::rendering::AOVType::EDGES);
11331145
}
11341146
}
11351147

@@ -1150,6 +1162,17 @@ void Viewport::ui_menubar()
11501162
m_depthVisualMinimum, m_depthVisualMaximum);
11511163
ImGui::EndDisabled();
11521164

1165+
ImGui::BeginDisabled(m_visualizeAOV != tsd::rendering::AOVType::EDGES);
1166+
bool edgeSettingsChanged = false;
1167+
edgeSettingsChanged |=
1168+
ImGui::DragFloat("edge threshold", &m_edgeThreshold, 0.01f, 0.f, 1.f);
1169+
edgeSettingsChanged |= ImGui::Checkbox("invert edges", &m_edgeInvert);
1170+
if (edgeSettingsChanged) {
1171+
m_visualizeAOVPass->setEdgeThreshold(m_edgeThreshold);
1172+
m_visualizeAOVPass->setEdgeInvert(m_edgeInvert);
1173+
}
1174+
ImGui::EndDisabled();
1175+
11531176
ImGui::Separator();
11541177

11551178
ImGui::BeginDisabled(m_showOnlySelected);

tsd/src/tsd/ui/imgui/windows/Viewport.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
// tsd_rendering
1616
#include "tsd/rendering/index/RenderIndex.hpp"
1717
#include "tsd/rendering/pipeline/RenderPipeline.h"
18-
#include "tsd/rendering/view/Manipulator.hpp"
1918
#include "tsd/rendering/view/CameraUpdateDelegate.hpp"
19+
#include "tsd/rendering/view/Manipulator.hpp"
2020

2121
// ImGuizmo
2222
#include <ImGuizmo.h>
@@ -27,8 +27,8 @@
2727
#include <future>
2828
#include <limits>
2929
#include <memory>
30-
#include <vector>
3130
#include <string>
31+
#include <vector>
3232

3333
namespace tsd::ui::imgui {
3434

@@ -109,6 +109,8 @@ struct Viewport : public Window
109109
bool m_showAxes{true};
110110
float m_depthVisualMinimum{0.f};
111111
float m_depthVisualMaximum{1.f};
112+
float m_edgeThreshold{0.5f};
113+
bool m_edgeInvert{false};
112114

113115
float m_fov{40.f};
114116

0 commit comments

Comments
 (0)