Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions visualization/include/balsa/scene_graph/MeshRenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,29 @@ class UnlitPointsRenderer final : public MeshRenderer {
float size = 3.0f;
};

/// What a PBR surface shows: the lit result or one input of the shading,
/// as in the Khronos glTF Sample Viewer's debug views. Colours are shown
/// as colours; data (normals, factors, coordinates) as raw values, vectors
/// mapped from [-1, 1] to [0, 1].
enum class PbrDebugChannel : uint8_t {
Final,
BaseColor,
Alpha,
Metallic,
Roughness,
Occlusion,
Emissive,
ShadingNormal,
GeometryNormal,
Tangent,
Bitangent,
TextureCoordinates0,
TextureCoordinates1,
};

/// Labels of the PbrDebugChannel values, in order.
[[nodiscard]] auto pbr_debug_channel_names() -> std::span<const std::string_view>;

/// A metallic-roughness (glTF) surface: Material's factors and textures,
/// lit by the scene light plus a small ambient term until image-based
/// lighting exists.
Expand All @@ -308,6 +331,9 @@ class GltfMetallicRoughnessRenderer final : public MeshRenderer {
// Shared with other layers drawing the same material; clones share it
// too. Never null.
std::shared_ptr<Material> material = std::make_shared<Material>();
// Per renderer, not per material: debugging one layer leaves others
// sharing its material alone.
PbrDebugChannel debug_channel = PbrDebugChannel::Final;
};

auto register_builtin_mesh_renderers() -> void;
Expand Down
42 changes: 41 additions & 1 deletion visualization/resources/glsl/gltf_metallic_roughness.frag
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ layout(set = 1, binding = 0) uniform Material {
vec4 u_emissive; // xyz = linear rgb, w = alpha cutoff
vec4 u_factors; // metallic, roughness, normal scale, occlusion strength
vec4 u_texture_coordinate_sets; // of base colour, metallic-roughness, normal, occlusion
vec4 u_options; // x = emissive set, y = alpha mode, z = double sided
vec4 u_options; // x = emissive set, y = alpha mode, z = double sided,
// w = debug channel (PbrDebugChannel)
};

// Empty slots hold white (normal: a flat normal) placeholders.
Expand Down Expand Up @@ -62,6 +63,29 @@ vec3 F_Schlick(vec3 f0, float VdotH) {
return f0 + (vec3(1.0) - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
}

// The swapchain encodes to sRGB; decoding first shows data values as is.
vec3 srgb_to_linear(vec3 c) {
c = clamp(c, 0.0, 1.0);
return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));
}
vec4 show_data(vec3 value) { return vec4(srgb_to_linear(value), 1.0); }
vec4 show_vector(vec3 value) { return show_data(value * 0.5 + 0.5); }

// Debug channels, matching PbrDebugChannel.
const int DEBUG_FINAL = 0;
const int DEBUG_BASE_COLOR = 1;
const int DEBUG_ALPHA = 2;
const int DEBUG_METALLIC = 3;
const int DEBUG_ROUGHNESS = 4;
const int DEBUG_OCCLUSION = 5;
const int DEBUG_EMISSIVE = 6;
const int DEBUG_SHADING_NORMAL = 7;
const int DEBUG_GEOMETRY_NORMAL = 8;
const int DEBUG_TANGENT = 9;
const int DEBUG_BITANGENT = 10;
const int DEBUG_TEXTURE_COORDINATES0 = 11;
const int DEBUG_TEXTURE_COORDINATES1 = 12;

