responsive-image-canvas/dist/distortion.frag.glsl
BaekRyang 48fdd5e17c Add sprite particle effects and improve lens distortion
- 스프라이트 기반 파티클 이펙트 관리 기능 추가 (SpriteEffectManager)
- 렌즈 왜곡 셰이더 로직 개선 및 픽셀 공간 기준 등방성 확대 적용
- 파티클 최적화를 위한 오브젝트 풀링(SpriteParticlePool) 도입
- DistortionArea 타입에 spriteEffects 설정 필드 추가
- ThreeScene에 씬 객체 접근 기능 및 렌더 순서 제어 추가
- useMouseInteraction 훅에서 마우스 상태 조회 기능 추가
- 버전 1.3.0 업데이트 및 관련 타입 정의 반영
2026-03-10 13:24:12 +09:00

119 lines
4.3 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

uniform vec2 u_resolution;
uniform sampler2D u_texture;
uniform vec2 u_points[32]; // 최대 8영역 × 4포인트 (정규화된 좌표 0-1)
uniform int u_numAreas;
uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1)
uniform float u_distortionStrengths[8];
uniform float u_lensEffects[8];
varying vec2 vUv;
// Flutter 원본 computeUV 함수 (정확히 동일하게 변환)
vec2 computeUV(vec2 xy, vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
vec2 minP = min(min(p0, p1), min(p2, p3));
vec2 maxP = max(max(p0, p1), max(p2, p3));
if (xy.x < minP.x || xy.x > maxP.x || xy.y < minP.y || xy.y > maxP.y) {
return vec2(-1.0, -1.0); // 외부
}
vec2 rectSize = maxP - minP;
if (rectSize.x == 0.0 || rectSize.y == 0.0) {
return vec2(-1.0, -1.0); // 축퇴
}
vec2 rectMin = minP;
vec2 rectUV = (xy - rectMin) / rectSize;
float u0 = rectUV.x;
float v0 = rectUV.y;
// 1회 Newton-Raphson (Flutter 원본과 동일)
vec2 left = mix(p0, p1, u0);
vec2 right = mix(p3, p2, u0);
vec2 xy0 = mix(left, right, v0);
vec2 dxy = xy - xy0;
vec2 du_vec = mix(p1 - p0, p2 - p3, v0);
vec2 dv_vec = mix(p3 - p0, p2 - p1, u0);
float det = du_vec.x * dv_vec.y - du_vec.y * dv_vec.x;
if (abs(det) > 1e-6) {
float inv_det = 1.0 / det;
float du = (dv_vec.y * dxy.x - dv_vec.x * dxy.y) * inv_det;
float dv = (-du_vec.y * dxy.x + du_vec.x * dxy.y) * inv_det;
u0 += du;
v0 += dv;
}
return vec2(u0, v0);
}
void main() {
vec2 xy = vUv * u_resolution; // 픽셀 좌표
vec2 texCoord = vUv;
// 모든 겹치는 영역의 왜곡을 누적 적용
for (int i = 0; i < 8; i++) {
if (i >= u_numAreas) break;
// 포인트는 정규화된 좌표로 전달받았으므로 픽셀 좌표로 변환
vec2 p0 = u_points[i * 4 + 0] * u_resolution;
vec2 p1 = u_points[i * 4 + 1] * u_resolution;
vec2 p2 = u_points[i * 4 + 2] * u_resolution;
vec2 p3 = u_points[i * 4 + 3] * u_resolution;
vec2 uv_local = computeUV(xy, p0, p1, p2, p3);
if (uv_local.x >= 0.0 && uv_local.x <= 1.0 && uv_local.y >= 0.0 && uv_local.y <= 1.0) {
vec2 uvCenter = vec2(0.5, 0.5);
float distToCenter = distance(uv_local, uvCenter);
float maxUvRadius = 0.5;
if (distToCenter < maxUvRadius) {
float influence = 1.0 - smoothstep(0.0, maxUvRadius, distToCenter);
// dragVector는 정규화된 좌표(0-1)이므로 바로 사용
vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i];
texCoord += distortion;
// 렌즈 왜곡 효과 (볼록: 중심 확대, 오목: 중심 축소)
if (abs(u_lensEffects[i]) > 0.001) {
// 영역 중심의 글로벌 UV 좌표
vec2 minP_area = min(min(p0, p1), min(p2, p3));
vec2 maxP_area = max(max(p0, p1), max(p2, p3));
vec2 areaSize = maxP_area - minP_area;
vec2 areaCenterUV = (minP_area + maxP_area) * 0.5 / u_resolution;
// 현재 픽셀에서 영역 중심까지의 글로벌 UV 오프셋
vec2 offset = vUv - areaCenterUV;
// 픽셀 공간 거리로 원형 감쇠 (긴 변 기준으로 영역 전체 커버)
float distPx = length(offset * u_resolution);
float maxRadiusPx = max(areaSize.x, areaSize.y) * 0.5;
float normalizedDist = distPx / maxRadiusPx;
if (normalizedDist < 1.0) {
// 중심에서 최대 강도, 가장자리로 갈수록 자연스럽게 0으로 감소
float lensAmount = u_lensEffects[i] * (1.0 - normalizedDist * normalizedDist);
// 볼록(+): 텍스처 좌표를 중심으로 당김 → 확대
// offset은 글로벌 UV이므로 픽셀 공간에서 등방성(isotropic) 확대
texCoord -= offset * lensAmount * u_distortionStrengths[i];
}
}
}
}
}
// 경계 근처에서 부드럽게 페이드 아웃
// 텍스처 좌표가 0~1 범위를 벗어나면 알파값을 줄여서 자연스럽게 처리
vec2 edgeDist = min(texCoord, 1.0 - texCoord);
float edgeFade = smoothstep(0.0, 0.05, min(edgeDist.x, edgeDist.y));
// 범위를 벗어난 좌표는 fract로 래핑하여 반복 효과 (더 자연스러움)
vec2 wrappedCoord = fract(texCoord);
vec4 color = texture2D(u_texture, wrappedCoord);
// 경계에서 페이드 아웃 적용
color.a *= edgeFade;
gl_FragColor = color;
}