2026-01-28 15:27:55 +01:00
|
|
|
#extension GL_OES_EGL_image_external : require
|
|
|
|
|
|
2026-01-28 15:26:41 +01:00
|
|
|
// Fragment shader for tilt-shift effect
|
2026-01-29 11:13:31 +01:00
|
|
|
// Supports both linear and radial blur modes
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
precision mediump float;
|
|
|
|
|
|
|
|
|
|
// Camera texture (external texture for camera preview)
|
|
|
|
|
uniform samplerExternalOES uTexture;
|
|
|
|
|
|
|
|
|
|
// Effect parameters
|
2026-01-29 11:13:31 +01:00
|
|
|
uniform int uMode; // 0 = linear, 1 = radial
|
2026-01-29 17:07:44 +01:00
|
|
|
uniform int uIsFrontCamera; // 0 = back camera, 1 = front camera
|
2026-01-28 15:26:41 +01:00
|
|
|
uniform float uAngle; // Rotation angle in radians
|
2026-01-28 15:55:17 +01:00
|
|
|
uniform float uPositionX; // Horizontal center of focus (0-1)
|
|
|
|
|
uniform float uPositionY; // Vertical center of focus (0-1)
|
2026-01-28 15:26:41 +01:00
|
|
|
uniform float uSize; // Size of in-focus region (0-1)
|
|
|
|
|
uniform float uBlurAmount; // Maximum blur intensity (0-1)
|
2026-01-29 11:13:31 +01:00
|
|
|
uniform float uFalloff; // Transition sharpness (0-1, higher = more gradual)
|
|
|
|
|
uniform float uAspectRatio; // Ellipse aspect ratio for radial mode
|
2026-01-28 15:26:41 +01:00
|
|
|
uniform vec2 uResolution; // Texture resolution for proper sampling
|
|
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Precomputed trig for the adjusted angle (avoids per-fragment cos/sin calls)
|
|
|
|
|
uniform float uCosAngle;
|
|
|
|
|
uniform float uSinAngle;
|
|
|
|
|
|
2026-01-28 15:26:41 +01:00
|
|
|
varying vec2 vTexCoord;
|
|
|
|
|
|
2026-01-29 11:13:31 +01:00
|
|
|
// Calculate signed distance from the focus region for LINEAR mode
|
|
|
|
|
float linearFocusDistance(vec2 uv) {
|
2026-01-28 15:55:17 +01:00
|
|
|
// Center point of the focus region
|
2026-01-29 17:07:44 +01:00
|
|
|
// Transform from screen coordinates to texture coordinates
|
|
|
|
|
// Back camera: Screen (x,y) -> Texture (y, 1-x)
|
|
|
|
|
// Front camera: Screen (x,y) -> Texture (1-y, 1-x) (additional X flip for mirror)
|
|
|
|
|
vec2 center;
|
|
|
|
|
if (uIsFrontCamera == 1) {
|
|
|
|
|
center = vec2(1.0 - uPositionY, 1.0 - uPositionX);
|
|
|
|
|
} else {
|
|
|
|
|
center = vec2(uPositionY, 1.0 - uPositionX);
|
|
|
|
|
}
|
2026-01-28 15:55:17 +01:00
|
|
|
vec2 offset = uv - center;
|
2026-01-28 15:26:41 +01:00
|
|
|
|
2026-01-29 16:57:01 +01:00
|
|
|
// Correct for screen aspect ratio to make coordinate space square
|
|
|
|
|
float screenAspect = uResolution.x / uResolution.y;
|
|
|
|
|
offset.y *= screenAspect;
|
|
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Use precomputed cos/sin for the adjusted angle
|
|
|
|
|
float rotatedY = -offset.x * uSinAngle + offset.y * uCosAngle;
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
return abs(rotatedY);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 11:13:31 +01:00
|
|
|
// Calculate signed distance from the focus region for RADIAL mode
|
|
|
|
|
float radialFocusDistance(vec2 uv) {
|
|
|
|
|
// Center point of the focus region
|
2026-01-29 17:07:44 +01:00
|
|
|
vec2 center;
|
|
|
|
|
if (uIsFrontCamera == 1) {
|
|
|
|
|
center = vec2(1.0 - uPositionY, 1.0 - uPositionX);
|
|
|
|
|
} else {
|
|
|
|
|
center = vec2(uPositionY, 1.0 - uPositionX);
|
|
|
|
|
}
|
2026-01-29 11:13:31 +01:00
|
|
|
vec2 offset = uv - center;
|
|
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Correct for screen aspect ratio
|
2026-01-29 11:13:31 +01:00
|
|
|
float screenAspect = uResolution.x / uResolution.y;
|
2026-01-29 16:57:01 +01:00
|
|
|
offset.y *= screenAspect;
|
2026-01-29 11:13:31 +01:00
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Use precomputed cos/sin for rotation
|
2026-01-29 11:13:31 +01:00
|
|
|
vec2 rotated = vec2(
|
2026-03-05 13:44:12 +01:00
|
|
|
offset.x * uCosAngle - offset.y * uSinAngle,
|
|
|
|
|
offset.x * uSinAngle + offset.y * uCosAngle
|
2026-01-29 11:13:31 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Apply ellipse aspect ratio
|
|
|
|
|
rotated.x /= uAspectRatio;
|
|
|
|
|
|
|
|
|
|
// Distance from center (elliptical)
|
|
|
|
|
return length(rotated);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 15:26:41 +01:00
|
|
|
// Calculate blur factor based on distance from focus
|
|
|
|
|
float blurFactor(float dist) {
|
|
|
|
|
float halfSize = uSize * 0.5;
|
2026-01-29 11:13:31 +01:00
|
|
|
// Falloff range scales with the falloff parameter
|
|
|
|
|
float transitionSize = halfSize * uFalloff;
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
if (dist < halfSize) {
|
|
|
|
|
return 0.0; // In focus region
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Smooth falloff using smoothstep
|
2026-01-29 11:13:31 +01:00
|
|
|
float normalizedDist = (dist - halfSize) / max(transitionSize, 0.001);
|
2026-01-28 15:26:41 +01:00
|
|
|
return smoothstep(0.0, 1.0, normalizedDist) * uBlurAmount;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Sample with Gaussian blur (9-tap, sigma ~= 2.0, unrolled for GLSL ES 1.00 compatibility)
|
2026-01-28 15:26:41 +01:00
|
|
|
vec4 sampleBlurred(vec2 uv, float blur) {
|
|
|
|
|
if (blur < 0.01) {
|
|
|
|
|
return texture2D(uTexture, uv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vec2 texelSize = 1.0 / uResolution;
|
|
|
|
|
|
2026-01-29 11:13:31 +01:00
|
|
|
// For radial mode, blur in radial direction from center
|
|
|
|
|
// For linear mode, blur perpendicular to focus line
|
|
|
|
|
vec2 blurDir;
|
|
|
|
|
if (uMode == 1) {
|
|
|
|
|
// Radial: blur away from center
|
2026-01-29 17:07:44 +01:00
|
|
|
vec2 center;
|
|
|
|
|
if (uIsFrontCamera == 1) {
|
|
|
|
|
center = vec2(1.0 - uPositionY, 1.0 - uPositionX);
|
|
|
|
|
} else {
|
|
|
|
|
center = vec2(uPositionY, 1.0 - uPositionX);
|
|
|
|
|
}
|
2026-01-29 11:13:31 +01:00
|
|
|
vec2 toCenter = uv - center;
|
|
|
|
|
float len = length(toCenter);
|
|
|
|
|
if (len > 0.001) {
|
|
|
|
|
blurDir = toCenter / len;
|
|
|
|
|
} else {
|
|
|
|
|
blurDir = vec2(1.0, 0.0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-03-05 13:44:12 +01:00
|
|
|
// Linear: blur perpendicular to focus line using precomputed trig
|
|
|
|
|
blurDir = vec2(uCosAngle, uSinAngle);
|
2026-01-29 11:13:31 +01:00
|
|
|
}
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
// Scale blur radius by blur amount
|
|
|
|
|
float radius = blur * 20.0;
|
2026-03-05 13:44:12 +01:00
|
|
|
vec2 step = blurDir * texelSize * radius;
|
2026-01-28 15:26:41 +01:00
|
|
|
|
2026-03-05 13:44:12 +01:00
|
|
|
// Unrolled 9-tap Gaussian blur (avoids integer-branched weight lookup)
|
|
|
|
|
vec4 color = vec4(0.0);
|
|
|
|
|
color += texture2D(uTexture, uv + step * -4.0) * 0.0162;
|
|
|
|
|
color += texture2D(uTexture, uv + step * -3.0) * 0.0540;
|
|
|
|
|
color += texture2D(uTexture, uv + step * -2.0) * 0.1216;
|
|
|
|
|
color += texture2D(uTexture, uv + step * -1.0) * 0.1933;
|
|
|
|
|
color += texture2D(uTexture, uv) * 0.2258;
|
|
|
|
|
color += texture2D(uTexture, uv + step * 1.0) * 0.1933;
|
|
|
|
|
color += texture2D(uTexture, uv + step * 2.0) * 0.1216;
|
|
|
|
|
color += texture2D(uTexture, uv + step * 3.0) * 0.0540;
|
|
|
|
|
color += texture2D(uTexture, uv + step * 4.0) * 0.0162;
|
2026-01-28 15:26:41 +01:00
|
|
|
|
|
|
|
|
return color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void main() {
|
2026-01-29 11:13:31 +01:00
|
|
|
float dist;
|
|
|
|
|
if (uMode == 1) {
|
|
|
|
|
dist = radialFocusDistance(vTexCoord);
|
|
|
|
|
} else {
|
|
|
|
|
dist = linearFocusDistance(vTexCoord);
|
|
|
|
|
}
|
2026-01-28 15:26:41 +01:00
|
|
|
float blur = blurFactor(dist);
|
|
|
|
|
|
|
|
|
|
gl_FragColor = sampleBlurred(vTexCoord, blur);
|
|
|
|
|
}
|