mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 10:06:47 +00:00
ifcviewer: edge enhancement post-pass
Adds a per-frame depth-laplacian pass that darkens pixels at sharp depth discontinuities — silhouettes, overlapping-surface boundaries, section-cut edges. Catches the wall-against-wall and slab-against- ceiling cases that the cavity hint in the lighting shader misses. Implementation: - New edge_depth_fbo_ / edge_depth_tex_ — single-sample D24S8 the size of the window. After the main draw, blit the default FB depth into it (handles MSAA resolve in the same call). - Fullscreen triangle generated from gl_VertexID, samples four cardinal neighbours, computes |4c - n - s - e - w| on linearized depth. Linearization branches between perspective and ortho via u_is_ortho. Threshold scales with depth so distant edges still register. - Output is multiplicatively blended (GL_DST_COLOR, GL_ZERO) so colours just darken; no separate composite step. - Runs before the pivot/section/axis gizmos so they aren't outlined themselves. HiZ pyramid build still runs after, unchanged. Per-frame cost is one MSAA depth blit + one fullscreen pass with five depth samples. Sub-millisecond at 1080p on a mid GPU. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,60 @@ out vec4 frag_color;
|
||||
void main() { frag_color = v_color; }
|
||||
)";
|
||||
|
||||
// Edge pass — fullscreen triangle generated from gl_VertexID, samples
|
||||
// resolved depth at four cardinal neighbours, computes a laplacian, and
|
||||
// outputs a per-pixel darkening factor that gets multiplied into the
|
||||
// existing colour via GL_DST_COLOR * GL_ZERO blending.
|
||||
static const char* EDGE_VERTEX_SHADER = R"(
|
||||
#version 450 core
|
||||
out vec2 v_uv;
|
||||
void main() {
|
||||
vec2 pos = vec2((gl_VertexID & 1) << 2, (gl_VertexID & 2) << 1) - 1.0;
|
||||
v_uv = pos * 0.5 + 0.5;
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* EDGE_FRAGMENT_SHADER = R"(
|
||||
#version 450 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_depth;
|
||||
uniform vec2 u_texel; // 1.0 / depth-texture size
|
||||
uniform float u_near;
|
||||
uniform float u_far;
|
||||
uniform float u_is_ortho; // 0 or 1
|
||||
uniform float u_scale; // edge-darkening multiplier
|
||||
uniform float u_threshold; // laplacian floor (relative to depth)
|
||||
out vec4 frag_color;
|
||||
|
||||
float linearize(float z) {
|
||||
if (u_is_ortho > 0.5) {
|
||||
// Ortho: depth buffer is already a linear remap of view-z.
|
||||
return mix(u_near, u_far, z);
|
||||
}
|
||||
// Perspective: standard NDC -> view-z reverse projection.
|
||||
float ndc = z * 2.0 - 1.0;
|
||||
return (2.0 * u_near * u_far) / (u_far + u_near - ndc * (u_far - u_near));
|
||||
}
|
||||
|
||||
void main() {
|
||||
float c = linearize(texture(u_depth, v_uv).r);
|
||||
float n = linearize(texture(u_depth, v_uv + vec2( 0.0, u_texel.y)).r);
|
||||
float s = linearize(texture(u_depth, v_uv + vec2( 0.0, -u_texel.y)).r);
|
||||
float e = linearize(texture(u_depth, v_uv + vec2( u_texel.x, 0.0)).r);
|
||||
float w = linearize(texture(u_depth, v_uv + vec2(-u_texel.x, 0.0)).r);
|
||||
|
||||
// Laplacian magnitude: ~0 on smooth surfaces, large at depth jumps.
|
||||
// Threshold scales with depth so distant edges still register.
|
||||
float lap = abs(4.0 * c - n - s - e - w);
|
||||
float t = u_threshold * c;
|
||||
float edge = clamp((lap - t) * u_scale, 0.0, 0.6);
|
||||
|
||||
// Multiplicative blend (GL_DST_COLOR, GL_ZERO): out = dst * rgb.
|
||||
frag_color = vec4(vec3(1.0 - edge), 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
static GLuint compileShader(QOpenGLFunctions_4_5_Core* gl, GLenum type, const char* source) {
|
||||
GLuint shader = gl->glCreateShader(type);
|
||||
gl->glShaderSource(shader, 1, &source, nullptr);
|
||||
@@ -573,11 +627,15 @@ ViewportWindow::~ViewportWindow() {
|
||||
if (pivot_vbo_) gl_->glDeleteBuffers(1, &pivot_vbo_);
|
||||
if (plane_vao_) gl_->glDeleteVertexArrays(1, &plane_vao_);
|
||||
if (plane_vbo_) gl_->glDeleteBuffers(1, &plane_vbo_);
|
||||
if (edge_vao_) gl_->glDeleteVertexArrays(1, &edge_vao_);
|
||||
if (edge_depth_fbo_) gl_->glDeleteFramebuffers(1, &edge_depth_fbo_);
|
||||
if (edge_depth_tex_) gl_->glDeleteTextures(1, &edge_depth_tex_);
|
||||
if (main_program_) gl_->glDeleteProgram(main_program_);
|
||||
if (pick_program_) gl_->glDeleteProgram(pick_program_);
|
||||
if (axis_program_) gl_->glDeleteProgram(axis_program_);
|
||||
if (pivot_program_) gl_->glDeleteProgram(pivot_program_);
|
||||
if (plane_program_) gl_->glDeleteProgram(plane_program_);
|
||||
if (edge_program_) gl_->glDeleteProgram(edge_program_);
|
||||
if (pick_fbo_) gl_->glDeleteFramebuffers(1, &pick_fbo_);
|
||||
if (pick_color_tex_) gl_->glDeleteTextures(1, &pick_color_tex_);
|
||||
if (pick_pos_tex_) gl_->glDeleteTextures(1, &pick_pos_tex_);
|
||||
@@ -725,6 +783,12 @@ void ViewportWindow::buildShaders() {
|
||||
GLuint fs = compileShader(gl_, GL_FRAGMENT_SHADER, PLANE_FRAGMENT_SHADER);
|
||||
plane_program_ = linkProgram(gl_, vs, fs);
|
||||
}
|
||||
{
|
||||
GLuint vs = compileShader(gl_, GL_VERTEX_SHADER, EDGE_VERTEX_SHADER);
|
||||
GLuint fs = compileShader(gl_, GL_FRAGMENT_SHADER, EDGE_FRAGMENT_SHADER);
|
||||
edge_program_ = linkProgram(gl_, vs, fs);
|
||||
gl_->glCreateVertexArrays(1, &edge_vao_);
|
||||
}
|
||||
{
|
||||
GLuint vs = compileShader(gl_, GL_VERTEX_SHADER, HIZ_DOWNSAMPLE_VS);
|
||||
GLuint fs = compileShader(gl_, GL_FRAGMENT_SHADER, HIZ_DOWNSAMPLE_FS);
|
||||
@@ -2593,6 +2657,7 @@ void ViewportWindow::render() {
|
||||
hiz_reject_count_.load());
|
||||
gl_->glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
|
||||
renderEdgePass();
|
||||
renderPivotIndicator();
|
||||
renderSectionPlanes();
|
||||
renderAxisGizmo();
|
||||
@@ -3118,6 +3183,71 @@ void ViewportWindow::updateSectionDrag(int x, int y) {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void ViewportWindow::renderEdgePass() {
|
||||
if (!edge_program_) return;
|
||||
const int w = width() * devicePixelRatio();
|
||||
const int h = height() * devicePixelRatio();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
// Lazy resize. D24S8 to match Qt's default FBO format (depth+stencil
|
||||
// even though we only sample depth) so the blit doesn't fail.
|
||||
if (edge_w_ != w || edge_h_ != h) {
|
||||
if (edge_depth_fbo_) gl_->glDeleteFramebuffers(1, &edge_depth_fbo_);
|
||||
if (edge_depth_tex_) gl_->glDeleteTextures(1, &edge_depth_tex_);
|
||||
gl_->glCreateTextures(GL_TEXTURE_2D, 1, &edge_depth_tex_);
|
||||
gl_->glTextureStorage2D(edge_depth_tex_, 1, GL_DEPTH24_STENCIL8, w, h);
|
||||
gl_->glCreateFramebuffers(1, &edge_depth_fbo_);
|
||||
gl_->glNamedFramebufferTexture(edge_depth_fbo_, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
edge_depth_tex_, 0);
|
||||
edge_w_ = w; edge_h_ = h;
|
||||
}
|
||||
|
||||
// Step 1: resolve MSAA depth from default FB → single-sample texture.
|
||||
gl_->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
gl_->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, edge_depth_fbo_);
|
||||
gl_->glBlitFramebuffer(0, 0, w, h, 0, 0, w, h,
|
||||
GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
|
||||
// Step 2: fullscreen darkening pass into default FB colour.
|
||||
gl_->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
gl_->glViewport(0, 0, w, h);
|
||||
gl_->glDisable(GL_DEPTH_TEST);
|
||||
gl_->glDepthMask(GL_FALSE);
|
||||
gl_->glEnable(GL_BLEND);
|
||||
gl_->glBlendFunc(GL_DST_COLOR, GL_ZERO);
|
||||
|
||||
gl_->glUseProgram(edge_program_);
|
||||
gl_->glTextureParameteri(edge_depth_tex_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl_->glTextureParameteri(edge_depth_tex_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl_->glTextureParameteri(edge_depth_tex_, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
gl_->glBindTextureUnit(0, edge_depth_tex_);
|
||||
|
||||
// Match the (near, far) used in updateCamera(). For ortho the near
|
||||
// plane is at -depth_extent, far at +depth_extent.
|
||||
const float depth_extent = camera_distance_ * 10.0f;
|
||||
const float near_z = projection_ortho_ ? -depth_extent : 0.1f;
|
||||
const float far_z = depth_extent;
|
||||
|
||||
gl_->glUniform1i(gl_->glGetUniformLocation(edge_program_, "u_depth"), 0);
|
||||
gl_->glUniform2f(gl_->glGetUniformLocation(edge_program_, "u_texel"),
|
||||
1.0f / float(w), 1.0f / float(h));
|
||||
gl_->glUniform1f(gl_->glGetUniformLocation(edge_program_, "u_near"), near_z);
|
||||
gl_->glUniform1f(gl_->glGetUniformLocation(edge_program_, "u_far"), far_z);
|
||||
gl_->glUniform1f(gl_->glGetUniformLocation(edge_program_, "u_is_ortho"),
|
||||
projection_ortho_ ? 1.0f : 0.0f);
|
||||
gl_->glUniform1f(gl_->glGetUniformLocation(edge_program_, "u_scale"), 6.0f);
|
||||
gl_->glUniform1f(gl_->glGetUniformLocation(edge_program_, "u_threshold"), 0.004f);
|
||||
|
||||
gl_->glBindVertexArray(edge_vao_);
|
||||
gl_->glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
gl_->glBindVertexArray(0);
|
||||
|
||||
gl_->glDisable(GL_BLEND);
|
||||
gl_->glDepthMask(GL_TRUE);
|
||||
gl_->glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void ViewportWindow::renderPivotIndicator() {
|
||||
if (!pivot_indicator_visible_ || !pivot_program_ || !pivot_vao_) return;
|
||||
|
||||
|
||||
@@ -289,6 +289,11 @@ private:
|
||||
void renderPivotIndicator();
|
||||
void renderSectionPlanes();
|
||||
void buildSectionPlaneGizmo();
|
||||
// Post-process edge enhancement: resolve MSAA depth into a single-
|
||||
// sample texture, then run a fullscreen pass that detects sharp
|
||||
// depth-laplacian peaks and darkens the colour buffer there. Catches
|
||||
// silhouettes and overlapping-surface boundaries as faint dark lines.
|
||||
void renderEdgePass();
|
||||
// Returns the index of the section plane whose arrow gizmo is under
|
||||
// (x, y), or -1 if none. Screen-space line-segment distance test.
|
||||
int hitTestSectionGizmo(int x, int y) const;
|
||||
@@ -548,6 +553,16 @@ private:
|
||||
int plane_arrow_offset_ = 0;
|
||||
int plane_arrow_count_ = 0;
|
||||
|
||||
// Edge-enhancement pass resources. edge_depth_tex_ is a single-sample
|
||||
// resolve target the size of the window; we blit the default FB depth
|
||||
// into it each frame, then sample it from the fullscreen edge shader.
|
||||
GLuint edge_program_ = 0;
|
||||
GLuint edge_depth_fbo_ = 0;
|
||||
GLuint edge_depth_tex_ = 0;
|
||||
GLuint edge_vao_ = 0; // empty VAO for fullscreen-triangle draw
|
||||
int edge_w_ = 0;
|
||||
int edge_h_ = 0;
|
||||
|
||||
// FPS smoothing
|
||||
int frame_count_ = 0;
|
||||
float accumulated_time_ = 0.0f;
|
||||
|
||||
Reference in New Issue
Block a user