개발 · 컴퓨터공학/three.js / / 2024. 10. 4. 12:49

three.js 모델 텍스처 적용하기 [Threejs Cloth Tailor 개발일지]

728x90
반응형

 

모델 텍스처 변경

배경을 꾸몄으니 다음으로 모델에 텍스처를 입히도록 하자. 

모델에 uv가 딱 적합한 것은 없으니 아무 반복적인 텍스처나 입혀야한다.

 

threejs에서도 다양한 uv map을 활용해서 실사그래픽을 표현할 수 있는데,

color 이외에 normal, roughness, AmbientOcclusion 등 맵을 이용할 수 있다. 

 

우선 하나의 color 텍스처 먼저 적용해보자. 

텍스처는 현재 순서 중 object를 scene에 추가하는 init 함수 내에서만 하면 된다.

 

async function init() {
  // ===== Managers =====
  initSkyBox(scene)
  initInputEvents(simulationStart)
  raycast.init(scene, camera, inputSimulClothList)
  mode.init(
    ()=>{ // common
      raycast.modeChangeEvent(scene, camera)
    },
    ()=>{ // NONE
      cameraControls.enabled = true
    },
    ()=>{ // RAYCAST
      cameraControls.enabled = false
    },
    ()=>{ // REMOVE_VERTEX
      cameraControls.enabled = false
    },
    ()=>{ // REMOVE_EDGE
      cameraControls.enabled = false
    },
    ()=>{ // TRANSFORM
      cameraControls.enabled = true
    },
    ()=>{ // EXPAND_VERTEX
      raycast.initAttachVetexStatus(scene)
      cameraControls.enabled = false
    },
    ()=>{ // ATTACH_VERTEX
      raycast.initAttachVetexStatus(scene)
      cameraControls.enabled = false
    },
    "NONE"
  )

  // ===== 💡 LIGHTS =====
  {
    ambientLight = new AmbientLight('white', 0.4)
    scene.add(ambientLight)
    directionalLight = new DirectionalLight('white', 0.5)
    scene.add(directionalLight)
    pointLight = new PointLight('white', 0.1)
    scene.add(pointLight)
  }
  // const planeGeometry = new PlaneGeometry(3, 3)
  const planeGeometry = new PlaneGeometry(3, 3)
  const planeMaterial = new MeshLambertMaterial({
    color: 'gray',
    emissive: 'teal',
    emissiveIntensity: 0.2,
    side: 2,
    transparent: true,
    opacity: 0.4,
    wireframe: false,
  })
  const plane = new Mesh(planeGeometry, planeMaterial)
  plane.rotateX(Math.PI / 2)
  plane.receiveShadow = true
  plane.position.setY(floorHeight)
  plane.name = 'floor'
  scene.add(plane)

  // model load
  //#region cloth object
  let objPath = 'cloth40x40.obj'
  let file = await customOBJLoader.load(objPath)
  cloth = new Cloth(customOBJLoader.parse(file), thickness, false)
  cloth.mesh.material = new MeshStandardMaterial({ color: 'red', wireframe: false, side:2})
  cloth.mesh.name = 'cloth'
  cloth.mesh.rotateOnAxis(new Vector3(1,0,0), -1.5)
  cloth.mesh.translateZ(1)
  //#endregion

  //#region cloth onepiece object
  objPath = 'onepiece.obj'
  file = await customOBJLoader.load(objPath)
  clothOnepiece = new Cloth(customOBJLoader.parse(file), thickness, false)
  clothOnepiece.mesh.material = new MeshStandardMaterial({ color: 'red', wireframe: false, side:2})
  //#endregion

  // modify this code to change object model
  scene.add(cloth.mesh)
  simulClothList.push(cloth)

  // Transform Controls
  transformControls = new TransformControls(camera, renderer.domElement)
  transformControls.addEventListener('dragging-changed', event => {
    cameraControls.enabled = !event.value
  })
  transformControls.name = 'TransformControls'
  scene.add(transformControls)
  raycast.initTransformControls(transformControls, scene, camera)

  // collision mesh
  objPath = 'mannequin.obj'
  file = await customOBJLoader.load(objPath)
  collisionMesh = customOBJLoader.parse(file)
  collisionMesh.material = new MeshStandardMaterial({ color: '#008dc5', wireframe: false, side:2})
  collisionMesh.name = 'obstacle'
  scene.add(collisionMesh)
  //

  // debugger
  gui.init()
  gui.changeEnvironment(scene)
  gui.vertexViewer(cloth.mesh, scene)
  gui.changeMode()

  hierarchy.buildHierarchy(scene)
}

현재 init 함수는 상당히 길다. 

 

  // ===== 🧱 TEXTURES =====
  collisionMesh.material; // mannequin
  cloth.mesh.material; // cloth

위 두 개의 텍스처를 변경해보면 된다.

 

TextureLoader

https://threejs.org/docs/#api/en/loaders/TextureLoader

 

three.js docs

 

threejs.org

텍스처를 불러오기 위해서는 texture loader를 이용하면 된다.

 

import * as THREE from 'three';

// 텍스처 로더 생성
const textureLoader = new THREE.TextureLoader();

// 텍스처 로드 (이 경로는 텍스처 파일의 실제 경로로 교체해야 합니다)
const mannequinTexture = textureLoader.load('path/to/mannequin_texture.jpg');
const clothTexture = textureLoader.load('path/to/cloth_texture.jpg');

// mannequin material에 텍스처 추가
if (collisionMesh.material instanceof THREE.MeshStandardMaterial) {
  collisionMesh.material.map = mannequinTexture;
  collisionMesh.material.needsUpdate = true; // 텍스처가 적용된 후 material 업데이트 필요
}

