jcsyshc 1 жил өмнө
commit
eb9c02565b

+ 101 - 0
CMakeLists.txt

@@ -0,0 +1,101 @@
+cmake_minimum_required(VERSION 3.26)
+project(TransparentAR)
+
+set(CMAKE_CXX_STANDARD 20)
+
+add_executable(${PROJECT_NAME} src/main.cpp
+        src/mesh_render.cpp
+        src/render_utility.cpp
+        src/texture_render.cpp)
+
+target_include_directories(${PROJECT_NAME} PRIVATE ./src)
+
+# copy shader programs
+#set(SHADER_FILES
+#        src/shader/mesh_render.vert
+#        src/shader/mesh_render.frag)
+#foreach (FILE ${SHADER_FILES})
+#    add_custom_command(
+#            TARGET ${PROJECT_NAME} POST_BUILD
+#            COMMAND ${CMAKE_COMMAND} -E copy_if_different
+#            "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}"
+#            $<TARGET_FILE_DIR:${PROJECT_NAME}>
+#            COMMENT "Copying ${FILE} to the output directory"
+#    )
+#endforeach (FILE)
+
+# spdlog config
+find_package(spdlog REQUIRED)
+target_link_libraries(${PROJECT_NAME} spdlog::spdlog)
+target_compile_definitions(${PROJECT_NAME} PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE)
+
+# OpenCV config
+find_package(OpenCV REQUIRED COMPONENTS cudaimgproc imgcodecs calib3d)
+target_include_directories(${PROJECT_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
+
+# glfw config
+if (WIN32)
+    set(GLFW_INCLUDE_DIR C:/BuildEssentials/VS2019Libs/include)
+    set(GLFW_LIB_DIR C:/BuildEssentials/VS2019Libs/lib)
+    find_library(GLFW_LIB glfw3 HINTS ${GLFW_LIB_DIR})
+    target_include_directories(${PROJECT_NAME} PRIVATE ${GLFW_INCLUDE_DIR})
+    target_link_libraries(${PROJECT_NAME} ${GLFW_LIB})
+else ()
+    find_package(glfw3 REQUIRED)
+    target_link_libraries(${PROJECT_NAME} glfw)
+endif ()
+
+# glad config
+if (WIN32)
+    set(GLAD_DIR C:/BuildEssentials/Library/glad)
+else ()
+    set(GLAD_DIR /home/tpx/src/glad)
+endif ()
+target_include_directories(${PROJECT_NAME} PRIVATE ${GLAD_DIR}/include)
+target_sources(${PROJECT_NAME} PRIVATE ${GLAD_DIR}/src/gl.c)
+
+# glm config
+find_package(glm REQUIRED)
+target_link_libraries(${PROJECT_NAME} glm::glm)
+target_compile_definitions(${PROJECT_NAME} PRIVATE GLM_ENABLE_EXPERIMENTAL)
+
+# imgui config
+if (WIN32)
+    set(IMGUI_DIR C:/BuildEssentials/Library/imgui-1.89.5)
+else ()
+    set(IMGUI_DIR /home/tpx/src/imgui-1.90.3)
+endif ()
+set(IMGUI_BACKENDS_DIR ${IMGUI_DIR}/backends)
+target_include_directories(${PROJECT_NAME} PRIVATE ${IMGUI_DIR} ${IMGUI_BACKENDS_DIR})
+target_sources(${PROJECT_NAME} PRIVATE
+        ${IMGUI_DIR}/imgui.cpp
+        ${IMGUI_DIR}/imgui_draw.cpp
+        ${IMGUI_DIR}/imgui_tables.cpp
+        ${IMGUI_DIR}/imgui_widgets.cpp
+        ${IMGUI_DIR}/imgui_demo.cpp
+        ${IMGUI_BACKENDS_DIR}/imgui_impl_glfw.cpp
+        ${IMGUI_BACKENDS_DIR}/imgui_impl_opengl3.cpp)
+target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_IMGUI)
+
+# imGuIZMO config
+set(IMGUIZMO_DIR /home/tpx/src/imGuIZMO.quat-3.0/imGuIZMO.quat)
+target_include_directories(${PROJECT_NAME} PRIVATE ${IMGUIZMO_DIR})
+target_sources(${PROJECT_NAME} PRIVATE
+        ${IMGUIZMO_DIR}/imGuIZMOquat.cpp)
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+        IMGUIZMO_IMGUI_FOLDER=${IMGUI_DIR}/)
+
+# Boost config
+find_package(Boost REQUIRED COMPONENTS iostreams)
+target_include_directories(${PROJECT_NAME} PRIVATE ${Boost_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
+
+# VTK config
+find_package(VTK REQUIRED)
+target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
+vtk_module_autoinit(TARGETS ${PROJECT_NAME} MODULES ${VTK_LIBRARIES})
+
+# Eigen3 config
+find_package(Eigen3 REQUIRED)
+target_link_libraries(${PROJECT_NAME} Eigen3::Eigen)

+ 232 - 0
src/main.cpp

@@ -0,0 +1,232 @@
+#include "mesh_render.h"
+
+#include <glad/gl.h>
+#include <GLFW/glfw3.h>
+
+#include <imGuIZMOquat.h>
+// #include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+
+#include <opencv2/core/mat.hpp>
+
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_access.hpp>
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtx/quaternion.hpp>
+#include <glm/gtx/string_cast.hpp>
+#include <glm/gtx/transform.hpp>
+
+#include <spdlog/spdlog.h>
+
+#include <bit>
+#include <cassert>
+
+struct imgui_disable_guard {
+    explicit imgui_disable_guard(bool enable = true) {
+        is_disabled = enable;
+        if (is_disabled) {
+            ImGui::BeginDisabled();
+        }
+    }
+
+    ~imgui_disable_guard() {
+        if (is_disabled) {
+            ImGui::EndDisabled();
+        }
+    }
+
+private:
+    bool is_disabled;
+};
+
+auto main_window_width = 800;
+auto main_window_height = 600;
+GLFWwindow *main_window = nullptr;
+
+void initialize_main_window() {
+    // set GLFW error handler
+    glfwSetErrorCallback([](int error, const char *desc) {
+        SPDLOG_ERROR("GLFW error: code = {}, description = {}", error, desc);
+    });
+
+    // create main window
+    auto ret = glfwInit();
+    assert(ret == GLFW_TRUE);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
+    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+    main_window = glfwCreateWindow(main_window_width, main_window_height,
+                                   "RemoteAR V3.-1", nullptr, nullptr);
+    assert(main_window != nullptr);
+    glfwMakeContextCurrent(main_window);
+    glfwSwapInterval(1);
+
+    // load opengl functions
+    auto version = gladLoadGL(glfwGetProcAddress);
+    assert(version > 0);
+    SPDLOG_INFO("Loaded OpenGL {}.{}", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
+
+    // enable depth test
+    glEnable(GL_DEPTH_TEST);
+
+    // enable color blending
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+#ifndef NDEBUG
+    // log opengl error
+    glEnable(GL_DEBUG_OUTPUT);
+    glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity,
+                              GLsizei length, const GLchar *message, const void *user_data) {
+        if (type == GL_DEBUG_TYPE_ERROR) {
+            SPDLOG_ERROR("OpenGL error: type = {}, severity = {}, message = {}", type, severity, message);
+            assert(false);
+        }
+    }, nullptr);
+#endif
+
+    // setup imgui context
+    IMGUI_CHECKVERSION();
+    ImGui::CreateContext();
+    auto io = ImGui::GetIO();
+    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+    ImGui::StyleColorsDark();
+    ImGui_ImplGlfw_InitForOpenGL(main_window, true);
+    ImGui_ImplOpenGL3_Init();
+
+    // elegant cleanup
+    std::atexit([] {
+        ImGui_ImplOpenGL3_Shutdown();
+        ImGui_ImplGlfw_Shutdown();
+        ImGui::DestroyContext();
+
+        glfwDestroyWindow(main_window);
+        glfwTerminate();
+    });
+}
+
+glm::mat4 transform_combine(const glm::vec3 &position, const vgm::Quat &rotation) {
+    return glm::translate(position) * glm::toMat4(std::bit_cast<glm::quat>(rotation));
+}
+
+int main() {
+
+    spdlog::set_level(spdlog::level::trace);
+    initialize_main_window();
+
+    static constexpr auto bg_path = "/home/tpx/project/TransparentAR/data/E1_AR/E1_Model.obj";
+    static constexpr auto model_path = "/home/tpx/project/TransparentAR/data/E1_AR/E1_Implant.obj";
+
+    auto render = std::make_unique<mesh_render>();
+    auto bg_mesh = std::unique_ptr<mesh_type>(mesh_type::from_obj(bg_path));
+    auto model_mesh = std::unique_ptr<mesh_type>(mesh_type::from_obj(model_path));
+
+    auto bg_info = mesh_property{};
+    bg_info.material.ambient = glm::vec3{0.0, 0.5, 0.0}; // green
+    bg_info.material.diffuse = glm::vec3{0.0, 1.0, 0.0};
+    auto bg_position = glm::vec3{};
+    auto bg_rotation = vgm::Quat{1.0f, 0.0f, 0.0f, 0.0f};
+    bool show_bg = true;
+
+    auto model_info = mesh_property{};
+    model_info.material.ambient = glm::vec3{0.1, 0.0, 0.0}; // red
+    model_info.material.diffuse = glm::vec3{1.0, 0.0, 0.0};
+    auto model_position = glm::vec3{};
+    auto model_rotation = vgm::Quat{1.0f, 0.0f, 0.0f, 0.0f};
+    float alpha_factor = 0.25f;
+
+    auto scene_info = scene_property{};
+    float camera_fov = 60.0f;
+    static constexpr auto camera_near = 0.1f;
+    static constexpr auto camera_far = 1000.0f;
+    auto camera_position = glm::vec3{-170.0f, -75.0f, -180.0f};
+    auto camera_rotation = vgm::Quat{1.0f, 0.0f, 0.0f, 0.0f};
+    auto light_direction = vgm::Vec3{1.0f, 0.0f, 0.0f};
+    bool light_follow_camera = false;
+
+    while (!glfwWindowShouldClose(main_window)) {
+
+        glfwPollEvents();
+        ImGui_ImplOpenGL3_NewFrame();
+        ImGui_ImplGlfw_NewFrame();
+        ImGui::NewFrame();
+
+        ImGui::ShowDemoWindow();
+
+        static constexpr auto item_width = 200;
+        if (ImGui::Begin("Control")) {
+            ImGui::PushItemWidth(item_width);
+
+            if (ImGui::TreeNode("Background")) {
+                ImGui::DragFloat3("Position", glm::value_ptr(bg_position),
+                                  0.5f, 0.0f, 0.0f, "%.01f");
+                ImGui::gizmo3D("##rotation", bg_rotation, item_width);
+                ImGui::Checkbox("Show", &show_bg);
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("Model")) {
+                ImGui::DragFloat3("Position", glm::value_ptr(model_position),
+                                  0.5f, 0.0f, 0.0f, "%.01f");
+                ImGui::gizmo3D("##rotation", model_rotation, item_width);
+                ImGui::ColorEdit3("Ambient", glm::value_ptr(model_info.material.ambient));
+                ImGui::ColorEdit3("Diffuse", glm::value_ptr(model_info.material.diffuse));
+                ImGui::DragFloat("Alpha Factor", &alpha_factor, 0.005f, 0.0f, 1.0f);
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("Camera")) {
+                ImGui::DragFloat3("Position", glm::value_ptr(camera_position),
+                                  0.5f, 0.0f, 0.0f, "%.01f");
+                ImGui::gizmo3D("##rotation", camera_rotation, item_width);
+                ImGui::DragFloat("FOV (deg)", &camera_fov, 0.5f, 10.0f, 178.0f, "%.01f");
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("Light")) {
+                ImGui::Checkbox("Follow Camera", &light_follow_camera);
+                {
+                    auto guard = imgui_disable_guard(light_follow_camera);
+                    ImGui::gizmo3D("##direction", light_direction, item_width);
+                }
+                ImGui::TreePop();
+            }
+
+            ImGui::PopItemWidth();
+        }
+        ImGui::End();
+
+        ImGui::Render();
+
+        cv::Size frame_size;
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+        glfwGetFramebufferSize(main_window, &frame_size.width, &frame_size.height);
+        glViewport(0, 0, frame_size.width, frame_size.height);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        // update object information
+        bg_info.transform = transform_combine(bg_position, bg_rotation);
+        model_info.transform = transform_combine(model_position, model_rotation);
+        scene_info.camera.transform = transform_combine(camera_position, camera_rotation);
+        scene_info.camera.projection = glm::perspective(glm::radians(camera_fov),
+                                                        (float) frame_size.aspectRatio(),
+                                                        camera_near, camera_far);
+        if (light_follow_camera) {
+            auto rot_mat = glm::toMat3(std::bit_cast<glm::quat>(camera_rotation)); // world in camera
+            rot_mat = glm::inverse(rot_mat); // camera in world;
+            light_direction = std::bit_cast<vgm::Vec3>(-glm::column(rot_mat, 2));
+        }
+        scene_info.light.direction = std::bit_cast<glm::vec3>(light_direction);
+
+        glClear(GL_DEPTH_BUFFER_BIT);
+        render->render(model_mesh.get(), model_info,
+                       bg_mesh.get(), bg_info,
+                       scene_info, alpha_factor, show_bg);
+
+        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+        glfwSwapBuffers(main_window);
+    }
+
+    return 0;
+}

+ 330 - 0
src/mesh_render.cpp

@@ -0,0 +1,330 @@
+#include "mesh_render.h"
+#include "render_utility.h"
+#include "texture_render.h"
+
+#include <vtkCellArray.h>
+#include <vtkCubeSource.h>
+#include <vtkFloatArray.h>
+#include <vtkNew.h>
+#include <vtkOBJReader.h>
+#include <vtkPointData.h>
+#include <vtkPoints.h>
+#include <vtkPolyData.h>
+#include <vtkPolyDataNormals.h>
+#include <vtkSTLReader.h>
+#include <vtkTriangleFilter.h>
+
+#include <glm/gtc/type_ptr.hpp>
+
+#include <spdlog/spdlog.h>
+
+#include <filesystem>
+
+struct mesh_type::impl {
+
+    struct vertex_info {
+        glm::vec3 position;
+        glm::vec3 normal;
+    };
+
+    size_t triangle_num;
+    smart_buffer<vertex_info> vbo_data;
+    smart_buffer<GLuint> ebo_data;
+
+    size_t modify_id = 0, last_upload_id = 0;
+    GLuint vao = 0, vbo = 0, ebo = 0;
+
+    void upload() {
+        if (last_upload_id == modify_id) [[likely]] return;
+        assert(last_upload_id < modify_id);
+
+        // delete old objects
+        glDeleteVertexArrays(1, &vao);
+        glDeleteBuffers(1, &vbo);
+        glDeleteBuffers(1, &ebo);
+
+        // config vertex buffer
+        glGenBuffers(1, &vbo);
+        glBindBuffer(GL_ARRAY_BUFFER, vbo);
+        glBufferData(GL_ARRAY_BUFFER, vbo_data.size(), vbo_data.ptr, GL_STATIC_DRAW);
+
+        // fill element buffer
+        glGenBuffers(1, &ebo);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, ebo_data.size(), ebo_data.ptr, GL_STATIC_DRAW);
+
+        // config vertex array
+        glGenVertexArrays(1, &vao);
+        glBindVertexArray(vao);
+        glEnableVertexAttribArray(0);
+        glEnableVertexAttribArray(1);
+        glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(vertex_info), (void *) offsetof(vertex_info, position));
+        glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof(vertex_info), (void *) offsetof(vertex_info, normal));
+
+        last_upload_id = modify_id;
+    }
+
+    void create_from_poly_data(vtkPolyData *poly) {
+        // calculate vertex normal if needed
+        vtkNew<vtkPolyDataNormals> filter;
+        if (poly->GetPointData()->GetNormals() == nullptr) {
+            filter->SetInputData(poly);
+            filter->Update();
+            poly = filter->GetOutput();
+        }
+
+        // copy vertex data
+        auto points = poly->GetPoints();
+        auto vertex_num = points->GetNumberOfPoints();
+        auto vertex_normal = (vtkFloatArray *) poly->GetPointData()->GetNormals();
+        assert(vertex_normal != nullptr);
+        assert(vertex_normal->GetNumberOfTuples() == vertex_num);
+        vbo_data.create(vertex_num);
+        for (auto k = 0; k < vertex_num; ++k) {
+            glm::dvec3 position, normal;
+            points->GetPoint(k, glm::value_ptr(position));
+            vertex_normal->GetTuple(k, glm::value_ptr(normal));
+            vbo_data.ptr[k].position = position;
+            vbo_data.ptr[k].normal = normal;
+        }
+
+        // copy index data
+        auto cells = poly->GetPolys();
+        triangle_num = cells->GetNumberOfCells();
+        ebo_data.create(triangle_num * 3);
+        vtkNew<vtkIdList> cell;
+        for (auto k = 0; k < triangle_num; ++k) {
+            auto ptr = (GLuint *) ebo_data.ptr + k * 3;
+            cells->GetCellAtId(k, cell);
+            assert(cell->GetNumberOfIds() == 3);
+            for (auto i = 0; i < 3; ++i) {
+                auto vertex_id = cell->GetId(i);
+                assert(vertex_id < vertex_num);
+                ptr[i] = vertex_id;
+            }
+        }
+
+        ++modify_id;
+    }
+
+    void draw() {
+        upload();
+        glBindVertexArray(vao);
+        glBindBuffer(GL_ARRAY_BUFFER, vbo);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+        glDrawElements(GL_TRIANGLES, 3 * triangle_num, GL_UNSIGNED_INT, nullptr);
+    }
+
+    static impl *from_reader(const char *path,
+                             vtkAbstractPolyDataReader *reader) {
+        reader->SetFileName(path);
+        reader->Update();
+        auto ret = new impl{};
+        ret->create_from_poly_data(reader->GetOutput());
+        return ret;
+    }
+
+    static impl *from_test_demo() {
+        vtkNew<vtkCubeSource> cube;
+        vtkNew<vtkTriangleFilter> filter;
+        filter->SetInputConnection(cube->GetOutputPort());
+        filter->Update();
+        auto ret = new impl{};
+        ret->create_from_poly_data(filter->GetOutput());
+        return ret;
+    }
+
+    static mesh_type *mesh_type_from_reader(const char *path,
+                                            vtkAbstractPolyDataReader *reader) {
+        auto ret = new mesh_type;
+        ret->pimpl.reset(
+                mesh_type::impl::from_reader(path, reader));
+        return ret;
+    }
+
+};
+
+mesh_type::mesh_type() = default;
+
+mesh_type::~mesh_type() = default;
+
+mesh_type *mesh_type::from_stl(const char *path) {
+    vtkNew<vtkSTLReader> reader;
+    return impl::mesh_type_from_reader(path, reader);
+}
+
+mesh_type *mesh_type::from_obj(const char *path) {
+    vtkNew<vtkOBJReader> reader;
+    return impl::mesh_type_from_reader(path, reader);
+}
+
+mesh_type *mesh_type::from_test_demo() {
+    auto ret = new mesh_type;
+    ret->pimpl.reset(mesh_type::impl::from_test_demo());
+    return ret;
+}
+
+namespace mesh_render_impl {
+
+    void ensure_program(smart_program *program) {
+#ifndef NDEBUG
+        GLuint program_id;
+        glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *) &program_id);
+        assert(program_id == program->id);
+#endif
+    }
+
+    void upload_scene_property(smart_program *program,
+                               const scene_property &scene_info) {
+        ensure_program(program);
+        program->set_uniform_mat4("camera_mat", scene_info.camera.projection
+                                                * scene_info.camera.transform);
+        program->set_uniform_vec3("light.direction", scene_info.light.direction);
+    }
+
+    void upload_mesh_property(smart_program *program,
+                              const mesh_property &mesh_info) {
+        ensure_program(program);
+        program->set_uniform_mat4("model_mat", mesh_info.transform);
+        program->set_uniform_vec3("material.ambient", mesh_info.material.ambient);
+        program->set_uniform_vec3("material.diffuse", mesh_info.material.diffuse);
+    }
+
+}
+
+using namespace mesh_render_impl;
+
+struct mesh_render::impl {
+
+    std::unique_ptr<smart_program> program_basic;
+    std::unique_ptr<smart_program> program_mask;
+    std::unique_ptr<smart_program> program_depth_alpha;
+
+    smart_frame_buffer bg_frame;
+    smart_frame_buffer model_frame;
+
+    texture_render tex_render;
+
+    impl() {
+        std::filesystem::path shader_folder = "/home/tpx/project/TransparentAR/src/shader";
+
+        auto vert_basic = shader_folder / "mesh_render.vert";
+        auto frag_basic = shader_folder / "mesh_render.frag";
+        program_basic.reset(smart_program::create(vert_basic.c_str(),
+                                                  frag_basic.c_str()));
+
+        auto vert_mask = shader_folder / "mesh_render_mask.vert";
+        auto frag_mask = shader_folder / "mesh_render_mask.frag";
+        program_mask.reset(smart_program::create(vert_mask.c_str(),
+                                                 frag_mask.c_str()));
+
+        auto frag_depth_alpha = shader_folder / "mesh_render_depth_alpha.frag";
+        program_depth_alpha.reset(smart_program::create(vert_basic.c_str(),
+                                                        frag_depth_alpha.c_str()));
+    }
+
+    void render(mesh_type *mesh,
+                const mesh_property &mesh_info,
+                const scene_property &scene_info) {
+        program_basic->use();
+        upload_mesh_property(program_basic.get(), mesh_info);
+        upload_scene_property(program_basic.get(), scene_info);
+        mesh->pimpl->draw();
+    }
+
+    void render(mesh_type *model, const mesh_property &model_info,
+                mesh_type *bg_mesh, const mesh_property &bg_info,
+                const scene_property &scene_info,
+                float alpha_factor, bool show_bg) {
+
+        // config frame buffers
+        GLint viewport[4]; // x, y, width, height
+        glGetIntegerv(GL_VIEWPORT, viewport);
+        auto view_size = cv::Size{viewport[2], viewport[3]};
+        bg_frame.create(view_size);
+        model_frame.create(view_size);
+
+        // render background
+        bg_frame.bind();
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+        if (show_bg) {
+            render(bg_mesh, bg_info, scene_info);
+        } else { // use a simpler program
+            auto bg_mat = scene_info.camera.projection
+                          * scene_info.camera.transform
+                          * bg_info.transform;
+            program_mask->use();
+            program_mask->set_uniform_mat4("transform_mat", bg_mat);
+            bg_mesh->pimpl->draw();
+        }
+        bg_frame.unbind();
+
+        // render model
+        model_frame.bind();
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+        render(model, model_info, scene_info);
+        model_frame.unbind();
+
+        // real draw frame
+        GLboolean depth_enable;
+        glGetBooleanv(GL_DEPTH_TEST, &depth_enable);
+        glDisable(GL_DEPTH_TEST);
+        if (show_bg) {
+            tex_render.render(bg_frame.color_tex());
+        }
+        tex_render.render_depth_alpha(model_frame.color_tex(), model_frame.depth_tex(), bg_frame.depth_tex(),
+                                      scene_info.camera.projection, alpha_factor);
+        if (depth_enable) {
+            glEnable(GL_DEPTH_TEST);
+        }
+
+//        // render model
+//        program_depth_alpha->use();
+//        upload_mesh_property(program_depth_alpha.get(), model_info);
+//        upload_scene_property(program_depth_alpha.get(), scene_info);
+//
+//        GLfloat depth_range[2];
+//        glGetFloatv(GL_DEPTH_RANGE, depth_range);
+//        auto viewport_min = glm::vec3{viewport[0], viewport[1], depth_range[0]};
+//        auto viewport_max = glm::vec3{viewport[0] + viewport[2], // x + width
+//                                      viewport[1] + viewport[3], // y + height
+//                                      depth_range[1]};
+//        auto viewport_middle = 0.5f * (viewport_max + viewport_min);
+//        auto viewport_half_inv = 2.0f / (viewport_max - viewport_min);
+//        auto project_inv = glm::inverse(scene_info.camera.projection);
+//        program_depth_alpha->set_uniform_vec3("viewport.middle", viewport_middle);
+//        program_depth_alpha->set_uniform_vec3("viewport.half_inv", viewport_half_inv);
+//        program_depth_alpha->set_uniform_mat4("viewport.proj_inv", project_inv);
+//
+//        alpha_factor = alpha_factor / (1.0f - alpha_factor); // [0, 1] -> [0, +inf)
+//        program_depth_alpha->set_uniform_f("alpha_factor", alpha_factor);
+//
+//        glActiveTexture(GL_TEXTURE0 + 0);
+//        glBindTexture(GL_TEXTURE_2D, bg_frame.depth_tex());
+//        program_depth_alpha->set_uniform_i("bg_depth_tex", 0);
+//
+//        glClear(GL_DEPTH_BUFFER_BIT);
+//        model->pimpl->draw();
+    }
+
+};
+
+mesh_render::mesh_render()
+        : pimpl(std::make_unique<impl>()) {}
+
+mesh_render::~mesh_render() = default;
+
+void mesh_render::render(mesh_type *mesh,
+                         const mesh_property &mesh_info,
+                         const scene_property &scene_info) {
+    pimpl->render(mesh, mesh_info, scene_info);
+}
+
+void
+mesh_render::render(mesh_type *model, const mesh_property &model_info,
+                    mesh_type *bg_mesh, const mesh_property &bg_info,
+                    const scene_property &scene_info,
+                    float alpha_factor, bool show_bg) {
+    pimpl->render(model, model_info, bg_mesh, bg_info,
+                  scene_info, alpha_factor, show_bg);
+}

