Three.js journey 10 - Textures

크롱·2024년 11월 23일
0

Three.js

목록 보기
7/12
post-thumbnail

Textures

  • Color (or albedo)
    • 가장 기본 텍스처 타입
      파일의 픽셀만 가져와 지오메트리에 적용

  • Alpha
  • grayscale image
    흰색: visible
    검은색: invisible

  • Height
  • grayscale image
  • 릴리프(조각처럼 튀어나온 느낌)를 만들기 위해 vertex(vertices)를 이동시킴
  • 이 텍스처를 활용하려면 subdivision을 추가해야함

  • Normal
  • 디테일 추가 ! 특히 빛lighting
  • subdivision 필요 없음
  • vertices 움직이지 않음
  • 빛의 방향을 조정
  • 효율적인 performance, -> vertex 숫자 적기 때문

  • Ambient Occlusion
  • grayscale image
  • 갈라진 틈에 fake 그림자를 넣는다
  • 디테일과 대조를 추가하는 용도
  • 하지만 물리적?으로 정확하진않음

  • Metalness
  • grayscale image
  • 흰색: 메탈릭
  • 검정색: 논 메탈릭
  • reflection 생성하는 용도

  • Roughness
  • grayscale image
  • metalness와 함께 사용된다
  • 흰색: 거친 부분
  • 검은색: 부드러운 부분
  • 빛 분산/소산 결정

예를들어 카페트를 위한 텍스처는 빛을 반사하지않아 roughness가 거의 흰색이다.


  • PBR(Physically Based Rendering)
    위의 텍스처들은 ( 특히 metalness 와 roughness) PBR principles를 따른다.
  • 사실적인 표현을 구현하기 위한 많은 기술들의 집합
  • PBR 을 통해 c4d, blender, unreal engine등 플랫폼에서 항상 같은 텍스처, 모델을 얻을 수 있다



Textures 사용

이미지 가져오기

/src/ 폴더 활용 (JS 모듈처럼 임포트해서 사용)


imageSource from './image.png'

/static/ 폴더 활용

웹팩 또는 vite를 사용하고있다면, src 폴더 같은 위치에 static 폴더를 만든 뒤 여기에 img가 있다고 가정해보자.


const imageSource = '/image.png'

더 간편하다



이미지 로드하기

Native JavaScript

보통 밑 Three.js class에 내장된 TextureLoader로 이미지를 텍스처로 변환하지만, Native JavaScript로 구현하는 법도 알아보자.

const image = new Image()
const texture = new THREE.Texture(image) //처음엔 image 비어있음

image.onload = () => { // 이미지가 로드되면 실행될 함수
  console.log('이미지 로드됨')
  texture.needsUpdate = true // 이미지가 업데이트되면 텍스쳐 너도 업데이트하렴
  console.log('텍스처 업데이트됨')
}
console.log('안녕')
image.src = '/textures/door/color.jpg' //이 코드가 있어야 위에 onload 함수 실행됨


👇출력 결과👇
안녕
이미지 로드됨
텍스처 업데이트됨

이 image를 cube에 적용하기 위해 image를 texture로 변환해야한다.
image 가 로드되면 texture를 업데이트 시켜야한다.
그리고 해당 texture를 new THREE.MeshBasicMaterial를 통해 생성된 material에 적용시키자


👇📚 전체 코드 📚👇

/**
 * Textures
 */

const image = new Image()
const texture = new THREE.Texture(image)
texture.colorSpace = THREE.SRGBColorSpace
// 최근 Three.js버전에선 이 코드를 추가해야지 texture의 컬러감이 정확하게 표시된다


/**
 * Object
 */
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ map: texture }) // texture 여기서 사용
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

TextureLoader

위처럼 기본 자바스크립트로 구현할 수 있지만
ThreeJS에서 제공하는 함수인 TextureLoader를 통해 텍스처를 적용시키는게 더 쉽다.

/**
 * Textures
 */

