Browse code

Add implementation

Robert Cranston authored on 08/03/2026 03:36:49
Showing 2 changed files

... ...
@@ -16,6 +16,18 @@ To build, run `make`. You need the following dependencies on your system:
16 16
 -   [GLEW][]
17 17
 -   [GLFW][]
18 18
 
19
+The code prioritizes conciseness and takes a number of shortcuts:
20
+
21
+-   We don't actually generate the SDFs and winding numbers from contours, we
22
+    just hard code simple circle SDFs with given winding numbers.
23
+-   We don't always take care to explicitly bind OpenGL state at the point of
24
+    use if we happen to know that the currently bound thing is correct or
25
+    harmless, which we often do for this simple program.
26
+-   We abuse the blend mode to add winding numbers, to avoid juggling multiple
27
+    textures. If you're using clever geometry tricks to generate the SDFs this
28
+    might be a problem. You might be able to use some overdraw reducing
29
+    technique, like a depth pre-pass, to mitigate that.
30
+
19 31
 [`sdf-winding`]:  https://git.rcrnstn.net/rcrnstn/sdf-winding
20 32
 [glyph]:          https://en.wikipedia.org/wiki/Glyph
21 33
 [winding number]: https://en.wikipedia.org/wiki/Winding_number
... ...
@@ -3,8 +3,8 @@
3 3
 #include <GL/glew.h>
4 4
 #include <GLFW/glfw3.h>
5 5
 
6
-constexpr auto width  = 640;
7
-constexpr auto height = 480;
6
+constexpr auto width  = 419;
7
+constexpr auto height = 293;
8 8
 constexpr auto title  = "sdf-winding";
9 9
 
10 10
 static void APIENTRY debug_message_callback(
... ...
@@ -63,27 +63,118 @@ int main()
63 63
         glLinkProgram(program);
64 64
         return program;
65 65
     };
66
-    auto tex_coord_program = make_program(R"(
66
+    auto circle_program = make_program(R"(
67 67
         #version 140
68 68
 
69
+        uniform float smoothing;
70
+        uniform vec2  center;
71
+        uniform float radius;
72
+        uniform int   winding;
73
+
74
+        out float windings;
75
+
76
+        void main()
77
+        {
78
+            vec2  pos = gl_FragCoord.xy;
79
+            float sdf = length(pos - center) - radius;
80
+            windings = winding * smoothstep(
81
+                -smoothing/2,
82
+                +smoothing/2,
83
+                -sdf
84
+            );
85
+        }
86
+    )");
87
+    auto resolve_program = make_program(R"(
88
+        #version 140
89
+
90
+        uniform int       view;
91
+        uniform sampler2D windings;
92
+
69 93
         in vec2 tex_coord;
70 94
 
71 95
         void main()
72 96
         {
73
-            gl_FragColor = vec4(tex_coord, 0, 1);
97
+            float winding = texture(windings, tex_coord).r;
98
+            switch (view)
99
+            {
100
+                case 0: gl_FragColor = vec4(0.5 + 0.25 * winding); break;
101
+                case 1: gl_FragColor = vec4(abs(winding));         break;
102
+            }
74 103
         }
75 104
     )");
76 105
 
106
+    /// Texture
107
+    GLuint windings;
108
+    glGenTextures(1, &windings);
109
+    glBindTexture(GL_TEXTURE_2D, windings);
110
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
111
+    glTexImage2D(
112
+        GL_TEXTURE_2D, 0,
113
+        GL_R32F,
114
+        width, height, 0,
115
+        GL_RED, GL_FLOAT, nullptr
116
+    );
117
+
118
+    /// Framebuffer
119
+    GLuint framebuffer;
120
+    glGenFramebuffers(1, &framebuffer);
121
+    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
122
+    glFramebufferTexture(
123
+        GL_FRAMEBUFFER,
124
+        GL_COLOR_ATTACHMENT0,
125
+        windings,
126
+        0
127
+    );
128
+
77 129
     /// Draw loop
130
+    float smoothing = 10;
131
+    int   view      = 0;
132
+    struct Circle
133
+    {
134
+        float center[2];
135
+        float radius;
136
+        int   winding;
137
+    };
138
+    glEnable(GL_BLEND);
139
+    glBlendFunc(GL_ONE, GL_ONE);
140
+    glBlendEquation(GL_FUNC_ADD);
78 141
     while (glfwPollEvents(), !glfwWindowShouldClose(window))
79 142
     {
80 143
         //// Input
81 144
         if (glfwGetKey(window, GLFW_KEY_Q))
82 145
             glfwSetWindowShouldClose(window, GLFW_TRUE);
146
+        if (glfwGetKey(window, GLFW_KEY_J))
147
+            smoothing += 1;
148
+        if (glfwGetKey(window, GLFW_KEY_K))
149
+            smoothing -= smoothing < 1 ? smoothing : 1;
150
+        if (glfwGetKey(window, GLFW_KEY_H))
151
+            view = 0;
152
+        if (glfwGetKey(window, GLFW_KEY_L))
153
+            view = 1;
154
+
155
+        //// Generate smoothed windings
156
+        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
157
+        glClear(GL_COLOR_BUFFER_BIT);
158
+        glUseProgram(circle_program);
159
+        for (auto const & circle : {
160
+            Circle{{(249+ 69)/2.0+0.5, height-1-(251+ 71)/2.0+0.5}, (251- 71)/2.0, +1},
161
+            Circle{{(192+133)/2.0+0.5, height-1-(241+182)/2.0+0.5}, (241-182)/2.0, +1},
162
+            Circle{{(138+ 79)/2.0+0.5, height-1-(199+140)/2.0+0.5}, (199-140)/2.0, -1},
163
+            Circle{{(274+173)/2.0+0.5, height-1-(134+ 33)/2.0+0.5}, (134- 33)/2.0, -1},
164
+        })
165
+        {
166
+            glUniform1fv(glGetUniformLocation(circle_program, "smoothing"), 1, &smoothing);
167
+            glUniform2fv(glGetUniformLocation(circle_program, "center"),    1,  circle.center);
168
+            glUniform1fv(glGetUniformLocation(circle_program, "radius"),    1, &circle.radius);
169
+            glUniform1iv(glGetUniformLocation(circle_program, "winding"),   1, &circle.winding);
170
+            glDrawArrays(GL_TRIANGLES, 0, 3);
171
+        }
83 172
 
84
-        //// Draw
173
+        //// Resolve
174
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
85 175
         glClear(GL_COLOR_BUFFER_BIT);
86
-        glUseProgram(tex_coord_program);
176
+        glUseProgram(resolve_program);
177
+        glUniform1iv(glGetUniformLocation(resolve_program, "view"), 1, &view);
87 178
         glDrawArrays(GL_TRIANGLES, 0, 3);
88 179
 
89 180
         //// Swap