TSL Tips

normalNode에 대한 적용을 위해 bumpMap 노드를 사용

const diffuseTex = loader.load('./brick_diffuse.jpg');
diffuseTex.colorSpace = THREE.SRGBColorSpace;

const bumpTex = loader.load('./brick_bump.jpg');

const wallMat = new THREE.MeshStandardNodeMaterial();

wallMat.colorNode = texture(diffuseTex);
wallMat.normalNode = bumpMap(texture(bumpTex), float(5));

각도와 거리에 대한 가하학적 계산식

GPU를 통해 쓰고 읽을 수 있는 텍스쳐

/* 필요한 API 임포트 */
import { texture, textureStore, Fn, instanceIndex, float, uvec2, vec4, time } from 'three/tsl';

/* 스토리지 텍스쳐 생성 */
const width = 512, height = 512;
const storageTexture = new THREE.StorageTexture(width, height);

/* 텍스쳐 내용 생성 함수 정의 */
const computeTexture = Fn(({ storageTexture }) => {
  const posX = instanceIndex.mod(width);
  const posY = instanceIndex.div(width);
  const indexUV = uvec2(posX, posY);

  const x = float(posX).div(50.0);
  const y = float(posY).div(50.0);

  const v1 = x.sin();
  const v2 = y.sin();
  const v3 = x.add(y.add(time.mul(1))).sin();
  const v4 = x.mul(x).add(y.mul(y)).sqrt().add(5.0).sin();
  const v = v1.add(v2, v3, v4);

  const r = v.sin();
  const g = v.add(Math.PI).sin();
  const b = v.add(Math.PI).sub(0.5).sin();

  textureStore(storageTexture, indexUV, vec4(r, g, b, 1)).toWriteOnly();
});

/* 텍스쳐 내용 생성 함수 실행 */
const computeNode = computeTexture({ storageTexture }).compute(width * height);
this._renderer.compute(computeNode);

/* 텍스쳐 활용 */
const material = new THREE.MeshBasicNodeMaterial({ color: 0x00ff00 });
material.colorNode = texture(storageTexture);

물리적 광원에 대한 개별 노드 지정 (TSL 방식)

three.js 쉐이더 언어인 TSL에서 물리적 광원에 대한 개별 노드를 지정하는 방식을 정리해 봅니다. TSL을 통해 표현하고자 하는 재질은 다음과 같습니다.

코드는 다음과 같습니다.

const geometry = new THREE.IcosahedronGeometry(1, 64);
const material = new THREE.MeshStandardNodeMaterial({
  color: "black",
  // wireframe: true,
});
const mesh = new THREE.Mesh(geometry, material)

const path = './Metal053B_2K-JPG';

const texColor = new THREE.TextureLoader().load(`${path}/Metal053B_2K-JPG_Color.jpg`);
material.colorNode = texture(texColor);

const texNormal = new THREE.TextureLoader().load(`${path}/Metal053B_2K-JPG_NormalGL.jpg`);
material.normalNode = normalMap(texture(texNormal), float(1.));

const texMetalness = new THREE.TextureLoader().load(`${path}/Metal053B_2K-JPG_Metalness.jpg`);
material.metalnessNode = mul(texture(texMetalness), 1.);

const texRoughness = new THREE.TextureLoader().load(`${path}/Metal053B_2K-JPG_Roughness.jpg`);
material.roughnessNode = mul(texture(texRoughness), float(0.7));

광원 노드에 집중하기 위해서 텍스쳐 데이터를 사용했습니다. 텍스쳐 본연의 표현을 위해 재질의 기본 색상을 블랙으로 지정했구요. 사용한 노드는 colorNode, normalNode, metalnessNode, roughnessNode입니다.

광원에 대한 노드는 아니지만 Displacement에 대한 노드를 알아보기 위해 표현하고자 하는 재질은 다음과 같습니다.

코드는 다음과 같습니다.

const geometry = new THREE.IcosahedronGeometry(1, 64);
const material = new THREE.MeshStandardNodeMaterial({
  color: "black",
  // wireframe: true,
});
const mesh = new THREE.Mesh(geometry, material)

const path = './Rock058_2K-JPG';

...

const texAO = new THREE.TextureLoader().load(`${path}/Rock058_2K-JPG_AmbientOcclusion.jpg`);
geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));
material.aoNode = mul(texture(texAO), float(1.));

const texDisplacement = new THREE.TextureLoader().load(`${path}/Rock058_2K-JPG_Displacement.jpg`);
const displacementNode = texture(texDisplacement);
const displaceStrength = 0.3;
const displacementValue = displacementNode.r.sub(0.5).mul(displaceStrength);
const newPosition = positionWorld.add(normalLocal.mul(displacementValue));
material.positionNode = newPosition;

Displacement 표현을 위해서는 Vertex Shader에 해당하는 positionNode를 이용해야 합니다. 추가로 AO 노드에 대한 사용 코드도 보입니다.