Skip to content

Canny edge detector

Romain Milbert edited this page Dec 8, 2022 · 5 revisions

⚠️ This page is a work in progress; the code presented here may not work as expected, or at all.


As the time of writing (2022-12-08, commit b3646d3), the following files give the result shown in this video:

RaZ Playground - 3D curl noise animation

main.cpp

#include <RaZ/Application.hpp>
#include <RaZ/Data/Mesh.hpp>
#include <RaZ/Data/ObjFormat.hpp>
#include <RaZ/Math/Transform.hpp>
#include <RaZ/Render/Camera.hpp>
#include <RaZ/Render/GaussianBlurRenderProcess.hpp>
#include <RaZ/Render/Material.hpp>
#include <RaZ/Render/MeshRenderer.hpp>
#include <RaZ/Render/RenderSystem.hpp>
#include <RaZ/Render/SobelFilterRenderProcess.hpp>

using namespace Raz::Literals;
using namespace std::literals;

int main() {
  Raz::Application app;
  Raz::World& world = app.addWorld(2);

  auto& renderSystem = world.addSystem<Raz::RenderSystem>(1280, 720, "RaZ");

  Raz::Window& window = renderSystem.getWindow();
  window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float) noexcept { app.quit(); });
  window.setCloseCallback([&app] () noexcept { app.quit(); });

  Raz::Entity& camera = world.addEntity();
  auto& cameraComp    = camera.addComponent<Raz::Camera>(window.getWidth(), window.getHeight());
  auto& cameraTrans   = camera.addComponent<Raz::Transform>(Raz::Vec3f(0.f, 5.f, 20.f));

  Raz::Entity& mesh    = world.addEntity();
  auto& meshRenderComp = mesh.addComponent<Raz::MeshRenderer>(Raz::ObjFormat::load("crytek_sponza.obj").second);
  mesh.addComponent<Raz::Transform>(Raz::Vec3f(0.f), Raz::Quaternionf(90_deg, Raz::Axis::Y), Raz::Vec3f(0.04f));

  // Changing the material so that no lighting is computed
  for (Raz::Material& mat : meshRenderComp.getMaterials())
    mat.loadType(Raz::MaterialType::SINGLE_TEXTURE_2D);

  Raz::RenderGraph& renderGraph = renderSystem.getRenderGraph();
  Raz::RenderPass& geometryPass = renderSystem.getGeometryPass();

  const auto depthBuffer = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::DEPTH);
  const auto colorBuffer = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::RGB);

  geometryPass.setWriteDepthTexture(depthBuffer);
  geometryPass.addWriteColorTexture(colorBuffer, 0);

  auto& blur = renderGraph.addRenderProcess<Raz::GaussianBlurRenderProcess>();
  blur.setInputBuffer(colorBuffer);
  blur.addParent(geometryPass);

  auto colorBuffer2 = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::RGB);

  auto& sobel = renderGraph.addRenderProcess<Raz::SobelFilterRenderProcess>();
  sobel.setInputBuffer(colorBuffer2);
  sobel.addParent(blur);
  blur.setOutputBuffer(colorBuffer2);

  auto sobelGradBuffer    = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::GRAY);
  auto sobelGradDirBuffer = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::GRAY);

  sobel.setOutputGradientBuffer(sobelGradBuffer);
  sobel.setOutputGradientDirectionBuffer(sobelGradDirBuffer);

  auto& canny = renderGraph.addNode(Raz::FragmentShader::loadFromSource(R"(
    in vec2 fragTexcoords;

    uniform sampler2D uniSobelGradients;
    uniform sampler2D uniSobelGradDirs;
    uniform vec2 uniInvBufferSize;

    layout(location = 0) out vec4 fragColor;

    void main() {
      vec3 gradient = texture(uniSobelGradients, fragTexcoords).rgb;
      vec3 gradDir  = texture(uniSobelGradDirs, fragTexcoords).rgb;

      vec3 rightGrad      = texture(uniSobelGradients, fragTexcoords + vec2( 1.0,  0.0) * uniInvBufferSize).rgb;
      vec3 leftGrad       = texture(uniSobelGradients, fragTexcoords + vec2(-1.0,  0.0) * uniInvBufferSize).rgb;
      vec3 downGrad       = texture(uniSobelGradients, fragTexcoords + vec2( 0.0, -1.0) * uniInvBufferSize).rgb;
      vec3 upGrad         = texture(uniSobelGradients, fragTexcoords + vec2( 0.0,  1.0) * uniInvBufferSize).rgb;
      vec3 lowerRightGrad = texture(uniSobelGradients, fragTexcoords + vec2( 1.0, -1.0) * uniInvBufferSize).rgb;
      vec3 upperLeftGrad  = texture(uniSobelGradients, fragTexcoords + vec2(-1.0,  1.0) * uniInvBufferSize).rgb;
      vec3 lowerLeftGrad  = texture(uniSobelGradients, fragTexcoords + vec2(-1.0, -1.0) * uniInvBufferSize).rgb;
      vec3 upperRightGrad = texture(uniSobelGradients, fragTexcoords + vec2( 1.0,  1.0) * uniInvBufferSize).rgb;

      for (int i = 0; i < 3; ++i) {
        // Merging the two halves together; we want to check opposite direction, and both will be combined that way
        gradDir[i] -= 0.5 * step(0.5, gradDir[i]);

        if (gradDir[i] <= 0.0625 || gradDir[i] > 0.4375) { // Right or left
          if (gradient[i] < rightGrad[i] || gradient[i] < leftGrad[i])
            gradient[i] = 0.0;
        } else if (gradDir[i] > 0.1875 && gradDir[i] <= 0.3125) { // Down or up
          if (gradient[i] < downGrad[i] || gradient[i] < upGrad[i])
            gradient[i] = 0.0;
        } else if (gradDir[i] > 0.0625 && gradDir[i] <= 0.1875) { // Lower-right or upper-left
          if (gradient[i] < lowerRightGrad[i] || gradient[i] < upperLeftGrad[i])
            gradient[i] = 0.0;
        } else if (gradDir[i] > 0.3125 && gradDir[i] <= 0.4375) { // Lower-left or upper-right
          if (gradient[i] < lowerLeftGrad[i] || gradient[i] < upperRightGrad[i])
            gradient[i] = 0.0;
        }
      }

      const float lowerBound = 0.1;
      const float upperBound = 0.25;

      for (int i = 0; i < 3; ++i) {
        if (gradient[i] <= lowerBound) {
          gradient[i] = 0.0;
        } else if (gradient[i] >= upperBound) {
          gradient[i] = 1.0;
        } else {
          if (rightGrad[i] >= upperBound || leftGrad[i] >= upperBound || downGrad[i] >= upperBound || upGrad[i] >= upperBound
           || lowerRightGrad[i] >= upperBound || upperLeftGrad[i] >= upperBound || lowerLeftGrad[i] >= upperBound || upperRightGrad[i] >= upperBound) {
            gradient[i] = 1.0;
          } else {
            gradient[i] = 0.0;
          }
        }
      }

      fragColor = vec4(gradient, 1.0);
    }
  )"sv));

  canny.getProgram().use();
  canny.getProgram().sendUniform("uniInvBufferSize", Raz::Vec2f(1.f / window.getWidth(), 1.f / window.getHeight()));

  canny.addReadTexture(sobelGradBuffer, "uniSobelGradients");
  canny.addReadTexture(sobelGradDirBuffer, "uniSobelGradDirs");
  sobel.addChild(canny);

  app.run();

  return EXIT_SUCCESS;
}
Clone this wiki locally