+ 76 - 0
src/mesh_render.h

@@ -0,0 +1,76 @@
+#ifndef TRANSPARENTAR_MESH_RENDER_H
+#define TRANSPARENTAR_MESH_RENDER_H
+
+#include "utility.hpp"
+
+#include <glad/gl.h>
+#include <glm/glm.hpp>
+
+#include <opencv2/core/types.hpp>
+
+#include <memory>
+
+struct mesh_type {
+public:
+    ~mesh_type();
+
+    static mesh_type *from_stl(const char *path);
+
+    static mesh_type *from_obj(const char *path);
+
+    static mesh_type *from_test_demo();
+
+private:
+    mesh_type();
+
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+
+    friend class mesh_render;
+};
+
+struct material_type {
+    glm::vec3 ambient;
+    glm::vec3 diffuse;
+};
+
+struct mesh_property {
+    glm::mat4 transform;
+    material_type material;
+};
+
+struct camera_property {
+    glm::mat4 transform;
+    glm::mat4 projection;
+};
+
+struct light_property {
+    glm::vec3 direction; // simulate daylight
+};
+
+struct scene_property {
+    camera_property camera;
+    light_property light;
+};
+
+class mesh_render {
+public:
+    mesh_render();
+
+    ~mesh_render();
+
+    void render(mesh_type *mesh,
+                const mesh_property &mesh_info,
+                const scene_property &scene_info);
+
+    void render(mesh_type *model, const mesh_property &model_info,
+                mesh_type *bg_mesh, const mesh_property &bg_info,
+                const scene_property &scene_info,
+                float alpha_factor = 0.25f, bool show_bg = false);
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+#endif //TRANSPARENTAR_MESH_RENDER_H

+ 258 - 0
src/render_utility.cpp

@@ -0,0 +1,258 @@
+#include "render_utility.h"
+#include "utility.hpp"
+
+#include <glm/gtc/type_ptr.hpp>
+
+#include <fmt/format.h>
+#include <spdlog/spdlog.h>
+
+#include <boost/iostreams/device/mapped_file.hpp>
+
+using boost::iostreams::mapped_file;
+
+struct smart_texture::impl {
+
+    smart_texture *q_this = nullptr;
+    cv::Size last_size = {};
+
+    void create(GLenum format, cv::Size size, GLint min_filter, GLint max_filter) {
+        if (size == last_size) [[likely]] return;
+        deallocate();
+        allocate(format, size, min_filter, max_filter);
+    }
+
+    void allocate(GLenum format, cv::Size size,
+                  GLint min_filter, GLint max_filter) {
+        glGenTextures(1, &q_this->id);
+        glBindTexture(GL_TEXTURE_2D, q_this->id);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, max_filter);
+        glTexStorage2D(GL_TEXTURE_2D, 1, format, size.width, size.height);
+        glBindTexture(GL_TEXTURE_2D, 0);
+        last_size = size;
+    }
+
+    void deallocate() {
+        if (q_this->id == 0) return;
+        glDeleteTextures(1, &q_this->id);
+        q_this->id = 0;
+        last_size = {};
+    }
+
+};
+
+smart_texture::smart_texture()
+        : pimpl(std::make_unique<impl>()) {
+    pimpl->q_this = this;
+}
+
+smart_texture::~smart_texture() = default;
+
+void smart_texture::create(GLenum format, cv::Size size, GLint min_filter, GLint max_filter) {
+    pimpl->create(format, size, min_filter, max_filter);
+}
+
+cv::Size smart_texture::size() const {
+    return pimpl->last_size;
+}
+
+struct smart_frame_buffer::impl {
+
+    smart_frame_buffer *q_this = nullptr;
+    cv::Size last_size = {};
+    smart_texture color_tex, depth_tex;
+
+    static void check_frame_buffer() {
+        auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) [[unlikely]] {
+            SPDLOG_ERROR("Framebuffer is not complete 0x{:x}.", status);
+            RET_ERROR;
+        }
+    }
+
+    void create(cv::Size size) {
+        if (size == last_size) [[likely]] return;
+        deallocate();
+        allocate(size);
+    }
+
+    void allocate(cv::Size size) {
+        // allocate buffer and textures
+        color_tex.create(GL_RGB8, size);
+//        depth_tex.create(GL_DEPTH_COMPONENT16, size);
+        depth_tex.create(GL_DEPTH_COMPONENT32F, size);
+
+        // create frame buffer
+        glGenFramebuffers(1, &q_this->id);
+        glBindFramebuffer(GL_FRAMEBUFFER, q_this->id);
+        glBindTexture(GL_TEXTURE_2D, color_tex.id);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex.id, 0);
+        glBindTexture(GL_TEXTURE_2D, depth_tex.id);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex.id, 0);
+        check_frame_buffer();
+
+        last_size = size;
+    }
+
+    void deallocate() {
+        if (q_this->id == 0) return;
+        glDeleteFramebuffers(1, &q_this->id);
+        last_size = {};
+    }
+
+};
+
+smart_frame_buffer::smart_frame_buffer()
+        : pimpl(std::make_unique<impl>()) {
+    pimpl->q_this = this;
+}
+
+smart_frame_buffer::~smart_frame_buffer() = default;
+
+void smart_frame_buffer::create(cv::Size size) {
+    pimpl->create(size);
+}
+
+cv::Size smart_frame_buffer::size() const {
+    return pimpl->last_size;
+}
+
+void smart_frame_buffer::bind() {
+    assert(id != 0);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, id);
+    glViewport(0, 0, pimpl->last_size.width, pimpl->last_size.height);
+}
+
+void smart_frame_buffer::unbind() {
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+}
+
+GLuint smart_frame_buffer::color_tex() const {
+    return pimpl->color_tex.id;
+}
+
+GLuint smart_frame_buffer::depth_tex() const {
+    return pimpl->depth_tex.id;
+}
+
+namespace smart_program_impl {
+
+    void compile_shader(GLuint shader, const char *path, const char *name) {
+        auto file = mapped_file{path, mapped_file::readonly};
+        assert(file.is_open());
+        auto file_content = file.const_data();
+        GLint file_size = file.size();
+        glShaderSource(shader, 1, &file_content, &file_size);
+        glCompileShader(shader);
+        GLint status, log_length;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+        auto info_log = (GLchar *) malloc(log_length);
+        glGetShaderInfoLog(shader, log_length, nullptr, info_log);
+        if (status == GL_TRUE) {
+            SPDLOG_INFO("Compile {} shader succeeded: {}", name, info_log);
+        } else {
+            SPDLOG_ERROR("Compile {} shader failed: {}", name, info_log);
+            RET_ERROR;
+        }
+        free(info_log);
+    }
+
+    static void check_program(GLuint program) {
+        GLint status, log_length;
+        glGetProgramiv(program, GL_LINK_STATUS, &status);
+        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+        auto info_log = (GLchar *) malloc(log_length);
+        glGetProgramInfoLog(program, log_length, nullptr, info_log);
+        if (status == GL_TRUE) {
+            SPDLOG_INFO("Link program succeeded: {}", info_log);
+        } else {
+            SPDLOG_ERROR("Link program failed: {}", info_log);
+            RET_ERROR;
+        }
+        free(info_log);
+    }
+
+    GLuint create_program(const char *vert_shader_path,
+                          const char *frag_shader_path) {
+        static auto index = 0;
+        ++index;
+
+        auto vert_shader = glCreateShader(GL_VERTEX_SHADER);
+        auto frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
+        compile_shader(vert_shader, vert_shader_path, fmt::format("vertex_shader_{}", index).c_str());
+        compile_shader(frag_shader, frag_shader_path, fmt::format("fragment_shader_{}", index).c_str());
+
+        auto program_id = glCreateProgram();
+        glAttachShader(program_id, vert_shader);
+        glAttachShader(program_id, frag_shader);
+        glLinkProgram(program_id);
+        check_program(program_id);
+
+        glDeleteShader(vert_shader);
+        glDeleteShader(frag_shader);
+
+        return program_id;
+    }
+
+}
+
+using namespace smart_program_impl;
+
+struct smart_program::impl {
+
+    smart_program *q_this = nullptr;
+
+    using uniform_locs_type = std::unordered_map<std::string, GLint>;
+    uniform_locs_type uniform_locs;
+
+    GLint query_uniform_loc(const char *name) {
+        if (!uniform_locs.contains(name)) [[unlikely]] {
+            auto loc = glGetUniformLocation(q_this->id, name);
+            uniform_locs.emplace(std::make_pair(name, loc));
+            if (loc == -1) {
+                SPDLOG_WARN("Uniform {} is not found.", name);
+            }
+        }
+        return uniform_locs.at(name);
+    }
+
+};
+
+smart_program::smart_program()
+        : pimpl(std::make_unique<impl>()) {
+    pimpl->q_this = this;
+}
+
+smart_program::~smart_program() = default;
+
+smart_program *smart_program::create(const char *vert_source_path,
+                                     const char *frag_source_path) {
+    auto ret = new smart_program{};
+    ret->id = create_program(vert_source_path, frag_source_path);
+    return ret;
+}
+
+void smart_program::use() const {
+    glUseProgram(id);
+}
+
+void smart_program::set_uniform_i(const char *name, GLint val) {
+    auto loc = pimpl->query_uniform_loc(name);
+    glUniform1i(loc, val);
+}
+
+void smart_program::set_uniform_f(const char *name, GLfloat val) {
+    auto loc = pimpl->query_uniform_loc(name);
+    glUniform1f(loc, val);
+}
+
+void smart_program::set_uniform_vec3(const char *name, const glm::vec3 &vec) {
+    auto loc = pimpl->query_uniform_loc(name);
+    glUniform3f(loc, vec.x, vec.y, vec.z);
+}
+
+void smart_program::set_uniform_mat4(const char *name, const glm::mat4 &mat) {
+    auto loc = pimpl->query_uniform_loc(name);
+    glUniformMatrix4fv(loc, 1, false, glm::value_ptr(mat));
+}

