커스텀 셰이더 머티리얼

Custom shader material

내장 머티리얼 대신 직접 GLSL 코드를 짜서 정점·픽셀 단위로 색과 모양을 완전히 제어하는 방법.

다른 이름: GLSLShaderMaterialVertex/Fragment shader
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x08060f);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0, 4.6);

const vertexShader = [
  'varying vec3 vPos;',
  'varying vec3 vNormal;',
  'void main() {',
  '  vPos = position;',
  '  vNormal = normalize(normalMatrix * normal);',
  '  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
  '}',
].join('\n');

const fragmentShader = [
  'uniform float uTime;',
  'varying vec3 vPos;',
  'varying vec3 vNormal;',
  'void main() {',
  '  float bands = sin((vPos.y * 4.0 + uTime * 1.4)) * 0.5 + 0.5;',
  '  float swirl = sin((vPos.x * 5.0 - vPos.z * 5.0 + uTime * 2.0)) * 0.5 + 0.5;',
  '  vec3 colorA = vec3(0.36, 0.36, 0.97);',
  '  vec3 colorB = vec3(0.95, 0.36, 0.54);',
  '  vec3 base = mix(colorA, colorB, bands * 0.6 + swirl * 0.4);',
  '  float rim = pow(1.0 - abs(dot(vNormal, vec3(0.0, 0.0, 1.0))), 2.0);',
  '  gl_FragColor = vec4(base + rim * 0.25, 1.0);',
  '}',
].join('\n');

const mat = new THREE.ShaderMaterial({
  uniforms: { uTime: { value: 0 } },
  vertexShader,
  fragmentShader,
});
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1.5, 96, 96), mat);
scene.add(mesh);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  mat.uniforms.uTime.value = t * 0.001;
  mesh.rotation.y = t * 0.00018;
  renderer.render(scene, camera);
});

THREE.ShaderMaterial은 vertexShader와 fragmentShader 두 문자열을 받습니다. 정점 셰이더는 정점마다 한 번 실행되어 화면 위치(gl_Position)를 정하고, 프래그먼트 셰이더는 화면에 찍히는 픽셀마다 한 번 실행되어 색(gl_FragColor)을 정합니다. 둘 사이에 값을 넘기려면 varying(또는 out/in) 변수를 씁니다.

JS에서 GLSL로 값을 넘기는 통로는 uniform입니다 — 모든 정점·픽셀이 동일한 값을 보는 전역 변수라고 생각하면 됩니다. 데모는 uniform float uTime 하나로 매 프레임 흐르는 줄무늬와 소용돌이 패턴을 프래그먼트 셰이더 안에서 sin() 함수로 계산합니다.

position, normal, uv 같은 attribute와 modelViewMatrix, projectionMatrix 같은 매트릭스는 three.js가 자동으로 셰이더에 주입해주므로 따로 선언할 필요가 없습니다. 직접 선언하면 오히려 중복 선언 에러가 납니다.

언제 쓰나

내장 머티리얼로는 못 만드는 독자적인 룩, 절차적 패턴, 데이터 시각화 셰이더를 만들 때.