zaplib/
std_shader.rs

1//! Collection of standard [`Shader`] functions.
2
3use crate::*;
4
5impl Cx {
6    /// Collection of standard [`Shader`] functions.
7    // Based on https://www.shadertoy.com/view/lslXW8
8    pub const STD_SHADER: CodeFragment = code_fragment!(
9        r#"
10        // See [`PassUniforms`] for documentation on these fields.
11        uniform camera_projection: mat4 in pass;
12        uniform camera_view: mat4 in pass;
13        uniform inv_camera_rot: mat4 in pass;
14        uniform dpi_factor: float in pass;
15        uniform dpi_dilate: float in pass;
16
17        // See [`DrawUniforms`] for documentation on these fields.
18        uniform draw_clip: vec4 in draw;
19        uniform draw_scroll: vec2 in draw;
20        uniform draw_local_scroll: vec2 in draw;
21        uniform draw_zbias: float in draw;
22
23        const PI: float = 3.141592653589793;
24        const E: float = 2.718281828459045;
25        const LN2: float = 0.6931471805599453;
26        const LN10: float = 2.302585092994046;
27        const LOG2E: float = 1.4426950408889634;
28        const LOG10E: float = 0.4342944819032518;
29        const SQRT1_2: float = 0.70710678118654757;
30        const TORAD: float = 0.017453292519943295;
31        const GOLDEN: float = 1.618033988749895;
32
33        // The current distance field
34        struct Df {
35            pos: vec2,
36            result: vec4,
37            last_pos: vec2,
38            start_pos: vec2,
39            shape: float,
40            clip: float,
41            has_clip: float,
42            old_shape: float,
43            blur: float,
44            aa: float,
45            scale: float,
46            field: float
47        }
48
49        impl Math{
50            // Rotate vector `v` by radians `a`
51            fn rotate_2d(v: vec2, a: float)->vec2 {
52                let ca = cos(a);
53                let sa = sin(a);
54                return vec2(v.x * ca - v.y * sa, v.x * sa + v.y * ca);
55            }
56        }
57
58        //http://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness
59        fn hsv2rgb(c: vec4) -> vec4 {
60            let K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
61            let p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
62            return vec4(c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y), c.w);
63        }
64
65        fn rgb2hsv(c: vec4) -> vec4 {
66            let K: vec4 = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
67            let p: vec4 = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
68            let q: vec4 = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
69
70            let d: float = q.x - min(q.w, q.y);
71            let e: float = 1.0e-10;
72            return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.w);
73        }
74
75        impl Df {
76            // Creates a distance field with the current position
77            fn viewport(pos: vec2) -> Df {
78                let df: Df;
79                df.pos = pos;
80                df.result = vec4(0.);
81                df.last_pos = vec2(0.);
82                df.start_pos = vec2(0.);
83                df.shape = 1e+20;
84                df.clip = -1e+20;
85                df.has_clip = 0.0;
86                df.old_shape = 1e+20;
87                df.blur = 0.00001;
88                df.aa = Df::antialias(pos);
89                df.scale = 1.0;
90                df.field = 0.0;
91                return df;
92            }
93
94            // Creates a distance field with the current position, matching pixel scale
95            fn viewport_px(pos: vec2) -> Df {
96                return Df::viewport(pos * dpi_factor);
97            }
98
99            // Adds a new field value to the current distance field
100            fn add_field(inout self, field: float) {
101                self.field = field / self.scale;
102                self.old_shape = self.shape;
103                self.shape = min(self.field, self.shape);
104            }
105
106            // Adds a clip mask to the current distance field
107            fn add_clip(inout self, d: float) {
108                d = d / self.scale;
109                self.clip = max(self.clip, d);
110                self.has_clip = 1.;
111            }
112
113            fn antialias(p: vec2) -> float {
114                return 1.0 / length(vec2(length(dFdx(p)), length(dFdy(p))));
115            }
116
117            // Translate a specified offset
118            fn translate(inout self, offset: vec2) -> vec2 {
119                self.pos -= offset;
120                return self.pos;
121            }
122
123            // Rotate by `a` radians around pivot
124            fn rotate(inout self, a: float, pivot: vec2) {
125                self.pos = Math::rotate_2d(self.pos - pivot, -a) + pivot;
126            }
127
128            // Uniformly scale by factor `f` around `pivot`
129            fn scale(inout self, f: float, pivot: vec2) {
130                self.scale *= f;
131                self.pos = (self.pos - pivot) * f + pivot;
132            }
133
134            // Sets clear color. Useful for specifying background colors before
135            // rendering a path.
136            fn clear(inout self, color: vec4) {
137                self.write_color(color, 1.0);
138            }
139
140            // Calculate antialiasing blur
141            // Private function
142            fn calc_blur(inout self, w: float) -> float {
143                let wa = clamp(-w * self.aa, 0.0, 1.0);
144                let wb = 1.0;
145                if self.blur > 0.001 {
146                    wb = clamp(-w / self.blur, 0.0, 1.0);
147                }
148                return wa * wb;
149            }
150
151            // Clears path in current distance field.
152            fn new_path(inout self) -> vec4 {
153                self.old_shape = self.shape = 1e+20;
154                self.clip = -1e+20;
155                self.has_clip = 0.;
156                return self.result;
157            }
158
159            // Writes a color to the distance field, using premultiplied alpha
160            // Private function. Users should instead use `clear`, `fill`, `stroke`.
161            fn write_color(inout self, src: vec4, w: float) -> vec4{
162                let src_a = src.a * w;
163                self.result = src * src_a + (1. - src_a) * self.result;
164                return self.result;
165            }
166
167            // Fills the current path with `color`.
168            fn fill(inout self, color: vec4) -> vec4 {
169                let f = self.calc_blur(self.shape);
170                self.write_color(color, f);
171                if self.has_clip > 0. {
172                    self.write_color(color, self.calc_blur(self.clip));
173                }
174                return self.result;
175            }
176
177            // Strokes the current path with `color` with a pixel width of `width`.
178            fn stroke(inout self, color: vec4, width: float) -> vec4 {
179                let f = self.calc_blur(abs(self.shape) - width / self.scale);
180                return self.write_color(color, f);
181            }
182
183            // Updates the current path by summing colors in `width`
184            // with the provided one.
185            fn glow(inout self, color: vec4, width: float) -> vec4 {
186                let f = self.calc_blur(abs(self.shape) - width / self.scale);
187                let source = vec4(color.rgb * color.a, color.a);
188                let dest = self.result;
189                self.result = vec4(source.rgb * f, 0.) + dest;
190                return self.result;
191            }
192
193            // Set field to the union of the current and previous field.
194            fn union(inout self) {
195                self.old_shape = self.shape = min(self.field, self.old_shape);
196            }
197
198            // Set field to the intersection of the current and previous field.
199            fn intersect(inout self) {
200                self.old_shape = self.shape = max(self.field, self.old_shape);
201            }
202
203            // Subtract current field from previous.
204            fn subtract(inout self) {
205                self.old_shape = self.shape = max(-self.field, self.old_shape);
206            }
207
208            // Interpolate current field and previous with factor k
209            fn blend(inout self, k: float) {
210                self.old_shape = self.shape = mix(self.old_shape, self.field, k);
211            }
212
213            // Renders a circle at p with radius r
214            fn circle(inout self, p: vec2, r: float) {
215                let c = self.pos - p;
216                self.add_field(length(c) - r);
217            }
218
219            // Render an arc at p with radius r between angles angle_start and angle_end.
220            fn arc(inout self, p: vec2, r: float, angle_start: float, angle_end: float) {
221                let c = self.pos - p;
222                let angle = mod(atan(c.x, -c.y) + 2.*PI, 2.*PI);
223                let d = max( angle_start - angle, angle - angle_end );
224                let len = max(length(c) * d, length(c) - r);
225                self.add_field(len / self.scale);
226            }
227
228            // Render a box with rounded corners at p with dimensions d.
229            // Use `r` to indicate the corner radius - if r is less than 1, render a basic
230            // rectangle. If r is bigger than min(w, h), the result will be a circle.
231            fn box(inout self, p: vec2, d: vec2, r: float) {
232                let s = 0.5 * d;
233                let o = p + s;
234                r = min(r, min(d.x, d.y));
235                s -= r;
236                let dist = abs(o - self.pos) - s;
237                let dmin = min(dist, 0.);
238                let dmax = max(dist, 0.);
239                let df = max(dmin.x, dmin.y) + length(dmax);
240                self.add_field(df - r);
241            }
242
243            // Render a rectangle at p with dimensions d.
244            fn rect(inout self, p: vec2, d: vec2) {
245                self.box(p, d, 0.);
246            }
247
248            // Render a triangle between points p0, p1, p2.
249            fn triangle(inout self, p0: vec2, p1: vec2, p2: vec2) {
250                let e0 = p1 - p0;
251                let e1 = p2 - p1;
252                let e2 = p0-p2;
253
254                let v0 = self.pos - p0;
255                let v1 = self.pos - p1;
256                let v2 = self.pos - p2;
257
258                let pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0);
259                let pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0);
260                let pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0);
261
262                let s = sign(e0.x * e2.y - e0.y * e2.x);
263                let d = min(min(vec2(dot(pq0, pq0), s*(v0.x * e0.y - v0.y * e0.x)),
264                        vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))),
265                        vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)));
266
267                self.add_field(-sqrt(d.x) * sign(d.y));
268            }
269
270            // Render a hexagon at p with side length r.
271            fn hexagon(inout self, p: vec2, r: float) {
272                let dx = abs(p.x - self.pos.x) * 1.15;
273                let dy = abs(p.y - self.pos.y);
274                self.add_field(max(dy + cos(60.0 * TORAD) * dx - r, dx - r));
275            }
276
277            // Move to p in current path, not drawing from current position.
278            fn move_to(inout self, p: vec2) {
279                self.last_pos =
280                self.start_pos = p;
281            }
282
283            // Render a line to p from current position.
284            fn line_to(inout self, p: vec2) {
285                let pa = self.pos - self.last_pos;
286                let ba = p - self.last_pos;
287                let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
288                let s = sign(pa.x * ba.y - pa.y * ba.x);
289                self.field = length(pa - ba * h) / self.scale;
290                self.old_shape = self.shape;
291                self.shape = min(self.shape, self.field);
292                self.clip = max(self.clip, self.field * s);
293                self.has_clip = 1.0;
294                self.last_pos = p;
295            }
296
297            // End the current field by rendering a line back to the start point
298            fn close_path(inout self) {
299                self.line_to(self.start_pos);
300            }
301        }
302    "#
303    );
304}