// cloth material에 텍스처 추가
if (cloth.mesh.material instanceof THREE.MeshStandardMaterial) {
  cloth.mesh.material.map = clothTexture;
  cloth.mesh.material.needsUpdate = true;
}

위 템플릿 코드를 이용해서 작성해보자. 

 

  // ===== 🧱 TEXTURES =====
  collisionMesh.material; // mannequin
  cloth.mesh.material; // cloth

  const clothTex = textureLoader.load('../../public/texture/0/Color.png');
  cloth.mesh.material.map = clothTex

JS단에서는 적용이 잘되고 있지만, TS에서는 컴파일 에러가 난다.

 

또 TS의 귀찮은 특징인 type 명시를 해주어야 해결된다.

 

이런 식이다. 

이게 JS의 자동 형변환 때문이라고 생각하면 필요한 과정이긴 하다. 

 

texture normal map 설정하기 

마네킹에도 텍스처를 적용하였는데 평면적인 느낌이 든다.

그래서 적용하는 것이 normal map인데 이게 제대로 적용이 될라나.

 

normal map을 적용한 모습이다.

눈에 확 띄게 티나지는 않지만 입체감이 조금 느껴진다.

 

빛이 일부만 비춰지는 아래쪽에서 보면 확실히 굴곡이 느껴진다.

 

Ambient Occlusion Map 적용하기

normal 맵 말고도, metalness, roughness, AO 등 다양한 맵을 모두 적용하면 조금씩 퀄이 올라간다.

조명이 다양하지 않아서 크게 티나지는 않는다. 

  // ===== 🧱 TEXTURES =====
  collisionMesh.material; // mannequin
  cloth.mesh.material; // cloth

  const clothTex = textureLoader.load('../../public/texture/0/Color.png');
  (cloth.mesh.material as MeshStandardMaterial).map = clothTex;
  const mannequinTex = textureLoader.load('../../public/texture/1/Color.png');
  const mannequinTexNormal = textureLoader.load('../../public/texture/1/Normal.png');
  const mannequinTexMetalness = textureLoader.load('../../public/texture/1/Metalness.png');
  const mannequinTexRoughness = textureLoader.load('../../public/texture/1/Roughness.png');
  const mannequinTexAO = textureLoader.load('../../public/texture/1/AmbientOcclusion.png');
  (collisionMesh.material as MeshStandardMaterial).map = mannequinTex;
  (collisionMesh.material as MeshStandardMaterial).normalMap = mannequinTexNormal;
  (collisionMesh.material as MeshStandardMaterial).metalnessMap = mannequinTexMetalness;
  (collisionMesh.material as MeshStandardMaterial).roughnessMap = mannequinTexRoughness;
  (collisionMesh.material as MeshStandardMaterial).aoMap = mannequinTexAO;

 

color만 적용한 상태와 모두 적용한 상태를 비교해보면 

확실히 다양한 map들을 적용할 수록 더욱 질감이 느껴지는 걸 볼 수 있다.

 

덧붙인 mateiral color를 제거한 원래 color map의 색상.

랜덤 텍스처 적용

  let texIdx1 = randInt(0,4);
  let texIdx2 = randInt(0,4);

  const clothTex = textureLoader.load(`../../public/texture/${texIdx1}/Color.png`);
  const clothTexNormal = textureLoader.load(`../../public/texture/${texIdx1}/Normal.png`);
  const clothTexMetalness = textureLoader.load(`../../public/texture/${texIdx1}/Metalness.png`);
  const clothTexRoughness = textureLoader.load(`../../public/texture/${texIdx1}/Roughness.png`);
  const clothTexAO = textureLoader.load(`../../public/texture/${texIdx1}/AmbientOcclusion.png`);
  (cloth.mesh.material as MeshStandardMaterial).map = clothTex;
  (cloth.mesh.material as MeshStandardMaterial).map = clothTex;
  (cloth.mesh.material as MeshStandardMaterial).normalMap = clothTexNormal;
  (cloth.mesh.material as MeshStandardMaterial).metalnessMap = clothTexMetalness;
  (cloth.mesh.material as MeshStandardMaterial).roughnessMap = clothTexRoughness;
  (cloth.mesh.material as MeshStandardMaterial).aoMap = clothTexAO;
  const mannequinTex = textureLoader.load(`../../public/texture/${texIdx2}/Color.png`);
  const mannequinTexNormal = textureLoader.load(`../../public/texture/${texIdx2}/Normal.png`);
  const mannequinTexMetalness = textureLoader.load(`../../public/texture/${texIdx2}/Metalness.png`);
  const mannequinTexRoughness = textureLoader.load(`../../public/texture/${texIdx2}/Roughness.png`);
  const mannequinTexAO = textureLoader.load(`../../public/texture/${texIdx2}/AmbientOcclusion.png`);
  (collisionMesh.material as MeshStandardMaterial).map = mannequinTex;
  (collisionMesh.material as MeshStandardMaterial).normalMap = mannequinTexNormal;
  (collisionMesh.material as MeshStandardMaterial).metalnessMap = mannequinTexMetalness;
  (collisionMesh.material as MeshStandardMaterial).roughnessMap = mannequinTexRoughness;
  (collisionMesh.material as MeshStandardMaterial).aoMap = mannequinTexAO;

처음 초기화할 때 경로를 랜덤하게 설정해서 

로딩할 때마다 랜덤한 텍스처를 각각 입혀보았다. 

 

각각 새로운 텍스처를 입혀 보았다.

상당히 질감이 있어보인다. 

 

텍스처를 입힌 상태로 시뮬레이션을 돌려보니 더 생동감 있다.

 

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유