#include #include #include #include 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(int argc, char const * argv[]) { /// 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); } /// Optionally write to file if (argc >= 2) { auto ostream = std::ofstream(argv[1]); ostream << "P6" << "\n" << width << " " << height << "\n" << "255" << "\n"; char data[3*width*height]; glPixelStorei(GL_PACK_ALIGNMENT, 1); for (int y = 0; y < height; ++y) glReadPixels( 0, y, width, 1, GL_RGB, GL_UNSIGNED_BYTE, &data[3*width*(height-1-y)] ); ostream.write(data, 3*width*height); } /// Cleanup glfwDestroyWindow(window); glfwTerminate(); }