dFdx와 dFdy를 이용한 법선 벡터 계산

버텍스 쉐이더에서 정점을 흔들었을 경우 법선 벡터 역시 다시 계산을 해줘야 하는데, 이때 정점의 x와 y에 대한 편미분 값을 얻을 수 있는 dFdx와 dFdy를 이용하면 법선 벡터를 얻을 수 있습니다.

이런 경우 버텍스 쉐이더에 전달된 normal을 vary를 통해 프레그먼트 쉐이더로 전달할 필요가 없고 프레그먼트 쉐이더에서 법선 벡터를 계산해 주면 되는데, 프레그먼트 쉐이더에서 이에 대한 코드는 다음과 같습니다.

vec3 normal = normalize(
    cross(
        dFdx(vPosition.xyz),
        dFdy(vPosition.xyz)
    )
);

위의 vPosition은 버텍스 쉐이이더에서 재계산된 정점인데, 버텍스 쉐이더의 코드에서 보면 다음과 같습니다.

varying vec3 vPosition;

void main() {	
    vec3 posClone = position;
    posClone = /* 정점 흔들기(변경) */
    
    ...

    vPosition = modelMatrix * vec4(posClone, 1.0)).xyz;

    ...
}

적용 결과로 비교하면 먼저 dFdx와 dFdy를 통한 법선 벡터를 사용하지 않고 지오메트리를 통해 제공되는 법선 벡터를 그대로 사용한 경우는 아래와 같습니다.

버텍스 쉐이더에서 정점을 흔들어서 원래 제공된 법선 벡터가 맞지 않아 음영 효과가 제대로 표현되지 않는데, 이를 개선하기 위해 dFdx와 dFdy를 통한 법선 벡터를 사용한 결과는 다음과 같습니다.

lil-gui 코드

공식 사이트는 https://lil-gui.georgealways.com/ 입니다.

패키지 설치는 다음과 같습니다.

npm i lil-gui

필요한 import 문은 다음과 같습니다.

import * as dat from "lil-gui"

참고로 three를 설치하면 lil-gui가 덤으로 제공됩니다. three 설치를 통해 제공되는 lil-gui를 가져다 쓰기 위한 import 문은 다음과 같습니다.

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

사용 코드 예시는 다음과 같습니다.

const gui = new dat.GUI()

const obj = {
  value: 0.5,
  color: "#ff0000"
}

gui.add(obj, "value").min(0).max(1).step(0.01).name("title")
gui.addColor(obj, "color").onChange((v) => { console.log(v, obj.color) })

간단히 클릭해서 코드를 실행하는 버튼을 추가하는 코드입니다.

const toggle = {
  func: () => {
    toggle.flag = !toggle.flag;
    if(toggle.flag) {
      // 블라블라..
    } else {
      // 쁠라쁠라..
    }
  },
  flag: 1
}

gui.add(toggle, "func").name("toggle");

DNF 오류 발생의 경우 해결 방안

아래의 명령어로 패키지를 설치를 시도함

dnf install epel-release

다음과 같은 에러가 발생

PostgreSQL 10 for RHEL / Rocky 8 - x86_64 61 B/s | 146 B 00:02
Errors during downloading metadata for repository 'pgdg10':
- Status code: 404 for https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-8-x86_64/repodata/repomd.xml (IP: 217.196.149.55)
오류: repo를 위한 메타자료 내려받기에 실패하였습니다 'pgdg10': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried

pgdg10과 관련된 repomd.xml 파일을 다운로드할 수 없다는 에러로 다음 명령을 실행하여 [pgdg10]에서 enabled=1을 enabled=0으로 변경하여 해결함

vi /etc/yum.repos.d/pgdg-redhat-all.repo

SkinedMesh의 Clone

애니메이션 모델 리소스로부터 가져온 데이터를 여러개 복사해서 사용하고자 할때 필요한 코드입니다.

1개만 사용할때는 문제가 없는 기본적으로 작성된 코드는 다음과 같습니다.

import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
import * as THREE from "three"

export function Robot({ color = "gray", ...props }) {
  const group = useRef()
  const { nodes, materials, animations } = useGLTF('/Robot.glb')
  const { actions } = useAnimations(animations, group)
  const [ animation, setAnimation ] = useState("Idle")

  useEffect(() => {
    actions[animation].reset().fadeIn(0.5).play()
    return () => actions[animation].fadeOut(0.5)
  }, [ animation ])

  materials.Alpha_Body_MAT.color = new THREE.Color(color)

  return (
    <group ref={group} {...props} dispose={null}>
      <group name="Scene">
        <group name="Armature" rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
          <primitive object={nodes.mixamorigHips} />
          <skinnedMesh name="Alpha_Joints" geometry={nodes.Alpha_Joints.geometry} material={materials.Alpha_Joints_MAT} skeleton={nodes.Alpha_Joints.skeleton} />
          <skinnedMesh name="Alpha_Surface" geometry={nodes.Alpha_Surface.geometry} material={materials.Alpha_Body_MAT} skeleton={nodes.Alpha_Surface.skeleton} />
        </group>
      </group>
    </group>
  )
}

useGLTF.preload('/Robot.glb')

위의 코드에 대한 모델 여러개를 장면에 추가하기 위해 다음처럼 코드를 수정해야 합니다.

import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
import * as THREE from "three"
import { useGraph } from '@react-three/fiber'
import { SkeletonUtils } from "three-stdlib"

export function Robot({ color = "gray", ...props }) {
  const group = useRef()
  const { scene, materials, animations } = useGLTF('/Robot.glb')
  const clone = useMemo(() => SkeletonUtils.clone(scene), [scene])
  const { nodes } = useGraph(clone)
  const { actions } = useAnimations(animations, group)
  const [ animation, setAnimation ] = useState("Idle")

  useEffect(() => {
    actions[animation].reset().fadeIn(0.5).play()
    return () => actions[animation].fadeOut(0.5)
  }, [ animation ])

  const material = materials.Alpha_Body_MAT.clone()
  material.color = new THREE.Color(color)

  return (
    <group ref={group} {...props} dispose={null}>
      <group name="Scene">
        <group name="Armature" rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
          <primitive object={nodes.mixamorigHips} />
          <skinnedMesh name="Alpha_Joints" geometry={nodes.Alpha_Joints.geometry} material={materials.Alpha_Joints_MAT} skeleton={nodes.Alpha_Joints.skeleton} />
          <skinnedMesh name="Alpha_Surface" geometry={nodes.Alpha_Surface.geometry} material={material} skeleton={nodes.Alpha_Surface.skeleton} />
        </group>
      </group>
    </group>
  )
}

useGLTF.preload('/Robot.glb')

위의 코드에서 SkeletonUtils를 사용하고 있는데 이를 위해 아래의 코드를 실행하여 three-stdlib를 설치해야 합니다.

npm i three-stdlib