모델 텍스처 변경
배경을 꾸몄으니 다음으로 모델에 텍스처를 입히도록 하자.
모델에 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
텍스처를 불러오기 위해서는 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;
처음 초기화할 때 경로를 랜덤하게 설정해서
로딩할 때마다 랜덤한 텍스처를 각각 입혀보았다.
각각 새로운 텍스처를 입혀 보았다.
상당히 질감이 있어보인다.
텍스처를 입힌 상태로 시뮬레이션을 돌려보니 더 생동감 있다.