/// Includes #include #include #include #include #include #include #include /// Constants constexpr auto width = 419; constexpr auto height = 293; constexpr auto title = "sdf-winding"; /// Helpers //// Debug void GLAPIENTRY debug_message_callback( GLenum /* source */, GLenum /* type */, GLuint /* id */, GLenum /* severity */, GLsizei /* length */, GLchar const * message, void const * /* param */ ) { std::cerr << message << std::endl; }; //// Uniform void uniform(GLint location, int value ) { glUniform1i (location, value); } void uniform(GLint location, bool value ) { glUniform1i (location, value); } void uniform(GLint location, float value ) { glUniform1f (location, value); } void uniform(GLint location, float const (& value)[1]) { glUniform1fv(location, 1, value); } void uniform(GLint location, float const (& value)[2]) { glUniform2fv(location, 1, value); } void uniform(GLint location, float const (& value)[3]) { glUniform3fv(location, 1, value); } void uniform(GLint location, float const (& value)[4]) { glUniform4fv(location, 1, value); } template void uniform(char const * name, T const & value) { GLuint program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&program); GLint location = glGetUniformLocation(program, name); uniform(location, value); } //// Write void write_tga(char const * base) { char header[] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, (char)(width >> 0U), (char)(width >> 8U), (char)(height >> 0U), (char)(height >> 8U), 4*8, 0, }; char data[4*width*height]; glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data); auto t = std::time(nullptr); auto pt = std::put_time(std::localtime(&t), "%Y-%m-%d_%H-%M-%S"); std::ofstream((std::ostringstream{} << base << "_" << pt << ".tga").str()) .write(header, sizeof(header)) .write(data, sizeof(data)); } /// Main int main(int /*argc*/, char * argv[]) { //// Window/context glfwInit(); auto window = glfwCreateWindow(width, height, title, nullptr, nullptr); glfwMakeContextCurrent(window); glewInit(); //// Callbacks glfwSetWindowUserPointer(window, argv[0]); glfwSetKeyCallback(window, [](GLFWwindow* w, int key, int /*scancode*/, int action, int /*mods*/) { auto ptr = glfwGetWindowUserPointer(w); if (action != GLFW_RELEASE) return; if (key == GLFW_KEY_Q) glfwSetWindowShouldClose(w, GLFW_TRUE); if (key == GLFW_KEY_W) write_tga((char *)ptr); } ); //// Debug glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(debug_message_callback, nullptr); //// Shader auto make_program = [](char const * vert_source, char const * frag_source) { auto program = glCreateProgram(); auto vert = glCreateShader(GL_VERTEX_SHADER); auto frag = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vert, 1, &vert_source, nullptr); glShaderSource(frag, 1, &frag_source, nullptr); glCompileShader(vert); glCompileShader(frag); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); return program; }; auto vert_source = 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); } )"; auto circle_program = make_program(vert_source, R"( #version 140 uniform float sharpness; 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 * 1 / (1 + exp2(sharpness*sdf)); } )"); auto resolve_program = make_program(vert_source, R"( #version 140 uniform int view; uniform bool debug; uniform float sharpness; uniform float smoothing; uniform sampler2D windings; in vec2 tex_coord; void main() { float winding = texture(windings, tex_coord).r; if (view == 1) winding = 0.5 + 0.25 * winding; // view packed, remapped if (view >= 2) winding = clamp(abs(winding), 0, 1); // discard sign/sum if (view == 3) winding = 0.5 + 0.25 * winding; // view flattened, remapped if (view >= 4) winding = log2(1 / winding - 1) / sharpness; // unpack (reversible) if (view == 5) winding = winding / 100; // view unpacked, expanded if (view >= 6) winding = smoothstep(-smoothing, +smoothing, winding); // smooth gl_FragColor = vec4(vec3(winding), 1); bvec3 debug_color = bvec3( any(lessThan (gl_FragColor, vec4(0))), any(greaterThan(gl_FragColor, vec4(1))), any(isinf (gl_FragColor)) || any(isnan (gl_FragColor)) ); if (debug && any(debug_color)) gl_FragColor = vec4(debug_color, 1); } )"); /// 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 ); //// Data struct Circle { GLfloat center[2]; GLfloat radius; GLint winding; } circles[] = { {{(249+ 69)/2.0+0.5, height-1-(251+ 71)/2.0+0.5}, (251- 71)/2.0, +1}, {{(192+133)/2.0+0.5, height-1-(241+182)/2.0+0.5}, (241-182)/2.0, +1}, {{(138+ 79)/2.0+0.5, height-1-(199+140)/2.0+0.5}, (199-140)/2.0, -1}, {{(274+173)/2.0+0.5, height-1-(134+ 33)/2.0+0.5}, (134- 33)/2.0, -1}, }; //// Main loop int view = 1; bool debug = true; float time = (float)glfwGetTime(); float sharpness = 1; float smoothing = 5; glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); while (glfwPollEvents(), !glfwWindowShouldClose(window)) { ///// Time float pt = time; float dt = (time = (float)glfwGetTime()) - pt; ///// Input for (int num = 0; num <= 9; ++num) if (glfwGetKey(window, GLFW_KEY_0 + num)) view = num; if (glfwGetKey(window, GLFW_KEY_E)) debug = true; if (glfwGetKey(window, GLFW_KEY_R)) debug = false; if (glfwGetKey(window, GLFW_KEY_H)) sharpness -= 0.5F * dt; if (glfwGetKey(window, GLFW_KEY_L)) sharpness += 0.5F * dt; if (glfwGetKey(window, GLFW_KEY_J)) smoothing -= 5.0F * dt; if (glfwGetKey(window, GLFW_KEY_K)) smoothing += 5.0F * dt; sharpness = std::max(0.1F, sharpness); smoothing = std::max(0.0F, smoothing); ///// Generate smoothed windings glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(circle_program); for (auto const & circle : circles) { uniform("sharpness", sharpness); uniform("center", circle.center); uniform("radius", circle.radius); uniform("winding", circle.winding); glDrawArrays(GL_TRIANGLES, 0, 3); } ///// Resolve glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(resolve_program); uniform("view", view); uniform("debug", debug); uniform("sharpness", sharpness); uniform("smoothing", smoothing); glDrawArrays(GL_TRIANGLES, 0, 3); ///// Swap glfwSwapBuffers(window); } //// Cleanup glfwDestroyWindow(window); glfwTerminate(); }