Browse Source

Implement degree 3 bezier curve purely in Fragment Shaders

rexim 4 years ago
parent
commit
76467fc3df
7 changed files with 264 additions and 3 deletions
  1. 3 2
      Makefile
  2. 3 1
      README.md
  3. 48 0
      bezier.frag
  4. 11 0
      bezier.vert
  5. 199 0
      main.c
  6. 0 0
      thumbnail-cpu.png
  7. BIN
      thumbnail-gpu.png

+ 3 - 2
Makefile

@@ -1,5 +1,6 @@
-CFLAGS=-Wall -Wextra -std=c11 -pedantic `pkg-config --cflags sdl2`
-LIBS=`pkg-config --libs sdl2` -lm
+PKGS=sdl2 gl
+CFLAGS=-Wall -Wextra -std=c11 -pedantic `pkg-config --cflags $(PKGS)`
+LIBS=`pkg-config --libs $(PKGS)` -lm
 
 bezier: main.c
 	$(CC) $(CFLAGS) -o bezier main.c $(LIBS)

+ 3 - 1
README.md

@@ -1,6 +1,7 @@
 # Bézier Curve
 
-![thumbnail](./thumbnail.png)
+![thumbnail-cpu](./thumbnail-cpu.png)
+![thumbnail-gpu](./thumbnail-gpu.png)
 
 Just a fun little project to learn how to render Bézier Curves.
 
@@ -14,3 +15,4 @@ $ ./bezier
 ## References
 
 - https://www.geogebra.org/m/WPHQ9rUt
+- https://gist.github.com/jordandee/94b187bcc51df9528a2f

+ 48 - 0
bezier.frag

@@ -0,0 +1,48 @@
+#version 130
+
+#define MARKER_COLOR vec4(0.75, 0.0, 0.0, 1.0)
+#define BEZIER_CURVE_THRESHOLD 10.0
+#define BEZIER_CURVE_COLOR vec4(0.0, 0.75, 0.0, 1.0)
+
+uniform vec2 p1;
+uniform vec2 p2;
+uniform vec2 p3;
+uniform float marker_radius;
+
+void main()
+{
+    if (length(gl_FragCoord.xy - p1) < marker_radius ||
+        length(gl_FragCoord.xy - p2) < marker_radius ||
+        length(gl_FragCoord.xy - p3) < marker_radius)
+    {
+        gl_FragColor = MARKER_COLOR;
+    }
+    else
+    {
+        vec2 p0 = gl_FragCoord.xy;
+        float a = p3.x - 2 * p2.x + p1.x;
+        float b = 2 * (p2.x - p1.x);
+        float c = p1.x - p0.x;
+        float dx = b * b - 4.0f * a * c;
+
+        if (dx >= 0.0f) {
+            float t1 = (-b + sqrt(dx)) / (2 * a);
+            float t2 = (-b - sqrt(dx)) / (2 * a);
+            float y1 = p1.y + 2 * t1 * (p2.y - p1.y) + t1 * t1 * (p3.y - 2 * p2.y + p1.y);
+            float y2 = p1.y + 2 * t2 * (p2.y - p1.y) + t2 * t2 * (p3.y - 2 * p2.y + p1.y);
+            if ((0.0f <= t1 && t1 <= 1.0f && abs(p0.y - y1) < BEZIER_CURVE_THRESHOLD) ||
+                (0.0f <= t2 && t2 <= 1.0f && abs(p0.y - y2) < BEZIER_CURVE_THRESHOLD))
+            {
+                gl_FragColor = BEZIER_CURVE_COLOR;
+            }
+            else
+            {
+                gl_FragColor = vec4(0.0);
+            }
+        }
+        else
+        {
+            gl_FragColor = vec4(0.0);
+        }
+    }
+}

+ 11 - 0
bezier.vert

@@ -0,0 +1,11 @@
+#version 130
+
+void main()
+{
+    int gray = gl_VertexID ^ (gl_VertexID >> 1);
+    gl_Position = vec4(
+        (gray        & 1) * 2.0 - 1.0,
+        ((gray >> 1) & 1) * 2.0 - 1.0,
+        0.0,
+        1.0);
+}

+ 199 - 0
main.c

@@ -1,7 +1,11 @@
+#include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <errno.h>
 
 #include <SDL.h>
+#define GL_GLEXT_PROTOTYPES
+#include <SDL_opengl.h>
 
 #define SCREEN_WIDTH 800
 #define SCREEN_HEIGHT 600
@@ -65,6 +69,11 @@ Vec2 vec2_add(Vec2 a, Vec2 b)
     return vec2(a.x + b.x, a.y + b.y);
 }
 
+float vec2_length(Vec2 a)
+{
+    return sqrtf(a.x * a.x + a.y * a.y);
+}
+
 float lerpf(float a, float b, float p)
 {
     return a + (b - a) * p;
@@ -172,7 +181,197 @@ int ps_at(Vec2 pos)
     return -1;
 }
 
