|
| 1 | +const STRAIGHT_LINE_THRESHOLD = 1e10; |
| 2 | +const EPSILON = 1e-10; |
| 3 | +const PI = 3.141592653589793; |
| 4 | +const FWIDTH_VALID_LIMIT = 100; |
| 5 | +// Shapes share a single SDF texture. Pixels not covered by any shape are |
| 6 | +// initialized to -3.402823466e+38 before per-shape SDF values are written. |
| 7 | +// This creates extremely large distance derivatives at the boundary between |
| 8 | +// real shape SDF values and the default background value, so we ignore |
| 9 | +// derivatives larger than FWIDTH_VALID_LIMIT. |
| 10 | + |
| 11 | +const UNIFORM_T_SAMPLING = 4.0; |
| 12 | + |
| 13 | +struct CubicBezier { |
| 14 | + p0: vec2f, |
| 15 | + p1: vec2f, |
| 16 | + p2: vec2f, |
| 17 | + p3: vec2f, |
| 18 | +}; |
| 19 | + |
| 20 | +fn bezier_point(curve: CubicBezier, t: f32) -> vec2f { |
| 21 | + let t2 = t * t; |
| 22 | + let t3 = t2 * t; |
| 23 | + let one_minus_t = 1.0 - t; |
| 24 | + let one_minus_t2 = one_minus_t * one_minus_t; |
| 25 | + let one_minus_t3 = one_minus_t2 * one_minus_t; |
| 26 | + |
| 27 | + return curve.p0 * one_minus_t3 + |
| 28 | + 3.0 * curve.p1 * t * one_minus_t2 + |
| 29 | + 3.0 * curve.p2 * t2 * one_minus_t + |
| 30 | + curve.p3 * t3; |
| 31 | +} |
| 32 | + |
| 33 | + |
| 34 | +struct Vertex { |
| 35 | + @location(0) position: vec4f, |
| 36 | +}; |
| 37 | + |
| 38 | +// @group(0) @binding(0) var ourSampler: sampler; |
| 39 | +@group(0) @binding(1) var texture: texture_2d<f32>; |
| 40 | +// @group(0) @binding(1) var texture: texture_storage_2d<rgba32float, read>; |
| 41 | +@group(0) @binding(2) var<uniform> camera_projection: mat4x4f; |
| 42 | +@group(0) @binding(3) var<storage, read> curves: array<vec2f>; |
| 43 | +// @group(0) @binding(4) var<storage, read> uniform_t: array<f32>; |
| 44 | +// @group(0) @binding(5) var ourSampler: sampler; |
| 45 | +// consider witchign to uniform if possible |
| 46 | + |
| 47 | +struct VSOutput { |
| 48 | + @builtin(position) position: vec4f, |
| 49 | + @location(0) uv: vec2f, |
| 50 | + @location(1) norm_uv: vec2f, |
| 51 | +}; |
| 52 | + |
| 53 | +@vertex fn vs(vert: Vertex) -> VSOutput { |
| 54 | + let size = textureDimensions(texture); |
| 55 | + return VSOutput( |
| 56 | + camera_projection * vec4f(vert.position.xy, 0.0, 1.0), |
| 57 | + vert.position.zw * (vec2f(size) + vec2f(0)), |
| 58 | + vert.position.zw, |
| 59 | + ); |
| 60 | +} |
| 61 | + |
| 62 | +const BILINEAR_T_THRESHOLD = 1; |
| 63 | +const BILINEAR_T_THRESHOLD_POS = 2; |
| 64 | +// all texels which has diff with nearest texel < BILINEAR_T_THRESHOLD |
| 65 | +// will be included in bilinear interpolation. |
| 66 | +// It helps avoid interpolating t from totally different places |
| 67 | + |
| 68 | +struct Sample { |
| 69 | + t: f32, |
| 70 | + sign: f32, |
| 71 | +}; |
| 72 | + |
| 73 | +fn getSample(pos: vec2f) -> Sample { |
| 74 | + let floor_pos = floor(pos - 0.5); |
| 75 | + let fract_pos = pos - 0.5 - floor_pos; |
| 76 | + |
| 77 | + let max_coord = vec2i(vec2i(textureDimensions(texture)) - vec2i(1, 1)); |
| 78 | + |
| 79 | + let p00 = vec2u(clamp(vec2i(floor_pos ), vec2i(0, 0), max_coord)); |
| 80 | + let p10 = vec2u(clamp(vec2i(floor_pos + vec2f(1.0, 0.0)), vec2i(0, 0), max_coord)); |
| 81 | + let p01 = vec2u(clamp(vec2i(floor_pos + vec2f(0.0, 1.0)), vec2i(0, 0), max_coord)); |
| 82 | + let p11 = vec2u(clamp(vec2i(floor_pos + vec2f(1.0, 1.0)), vec2i(0, 0), max_coord)); |
| 83 | + |
| 84 | + // textureSample(ourTexture, ourSampler, fsInput.texcoord); |
| 85 | + let raw_c00 = textureLoad(texture, p00, 0).r; |
| 86 | + let raw_c10 = textureLoad(texture, p10, 0).r; |
| 87 | + let raw_c01 = textureLoad(texture, p01, 0).r; |
| 88 | + let raw_c11 = textureLoad(texture, p11, 0).r; |
| 89 | + |
| 90 | + let c00 = abs(raw_c00) - 1; |
| 91 | + let c10 = abs(raw_c10) - 1; |
| 92 | + let c01 = abs(raw_c01) - 1; |
| 93 | + let c11 = abs(raw_c11) - 1; |
| 94 | + |
| 95 | + let g10 = select(c00, c10, abs(c10 - c00) < BILINEAR_T_THRESHOLD); |
| 96 | + let g01 = select(c00, c01, abs(c01 - c00) < BILINEAR_T_THRESHOLD); |
| 97 | + let g11 = select(c00, c11, abs(c11 - c00) < BILINEAR_T_THRESHOLD); |
| 98 | + |
| 99 | + let top = mix(c00, g10, fract_pos.x); |
| 100 | + let bottom = mix(g01, g11, fract_pos.x); |
| 101 | + let final_t = mix(top, bottom, fract_pos.y); |
| 102 | + |
| 103 | + // Pick sign from the nearest texel to pos (not always p00) |
| 104 | + let nearest_raw = select( |
| 105 | + select(raw_c00, raw_c01, fract_pos.y >= 0.5), |
| 106 | + select(raw_c10, raw_c11, fract_pos.y >= 0.5), |
| 107 | + fract_pos.x >= 0.5 |
| 108 | + ); |
| 109 | + |
| 110 | + return Sample(final_t, sign(nearest_raw)); |
| 111 | +} |
| 112 | + |
| 113 | +fn g_to_bezier_pos(global_t: f32) -> vec2f { |
| 114 | + let idx = u32(global_t); |
| 115 | + let local_t = fract(global_t); |
| 116 | + let curve = CubicBezier( |
| 117 | + curves[idx * 4 + 0], |
| 118 | + curves[idx * 4 + 1], |
| 119 | + curves[idx * 4 + 2], |
| 120 | + curves[idx * 4 + 3] |
| 121 | + ); |
| 122 | + |
| 123 | + let is_straight_line = curve.p1.x > STRAIGHT_LINE_THRESHOLD; |
| 124 | + if (is_straight_line) { |
| 125 | + return mix(curve.p0, curve.p3, local_t); |
| 126 | + } |
| 127 | + |
| 128 | + return bezier_point(curve, local_t); |
| 129 | +} |
| 130 | + |
| 131 | +@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f { |
| 132 | + let sample = getSample(vsOut.uv); |
| 133 | + let curve_pos = g_to_bezier_pos(sample.t); |
| 134 | + let distance = length(curve_pos - vsOut.uv); |
| 135 | + let signed_distance = distance * sample.sign; |
| 136 | + |
| 137 | + ${TEST} |
| 138 | + |
| 139 | + let dist_derivative = fwidth(signed_distance); |
| 140 | + |
| 141 | + let safe_dist_derivative = select(0.0, dist_derivative, dist_derivative <= FWIDTH_VALID_LIMIT); // if too large -> 0 |
| 142 | + let alpha_smooth_factor = safe_dist_derivative * 1; |
| 143 | + // let alpha_smooth_factor = max(safe_dist_derivative * 1, EPSILON); |
| 144 | + |
| 145 | + let inner_alpha = smoothstep(u.dist_start - alpha_smooth_factor, u.dist_start + alpha_smooth_factor, signed_distance); |
| 146 | + let outer_alpha = smoothstep(u.dist_end - alpha_smooth_factor, u.dist_end + alpha_smooth_factor, signed_distance); |
| 147 | + let alpha = outer_alpha - inner_alpha; |
| 148 | + let color = getColor(vec4f(signed_distance, 0, 0, 1), vsOut.uv, vsOut.norm_uv); |
| 149 | + let result = vec4f(color.rgb, color.a * alpha); |
| 150 | + |
| 151 | + // if (result.a < EPSILON) { |
| 152 | + // return vec4f(0.5); |
| 153 | + // } |
| 154 | + |
| 155 | + return result; |
| 156 | + |
| 157 | + // let stroke_factor = select(0.5, 0.0, sdf.g > 1.0); |
| 158 | + // color = vec4f(0, sdf.g % 1, 0, 1.0); |
| 159 | + // color = vec4f(0, 0, sdf.b / (2 * PI), 1.0); |
| 160 | + // color = vec4f(sdf.r / 100.0, sdf.g % 1, sdf.b / (2 * PI), 1.0); |
| 161 | + // color = select(vec4f(0.5, 0, 0, 1), vec4f(0, 0, 0.5, 1), u32(sdf.r / 20.0) % 2 == 0); |
| 162 | +} |
0 commit comments