From b63ce7f900ee6a4631eacd1db07b2c9c9be12780 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 7 Sep 2021 20:12:35 -0400 Subject: graphics: path: Add support for linear/radial gradient fills. --- chickadee/graphics/path.scm | 109 ++++++++++++++++++++++++++++++++++++--- data/shaders/path-fill-frag.glsl | 28 +++++++++- data/shaders/path-fill-vert.glsl | 9 +++- 3 files changed, 136 insertions(+), 10 deletions(-) diff --git a/chickadee/graphics/path.scm b/chickadee/graphics/path.scm index 24796a7..4978fbb 100644 --- a/chickadee/graphics/path.scm +++ b/chickadee/graphics/path.scm @@ -62,6 +62,15 @@ regular-polygon ellipse circle + gradient? + gradient-type + gradient-matrix + gradient-start-color + gradient-end-color + gradient-range + gradient-radial-ratio + linear-gradient + radial-gradient stroke fill fill-and-stroke @@ -467,6 +476,67 @@ (define (circle center r) (ellipse center r r)) + +;;; +;;; Gradients +;;; + +(define-record-type + (make-gradient type matrix start-color end-color range radial-ratio) + gradient? + (type gradient-type) + (matrix gradient-matrix) + (start-color gradient-start-color) + (end-color gradient-end-color) + (range gradient-range) + ;; This x:y ratio is used to squash/stretch radial gradients to give + ;; an elliptical appearance. + (radial-ratio gradient-radial-ratio)) + +(define (angle->vec2 theta) + (vec2 (cos theta) (sin theta))) + +(define (make-range offset length) + (vec2 offset (+ offset length))) + +(define (make-gradient-matrix origin rotation) + (matrix3* (matrix3-translate (vec2* origin -1.0)) + (matrix3-rotate rotation))) + +(define (transform-gradient gradient matrix) + (make-gradient (gradient-type gradient) + ;; The matrix needs to be inverted in order to + ;; convert world space coordinates back into local + ;; coordinates within the fragment shader. We need + ;; the local coordinates for the gradient math to + ;; produce the correct result. + (matrix3* (matrix3-inverse matrix) (gradient-matrix gradient)) + (gradient-start-color gradient) + (gradient-end-color gradient) + (gradient-range gradient) + (gradient-radial-ratio gradient))) + +(define* (linear-gradient #:key (origin %origin) (start-color white) + (end-color black) (rotation 0.0) (offset 0.0) + (length 100.0)) + (make-gradient 'linear + (make-gradient-matrix origin rotation) + start-color + end-color + (make-range offset length) + 0.0)) + +(define* (radial-gradient #:key (origin %origin) (start-color white) + (end-color black) (radius 50.0) + (radius-x radius) (radius-y radius) + (rotation 0.0) (offset 0.0)) + (make-gradient 'radial + (make-gradient-matrix origin rotation) + start-color + end-color + (make-range offset (- radius-x offset)) + (/ radius-x radius-y))) + ;;; ;;; Path Tesselation @@ -1106,7 +1176,6 @@ (define *debug?* #f) -;; TODO: gradients (define* (draw-filled-path filled-path matrix) (let ((shader (graphics-variable-ref fill-shader)) (mvp (graphics-variable-ref mvp-matrix)) @@ -1151,10 +1220,26 @@ ;; we only draw fragments that are part of the filled path. (with-graphics-state ((g:stencil-test stencil-cover-and-clear) (g:blend-mode (filled-path-blend-mode filled-path))) - (shader-apply shader - (geometry-vertex-array quad-geometry) - #:mvp mvp - #:color (filled-path-color filled-path)))))) + (let ((color (filled-path-color filled-path))) + (if (gradient? color) + ;; Linear/radial gradient fill. + (shader-apply shader + (geometry-vertex-array quad-geometry) + #:mvp mvp + #:color (gradient-start-color color) + #:end-color (gradient-end-color color) + #:gradient-matrix (gradient-matrix color) + #:gradient-range (gradient-range color) + #:radial-gradient-ratio (gradient-radial-ratio color) + #:mode (case (gradient-type color) + ((linear) 1) + ((radial) 2))) + ;; Solid fill. + (shader-apply shader + (geometry-vertex-array quad-geometry) + #:mvp mvp + #:color (filled-path-color filled-path) + #:mode 0))))))) ;; TODO: dashed stroke ;; TODO: miter styles and miter limit @@ -1222,7 +1307,9 @@ (array-list-pop! filled-paths)))) (fill-path filled-path compiled-path #:blend-mode blend-mode - #:color fill-color) + #:color (if (gradient? fill-color) + (transform-gradient fill-color matrix) + fill-color)) (array-list-push! result filled-path) (loop rest matrix blend-mode fill-color stroke-color stroke-width stroke-feather stroke-cap))) @@ -1250,8 +1337,14 @@ stroke-feather stroke-cap))) ;; Apply transformation matrix. (('transform transform) - (loop rest (matrix3* matrix transform) blend-mode fill-color - stroke-color stroke-width stroke-feather stroke-cap)) + (loop rest + (matrix3* transform matrix) + blend-mode + fill-color + stroke-color + stroke-width + stroke-feather + stroke-cap)) ;; Set style properties. ((or ('set-style 'blend-mode blend-mode) ('set-style 'fill-color fill-color) diff --git a/data/shaders/path-fill-frag.glsl b/data/shaders/path-fill-frag.glsl index 3af191f..e336e76 100644 --- a/data/shaders/path-fill-frag.glsl +++ b/data/shaders/path-fill-frag.glsl @@ -6,12 +6,38 @@ out vec4 fragColor; #define fragColor gl_FragColor #endif +#ifdef GLSL120 +attribute vec2 fragPosition; +#else +in vec2 fragPosition; +#endif + +uniform int mode; uniform vec4 color; +uniform vec4 endColor; +uniform mat3 gradientMatrix; +uniform vec2 gradientRange; +uniform float radialGradientRatio; + +vec4 gradientMix(float x) { + float start = gradientRange.x; + float end = gradientRange.y; + float t = clamp((x - start) / (end - start), 0.0, 1.0); + return mix(color, endColor, t); +} void main(void) { if (color.a <= 0.0) { discard; } - fragColor = color; + if(mode == 0) { // solid color + fragColor = color; + } else if(mode == 1) { // linear gradient + float x = (gradientMatrix * vec3(fragPosition, 1.0)).x; + fragColor = gradientMix(x); + } else if(mode == 2) { // radial gradient + vec2 p = (gradientMatrix * vec3(fragPosition, 1.0)).xy; + fragColor = gradientMix(length(p * vec2(1.0, radialGradientRatio))); + } } diff --git a/data/shaders/path-fill-vert.glsl b/data/shaders/path-fill-vert.glsl index d95b71b..1752978 100644 --- a/data/shaders/path-fill-vert.glsl +++ b/data/shaders/path-fill-vert.glsl @@ -8,6 +8,12 @@ in vec2 position; attribute vec2 position; #endif +#ifdef GLSL120 +varying vec2 fragPosition; +#else +out vec2 fragPosition; +#endif + uniform mat4 mvp; uniform vec4 color; @@ -17,6 +23,7 @@ void main(void) { if (color.a <= 0.0) { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); } else { - gl_Position = mvp * vec4(position.xy, 0.0, 1.0); + fragPosition = position; + gl_Position = mvp * vec4(position, 0.0, 1.0); } } -- cgit v1.2.3