diff --git a/CHANGELOG b/CHANGELOG index 46abcd48..ace83b98 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,14 @@ https://glvis.org +Version 4.5.1 (development) +=========================== +- Added optional order-independent transparency (OIT) rendering for translucent + objects, toggled with 'Alt+o'. OIT is enabled only when supported by the + current OpenGL/WebGL context (requires float color-buffer render targets) and + falls back to legacy blending otherwise. + + Version 4.5 released on Feb 6, 2026 =================================== diff --git a/README.md b/README.md index 2a78818e..5e6d775d 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ Key commands - 1, 2, 3, 4, 5, 6, 7, 8, 9 – Manual rotation along coordinate axes - Alt + a – Set axes number format - Alt + c – Set colorbar number format +- Alt + o – Toggle order-independent transparency (OIT) for translucent objects (when supported) - Ctrl + , , , – Translate the viewpoint - Ctrl + o – Toggle an element ordering curve - n / N – Cycle through numbering: `None` → `Elements` → `Edges` → `Vertices` → `DOFs` diff --git a/lib/gl/renderer.cpp b/lib/gl/renderer.cpp index bb601a54..0a1cbfeb 100644 --- a/lib/gl/renderer.cpp +++ b/lib/gl/renderer.cpp @@ -10,12 +10,23 @@ // CONTRIBUTING.md for details. #include "renderer.hpp" +#include "renderer_core.hpp" namespace gl3 { using namespace resource; +namespace +{ +const std::string kOITFullscreenVs = +#include "shaders/oit_fullscreen.vert" + ; +const std::string kOITFinalizeFs = +#include "shaders/oit_finalize.frag" + ; +} + // Beginning in OpenGL 3.0, there were two changes in texture format support: // - The older single-channel internal format GL_ALPHA was deprecated in favor // of GL_RED @@ -108,6 +119,234 @@ void MeshRenderer::init() #endif } +bool MeshRenderer::canUseOIT(const RenderQueue& queue) const +{ + if (!oit_enable || !device || !feat_use_fbo_antialias) + { + return false; + } + if (device->getType() != GLDevice::CORE_DEVICE) + { + return false; + } + return std::any_of(queue.begin(), queue.end(), + [](const RenderQueue::value_type& q) + { + return q.first.contains_translucent; + }); +} + +bool MeshRenderer::ensureOITTargets(int width, int height) +{ + if (!oit_support_checked) + { + oit_support = probeOITSupport(); + oit_support_checked = true; + if (!oit_support) + { + std::cerr << + "OIT: Disabled (float color-buffer render targets are not supported " + "by this OpenGL/WebGL context)." << std::endl; + } + } + if (!oit_support) + { + return false; + } + + if (width <= 0 || height <= 0) + { + return false; + } + + const bool size_changed = (width != oit_width) || (height != oit_height); + const bool msaa_changed = (oit_msaa_samples != (msaa_enable ? msaa_samples : + 0)); + const bool need_realloc = + size_changed || msaa_changed || + !oit_scene_color_tex || !oit_scene_depth_rb || !oit_scene_fb || + !oit_accum_tex || !oit_reveal_tex || !oit_accum_fb || !oit_reveal_fb; + + if (!need_realloc) + { + return true; + } + + oit_width = width; + oit_height = height; + + auto createTexture2D = [&](resource::TextureHandle& tex, + GLint internal_fmt, + GLenum fmt, + GLenum type, + GLint min_filter, + GLint mag_filter) -> void + { + GLuint tex_id = 0; + glGenTextures(1, &tex_id); + tex = TextureHandle(tex_id); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, internal_fmt, width, height, 0, fmt, + type, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + }; + + auto createDepthRenderbuffer = [&](resource::RenderBufHandle& rb, + GLenum depth_fmt) -> void + { + GLuint rb_id = 0; + glGenRenderbuffers(1, &rb_id); + rb = RenderBufHandle(rb_id); + glBindRenderbuffer(GL_RENDERBUFFER, rb); + glRenderbufferStorage(GL_RENDERBUFFER, depth_fmt, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + }; + + auto createFramebuffer = [&]( + resource::FBOHandle& fb, + const resource::TextureHandle& color_tex, + const resource::RenderBufHandle& depth_rb) -> bool + { + GLuint fb_id = 0; + glGenFramebuffers(1, &fb_id); + fb = FBOHandle(fb_id); + glBindFramebuffer(GL_FRAMEBUFFER, fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + color_tex, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, depth_rb); + const bool ok = (glCheckFramebufferStatus(GL_FRAMEBUFFER) + == GL_FRAMEBUFFER_COMPLETE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return ok; + }; + + // Scene color target (opaque render target) + createTexture2D(oit_scene_color_tex, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, + GL_LINEAR, GL_LINEAR); + + // Shared depth buffer for opaque + OIT passes + createDepthRenderbuffer(oit_scene_depth_rb, GL_DEPTH_COMPONENT24); + + // Scene framebuffer + if (!createFramebuffer(oit_scene_fb, oit_scene_color_tex, oit_scene_depth_rb)) + { + return false; + } + + // Accumulation + revealage textures +#ifdef __EMSCRIPTEN__ + const GLenum kOITAccumType = GL_HALF_FLOAT; +#else + const GLenum kOITAccumType = GL_FLOAT; +#endif + createTexture2D(oit_accum_tex, GL_RGBA16F, GL_RGBA, kOITAccumType, + GL_NEAREST, GL_NEAREST); + createTexture2D(oit_reveal_tex, GL_R8, GL_RED, GL_UNSIGNED_BYTE, + GL_NEAREST, GL_NEAREST); + + // Accumulation framebuffer + if (!createFramebuffer(oit_accum_fb, oit_accum_tex, oit_scene_depth_rb)) + { + return false; + } + + // Revealage framebuffer + if (!createFramebuffer(oit_reveal_fb, oit_reveal_tex, oit_scene_depth_rb)) + { + return false; + } + + // Optional MSAA framebuffer for the opaque pass + oit_msaa_fb = FBOHandle(0); + oit_msaa_samples = 0; + if (msaa_enable && msaa_samples > 0) + { + GLuint color_rb = 0, depth_rb = 0, fb_id = 0; + glGenRenderbuffers(1, &color_rb); + glGenRenderbuffers(1, &depth_rb); + oit_msaa_color_rb = RenderBufHandle(color_rb); + oit_msaa_depth_rb = RenderBufHandle(depth_rb); + + glGenFramebuffers(1, &fb_id); + oit_msaa_fb = FBOHandle(fb_id); + + glBindRenderbuffer(GL_RENDERBUFFER, oit_msaa_color_rb); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_samples, + GL_RGBA8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, oit_msaa_depth_rb); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_samples, + GL_DEPTH_COMPONENT24, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, oit_msaa_fb); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, oit_msaa_color_rb); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, oit_msaa_depth_rb); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + oit_msaa_fb = FBOHandle(0); + oit_msaa_samples = 0; + } + else + { + oit_msaa_samples = msaa_samples; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return true; +} + +bool MeshRenderer::probeOITSupport() +{ + // Probe whether GL_RGBA16F textures are color-renderable in this context. + // This is required for the OIT accumulation buffer. + GLuint tex_id = 0; + glGenTextures(1, &tex_id); + TextureHandle tex(tex_id); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + +#ifdef __EMSCRIPTEN__ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, + GL_HALF_FLOAT, nullptr); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, + GL_FLOAT, nullptr); +#endif + glBindTexture(GL_TEXTURE_2D, 0); + + GLuint depth_id = 0; + glGenRenderbuffers(1, &depth_id); + RenderBufHandle depth(depth_id); + glBindRenderbuffer(GL_RENDERBUFFER, depth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 1, 1); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + GLuint fb_id = 0; + glGenFramebuffers(1, &fb_id); + FBOHandle fb(fb_id); + glBindFramebuffer(GL_FRAMEBUFFER, fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, + 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, + depth); + + const bool ok = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == + GL_FRAMEBUFFER_COMPLETE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return ok; +} + void MeshRenderer::render(const RenderQueue& queue) { // elements containing opaque objects should be rendered first @@ -117,6 +356,270 @@ void MeshRenderer::render(const RenderQueue& queue) { return !renderPair.first.contains_translucent; }); + + struct DrawableBuffers + { + std::vector tex_bufs; + std::vector no_tex_bufs; + TextBuffer* text_buf = nullptr; + }; + + auto collectBuffers = [&](GlDrawable* drawable) -> DrawableBuffers + { + DrawableBuffers out; + for (int i = 0; i < NUM_LAYOUTS; i++) + { + for (size_t j = 0; j < GlDrawable::NUM_SHAPES; j++) + { + if (drawable->buffers[i][j]) + { + if (i == LAYOUT_VTX_TEXTURE0 || i == LAYOUT_VTX_NORMAL_TEXTURE0) + { + out.tex_bufs.emplace_back(drawable->buffers[i][j].get()->getHandle()); + } + else + { + out.no_tex_bufs.emplace_back(drawable->buffers[i][j].get()->getHandle()); + } + } + if (drawable->indexed_buffers[i][j]) + { + if (i == LAYOUT_VTX_TEXTURE0 || i == LAYOUT_VTX_NORMAL_TEXTURE0) + { + out.tex_bufs.emplace_back( + drawable->indexed_buffers[i][j].get()->getHandle()); + } + else + { + out.no_tex_bufs.emplace_back( + drawable->indexed_buffers[i][j].get()->getHandle()); + } + } + } + } + out.text_buf = &drawable->text_buffer; + return out; + }; + + CoreGLDevice* core_dev = dynamic_cast(device.get()); + if (canUseOIT(sorted_queue) && core_dev && core_dev->hasOITPrograms()) + { + int vp[4]; + device->getViewport(vp); + int width = vp[2]; + int height = vp[3]; + + if (!ensureOITTargets(width, height)) + { + std::cerr << "OIT: Unable to create render targets, falling back to legacy " + "transparency." << std::endl; + bool prev_oit = oit_enable; + oit_enable = false; + render(queue); + oit_enable = prev_oit; + return; + } + + auto setRenderParams = [&](const RenderParams& params) + { + device->setTransformMatrices(params.model_view.mtx, params.projection.mtx); + device->setMaterial(params.mesh_material); + device->setNumLights(params.num_pt_lights); + for (int i = 0; i < params.num_pt_lights; i++) + { + device->setPointLight(i, params.lights[i]); + } + device->setAmbientLight(params.light_amb_scene); + device->setStaticColor(params.static_color); + device->setClipPlaneUse(params.use_clip_plane); + device->setClipPlaneEqn(params.clip_plane_eqn); + }; + + auto drawDrawableGeometry = [&](GlDrawable* drawable) + { + DrawableBuffers bufs = collectBuffers(drawable); + device->attachTexture(GLDevice::SAMPLER_COLOR, color_tex); + device->attachTexture(GLDevice::SAMPLER_ALPHA, alpha_tex); + for (auto buf : bufs.tex_bufs) + { + device->drawDeviceBuffer(buf); + } + device->detachTexture(GLDevice::SAMPLER_COLOR); + device->detachTexture(GLDevice::SAMPLER_ALPHA); + for (auto buf : bufs.no_tex_bufs) + { + device->drawDeviceBuffer(buf); + } + }; + + auto drawDrawableText = [&](const RenderParams& params, GlDrawable* drawable) + { + DrawableBuffers bufs = collectBuffers(drawable); + device->setTransformMatrices(params.model_view.mtx, params.projection.mtx); + device->setClipPlaneUse(params.use_clip_plane); + device->setClipPlaneEqn(params.clip_plane_eqn); + device->drawDeviceBuffer(*bufs.text_buf); + }; + + // === Opaque pass (optionally MSAA-resolved into oit_scene_fb) === + glBindFramebuffer(GL_FRAMEBUFFER, oit_msaa_fb ? (GLuint)oit_msaa_fb + : (GLuint)oit_scene_fb); +#ifndef __EMSCRIPTEN__ + if (oit_msaa_fb) { glEnable(GL_MULTISAMPLE); } +#endif + glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + core_dev->bindDefaultProgram(); + glDisable(GL_BLEND); + device->enableDepthWrite(); + for (auto& q_elem : sorted_queue) + { + const RenderParams& params = q_elem.first; + if (params.contains_translucent) + { + continue; + } + setRenderParams(params); + drawDrawableGeometry(q_elem.second); + } + + if (oit_msaa_fb) + { + glBindFramebuffer(GL_READ_FRAMEBUFFER, oit_msaa_fb); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oit_scene_fb); + glBlitFramebuffer(0, 0, width, height, + 0, 0, width, height, + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, + GL_NEAREST); +#ifndef __EMSCRIPTEN__ + glDisable(GL_MULTISAMPLE); +#endif + } + + // === Accumulation pass === + glBindFramebuffer(GL_FRAMEBUFFER, oit_accum_fb); + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + device->disableDepthWrite(); + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE); + core_dev->bindOITAccumProgram(); + for (auto& q_elem : sorted_queue) + { + const RenderParams& params = q_elem.first; + if (!params.contains_translucent) + { + continue; + } + setRenderParams(params); + drawDrawableGeometry(q_elem.second); + } + + // === Revealage pass === + glBindFramebuffer(GL_FRAMEBUFFER, oit_reveal_fb); + glClearColor(1.f, 1.f, 1.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + device->disableDepthWrite(); + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + core_dev->bindOITRevealProgram(); + for (auto& q_elem : sorted_queue) + { + const RenderParams& params = q_elem.first; + if (!params.contains_translucent) + { + continue; + } + setRenderParams(params); + drawDrawableGeometry(q_elem.second); + } + + // === Composite to default framebuffer === + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + if (!oit_finalize_ready) + { + if (!oit_finalize_prgm.create(kOITFullscreenVs, kOITFinalizeFs, {}, 1)) + { + std::cerr << "OIT: Failed to compile composite shader, falling back to " + "legacy transparency." << std::endl; + glEnable(GL_DEPTH_TEST); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + glActiveTexture(GL_TEXTURE0); + bool prev_oit = oit_enable; + oit_enable = false; + render(queue); + oit_enable = prev_oit; + return; + } + oit_finalize_prgm.bind(); + glUniform1i(oit_finalize_prgm.uniform("sceneTex"), 0); + glUniform1i(oit_finalize_prgm.uniform("accumTex"), 1); + glUniform1i(oit_finalize_prgm.uniform("revealTex"), 2); + oit_finalize_ready = true; + } + + if (!oit_finalize_vao && (GLEW_VERSION_3_0 || GLEW_ARB_vertex_array_object)) + { + GLuint vao_id; + glGenVertexArrays(1, &vao_id); + oit_finalize_vao = VtxArrayHandle(vao_id); + } + if (oit_finalize_vao) + { + glBindVertexArray(oit_finalize_vao); + } + + oit_finalize_prgm.bind(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, oit_scene_color_tex); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, oit_accum_tex); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, oit_reveal_tex); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + + // Restore expected baseline state for the rest of GLVis. + glEnable(GL_DEPTH_TEST); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + glActiveTexture(GL_TEXTURE0); + + // === Text overlay pass === + core_dev->bindDefaultProgram(); + device->enableBlend(); + device->disableDepthWrite(); + device->attachTexture(1, font_tex); + device->setNumLights(0); + for (auto& q_elem : sorted_queue) + { + drawDrawableText(q_elem.first, q_elem.second); + } + device->enableDepthWrite(); + device->disableBlend(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glActiveTexture(GL_TEXTURE0); + glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + return; + } + RenderBufHandle renderBufs[2]; FBOHandle msaaFb; if (feat_use_fbo_antialias && msaa_enable) diff --git a/lib/gl/renderer.hpp b/lib/gl/renderer.hpp index 588235a3..db799044 100644 --- a/lib/gl/renderer.hpp +++ b/lib/gl/renderer.hpp @@ -16,6 +16,7 @@ #include #include "platform_gl.hpp" +#include "shader.hpp" #include "types.hpp" #include "../material.hpp" #include "../palettes.hpp" @@ -194,18 +195,59 @@ class MeshRenderer std::unique_ptr device; bool msaa_enable; int msaa_samples; + + // Order-independent transparency (OIT) rendering is implemented using an + // off-screen accumulation/revealage pass and a final compositing pass. It is + // enabled per-context only when float color-buffer render targets are + // supported. + bool oit_enable; + ShaderProgram oit_finalize_prgm; + resource::VtxArrayHandle oit_finalize_vao; + bool oit_finalize_ready; + int oit_width; + int oit_height; + int oit_msaa_samples; + resource::TextureHandle oit_scene_color_tex; + resource::RenderBufHandle oit_scene_depth_rb; + resource::FBOHandle oit_scene_fb; + resource::TextureHandle oit_accum_tex; + resource::TextureHandle oit_reveal_tex; + resource::FBOHandle oit_accum_fb; + resource::FBOHandle oit_reveal_fb; + resource::RenderBufHandle oit_msaa_color_rb; + resource::RenderBufHandle oit_msaa_depth_rb; + resource::FBOHandle oit_msaa_fb; GLuint color_tex, alpha_tex, font_tex; float line_w, line_w_aa; PaletteState* palette; + std::array clear_color; + bool oit_support_checked; + bool oit_support; bool feat_use_fbo_antialias; + bool canUseOIT(const RenderQueue& queued) const; + // Probe if OIT render targets are supported (e.g. RGBA16F is color-renderable). + bool probeOITSupport(); + bool ensureOITTargets(int width, int height); void init(); public: MeshRenderer() : msaa_enable(false) , msaa_samples(0) + , oit_enable(false) + , oit_finalize_ready(false) + , oit_width(0) + , oit_height(0) + , oit_msaa_samples(0) + , color_tex(0) + , alpha_tex(0) + , font_tex(0) , line_w(1.f) - , line_w_aa(LINE_WIDTH_AA) { init(); } + , line_w_aa(LINE_WIDTH_AA) + , palette(nullptr) + , clear_color{0.f, 0.f, 0.f, 1.f} + , oit_support_checked(false) + , oit_support(false) { init(); } template void setDevice() @@ -232,6 +274,9 @@ class MeshRenderer void setAntialiasing(bool aa_status); bool getAntialiasing() { return msaa_enable; } + // Enables/disables OIT (when supported by the current OpenGL/WebGL context). + void setOrderIndependentTransparency(bool enable) { oit_enable = enable; } + bool getOrderIndependentTransparency() const { return oit_enable; } void setSamplesMSAA(int samples) { if (msaa_samples < samples) @@ -253,7 +298,11 @@ class MeshRenderer void setLineWidthMS(float w); float getLineWidthMS() { return line_w_aa; } - void setClearColor(float r, float g, float b, float a) { glClearColor(r, g, b, a); } + void setClearColor(float r, float g, float b, float a) + { + clear_color = {r, g, b, a}; + glClearColor(r, g, b, a); + } void setViewport(GLsizei w, GLsizei h) { device->setViewport(w, h); } void render(const RenderQueue& queued); diff --git a/lib/gl/renderer_core.cpp b/lib/gl/renderer_core.cpp index 9db5ab24..c2f20372 100644 --- a/lib/gl/renderer_core.cpp +++ b/lib/gl/renderer_core.cpp @@ -31,6 +31,14 @@ const std::string DEFAULT_FS = BLINN_PHONG_FS + #include "shaders/default.frag" ; +const std::string OIT_ACCUM_FS = + BLINN_PHONG_FS + +#include "shaders/oit_accum.frag" + ; +const std::string OIT_REVEAL_FS = + BLINN_PHONG_FS + +#include "shaders/oit_reveal.frag" + ; const std::string PRINTING_VS = BLINN_PHONG_FS + #include "shaders/printing.vert" @@ -110,6 +118,18 @@ bool CoreGLDevice::compileShaders() return false; } + if (!oit_accum_prgm.create(DEFAULT_VS, OIT_ACCUM_FS, attribMap, 1)) + { + std::cerr << "Failed to create the OIT accumulation shader program." << + std::endl; + return false; + } + if (!oit_reveal_prgm.create(DEFAULT_VS, OIT_REVEAL_FS, attribMap, 1)) + { + std::cerr << "Failed to create the OIT revealage shader program." << std::endl; + return false; + } + #ifndef __EMSCRIPTEN__ if (GLEW_EXT_transform_feedback || GLEW_VERSION_3_0) { diff --git a/lib/gl/renderer_core.hpp b/lib/gl/renderer_core.hpp index e3956fe1..c684d8c8 100644 --- a/lib/gl/renderer_core.hpp +++ b/lib/gl/renderer_core.hpp @@ -43,6 +43,8 @@ class CoreGLDevice : public GLDevice private: ShaderProgram default_prgm; + ShaderProgram oit_accum_prgm; + ShaderProgram oit_reveal_prgm; ShaderProgram feedback_prgm; resource::VtxArrayHandle global_vao; @@ -114,10 +116,22 @@ class CoreGLDevice : public GLDevice } void bindExternalProgram(const ShaderProgram& prog) { + // Core profile rendering requires a VAO to be bound. + if (global_vao) + { + glBindVertexArray(global_vao); + } glDisable(GL_RASTERIZER_DISCARD); initializeShaderState(prog); glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); } + bool hasOITPrograms() const + { + return oit_accum_prgm.isCompiled() && oit_reveal_prgm.isCompiled(); + } + void bindOITAccumProgram() { bindExternalProgram(oit_accum_prgm); } + void bindOITRevealProgram() { bindExternalProgram(oit_reveal_prgm); } + void bindDefaultProgram() { bindExternalProgram(default_prgm); } void captureXfbBuffer(PaletteState& pal, CaptureBuffer& cbuf, int hnd) override; }; diff --git a/lib/gl/shaders/oit_accum.frag b/lib/gl/shaders/oit_accum.frag new file mode 100644 index 00000000..84274166 --- /dev/null +++ b/lib/gl/shaders/oit_accum.frag @@ -0,0 +1,48 @@ +R"( +// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-443271. +// +// This file is part of the GLVis visualization tool and library. For more +// information and source code availability see https://glvis.org. +// +// GLVis is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +uniform sampler2D alphaTex; +uniform sampler2D colorTex; + +varying vec3 fNormal; +varying vec3 fPosition; +varying vec4 fColor; +varying vec2 fTexCoord; + +uniform bool useClipPlane; +varying float fClipVal; + +void fragmentClipPlane() +{ + if (useClipPlane && fClipVal < 0.0) + { + discard; + } +} + +void main() +{ + fragmentClipPlane(); + vec4 color = fColor * texture2D(colorTex, vec2(fTexCoord)); + color = blinnPhong(fPosition, fNormal, color); +#ifdef USE_ALPHA + color.a *= texture2D(alphaTex, vec2(fTexCoord)).a; +#else + color.a *= texture2D(alphaTex, vec2(fTexCoord)).r; +#endif + + float alpha = clamp(color.a, 0.0, 1.0); + float weight = clamp(pow(alpha, 4.0) * 1000.0 + 0.01, 0.01, 3000.0); + gl_FragColor = vec4(color.rgb * alpha * weight, alpha * weight); +} +)" + diff --git a/lib/gl/shaders/oit_finalize.frag b/lib/gl/shaders/oit_finalize.frag new file mode 100644 index 00000000..93780649 --- /dev/null +++ b/lib/gl/shaders/oit_finalize.frag @@ -0,0 +1,31 @@ +R"( +// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-443271. +// +// This file is part of the GLVis visualization tool and library. For more +// information and source code availability see https://glvis.org. +// +// GLVis is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +uniform sampler2D sceneTex; +uniform sampler2D accumTex; +uniform sampler2D revealTex; + +varying vec2 vUv; + +void main() +{ + vec3 scene = texture2D(sceneTex, vUv).rgb; + vec4 accum = texture2D(accumTex, vUv); + float reveal = texture2D(revealTex, vUv).r; + + float transAlpha = 1.0 - reveal; + vec3 transColor = (accum.a > 1e-5) ? (accum.rgb / accum.a) : vec3(0.0); + vec3 outColor = transColor * transAlpha + scene * reveal; + gl_FragColor = vec4(outColor, 1.0); +} +)" + diff --git a/lib/gl/shaders/oit_fullscreen.vert b/lib/gl/shaders/oit_fullscreen.vert new file mode 100644 index 00000000..48aa2285 --- /dev/null +++ b/lib/gl/shaders/oit_fullscreen.vert @@ -0,0 +1,34 @@ +R"( +// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-443271. +// +// This file is part of the GLVis visualization tool and library. For more +// information and source code availability see https://glvis.org. +// +// GLVis is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +varying vec2 vUv; + +void main() +{ + vec2 pos; + if (gl_VertexID == 0) + { + pos = vec2(-1.0, -1.0); + } + else if (gl_VertexID == 1) + { + pos = vec2(3.0, -1.0); + } + else + { + pos = vec2(-1.0, 3.0); + } + vUv = 0.5 * pos + 0.5; + gl_Position = vec4(pos, 0.0, 1.0); +} +)" + diff --git a/lib/gl/shaders/oit_reveal.frag b/lib/gl/shaders/oit_reveal.frag new file mode 100644 index 00000000..d2c13709 --- /dev/null +++ b/lib/gl/shaders/oit_reveal.frag @@ -0,0 +1,47 @@ +R"( +// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-443271. +// +// This file is part of the GLVis visualization tool and library. For more +// information and source code availability see https://glvis.org. +// +// GLVis is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +uniform sampler2D alphaTex; +uniform sampler2D colorTex; + +varying vec3 fNormal; +varying vec3 fPosition; +varying vec4 fColor; +varying vec2 fTexCoord; + +uniform bool useClipPlane; +varying float fClipVal; + +void fragmentClipPlane() +{ + if (useClipPlane && fClipVal < 0.0) + { + discard; + } +} + +void main() +{ + fragmentClipPlane(); + vec4 color = fColor * texture2D(colorTex, vec2(fTexCoord)); + color = blinnPhong(fPosition, fNormal, color); +#ifdef USE_ALPHA + color.a *= texture2D(alphaTex, vec2(fTexCoord)).a; +#else + color.a *= texture2D(alphaTex, vec2(fTexCoord)).r; +#endif + + float alpha = clamp(color.a, 0.0, 1.0); + gl_FragColor = vec4(alpha, alpha, alpha, alpha); +} +)" + diff --git a/lib/gl/types.hpp b/lib/gl/types.hpp index 4ad241cf..d543eafc 100644 --- a/lib/gl/types.hpp +++ b/lib/gl/types.hpp @@ -56,6 +56,10 @@ class Handle { if (this != &other) { + if (hnd) + { + GLFinalizer(hnd); + } hnd = other.hnd; other.hnd = 0; } diff --git a/lib/vssolution.cpp b/lib/vssolution.cpp index 7d25f38f..dc0bef43 100644 --- a/lib/vssolution.cpp +++ b/lib/vssolution.cpp @@ -87,6 +87,7 @@ std::string VisualizationSceneSolution::GetHelpString() const << "| Alt+a - Axes number format |" << endl << "| Alt+c - Colorbar number format |" << endl << "| Alt+n - Numberings method |" << endl + << "| Alt+o - Toggle OIT transparency |" << endl << "| Ctrl+o - Element ordering curve |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl @@ -304,7 +305,15 @@ static void KeyNPressed(GLenum state) static void KeyoPressed(GLenum state) { - if (state & KMOD_CTRL) + if (state & KMOD_ALT) + { + const bool enable = + !GetAppWindow()->getRenderer().getOrderIndependentTransparency(); + GetAppWindow()->getRenderer().setOrderIndependentTransparency(enable); + cout << "Order-independent transparency: " << (enable ? "on" : "off") << endl; + SendExposeEvent(); + } + else if (state & KMOD_CTRL) { vssol -> ToggleDrawOrdering(); vssol -> PrepareOrderingCurve(); diff --git a/lib/vssolution3d.cpp b/lib/vssolution3d.cpp index 2383fb3e..b93e407f 100644 --- a/lib/vssolution3d.cpp +++ b/lib/vssolution3d.cpp @@ -87,6 +87,7 @@ std::string VisualizationSceneSolution3d::GetHelpString() const << "| \\ - Set light source position |" << endl << "| Alt+a - Axes number format |" << endl << "| Alt+c - Colorbar number format |" << endl + << "| Alt+o - Toggle OIT transparency |" << endl << "| Ctrl+o - Element ordering curve |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl @@ -353,7 +354,15 @@ static void KeyFPressed() static void KeyoPressed(GLenum state) { - if (state & KMOD_CTRL) + if (state & KMOD_ALT) + { + const bool enable = + !GetAppWindow()->getRenderer().getOrderIndependentTransparency(); + GetAppWindow()->getRenderer().setOrderIndependentTransparency(enable); + cout << "Order-independent transparency: " << (enable ? "on" : "off") << endl; + SendExposeEvent(); + } + else if (state & KMOD_CTRL) { vssol3d -> ToggleDrawOrdering(); vssol3d -> PrepareOrderingCurve();