const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/textures/door/color.jpg')
texture.colorSpace = THREE.SRGBColorSpace
// 최근 Three.js버전에선 이 코드를 추가해야지 texture의 컬러감이 정확하게 표시된다


/**
 * Object
 */
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ map: texture }) // texture 여기서 사용
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
  • new THREE.TextureLoader()로 생성된 textureLoader는 여러개의 texture을 로드할수있다.
  • textureLoader.load 함수의 2,3,4 번째 인자로 함수를 넣을 수 있다.
    2-> 로드될때 실행
    3-> progress될때 실행 (사용거의안함)
    4-> 에러 발생 시 실행
    궁금하면 소스코드 보기


Loading Manager

현재 하나의 텍스처를 로딩하기때문에 하나의 textureLoader만 사용하고 있지만 추후 여러가지 텍스처들을 로드할때 이 모든 로드들의 progress를 알기위해 LoadingManager를 사용한다
예를들어 웹사이트 초반에 로딩바를 띄우거나 할 때 유용하다.

또한 이벤트들을 상호작용하기 위해 쓰인다고한다 = mutualize(상호작용) the events


const loadingManager = new THREE.LoadingManager()

loadingManager.onStart = () => {
  console.log('onStart')
}
loadingManager.onProgress = () => {
  console.log('onProgress')
}
loadingManager.onLoad = () => {
  console.log('onLoad')
}
loadingManager.onError = () => {
  console.log('onError')
}

const textureLoader = new THREE.TextureLoader(loadingManager)


const colorTexture = textureLoader.load('/textures/door/color.jpg')
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const metalnessTexture = textureLoader.load('/textures/door/metalness.jpg')

현재 4개의 텍스처를 로드하고있기때문에 onProgress가 4번 찍힌다.



UV Unwrapping

텍스처는 어떻게 입혀지는걸까? 에 해당하는 의문에는 UV unwrapping이라는 개념이 도입된다.

UV unwrapping란 종이접기로된 모형을 펼치는 것과 같다.

const geometry = new THREE.BoxGeometry(1, 1, 1)

console.log(geometry.attributes.uv)

geometry.attributes.uv 을 통해 해당 geometry UV의 2D 좌표를 살펴볼 수 있다. 직접 Geometry를 만들경우에 이 UV 좌표를 우리가 따로 지정해야한다. 블렌더 프로그램을 이용할때도 마찬가지인데 보통 저절로 생성해준다고한다.

아무튼 텍스처가 geometry에 적용될때 geometry attribute의 uv coordinates 가 있기 때문에 텍스처가 일정한 방향으로 적용된다는 것이다.



Transforming the Texture

repeat

const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace


// repeat 은 vector2 여서 x,y 접근 가능
colorTexture.repeat.x = 2
colorTexture.repeat.y = 3

colorTexture.wrapS=THREE.RepeatWrapping //  x , THREE.MirroredRepeatWrapping
colorTexture.wrapT=THREE.RepeatWrapping // y


👆 RepeatWrapping을 하지 않으면 위와같이 결과가 나온다.


👆 THREE.RepeatWrapping 의 결과


👆 THREE.MirroredRepeatWrapping의 결과

rotation

텍스처를 돌려보자


만약 반원만큼 회전을 하려고하면 π 만큼 곱하면 된다
(원둘레를 구하는 공식이 원둘레 = 2 π 반지름 이니까)
빨간 화살표처럼 1/8 만큼 돌리고싶다면 π * 1/4

colorTexture.rotation = Math.PI / 4

colorTexture.rotation = Math.PI / 4 를 적용하면 위 처럼 되는데, 이 이유는 회전축이 아래 사진에서 하늘색으로 표시된 0,0에 위치하기때문이다

우리는 회전축을 가운데로 옮겨주면된다


colorTexture.center.x = 0.5
colorTexture.center.y = 0.5




Filtering and Mipmapping

이렇게 비스듬하게 큐브를 바라볼때 윗 면의 텍스처가 블러처리가 된 것을 확인할 수 있는데 바로 필터링과 밉매핑덕분이다.

