#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
constexpr auto width = 419;
constexpr auto height = 293;
constexpr auto title = "sdf-winding";
static void APIENTRY debug_message_callback(
GLenum /* source */,
GLenum /* type */,
GLuint /* id */,
GLenum /* severity */,
GLsizei /* length */,
GLchar const * message,
void const * /* param */
)
{
std::cerr << message << std::endl;
};
int main()
{
/// Window/context
glfwInit();
auto window = glfwCreateWindow(width, height, title, nullptr, nullptr);
glfwMakeContextCurrent(window);
glewInit();
/// Debug
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(debug_message_callback, nullptr);
/// Shader
auto make_program = [](char const * fragment_source)
{
auto program = glCreateProgram();
auto attach = [&](GLenum type, char const * source)
{
auto shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
glAttachShader(program, shader);
};
attach(GL_VERTEX_SHADER, R"(
#version 140
out vec2 tex_coord;
void main()
{
tex_coord = vec2[](
vec2(0, 0),
vec2(2, 0),
vec2(0, 2)
)[gl_VertexID];
gl_Position = vec4(tex_coord*2-1, 0, 1);
}
)");
attach(GL_FRAGMENT_SHADER, fragment_source);
glLinkProgram(program);
return program;
};
auto circle_program = make_program(R"(
#version 140
uniform float smoothing;
uniform vec2 center;
uniform float radius;
uniform int winding;
out float windings;
void main()
{
vec2 pos = gl_FragCoord.xy;
float sdf = length(pos - center) - radius;
windings = winding * smoothstep(
-smoothing/2,
+smoothing/2,
-sdf
);
}
)");
auto resolve_program = make_program(R"(
#version 140
uniform int view;
uniform sampler2D windings;
in vec2 tex_coord;
void main()
{
float winding = texture(windings, tex_coord).r;
switch (view)
{
case 0: gl_FragColor = vec4(0.5 + 0.25 * winding); break;
case 1: gl_FragColor = vec4(abs(winding)); break;
}
}
)");
/// Texture
GLuint windings;
glGenTextures(1, &windings);
glBindTexture(GL_TEXTURE_2D, windings);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(
GL_TEXTURE_2D, 0,
GL_R32F,
width, height, 0,
GL_RED, GL_FLOAT, nullptr
);
/// Framebuffer
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
windings,
0
);
/// Draw loop
float smoothing = 10;
int view = 0;
struct Circle
{
float center[2];
float radius;
int winding;
};
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
while (glfwPollEvents(), !glfwWindowShouldClose(window))
{
//// Input
if (glfwGetKey(window, GLFW_KEY_Q))
glfwSetWindowShouldClose(window, GLFW_TRUE);
if (glfwGetKey(window, GLFW_KEY_J))
smoothing += 1;
if (glfwGetKey(window, GLFW_KEY_K))
smoothing -= smoothing < 1 ? smoothing : 1;
if (glfwGetKey(window, GLFW_KEY_H))
view = 0;
if (glfwGetKey(window, GLFW_KEY_L))
view = 1;
//// Generate smoothed windings
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(circle_program);
for (auto const & circle : {
Circle{{(249+ 69)/2.0+0.5, height-1-(251+ 71)/2.0+0.5}, (251- 71)/2.0, +1},
Circle{{(192+133)/2.0+0.5, height-1-(241+182)/2.0+0.5}, (241-182)/2.0, +1},
Circle{{(138+ 79)/2.0+0.5, height-1-(199+140)/2.0+0.5}, (199-140)/2.0, -1},
Circle{{(274+173)/2.0+0.5, height-1-(134+ 33)/2.0+0.5}, (134- 33)/2.0, -1},
})
{
glUniform1fv(glGetUniformLocation(circle_program, "smoothing"), 1, &smoothing);
glUniform2fv(glGetUniformLocation(circle_program, "center"), 1, circle.center);
glUniform1fv(glGetUniformLocation(circle_program, "radius"), 1, &circle.radius);
glUniform1iv(glGetUniformLocation(circle_program, "winding"), 1, &circle.winding);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
//// Resolve
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(resolve_program);
glUniform1iv(glGetUniformLocation(resolve_program, "view"), 1, &view);
glDrawArrays(GL_TRIANGLES, 0, 3);
//// Swap
glfwSwapBuffers(window);
}
/// Cleanup
glfwDestroyWindow(window);
glfwTerminate();
}