R3F(React Three Fiber) - Material Texture Mapping

H.GOO·2024년 4월 5일
1

WEB-3D-Project

목록 보기
4/10
post-thumbnail

🪴Texture Mapping

3차원 모델의 표면에 이미지나 패턴을 입혀 보다 사실적인 모델을 렌더링하는 것을 말함.

3D Textures 데이터들


택스처 매핑은 다음의 단계로 이루어짐.

1. 텍스처 생성
2차원 이미지 파일

2. 텍스처 좌표 매핑
3D 모델의 각 정점에 대응되는 텍스처 좌표 결정

3. 텍스처 적용
알고리즘을 사용하여 텍스처 좌표를 기반으로 이미지가 정점과 면 사이에서 보간되어 적용


three.js의 재질에는 각 용도에 따른 매핑 속성들이 있음. (가장 기본 재질 ~ 고급 재질)




🪴Texture Mapping 적용

map(기본 색상 매핑), roughnessMap(거칠기 매핑)

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;

metalnessMap (금속도 매핑)

금속도는 meshStandardMaterial의 metalnessmetalnessMap의 값이 곱해져 도출되는데, 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;

normalMap (입체감 매핑)


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;

displacementMap (입체감 매핑)

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;

ambientOcclusionMap (음영 매핑)

메시 표면에 미리 그림자를 그려넣는 작업. 이 텍스처를 표현하기 위해서는 두가지가 필수로 필요함.

  1. 조명
    <ambientLight />
  2. mesh uv2
    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;

alphaMap (투명도 매핑)

원본 이미지의 필셀 값이 투명도로 적용됨. 값이 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;

textures.map.repeat(텍스처 반복), textures.map.wrapS, textures.map.needsUpdate

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;


0개의 댓글