텍스처는 매우 많은 pixel로 이루어져있는데, Mipmapping은 1x1 텍스쳐를 얻을 때까지 계속해서 더 작은 버전의 텍스쳐를 생성하는 기술이다. 이때 생성되는 모든 텍스쳐의 변형이 GPU로 전송되고, GPU는 가장 적합한 버전의 텍스쳐를 선택하게 된다. THree.js 에서 알아서 처리하고있다. 우리는 어떤 필터 알고리즘을 사용할지 설정하면 된다. 필터 알고리즘에는 두 가지 유형이 있다: mignification 필터와 magnification 필터입니다.

Minification filter


위 사진처럼 텍스처의 픽셀이 렌더링되는 픽셀보다 작을 때 minification filter가 발생한다

텍스처의 Minification 필터는 minFilter 속성을 사용하여 변경할 수 있는데

  • THREE.NearestFilter
  • THREE.LinearFilter
  • THREE.NearestMipmapNearestFilter
  • THREE.NearestMipmapLinearFilter
  • THREE.LinearMipmapNearestFilter
  • THREE.LinearMipmapLinearFilter
    기본값은 마지막 THREE.LinearMipmapLinearFilter이다
colorTexture.minFilter = THREE.NearestFilter

이렇게 바꿀수있는데, THREE.NearestFilter를 사용하면 굉장히 샤프하고 선명한 결과를 얻을 수 있다

만약 minFilter에서 THREE.NearestFilte를 사용하고 있다면 Mipmapping이 필요 없으므로, 아래와 같이 비활성화할 수 있다:

colorTexture.generateMipmaps = false;
colorTexture.minFilter = THREE.NearestFilter;

이렇게 하면 GPU의 부담이 약간 줄어든다고한다.

Magnification filter

텍스처의 픽셀이 렌더링되는 픽셀보다 클 때 적용되는 Magnification filter. 다시 말해, 텍스처가 표면에 비해 너무 작아서 확대되는 경우이다


이렇게 엄청 작은 텍스처를 큐브에 적용시키면

텍스처가 stretch 되면서 블러처리가된다.

magFilter 속성을 사용하여 변경할 수 있다

  • THREE.NearestFilter
  • THREE.LinearFilter

디폴트는 THREE.LinearFilter이다.

colorTexture.magFilter = THREE.NearestFilter

짱선명해짐




Texture Format and Optimization

텍스쳐를 사용할때 생각해야할 것들

  • weight

사용자가 내 웹사이트를 방문하면 텍스쳐를 다운로드해야하므로, 용량이 가벼운 파일이 좋다. jpg 가 보통 png보다 더 가벼운데 png를 사용한다면 TinyPNG같은 곳에서 파일 용량을 줄이도록하자.

  • size

텍스쳐의 모든 픽셀은 GPU에 저장되어야하므로 이미지의 크기를 줄이는 것이 좋다.
자동생성되는 mipmapping , 즉 Three.js는 텍스처의 절반 크기 버전을 반복적으로 생성하여 1x1 텍스처를 만든다. 따라서 텍스처의 너비와 높이는 2의 거듭제곱이어야 한다. 이는 Three.js가 텍스처의 크기를 2로 나눌 수 있도록 하기 위해서임

예시: 512x512, 1024x1024 또는 512x2048

512, 1024, 2048은 1에 도달할 때까지 2로 나눌 수 있으므로 good
만약 2의 거듭제곱이 아닌 너비나 높이를 가진 텍스처를 사용하면, Three.js는 가장 가까운 2의 거듭제곱으로 늘리려고 시도하게 되며, 이는 퍼포먼스에 악영향을 끼친다.

  • data

텍스쳐는 투명도를 지원한다. jpg 파일에는 알파채널이 없기 때문에 png를 활용하는 것이 좋다.
특히 노말 텍스처를 사용하면 png를 사용해야한다.


참고: https://velog.io/@9rganizedchaos/Three.js-journey-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-11

profile
👩‍💻안녕하세요🌞

0개의 댓글