TSL에서 2D 회전

어떤 2차원 좌표(st)를 정해진 중심점(mid)을 기준으로 원하는 각도(rotation)만큼 회전된 결과를 얻는 TSL 함수 정의는 다음과 같다.

const _rotate = Fn(([st, rotation, mid]) => {
  return vec2(
    cos(rotation).mul(st.x.sub(mid.x)).add(sin(rotation).mul(st.y.sub(mid.y))).add(mid.x),
    cos(rotation).mul(st.y.sub(mid.y)).sub(sin(rotation).mul(st.x.sub(mid.x))).add(mid.y),
  );
}, { st: 'vec2', rotation: 'float', mid: 'vec2', return: 'vec2'})

TSL에서 위의 함수처럼 2D 회전에 대한 내장 함수를 제공하는데 바로 rotateUV 노드 함수이다. rotateUV 노드 함수는 3차원 좌표를 회전하는 TSL 내장 함수인 rotate를 사용한 것에 지나지 않는다.

즉, 아래의 결과는 모두 같다.

material.fragmentNode = Fn(([]) => {
  const { x, y } = uv().toVar();

  const rotatedUv = _rotate(vec2(x, y), Math.PI * 0.25, vec2(0.5));
  // const rotatedUv = rotateUV(vec2(x, y), Math.PI * 0.25, vec2(0.5)); // 위와 같고
  // const rotatedUv = rotate(vec2(x, y).sub(vec2(0.5)), Math.PI * 0.25).add(vec2(0.5)) // 또 위와 같고..

  const l1 = float(0.03).div(length(rotatedUv.sub(vec2(.5, .5)).mul(vec2(.2, 1))));
  const l2 = float(0.03).div(length(rotatedUv.sub(vec2(.5, .5)).mul(vec2(1, .2))));

  return vec4(vec3(mul(l1, l2)), 2);
})();

아래 이미지는 위의 코드를 통해 45도 회전된 결과이다.

HDR 이미지로 캡쳐가 안되서 너무 구리게 보이는데….

Y축으로 회전 (TSL 관점)

Y축으로 회전하는 TSL 코드를 2가지 관점에서 정리하면, 먼저 Y축으로 회전하는 행렬을 직접 구성해 회전하고자 하는 정점에 적용하는 방식이다.

export const rotateY = /*@__PURE__*/ Fn( ( [ theta ] ) => {
	const c = cos( theta );
	const s = sin( theta );
    return mat3(
        c, 0, s,
        0, 1, 0,
        s.negate(), 0, c
    );

	// same as : return mat3( vec3( c, 0, s ), vec3( 0, 1, 0 ), vec3( s.negate(), 0, c ) );
}, { theta: 'float', return: 'mat3' } );

...

const p = vec3(x, y, z);
const rMat = rotateY(angle);
const rotatedP = rMat.mul(p).add(offset); // 회전하고 offsect 만큼 이동

두번째는 TSL에서 제공하는 범용적인 회전 노드 함수를 이용하는 것이다.

const rotatedP = rotate(p, vec3(0, angle, 0)).add(offset); // 회전하고 offsect 만큼 이동

uv로 파이썬 프로젝트 생성

먼저 터미널로 프로젝트를 구성할 폴더로 이동하고 ..

uv init

위의 명령이 실행되면 해당 폴더에 프로젝트 구성 파일이 생성된다. 그중 main.py 파일이 보이는데 이 파일을 다음처럼 실행하면 필요한 가상환경이 생성되면서 해당 py 파일이 실행된다.

uv run main.py

참고로 uv는 다음처럼 설치할 수 있다.

pip install uv

uv 통한 패키지 설치는 다음과 같다. (예: requests)

uv add requests

위의 방식을 권장하지만 다음으로도 가능하다.

uv pip install requests

uniform TSL 노드 함수의 update 이벤트

TSL의 uniform은 GLSL의 uniform이다. 다음처럼 간단히 정의할 수 있다.

const myColor = uniform(new THREE.Color(0x0066ff));
material.colorNode = myColor;

TSL의 uniform은 update에 대한 3가지 이벤트를 제공한다.

이 uniform 노드를 사용하는 재질이 적용되는 Object(Mesh 등)에 대해서 호출(하나의 재질은 여러개의 매시에 적용될 수 있음)된다.

myColor.onFrameUpdate(({ object }) => { 
  myColor.value = new THREE.Color(Math.random() * 0xffffff)
})

한번의 프레임을 완성하기 위해 여러번의 렌더 pass가 필요할 수 있는데, 렌더 pass마다 호출된다.

myColor.onRenderUpdate(({ object }) => {
  myColor.value = new THREE.Color(Math.random() * 0xffffff)
})

렌더 pass가 여러번일지라도 프레임 당 딱 1번 호출된다.

myColor.onFrameUpdate(({ object }) => {
  myColor.value = new THREE.Color(Math.random() * 0xffffff)
})

GLSL vs TSL Nodes

GLSL TSL
position positionGeometry
transformed positionLocal
transformedNormal normalLocal
vWorldPosition positionWorld
normalWorld
gl_FrongFacing frontFacing
vColor vertexColor()
vUv / uv uv()
vNormal normalView
cameraPosition
cameraPosition
cameraNear
cameraFar
modelMatrix modelWorldMatrix
modelViewMatrix modelViewMatrix
viewMatrix cameraViewMatrix
projectionMatrix cameraProjectionMatrix
diffuseColor material.colorNode
gl_FragColor material.fragmentNode
texture2D(tex, uv) texture(tex, uv)
textureCube(tex, dir) cubeTexture(tex, dir)
gl_FragCoord screenCoordinate
screenSize
gl_PointCoord SpriteNodeMaterial/PointsNodeMaterial에서 uv()
gl_InstanceID instanceIndex
gl_VertexIndex vertexIndex
time
deltaTime
frameId