+ 77 - 0
src/render_utility.h

@@ -0,0 +1,77 @@
+#ifndef TRANSPARENTAR_RENDER_UTILITY_H
+#define TRANSPARENTAR_RENDER_UTILITY_H
+
+#include <glad/gl.h>
+#include <glm/glm.hpp>
+
+#include <opencv2/core/types.hpp>
+
+struct smart_texture {
+    GLuint id = 0;
+
+    smart_texture();
+
+    ~smart_texture();
+
+    void create(GLenum format, cv::Size size,
+                GLint min_filter = GL_NEAREST, GLint max_filter = GL_NEAREST);
+
+    cv::Size size() const;
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+class smart_frame_buffer {
+public:
+    GLuint id = 0;
+
+    smart_frame_buffer();
+
+    ~smart_frame_buffer();
+
+    void create(cv::Size size);
+
+    cv::Size size() const;
+
+    void bind();
+
+    static void unbind();
+
+    GLuint color_tex() const;
+
+    GLuint depth_tex() const;
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+class smart_program {
+public:
+    GLuint id = 0;
+
+    ~smart_program();
+
+    static smart_program *create(const char *vert_source_path,
+                                 const char *frag_source_path);
+
+    void use() const;
+
+    void set_uniform_i(const char *name, GLint val);
+
+    void set_uniform_f(const char *name, GLfloat val);
+
+    void set_uniform_vec3(const char *name, const glm::vec3 &vec);
+
+    void set_uniform_mat4(const char *name, const glm::mat4 &mat);
+
+private:
+    smart_program();
+
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+#endif //TRANSPARENTAR_RENDER_UTILITY_H

+ 23 - 0
src/shader/mesh_render.frag

@@ -0,0 +1,23 @@
+#version 460
+
+struct material_type {
+    vec3 ambient;
+    vec3 diffuse;
+};
+
+struct light_property {
+    vec3 direction;
+};
+
+uniform material_type material;
+uniform light_property light;
+
+in vec3 frag_normal;
+
+layout (location = 0) out vec4 frag_color;
+
+void main() {
+    float diffuse_weight = max(-dot(frag_normal, light.direction), 0.0);
+    vec3 color = material.ambient + diffuse_weight * material.diffuse;
+    frag_color = vec4(color, 1.0);
+}

+ 14 - 0
src/shader/mesh_render.vert

@@ -0,0 +1,14 @@
+#version 460
+
+uniform mat4 model_mat;
+uniform mat4 camera_mat;
+
+layout (location = 0) in vec3 position;
+layout (location = 1) in vec3 normal;
+
+out vec3 frag_normal;
+
+void main() {
+    frag_normal = mat3(model_mat) * normal;
+    gl_Position = camera_mat * model_mat * vec4(position, 1.0);
+}

+ 55 - 0
src/shader/mesh_render_depth_alpha.frag

@@ -0,0 +1,55 @@
+#version 460
+
+struct material_type {
+    vec3 ambient;
+    vec3 diffuse;
+};
+
+struct light_property {
+    vec3 direction;
+};
+
+struct viewport_property {
+    vec3 middle; // (max + min) / 2
+    vec3 half_inv; // 2 / (max - min)
+    mat4 proj_inv; // projection^-1
+};
+
+uniform material_type material;
+uniform light_property light;
+uniform viewport_property viewport;
+
+uniform float alpha_factor;
+
+uniform sampler2D bg_depth_tex;
+
+in vec3 frag_normal;
+
+layout (location = 0) out vec4 frag_color;
+
+void main() {
+    // determine color
+    float diffuse_weight = max(-dot(frag_normal, light.direction), 0.0);
+    vec3 color = material.ambient + diffuse_weight * material.diffuse;
+
+    // fragment depth
+    vec3 frag_ndc = viewport.half_inv * (gl_FragCoord.xyz - viewport.middle); // window space -> NDC
+    float frag_depth = frag_ndc.z;
+    vec4 frag_position = viewport.proj_inv * vec4(frag_ndc, 1.0); // NDC -> camera
+    frag_position.xyz /= frag_position.w; // homogeneous -> cartesian
+
+    // background depth
+    vec2 bg_tex_coord = 0.5 * (frag_ndc.xy + 1);
+    float bg_depth = texture(bg_depth_tex, bg_tex_coord).x; // [0, 1]
+    bg_depth = 2 * bg_depth - 1; // [0, 1] -> NDC
+    vec4 bg_position = viewport.proj_inv * vec4(frag_ndc.xy, bg_depth, 1.0); // NDC -> camera
+    bg_position.xyz /= bg_position.w; // homogeneous -> cartesian
+
+    float alpha = 1.0;
+    if (frag_depth > bg_depth) {
+        float depth_dis = distance(frag_position.xyz, bg_position.xyz);
+        alpha = exp(-alpha_factor * depth_dis);
+    }
+
+    frag_color = vec4(color, alpha);
+}

+ 7 - 0
src/shader/mesh_render_mask.frag

@@ -0,0 +1,7 @@
+#version 460
+
+layout (location = 0) out vec4 frag_color;
+
+void main() {
+    frag_color = vec4(1.0);
+}

+ 9 - 0
src/shader/mesh_render_mask.vert

@@ -0,0 +1,9 @@
+#version 460
+
+uniform mat4 transform_mat;
+
+layout (location = 0) in vec3 position;
+
+void main() {
+    gl_Position = transform_mat * vec4(position, 1.0);
+}

+ 11 - 0
src/shader/texture_render.frag

@@ -0,0 +1,11 @@
+#version 460
+
+uniform sampler2D tex;
+
+in vec2 frag_uv;
+
+layout (location = 0) out vec4 frag_color;
+
+void main() {
+    frag_color = texture(tex, frag_uv);
+}

+ 11 - 0
src/shader/texture_render.vert

@@ -0,0 +1,11 @@
+#version 460
+
+layout (location = 0) in vec2 position;
+layout (location = 1) in vec2 tex_uv;
+
+out vec2 frag_uv;
+
+void main() {
+    gl_Position = vec4(position, 0.0, 1.0);
+    frag_uv = tex_uv;
+}

+ 45 - 0
src/shader/texture_render_depth_alpha.frag

@@ -0,0 +1,45 @@
+#version 460
+
+uniform float alpha_factor;
+uniform mat4 proj_inv;
+
+uniform sampler2D color_tex;
+uniform sampler2D main_depth_tex;
+uniform sampler2D bg_depth_tex;
+
+in vec2 frag_uv;
+
+layout (location = 0) out vec4 frag_color;
+
+vec3 ndc_to_camera(in vec3 ndc) {
+    vec4 position = proj_inv * vec4(ndc, 1.0);// NDC -> camera
+    return position.xyz / position.w;// homogeneous -> cartesian
+}
+
+vec3 tex_to_ndc(in vec3 coord) {
+    return 2 * coord - 1;
+}
+
+vec3 tex_to_camera(in vec3 coord) {
+    return ndc_to_camera(tex_to_ndc(coord));
+}
+
+void main() {
+    float main_depth = texture(main_depth_tex, frag_uv).x;
+    if (main_depth == 1.0) {
+        frag_color = vec4(0.0);
+        return;
+    }
+
+    float alpha = 1.0;
+    float bg_depth = texture(bg_depth_tex, frag_uv).x;
+    if (main_depth > bg_depth) {
+        vec3 main_position = tex_to_camera(vec3(frag_uv, main_depth));
+        vec3 bg_position = tex_to_camera(vec3(frag_uv, bg_depth));
+        float depth_dis = distance(main_position, bg_position);
+        alpha = exp(-alpha_factor * depth_dis);
+    }
+
+    vec3 color = texture(color_tex, frag_uv).xyz;
+    frag_color = vec4(color, alpha);
+}

+ 137 - 0
src/texture_render.cpp

@@ -0,0 +1,137 @@
+#include "texture_render.h"
+#include "render_utility.h"
+
+#include <filesystem>
+
+namespace texture_render_impl {
+
+    GLuint rect_indices[] = {
+            0, 1, 3, // first triangle
+            1, 2, 3 // second triangle
+    };
+
+}
+
+using namespace texture_render_impl;
+
+struct texture_render::impl {
+
+    GLuint vao = 0, vbo = 0, ebo = 0;
+
+    std::unique_ptr<smart_program> program_basic;
+    std::unique_ptr<smart_program> program_depth_alpha;
+
+    impl() {
+        create_programs();
+        create_buffers();
+    }
+
+    void create_buffers() {
+        // create vertex buffer
+        glGenBuffers(1, &vbo);
+        glBindBuffer(GL_ARRAY_BUFFER, vbo);
+        glBufferStorage(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), nullptr, GL_DYNAMIC_STORAGE_BIT);
+
+        // create index buffer
+        glGenBuffers(1, &ebo);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(rect_indices), rect_indices, GL_STATIC_DRAW);
+
+        // config vertex array
+        glGenVertexArrays(1, &vao);
+        glBindVertexArray(vao);
+        glEnableVertexAttribArray(0);
+        glEnableVertexAttribArray(1);
+        glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * sizeof(GLfloat), (void *) 0);
+        glVertexAttribPointer(1, 2, GL_FLOAT, false, 4 * sizeof(GLfloat), (void *) (2 * sizeof(GLfloat)));
+    }
+
+    void create_programs() {
+        std::filesystem::path shader_folder = "/home/tpx/project/TransparentAR/src/shader";
+
+        auto vert_basic = shader_folder / "texture_render.vert";
+        auto frag_basic = shader_folder / "texture_render.frag";
+        program_basic.reset(smart_program::create(vert_basic.c_str(),
+                                                  frag_basic.c_str()));
+
+        auto frag_depth_alpha = shader_folder / "texture_render_depth_alpha.frag";
+        program_depth_alpha.reset(smart_program::create(vert_basic.c_str(),
+                                                        frag_depth_alpha.c_str()));
+    }
+
+    void config_buffer(const simple_rect &rect, bool flip_y) {
+        // bind buffers
+        glBindVertexArray(vao);
+        glBindBuffer(GL_ARRAY_BUFFER, vbo);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+
+        // fill vertex buffer
+        GLfloat tex_top = flip_y ? 0 : 1;
+        GLfloat tex_bottom = flip_y ? 1 : 0;
+        GLfloat vertices[] = {
+                // 2 for position; 2 for texture
+                rect.x + rect.width, rect.y + rect.height, 1, tex_top, // top right
+                rect.x + rect.width, rect.y, 1, tex_bottom, // bottom right
+                rect.x, rect.y, 0, tex_bottom, // bottom left
+                rect.x, rect.y + rect.height, 0, tex_top // top left
+        };
+        static_assert(sizeof(vertices) == 16 * sizeof(GLfloat));
+        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
+    }
+
+    void draw() {
+        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
+    }
+
+    void render(GLuint tex, const simple_rect &rect, bool flip_y) {
+        program_basic->use();
+        config_buffer(rect, flip_y);
+
+        glActiveTexture(GL_TEXTURE0 + 0);
+        glBindTexture(GL_TEXTURE_2D, tex);
+        program_basic->set_uniform_i("tex", 0);
+
+        draw();
+    }
+
+    void render_depth_alpha(GLuint color_tex, GLuint main_depth_tex, GLuint bg_depth_tex,
+                            const glm::mat4 &projection, float alpha_factor,
+                            const simple_rect &rect, bool flip_y) {
+        program_depth_alpha->use();
+        config_buffer(rect, flip_y);
+
+        auto project_inv = glm::inverse(projection);
+        program_depth_alpha->set_uniform_mat4("proj_inv", project_inv);
+        alpha_factor = alpha_factor / (1.0f - alpha_factor); // [0, 1] -> [0, +inf)
+        program_depth_alpha->set_uniform_f("alpha_factor", alpha_factor);
+
+        glActiveTexture(GL_TEXTURE0 + 0);
+        glBindTexture(GL_TEXTURE_2D, color_tex);
+        program_depth_alpha->set_uniform_i("color_tex", 0);
+        glActiveTexture(GL_TEXTURE0 + 1);
+        glBindTexture(GL_TEXTURE_2D, main_depth_tex);
+        program_depth_alpha->set_uniform_i("main_depth_tex", 1);
+        glActiveTexture(GL_TEXTURE0 + 2);
+        glBindTexture(GL_TEXTURE_2D, bg_depth_tex);
+        program_depth_alpha->set_uniform_i("bg_depth_tex", 2);
+
+        draw();
+    }
+
+};
+
+texture_render::texture_render()
+        : pimpl(std::make_unique<impl>()) {}
+
+texture_render::~texture_render() = default;
+
+void texture_render::render(GLuint tex, const simple_rect &rect, bool flip_y) {
+    pimpl->render(tex, rect, flip_y);
+}
+
+void texture_render::render_depth_alpha(GLuint color_tex, GLuint main_depth_tex, GLuint bg_depth_tex,
+                                        const glm::mat4 &projection, float alpha_factor,
+                                        const simple_rect &rect, bool flip_y) {
+    pimpl->render_depth_alpha(color_tex, main_depth_tex, bg_depth_tex,
+                              projection, alpha_factor, rect, flip_y);
+}