void main() {
// ── Base colour and alpha ──
vec4 base = u_base_color * v_color
Expand Down Expand Up @@ -136,6 +160,22 @@ void main() {
vec3 emissive = u_emissive.rgb
* texture(u_emissive_texture, texture_coordinates(u_options.x)).rgb;

switch (int(u_options.w + 0.5)) {
case DEBUG_BASE_COLOR: outColor = vec4(base.rgb, 1.0); return;
case DEBUG_ALPHA: outColor = show_data(vec3(base.a)); return;
case DEBUG_METALLIC: outColor = show_data(vec3(metallic)); return;
case DEBUG_ROUGHNESS: outColor = show_data(vec3(roughness)); return;
case DEBUG_OCCLUSION: outColor = show_data(vec3(occlusion)); return;
case DEBUG_EMISSIVE: outColor = vec4(emissive, 1.0); return;
case DEBUG_SHADING_NORMAL: outColor = show_vector(n); return;
case DEBUG_GEOMETRY_NORMAL: outColor = show_vector(ng); return;
case DEBUG_TANGENT: outColor = show_vector(t); return;
case DEBUG_BITANGENT: outColor = show_vector(b); return;
case DEBUG_TEXTURE_COORDINATES0: outColor = show_data(vec3(fract(v_texture_coordinates0), 0.0)); return;
case DEBUG_TEXTURE_COORDINATES1: outColor = show_data(vec3(fract(v_texture_coordinates1), 0.0)); return;
default: break;
}

// Linear; an sRGB swapchain encodes it.
outColor = vec4(direct + ambient + emissive, base.a);
}
30 changes: 28 additions & 2 deletions visualization/src/scene_graph/MeshRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,8 @@ auto GltfMetallicRoughnessRenderer::write_material(
// occlusion strength
Vec4f texture_coordinate_sets; // base colour, metallic-roughness,
// normal, occlusion
Vec4f options; // emissive set, alpha mode, double sided
Vec4f options; // emissive set, alpha mode, double sided,
// debug channel
};
const auto &m = *material;
const auto set = [](const std::optional<MaterialTexture> &t) {
Expand All @@ -469,7 +470,7 @@ auto GltfMetallicRoughnessRenderer::write_material(
.texture_coordinate_sets = {set(m.base_color_texture), set(m.metallic_roughness_texture),
set(m.normal_texture), set(m.occlusion_texture)},
.options = {set(m.emissive_texture), static_cast<float>(m.alpha_mode),
m.double_sided ? 1.0f : 0.0f, 0.0f}});
m.double_sided ? 1.0f : 0.0f, static_cast<float>(debug_channel)}});
// In the program's texture slot order.
out.textures = {m.base_color_texture, m.metallic_roughness_texture, m.normal_texture,
m.occlusion_texture, m.emissive_texture};
Expand All @@ -492,9 +493,34 @@ auto GltfMetallicRoughnessRenderer::build_controls(const MeshLayerContext &)
cutoff.visible_when = [&m] { return m.alpha_mode == AlphaMode::Mask; };
layout.add_property(std::move(cutoff));
layout.add_property(bool_property("Double Sided", m.double_sided));
layout.add_property(
{.label = "Debug View",
.tooltip = "Show one input of the shading instead of the lit surface",
.widget = ChoiceInput{
.options =
[] {
const auto names = pbr_debug_channel_names();
return std::vector<std::string>(names.begin(), names.end());
},
.get_index = [this] { return static_cast<int>(debug_channel); },
.set_index =
[this](const int &i) {
if (i >= 0 && static_cast<std::size_t>(i) < pbr_debug_channel_names().size()) {
debug_channel = static_cast<PbrDebugChannel>(i);
}
}}});
return layout;
}

auto pbr_debug_channel_names() -> std::span<const std::string_view> {
static constexpr std::array<std::string_view, 13> names{
"Final", "Base Color", "Alpha", "Metallic", "Roughness",
"Occlusion", "Emissive", "Shading Normal", "Geometry Normal",
"Tangent", "Bitangent", "Texture Coordinates 0", "Texture Coordinates 1"};
static_assert(names.size() == static_cast<std::size_t>(PbrDebugChannel::TextureCoordinates1) + 1);
return names;
}

auto register_builtin_mesh_renderers() -> void {
static const bool once = [] {
register_mesh_renderer({"balsa.phong_surface", "Phong Surface", [] {
Expand Down
Loading
Loading