+char *cstr_slurp_file(const char *file_path)
+{
+    FILE *f = fopen(file_path, "r");
+    if (f == NULL) {
+        fprintf(stderr, "ERROR: Could not read file `%s`: %s\n",
+                file_path, strerror(errno));
+        exit(1);
+    }
+
+    if (fseek(f, 0, SEEK_END) < 0) {
+        fprintf(stderr, "ERROR: Could not read file `%s`: %s\n",
+                file_path, strerror(errno));
+        exit(1);
+    }
+
+    long m = ftell(f);
+    if (m < 0) {
+        fprintf(stderr, "ERROR: Could not read file `%s`: %s\n",
+                file_path, strerror(errno));
+        exit(1);
+    }
+
+    char *buffer = malloc(m + 1);
+    if (buffer == NULL) {
+        fprintf(stderr, "ERROR: Could not allocate memory for file: %s\n",
+                strerror(errno));
+        exit(1);
+    }
+
+    if (fseek(f, 0, SEEK_SET) < 0) {
+        fprintf(stderr, "ERROR: Could not read file `%s`: %s\n",
+                file_path, strerror(errno));
+        exit(1);
+    }
+
+    size_t n = fread(buffer, 1, m, f);
+    if (ferror(f)) {
+        fprintf(stderr, "ERROR: Could not read file `%s`: %s\n",
+                file_path, strerror(errno));
+        exit(1);
+    }
+
+    buffer[n] = '\0';
+
+    fclose(f);
+
+    return buffer;
+}
+
+GLuint compile_shader_file(const char *file_path, GLenum shader_type)
+{
+    char *buffer = cstr_slurp_file(file_path);
+    const GLchar * const source_cstr = buffer;
+
+    GLuint shader = glCreateShader(shader_type);
+    glShaderSource(shader, 1, &source_cstr, NULL);
+    glCompileShader(shader);
+
+    GLint compiled;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+    if (compiled != GL_TRUE) {
+        GLchar log[1024];
+        GLsizei log_size = 0;
+        glGetShaderInfoLog(shader, sizeof(log), &log_size, log);
+        fprintf(stderr, "Failed to compile %s:%.*s\n", file_path,
+                log_size, log);
+        exit(1);
+    }
+
+    free(buffer);
+
+    return shader;
+}
+
+GLuint link_program(GLuint vert_shader, GLuint frag_shader)
+{
+    GLuint program = glCreateProgram();
+
+    glAttachShader(program, vert_shader);
+    glAttachShader(program, frag_shader);
+    glLinkProgram(program);
+
+    GLint linked = 0;
+    glGetProgramiv(program, GL_LINK_STATUS, &linked);
+    if (linked != GL_TRUE) {
+        GLsizei log_size = 0;
+        GLchar log[1024];
+        glGetProgramInfoLog(program, sizeof(log), &log_size, log);
+        fprintf(stderr, "Failed to link the shader program: %.*s\n", log_size, log);
+        exit(1);
+    }
+
+    return program;
+}
+
+#define MARKER_RADIUS 20.0f
+
 int main(void)
+{
+    check_sdl_code(
+        SDL_Init(SDL_INIT_VIDEO));
+
+    SDL_Window * const window =
+        check_sdl_ptr(
+            SDL_CreateWindow(
+                "Bezier Curves",
+                0, 0,
+                SCREEN_WIDTH,
+                SCREEN_HEIGHT,
+                SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL));
+
+    /*SDL_GLContext context = */SDL_GL_CreateContext(window);
+
+    GLuint vert_shader = compile_shader_file("./bezier.vert", GL_VERTEX_SHADER);
+    GLuint frag_shader = compile_shader_file("./bezier.frag", GL_FRAGMENT_SHADER);
+    GLuint program = link_program(vert_shader, frag_shader);
+    glUseProgram(program);
+
+    GLint u_p1 = glGetUniformLocation(program, "p1");
+    GLint u_p2 = glGetUniformLocation(program, "p2");
+    GLint u_p3 = glGetUniformLocation(program, "p3");
+    GLint u_marker_radius = glGetUniformLocation(program, "marker_radius");
+
+    Vec2 p1 = vec2(100.0f, 100.0f);
+    Vec2 p2 = vec2(200.0f, 200.0f);
+    Vec2 p3 = vec2(300.0f, 300.0f);
+    Vec2 *p_selected = NULL;
+
+    int quit = 0;
+    while (!quit) {
+        SDL_Event event;
+        while (SDL_PollEvent(&event)) {
+            switch (event.type) {
+            case SDL_QUIT: {
+                quit = 1;
+            } break;
+
+            case SDL_MOUSEBUTTONDOWN: {
+                const Vec2 mouse_pos = vec2(
+                    event.button.x,
+                    SCREEN_HEIGHT - event.button.y);
+                if (event.button.button == SDL_BUTTON_LEFT) {
+                    if (vec2_length(vec2_sub(p1, mouse_pos)) < MARKER_RADIUS) {
+                        p_selected = &p1;
+                    }
+
+                    if (vec2_length(vec2_sub(p2, mouse_pos)) < MARKER_RADIUS) {
+                        p_selected = &p2;
+                    }
+
+                    if (vec2_length(vec2_sub(p3, mouse_pos)) < MARKER_RADIUS) {
+                        p_selected = &p3;
+                    }
+                }
+            } break;
+
+            case SDL_MOUSEBUTTONUP: {
+                if (event.button.button == SDL_BUTTON_LEFT) {
+                    p_selected = NULL;
+                }
+            } break;
+
+            case SDL_MOUSEMOTION: {
+                const Vec2 mouse_pos = vec2(
+                    event.button.x,
+                    SCREEN_HEIGHT - event.button.y);
+                if (p_selected) {
+                    *p_selected = mouse_pos;
+                }
+            } break;
+            }
+        }
+
+        glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        glUniform2f(u_p1, p1.x, p1.y);
+        glUniform2f(u_p2, p2.x, p2.y);
+        glUniform2f(u_p3, p3.x, p3.y);
+        glUniform1f(u_marker_radius, MARKER_RADIUS);
+        glDrawArrays(GL_QUADS, 0, 4);
+
+        SDL_GL_SwapWindow(window);
+    }
+
+
+    return 0;
+}
+
+int main2(void)
 {
     check_sdl_code(
         SDL_Init(SDL_INIT_VIDEO));

+ 0 - 0
thumbnail.png → thumbnail-cpu.png


BIN
thumbnail-gpu.png