관리 메뉴

웹솔루션개발 25년 노하우! 해피CGI의 모든것

[해피CGI][cgimall] 자바스크립트를 이용한 물방울 효과(VFX) VFX-JS + webfont + dynamic text + raymarching 본문

웹프로그램밍 자료실/JAVA 자료

[해피CGI][cgimall] 자바스크립트를 이용한 물방울 효과(VFX) VFX-JS + webfont + dynamic text + raymarching

해피CGI윤실장 2025. 11. 12. 09:11




자바스크립트로 만든 시각 효과(VFX) 기반의 페이지에서 웹폰트를 사용한 글자를 동적으로 변화시키고,

레이마칭 기술로 화려한 3D 시각 효과를 표현한 디자인입니다.




HTML 구조

<h1 contenteditable>Type<br>here.<br>please.</h1>



CSS 소스

@import url('https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700;900&display=swap');

 

 

html, body { height: 100%; margin: 0; }

body {

  background: black;

  background-image: linear-gradient(

    340deg,

    hsl(220deg 50% 30%) 0%,

    hsl(200deg 30% 40%) 20%,

    hsl(180deg 20% 50%) 35%,

    hsl(220deg 30% 60%) 70%,

    hsl(270deg 30% 70%) 90%,

    hsl(300deg 30% 70%) 100%

  );

  display: grid;

  place-content: center;

  overflow-x: hidden;

}

img {

  position: fixed;

  width: 100%;

  height: 100%;

  object-fit: stretch;

}

 

h1 {

  margin: 0;

  font-size: 15vh;

  line-height: 1.2;

  max-width: 100%; 

 

  font-family: "Cinzel Decorative";

  text-align: center;

  color: white;

}

 

 

JS 소스

// Using VFX-JS.

// https://amagi.dev/vfx-js

import { VFX } from "https://esm.sh/@vfx-js/core@0.6.0";

 

function lerp(a,b,t) {

  return a * (1 - t) + b * t;

}

 

const shader = `

precision highp float;

uniform vec2 resolution;

uniform vec2 mouse;

uniform vec2 offset;

uniform float time;

uniform sampler2D src;

uniform vec3 mouseDir;

#define PI 3.141593

 

mat2 rot(float t) {

  return mat2(cos(t), -sin(t), sin(t), cos(t));

}

 

float rand(vec2 p) {

  return fract(sin(dot(p, vec2(484., 398.)) * 984.));

}

 

float sdSphere(vec3 p, float r) {

  return length(p) - r;

}

 

float map(vec3 p) {

  // Center sphere

  float d = sdSphere(p - mouseDir * 8., 3.);

 

  // Rotate world

  p.xz *= rot(sin(time * 0.3) * 0.5);  

  p.xy *= rot(time * 0.7);  

  

  // Place spheres

  float l = 4.4;

  d = min(d, sdSphere(p + vec3(1, 1, 1) * l, 2.0));

  d = min(d, sdSphere(p + vec3(-1, -1, 1) * l * 2., 1.7));  

  d = min(d, sdSphere(p + vec3(-1, 1, -1) * l, 1.2));  

  d = min(d, sdSphere(p + vec3(1, -1, -1) * l, 2.9));    

 

  return d;

}

 

vec3 getNormal(vec3 p) {

  vec2 d = vec2(1, 0);

  return normalize(vec3(

    map(p + d.xyy) - map(p - d.xyy),

    map(p + d.yxy) - map(p - d.yxy),

    map(p + d.yyx) - map(p - d.yyx)

  ));

}

 

vec3 spectrum(float x) {

    return cos((x - vec3(0, .5, 1)) * vec3(.6, 1., .5) * PI);

}

 

float surface(vec3 p, vec3 n, vec3 rd, vec3 lig) {        

  float c = 0.;

  

  // specular

  vec3 hal = normalize(lig - rd);

  float spe = pow(clamp(dot(hal, n), 0., 1.), 250.);

  c += spe * 0.9;

      

  // diffuse

  c += clamp(dot(n, lig), 0., 1.) * 0.4;

 

  return c;

}

 

void main() {

  vec2 uv = (gl_FragCoord.xy - offset) / resolution;

  vec2 p = uv * 2. - 1.;

  p.x *= resolution.x / resolution.y;

  

  p *= resolution.y / 1000.;

    

  vec3 ro = vec3(0, 0, 30);

  vec3 rd = normalize(vec3(p, -7));

  vec3 rp;

  

  float t = 0.;

  float d;

   

  vec4 c = vec4(0);

  vec4 light = vec4(0);  

  

  vec3 n = vec3(-1);

  

  for (int i = 0; i < 50; i++) {

    rp = ro + rd * t;

    d = map(rp);

 

    if (d < 0.01) {     

      n = getNormal(rp); 

      break;

    }

    if (t > 50.) {

      break;

    }

 

    t += d;

  }

  

  if (n.z > 0.) {

    float glaze = clamp(1. + dot(rd, n), 0., 1.);

 

    // Cheap dispersion

    float z = rp.z + 5.; // wall at z = -5.

    for (float x = 0.; x <= 1.; x += 0.1) { 

      float xx = x * 2. - 1.; // remap to [-1, 1]

    

      float ior = 1.5 + x * 2.;

      vec3 rd2 = rp + refract(rd, n, 1. / ior);

       

      vec2 uv2 = uv + rd2.xy * z * 0.003 * xx * glaze;

      uv2 += rand(rp.xy) * 0.1 * pow(glaze, 3.);

 

      vec4 tc = texture2D(src, uv2);

      c += vec4(spectrum(x * 0.7) * 2., 1) * tc.a;

    }

    c /= 11.;    

    

    // fresnel

    c += pow(glaze, 3.) * 0.8 * vec4(.6, .9, 1, 1);

 

    // lighting

    c += surface(rp, n, rd, normalize(vec3(-.8, 1., .2))) * vec4(.8, .9, 1, 1);

    c += surface(rp, n, rd, normalize(vec3(1., -0.7, .1)));

    

    c = clamp(c, 0., 1.);    

    c += vec4(0.0, 0.02, 0.03, 0.1);

  }

 

  // Vignette

  c += vec4(0, 0, 0, pow(length(p), 2.) * 0.04);

  c += rand(uv * 2.) * 0.1;

 

  c.rgb /= c.a;

  gl_FragColor = c;

}

`;

 

// Constants

const padding = 30;

const lineHeight = 1.2;

const leading = 0.17;

const ratio = window.devicePixelRatio || 1;

 

// Source element

const e = document.querySelector('h1');

 

// Canvas

const canvas = document.createElement('canvas');

const ctx = canvas.getContext("2d");

document.body.appendChild(canvas);

 

const vfx = new VFX();

 

// Sync text to canvas

function sync() {   

  // Dimensions

  const rect = e.getBoundingClientRect();  

  canvas.style.position = "fixed";

  canvas.style.left = `${rect.left - padding}px`;

  canvas.style.top = `${rect.top - padding}px`;   

  canvas.style.width = `${rect.width + padding * 2}px`;

  canvas.style.height = `${rect.height + padding * 2}px`; 

  canvas.width = (rect.width + padding * 2) * ratio;

  canvas.height = (rect.height + padding * 2) * ratio;

  canvas.style.pointerEvents = "none";

.
.
.


해당 사이트로 이동하여 전체 소스를 확인하실 수 있습니다.

 

 

Comments