한국어
가이드
커스텀 셰이더

커스텀 셰이더

모든 BOX 메시는 Painter 패키지가 생성한 ShaderMaterial 하나를 사용합니다. 머티리얼을 교체하지 않고도 프래그먼트 셰이더의 세 지점에 직접 만든 GLSL을 끼워 넣을 수 있습니다.

ShaderHooks 객체

interface ShaderHooks {
  uniforms?: Record<string, any>;
  uvModifier?: string;
  colorModifier?: string;
}

요소에는 엄격한 JSON으로 선언합니다 (큰따옴표 필수).

<div
  data-mirage-shader='{
    "uniforms": { "uTime": 0, "uAmount": 0.05 },
    "uvModifier": "resultUv.x += sin(resultUv.y * 10.0 + uTime) * uAmount;",
    "colorModifier": "finalColor.rgb = mix(finalColor.rgb, vec3(1.0, 0.2, 0.4), 0.3);"
  }'
></div>

각 훅이 들어가는 위치

uniform vec2 uSize;
uniform vec4 uBgColor;
// …
#INJECT_DECLARATIONS        // ← 유니폼 선언
 
void main() {
  vec2 p = (vUv - 0.5) * uMeshSize;
  // border-radius 클램핑…
 
  #INJECT_UV_MODIFIER       // ← uvModifier
 
  vec4 baseColor = vec4(uBgColor.rgb, uBgColor.a);
  // 그라디언트 레이어…
 
  #INJECT_BASE_COLOR        // ← 텍스처 샘플링
 
  // SDF 박스, 테두리, 그림자…
 
  #INJECT_COLOR_MODIFIER    // ← colorModifier
 
  float finalOpacity = finalColor.a * uOpacity;
  if (finalOpacity < 0.001) discard;
  gl_FragColor = vec4(finalColor.rgb, finalOpacity);
}

uvModifier

텍스처 샘플링 전에 실행됩니다. resultUv를 수정하세요.

사용 가능타입의미
resultUvvec2샘플링에 쓰이는 UV — 여기에 쓰세요
screenUvvec2이 프래그먼트의 정규화된 화면 좌표
localUvvec2요소 로컬 UV (traveler가 아닐 때만)
pvec2메시 중심 기준 픽셀 단위 프래그먼트 위치
vUvvec2원본 쿼드 UV, 0…1
uSize, uMeshSizevec2요소 크기, 메시 크기(그림자 여백 포함)

traveler에서는 resultUv가 screenUv로 초기화됩니다. 일반 요소에서는 uTextureRepeat / uTextureOffset으로 스케일된 localUv에서 옵니다. object-fit: cover 동작이 유지되는 이유입니다.

colorModifier

박스·테두리·그림자가 합성된 뒤 실행됩니다. finalColor를 수정하세요.

사용 가능타입의미
finalColorvec4합성된 RGBA — 여기에 쓰세요
p, vUv, resultUvvec2위와 동일
직접 선언한 유니폼—uniforms에 선언한 것
⚠️

여기서 gl_FragColor에 대입하지 마세요. 훅이 실행된 뒤 finalColor로부터 gl_FragColor가 기록되므로, 직접 쓴 값은 조용히 버려집니다.

유니폼 타입

타입은 JSON 값에서 추론됩니다.

JSON 값GLSL 타입
0.5float
[0.5, 1.0]vec2
[1, 0, 0]vec3
[1, 0, 0, 1]vec4
{ "type": "sampler2D", "value": null }명시한 타입
<div data-mirage-shader='{
  "uniforms": {
    "uTime": 0,
    "uMouse": [0.5, 0.5],
    "uTint": [1.0, 0.4, 0.8]
  },
  "colorModifier": "finalColor.rgb *= uTint;"
}'></div>

매 프레임 유니폼 갱신

애니메이션하겠다고 속성을 다시 쓰지 마세요 — 속성이 바뀌면 셰이더 해시가 바뀌고, Mirage가 메시를 해제한 뒤 다시 만듭니다. updateUniforms()를 쓰세요.

const el = document.querySelector("#hero") as HTMLElement;
const start = performance.now();
 
mirage.getTracker().onRender.add(() => {
  mirage.updateUniforms(el, {
    uTime: (performance.now() - start) / 1000,
  });
});
 
window.addEventListener("pointermove", (e) => {
  mirage.updateUniforms(el, {
    uMouse: [e.clientX / window.innerWidth, 1 - e.clientY / window.innerHeight],
  });
});

레시피

스캔라인

{
  "uniforms": { "uDensity": 400 },
  "colorModifier": "finalColor.rgb *= 0.85 + 0.15 * step(0.5, fract(vUv.y * uDensity));"
}

움직이는 그라디언트 틴트

{
  "uniforms": { "uTime": 0 },
  "colorModifier": "finalColor.rgb = mix(finalColor.rgb, vec3(0.4 + 0.6 * sin(uTime), 0.3, 0.9), 0.35);"
}

이미지 배럴 왜곡

{
  "uniforms": { "uStrength": 0.15 },
  "uvModifier": "vec2 c = resultUv - 0.5; resultUv = 0.5 + c * (1.0 + uStrength * dot(c, c));"
}

디졸브

{
  "uniforms": { "uProgress": 0 },
  "colorModifier": "float n = fract(sin(dot(vUv, vec2(12.9898, 78.233))) * 43758.5453); if (n > uProgress) finalColor.a = 0.0;"
}

함정

증상원인해결
셰이더가 무시됨속성이 엄격한 JSON이 아님모든 키와 문자열에 큰따옴표 사용
메시가 깜빡이거나 초기화됨매 프레임 속성을 다시 씀updateUniforms() 사용
uTexture가 정의되지 않음요소에 이미지도 훅도 없음텍스처 청크는 텍스처나 훅이 있을 때만 주입됨
효과 위치가 어긋남vUv와 resultUv를 혼동vUv는 쿼드 로컬, resultUv는 실제 샘플링용

디버깅

머티리얼에서 컴파일된 셰이더를 읽어 볼 수 있습니다. private 필드를 뚫는 방식이라 API가 아닌 디버깅 요령으로만 쓰세요.

// start() 이후 — 내부 접근이며 버전에 따라 깨질 수 있음
const registry = (mirage as any)._engine.registry;
const mesh = registry.get(el);
console.log(mesh.material.fragmentShader);

WebGL 컴파일 오류는 브라우저 콘솔에 줄 번호와 함께 표시되며, 이 생성된 소스에 대응됩니다.


Mirage Engine — MIT 라이선스 © 2026 dltldn333