3차원 모델의 표면에 이미지나 패턴을 입혀 보다 사실적인 모델을 렌더링하는 것을 말함.
택스처 매핑은 다음의 단계로 이루어짐.
1. 텍스처 생성
2차원 이미지 파일
2. 텍스처 좌표 매핑
3D 모델의 각 정점에 대응되는 텍스처 좌표 결정
3. 텍스처 적용
알고리즘을 사용하여 텍스처 좌표를 기반으로 이미지가 정점과 면 사이에서 보간되어 적용
three.js의 재질에는 각 용도에 따른 매핑 속성들이 있음. (가장 기본 재질 ~ 고급 재질)
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
// texture.map 으로 접근할 수 있으며, map은 원하는 이름으로 바꿔도 됨.
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
});
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map} // 텍스처 색상 매핑
// 거칠기 매핑
roughnessMap={textures.roughnessMap} // 거칠기 텍스처 매핑
roughnessMap-colorSpace={THREE.NoColorSpace} // 텍스처의 색상 공간 지정 (NoColorSpace: 텍스처의 색상 공간 변환을 비활성화 하여 원본 색상 값을 사용하도록 지시)
/>
</mesh>
</>
);
};
export default Box;
금속도는 meshStandardMaterial의 metalness
와 metalnessMap
의 값이 곱해져 도출되는데, meshStandardMaterial의 경우 metalness의 기본값이 0임. 이를 해결하기 위해 metalness와 metalnessMap-colorSpace를 다음과 같이 설정
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg', // 원본 이미지의 어두운부분: 메탈 성질이 약, 밝은부분: 메탈성질 강
});
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide}
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5} // 0 ~ 1
metalnessMap-colorSpace={THREE.NoColorSpace}
/>
</mesh>
</>
);
};
export default Box;
normal 텍스처 이미지를 사용하면, 광원의 쉐이딩 연산에 적용하여 시각적으로 입체감을 나타냄. normal 텍스처 이미지 픽셀의 RGB 값을 법선 벡터의 xyz 값으로 파싱하여 사용함.
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg',
normalMap: './images/texture/window/Glass_Window_004_normal.jpg',
});
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5}
metalnessMap-colorSpace={THREE.NoColorSpace}
// 입체감 매핑
normalMap={textures.normalMap}
normalMap-colorSpace={THREE.NoColorSpace}
normalScale={1} // -1 ~ 1 (기본값: 1, 필요에 따라 벗어날 수 있음)
/>
</mesh>
</>
);
};
export default Box;
normalMap이 눈속임 입체감이라고 한다면, displaymentMap은 메시의 지오메트리 좌표를 변경하여 진짜 입체감을 나타내는 방법임. 하지만 지오메트리 좌표값을 변경하는 작업이므로 많은 연산량을 필요로함.
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg',
normalMap: './images/texture/window/Glass_Window_004_normal.jpg',
displacementMap: './images/texture/window/Glass_Window_004_height.png', // 원본 이미지의 어두운부분: 지오메트리 변경도 약, 밝은부분: 지오메트리 변경도 강 / 변경의 방향은 normal 벡터의 방향과 동일
});
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5}
metalnessMap-colorSpace={THREE.NoColorSpace}
// 입체감 매핑
normalMap={textures.normalMap}
normalMap-colorSpace={THREE.NoColorSpace}
normalScale={1}
// 입체감 매핑
displacementMap={textures.displacementMap}
displacementMap-colorSpace={THREE.NoColorSpace}
displacementScale={0.2}
displacementBias={-0.02}
/>
</mesh>
</>
);
};
export default Box;
메시 표면에 미리 그림자를 그려넣는 작업. 이 텍스처를 표현하기 위해서는 두가지가 필수로 필요함.
<ambientLight />
useEffect(() => {
mesh.current.geometry.setAttribute('uv2', new THREE.BufferAttribute(mesh.current.geometry.attributes.uv.array, 2));
}, []);
import { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const mesh = useRef();
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg',
normalMap: './images/texture/window/Glass_Window_004_normal.jpg',
displacementMap: './images/texture/window/Glass_Window_004_height.png',
aoMap: './images/texture/window/Glass_Window_004_ambientOcclusion.jpg',
});
// 2. mesh uv2
useEffect(() => {
mesh.current.geometry.setAttribute('uv2', new THREE.BufferAttribute(mesh.current.geometry.attributes.uv.array, 2));
}, []);
return (
<>
<OrbitControls />
{/* 1. 조명 */}
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh ref={mesh}>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5}
metalnessMap-colorSpace={THREE.NoColorSpace}
// 입체감 매핑
normalMap={textures.normalMap}
normalMap-colorSpace={THREE.NoColorSpace}
normalScale={1}
// 입체감 매핑
displacementMap={textures.displacementMap}
displacementMap-colorSpace={THREE.NoColorSpace}
displacementScale={0.2}
displacementBias={-0.02}
// 음영 매핑
aoMap={textures.aoMap}
/>
</mesh>
</>
);
};
export default Box;
원본 이미지의 필셀 값이 투명도로 적용됨. 값이 0에 가까울수록 투명하고, 1에 가까울수록 불투명해짐.
import { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const mesh = useRef();
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg',
normalMap: './images/texture/window/Glass_Window_004_normal.jpg',
displacementMap: './images/texture/window/Glass_Window_004_height.png',
aoMap: './images/texture/window/Glass_Window_004_ambientOcclusion.jpg',
alphaMap: './images/texture/window/Glass_Window_004_opacity_.jpg', // 원본 이미지의 어두운부분: 투명도 강, 밝은부분: 투명도 약
});
useEffect(() => {
mesh.current.geometry.setAttribute('uv2', new THREE.BufferAttribute(mesh.current.geometry.attributes.uv.array, 2));
}, []);
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh ref={mesh}>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5}
metalnessMap-colorSpace={THREE.NoColorSpace}
// 입체감 매핑
normalMap={textures.normalMap}
normalMap-colorSpace={THREE.NoColorSpace}
normalScale={1}
// 입체감 매핑
displacementMap={textures.displacementMap}
displacementMap-colorSpace={THREE.NoColorSpace}
displacementScale={0.2}
displacementBias={-0.02}
// 음영 매핑
aoMap={textures.aoMap}
// 투명도 매핑
alphaMap={textures.alphaMap}
transparent // 재질이 투명한지 지정
alphaToCoverage // 재질의 투명한 부분을 alpha coverage로 처질할지 지정
/>
</mesh>
</>
);
};
export default Box;
import { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls, useTexture } from '@react-three/drei';
const Box = () => {
const mesh = useRef();
const textures = useTexture({
map: './images/texture/window/Glass_Window_004_basecolor.jpg',
roughnessMap: './images/texture/window/Glass_Window_004_roughness.jpg',
metalnessMap: './images/texture/window/Glass_Window_004_metallic.jpg',
normalMap: './images/texture/window/Glass_Window_004_normal.jpg',
displacementMap: './images/texture/window/Glass_Window_004_height.png',
aoMap: './images/texture/window/Glass_Window_004_ambientOcclusion.jpg',
alphaMap: './images/texture/window/Glass_Window_004_opacity_.jpg',
});
useEffect(() => {
// 텍스쳐 수평방향 반복
textures.map.repeat.x =
textures.displacementMap.repeat.x =
textures.aoMap.repeat.x =
textures.roughnessMap.repeat.x =
textures.metalnessMap.repeat.x =
textures.normalMap.repeat.x =
textures.alphaMap.repeat.x =
4;
// wrapS(수평) / wrapT(수직): 반복이 시작되는 시점에서 텍스처 이미지를 어떻게 처리할 것인지
textures.map.wrapS =
textures.displacementMap.wrapS =
textures.aoMap.wrapS =
textures.roughnessMap.wrapS =
textures.metalnessMap.wrapS =
textures.normalMap.wrapS =
textures.alphaMap.wrapS =
THREE.MirroredRepeatWrapping;
// 3D 객체 업데이트
textures.map.needsUpdate =
textures.displacementMap.needsUpdate =
textures.aoMap.needsUpdate =
textures.roughnessMap.needsUpdate =
textures.metalnessMap.needsUpdate =
textures.normalMap.needsUpdate =
textures.alphaMap.needsUpdate =
true;
mesh.current.geometry.setAttribute('uv2', new THREE.BufferAttribute(mesh.current.geometry.attributes.uv.array, 2));
}, []);
return (
<>
<OrbitControls />
<ambientLight intensity={0.2} />
<directionalLight position={[0, 1, -8]} intensity={0.4} />
<directionalLight position={[1, 2, 8]} intensity={0.4} />
<mesh ref={mesh}>
<cylinderGeometry args={[2, 2, 3, 1024, 1024, true]} />
<meshStandardMaterial
side={THREE.DoubleSide} // 양면 렌더링
// 색상 매핑
map={textures.map}
// 거칠기 매핑
roughnessMap={textures.roughnessMap}
roughnessMap-colorSpace={THREE.NoColorSpace}
// 금속도 매핑
metalnessMap={textures.metalnessMap}
metalness={0.5}
metalnessMap-colorSpace={THREE.NoColorSpace}
// 입체감 매핑
normalMap={textures.normalMap}
normalMap-colorSpace={THREE.NoColorSpace}
normalScale={1}
// 입체감 매핑
displacementMap={textures.displacementMap}
displacementMap-colorSpace={THREE.NoColorSpace}
displacementScale={0.08}
displacementBias={-0.2}
// 음영 매핑
aoMap={textures.aoMap}
// 투명도 매핑
alphaMap={textures.alphaMap}
transparent
alphaToCoverage
/>
</mesh>
</>
);
};
export default Box;