165 lines
4.5 KiB
WebGPU Shading Language
165 lines
4.5 KiB
WebGPU Shading Language
struct Settings {
|
|
eraserRadiusSquared: f32,
|
|
lineDistanceEpsilon: f32,
|
|
clearRed: f32,
|
|
clearGreen: f32,
|
|
clearBlue: f32,
|
|
clearAlpha: f32,
|
|
eraserRadius: f32,
|
|
padding1: f32,
|
|
};
|
|
|
|
@group(1) @binding(0) var<uniform> settings: Settings;
|
|
|
|
struct VertexOutput {
|
|
@builtin(position) position: vec4<f32>,
|
|
@location(0) screenPosition: vec2<f32>,
|
|
@location(1) @interpolate(flat) start: vec2<f32>,
|
|
@location(2) @interpolate(flat) direction: vec2<f32>,
|
|
@location(3) @interpolate(flat) inverseLengthSquared: f32,
|
|
}
|
|
|
|
struct EraserTextureTargets {
|
|
@location(0) source: vec4<f32>,
|
|
@location(1) influence: vec4<f32>,
|
|
@location(2) trail: vec4<f32>,
|
|
}
|
|
|
|
struct EraserCombinedTargets {
|
|
@location(0) mask: vec4<f32>,
|
|
@location(1) source: vec4<f32>,
|
|
@location(2) influence: vec4<f32>,
|
|
@location(3) trail: vec4<f32>,
|
|
}
|
|
|
|
@vertex
|
|
fn vertex(
|
|
@builtin(vertex_index) vertexIndex: u32,
|
|
@location(0) start: vec2<f32>,
|
|
@location(1) end: vec2<f32>
|
|
) -> VertexOutput {
|
|
let direction = end - start;
|
|
let denominator = dot(direction, direction);
|
|
var inverseLengthSquared = 0.0;
|
|
if denominator > settings.lineDistanceEpsilon {
|
|
inverseLengthSquared = 1.0 / denominator;
|
|
}
|
|
let screenPosition = segment_vertex_position(vertexIndex, start, end, settings.eraserRadius);
|
|
let uv = screenPosition / state.size;
|
|
let position = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
|
|
return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, direction, inverseLengthSquared);
|
|
}
|
|
|
|
@fragment
|
|
fn fragment(
|
|
@location(0) screenPosition: vec2<f32>,
|
|
@location(1) @interpolate(flat) start: vec2<f32>,
|
|
@location(2) @interpolate(flat) direction: vec2<f32>,
|
|
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
|
) -> @location(0) vec4<f32> {
|
|
if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) {
|
|
discard;
|
|
}
|
|
|
|
return getEraserClearValue();
|
|
}
|
|
|
|
@fragment
|
|
fn fragmentMrt(
|
|
@location(0) screenPosition: vec2<f32>,
|
|
@location(1) @interpolate(flat) start: vec2<f32>,
|
|
@location(2) @interpolate(flat) direction: vec2<f32>,
|
|
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
|
) -> EraserTextureTargets {
|
|
if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) {
|
|
discard;
|
|
}
|
|
|
|
let cleared = getEraserClearValue();
|
|
return EraserTextureTargets(cleared, cleared, cleared);
|
|
}
|
|
|
|
@fragment
|
|
fn fragmentCombined(
|
|
@location(0) screenPosition: vec2<f32>,
|
|
@location(1) @interpolate(flat) start: vec2<f32>,
|
|
@location(2) @interpolate(flat) direction: vec2<f32>,
|
|
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
|
) -> EraserCombinedTargets {
|
|
if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) {
|
|
discard;
|
|
}
|
|
|
|
let cleared = getEraserClearValue();
|
|
return EraserCombinedTargets(getEraserMaskValue(), cleared, cleared, cleared);
|
|
}
|
|
|
|
fn getEraserMaskValue() -> vec4<f32> {
|
|
return vec4<f32>(settings.clearAlpha, 0.0, 0.0, 1.0);
|
|
}
|
|
|
|
fn getEraserClearValue() -> vec4<f32> {
|
|
return vec4<f32>(
|
|
settings.clearRed,
|
|
settings.clearGreen,
|
|
settings.clearBlue,
|
|
settings.clearAlpha
|
|
);
|
|
}
|
|
|
|
fn shouldDiscardEraserFragment(
|
|
screenPosition: vec2<f32>,
|
|
start: vec2<f32>,
|
|
direction: vec2<f32>,
|
|
inverseLengthSquared: f32
|
|
) -> bool {
|
|
return distanceSquaredFromLine(screenPosition, start, direction, inverseLengthSquared) > settings.eraserRadiusSquared;
|
|
}
|
|
|
|
fn distanceSquaredFromLine(
|
|
position: vec2<f32>,
|
|
start: vec2<f32>,
|
|
direction: vec2<f32>,
|
|
inverseLengthSquared: f32
|
|
) -> f32 {
|
|
let pa = position - start;
|
|
|
|
if inverseLengthSquared <= 0.0 {
|
|
return dot(pa, pa);
|
|
}
|
|
|
|
let q = clamp(dot(pa, direction) * inverseLengthSquared, 0.0, 1.0);
|
|
let nearestOffset = pa - direction * q;
|
|
return dot(nearestOffset, nearestOffset);
|
|
}
|
|
|
|
fn segment_vertex_position(
|
|
vertexIndex: u32,
|
|
start: vec2<f32>,
|
|
end: vec2<f32>,
|
|
radius: f32
|
|
) -> vec2<f32> {
|
|
let directionVector = end - start;
|
|
let segmentLength = length(directionVector);
|
|
var direction = vec2<f32>(1.0, 0.0);
|
|
if segmentLength > 0.0 {
|
|
direction = directionVector / segmentLength;
|
|
}
|
|
let perpendicular = vec2<f32>(direction.y, -direction.x);
|
|
let corner = segment_vertex_corner(vertexIndex % 6u);
|
|
let center = mix(start, end, (corner.x + 1.0) * 0.5);
|
|
return center + direction * corner.x * radius + perpendicular * corner.y * radius;
|
|
}
|
|
|
|
fn segment_vertex_corner(index: u32) -> vec2<f32> {
|
|
if index == 0u {
|
|
return vec2<f32>(-1.0, 1.0);
|
|
}
|
|
if index == 1u || index == 3u {
|
|
return vec2<f32>(-1.0, -1.0);
|
|
}
|
|
if index == 2u || index == 4u {
|
|
return vec2<f32>(1.0, 1.0);
|
|
}
|
|
return vec2<f32>(1.0, -1.0);
|
|
}
|