+ 32 - 0
src/texture_render.h

@@ -0,0 +1,32 @@
+#ifndef TRANSPARENTAR_TEXTURE_RENDER_H
+#define TRANSPARENTAR_TEXTURE_RENDER_H
+
+#include <glad/gl.h>
+#include <glm/glm.hpp>
+
+#include <memory>
+
+struct simple_rect {
+    GLfloat x = -1.0f, y = -1.0f;
+    GLfloat width = 2.0f, height = 2.0f;
+};
+
+class texture_render {
+public:
+    texture_render();
+
+    ~texture_render();
+
+    void render(GLuint tex, const simple_rect &rect = {}, bool flip_y = false);
+
+    void render_depth_alpha(GLuint color_tex, GLuint main_depth_tex, GLuint bg_depth_tex,
+                            const glm::mat4 &projection, float alpha_factor = 0.2,
+                            const simple_rect &rect = {}, bool flip_y = false);
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+
+#endif //TRANSPARENTAR_TEXTURE_RENDER_H

+ 54 - 0
src/utility.hpp

@@ -0,0 +1,54 @@
+#ifndef TRANSPARENTAR_UTILITY_HPP
+#define TRANSPARENTAR_UTILITY_HPP
+
+#include <cstdlib>
+
+// https://en.cppreference.com/w/cpp/utility/unreachable
+[[noreturn]] inline void unreachable() {
+    // Uses compiler specific extensions if possible.
+    // Even if no extension is used, undefined behavior is still raised by
+    // an empty function body and the noreturn attribute.
+#ifdef __GNUC__ // GCC, Clang, ICC
+    __builtin_unreachable();
+// #elifdef _MSC_VER // MSVC
+#else
+    __assume(false);
+#endif
+}
+
+#define RET_ERROR \
+    assert(false);\
+    unreachable()
+
+template<typename T>
+struct smart_buffer {
+    T *ptr = nullptr;
+    size_t length = 0;
+
+    smart_buffer() = default;
+
+    template<typename U=T>
+    smart_buffer(const smart_buffer<U> &other) = delete;
+
+    ~smart_buffer() {
+        delete ptr;
+    }
+
+    void create(size_t req_length) {
+        if (req_length > capacity) [[unlikely]] {
+            delete ptr;
+            ptr = new T[req_length];
+            capacity = req_length;
+        }
+        length = req_length;
+    }
+
+    size_t size() const {
+        return length * sizeof(T);
+    }
+
+private:
+    size_t capacity = 0;
+};
+
+#endif //TRANSPARENTAR_UTILITY_HPP