mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: masks, properties refactor, shaders, storage migrations, and more
This commit is contained in:
+43
-13
@@ -28,11 +28,41 @@ renderer: {
|
||||
}
|
||||
```
|
||||
|
||||
All WebGL rendering — both the main renderer and the effect preview — goes through `applyMultiPassEffect` in `apps/web/src/services/renderer/webgl-utils.ts`. Don't add a new rendering path somewhere else; update that function if needed.
|
||||
### Dynamic pass counts with `buildPasses`
|
||||
|
||||
Some effects need a variable number of passes depending on their parameters (e.g. blur needs more iterations at high intensity to keep quality). For these, add a `buildPasses` function to the renderer:
|
||||
|
||||
```typescript
|
||||
renderer: {
|
||||
type: "webgl",
|
||||
passes: [ /* static fallback — used if buildPasses is absent */ ],
|
||||
buildPasses: ({ effectParams, width, height }) => {
|
||||
// return ResolvedEffectPass[] with pre-computed uniforms
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
When `buildPasses` is present, all rendering paths use it instead of the static `passes` array. The static array is kept as a structural reference and fallback for effects that don't need dynamic pass counts.
|
||||
|
||||
### Resolving passes — always use `resolveEffectPasses`
|
||||
|
||||
All code that consumes effect passes should go through the helper, never access `definition.renderer.passes` directly:
|
||||
|
||||
```typescript
|
||||
import { resolveEffectPasses } from "@/lib/effects";
|
||||
|
||||
const passes = resolveEffectPasses({ definition, effectParams, width, height });
|
||||
```
|
||||
|
||||
This handles the `buildPasses` vs static `passes` dispatch automatically.
|
||||
|
||||
### Pipeline
|
||||
|
||||
Linear effect chains (blur, color grading, bloom) go through `applyMultiPassEffect` in `apps/web/src/services/renderer/webgl-utils.ts`. Non-linear GPU pipelines that need branching or multi-texture passes (like JFA for signed distance fields) get their own orchestrator in `services/renderer/` and share the WebGL context via `webgl-context.ts`.
|
||||
|
||||
## Writing fragment shaders
|
||||
|
||||
Shaders live in `apps/web/src/lib/effects/definitions/`. The shared vertex shader (`effect.vert.glsl`) maps clip space to UV coordinates — don't replace it unless you have a specific reason.
|
||||
Effect-specific shaders live in `apps/web/src/lib/effects/definitions/`. General-purpose GPU algorithm shaders (like JFA) live in `apps/web/src/lib/shaders/`. Domain-specific shaders that consume a general algorithm (like the mask feather smoothstep) live with their domain (e.g. `lib/masks/shaders/`). The shared vertex shader (`effect.vert.glsl`) maps clip space to UV coordinates — don't replace it unless you have a specific reason.
|
||||
|
||||
Available uniforms (automatically injected, no need to pass them manually):
|
||||
- `u_texture` — the input texture (sampler2D)
|
||||
@@ -40,22 +70,22 @@ Available uniforms (automatically injected, no need to pass them manually):
|
||||
|
||||
Any additional uniforms come from the `uniforms()` function in the pass definition.
|
||||
|
||||
**Sampling density — the most common mistake**
|
||||
**Sampling density and step scaling**
|
||||
|
||||
Always use a step of 1 texel when sampling neighbors. Do not scale the step size with the blur radius or intensity — it creates visible discrete artifacts (ghosting/glow look) because there are large gaps between samples that the GPU fills with linear interpolation instead of your intended curve.
|
||||
A fixed kernel (e.g. ±30 samples) can only cover ±30 texels at step=1. When the target sigma grows beyond ~10, the kernel can't cover enough of the Gaussian curve and the result degrades into a box filter.
|
||||
|
||||
The fix is a `u_step` uniform that spaces samples further apart. With step=4 the same 61-sample kernel covers ±120 texels. Bilinear texture filtering smooths the gaps between samples. For very large sigma, combine step scaling with **multi-iteration stacking** (multiple H+V pass pairs via `buildPasses`) — each iteration compounds the blur, and the effective sigma = per-pass sigma × √iterations.
|
||||
|
||||
Keep the step size moderate (≤4) to avoid visible banding. If you need more blur than step=4 allows in a single iteration, add iterations instead of increasing the step further.
|
||||
|
||||
```glsl
|
||||
// correct — step is always 1 texel, loop count controls radius
|
||||
for (int i = -30; i <= 30; i++) {
|
||||
color += texture2D(u_texture, v_texCoord + texelSize * u_direction * float(i)) * weight;
|
||||
}
|
||||
|
||||
// wrong — stepping 6 texels at a time looks ghosty at high intensity
|
||||
vec2 offset = texelSize * u_direction * u_radius;
|
||||
color += texture2D(u_texture, v_texCoord + offset * 2.0) * someWeight;
|
||||
// u_step scales the distance between samples
|
||||
float pos = float(i) * u_step;
|
||||
float weight = exp(-(pos * pos) / (2.0 * u_sigma * u_sigma));
|
||||
color += texture2D(u_texture, v_texCoord + texelSize * u_direction * pos) * weight;
|
||||
```
|
||||
|
||||
If you need a large radius with a fixed kernel size, increase the number of samples rather than the step.
|
||||
Do **not** use large step sizes (>6) in a single pass — it creates visible banding regardless of bilinear interpolation. Use multiple iterations instead.
|
||||
|
||||
## Y-flip and coordinate systems
|
||||
|
||||
|
||||
Reference in New Issue
Block a user