[three.js] Geometry(모양), Material(재질)+Texture

chaewon Im·2022년 11월 22일
0

공부 기록✏️

목록 보기
13/15
post-thumbnail

Geometry

점,선,면으로 이루어진 어떠한 모양

대표적인 Geometry 종류

  • 육면체: BoxGeometry(width, height, depth, widthSegments, heightSegments, deptrhSegments)
  • 평면 원 모양: CircleGeometry(radius, segments, thetaStart, thetaLength)
  • 원뿔: ConeGeometry(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)
  • 원기둥: CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)
  • 평면 모양: PlaneGeometry(width, height, widthSegment, heightSegment)
  • 구: SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) 
  • 도넛 모양: TorusGeometry(radius, tube, radialSegments,  tubularSegments, arcs)

그 외에도 여러가지 Geometry가 존재
→ 공식 문서: https://threejs.org/docs/index.html#api/en/geometries/BoxGeometry

Geometry 형태 조작

콘솔에 geometry를 출력해보면 attributes가 있는 걸 확인할 수 있는데, 그 중 position 속성을 이용해 geometry의 형태를 조작할 수도 있다.

position 속성의 array는 geometry의 각 정점(vertex)들의 좌표값을 가진 배열이다.

점들의 순서대로 x,y,z, x,y,z, x,y,z의 값을 가지고 있다. 해당 점들의 위치를 조작하면 geometry의 형태가 변한다.

geometry.attributes.position.array 배열은 일반적인 자바스크립트 배열과는 다르게 특정 형식만 배열에 삽입할 수 있는 typed array(형식화 배열)을 사용하고 있다. typed array는 타입을 제한하는 대신 성능이 좋아진다는 장점이 있다.
➡ geometry는 형태에 따라 무수히 많은 x,y,z 점들의 좌표값 숫자를 저장해야 하기 때문에 Float32Array 타입의 배열을 사용하여 배열에 저장되는 값의 타입을 제한하고, 성능을 올리고 있다.

[ geometry 형태 조작 예 ]

// Mesh
const geometry = new THREE.SphereGeometry(5, 64, 64);
const material = new THREE.MeshStandardMaterial({
    color: 'orangered',
    side: THREE.DoubleSide, //물체의 안쪽,바깥쪽에 material이 모두 적용되도록 하는 옵션
    flatShading: true //나눈 세그먼트들이 각지게(플랫하게) 보이게끔 설정, 해당 속성을 적용하지 않으면 부드러운 구 형태로 보여짐
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

console.log(geometry.attributes.position.array);

각 세그먼트들의 정점 x,y,z의 좌표값을 나타내는 배열이 출력된다.

이 배열 내의 값들은 순서대로 점 1개의 x,y,z 좌표값을 순서대로 저장한 것이다.
ex) 점 1의 x,y,z 값은 순서대로 -0, 5, 0, 점 2의 x,y,z 값도  -0, 5, 0 ...

즉, 이 SphereGeometry의 정점의 개수는 12,675 / 3 인 4,225개의 점을 가졌다는 것을 의미하고, 3의 배수 순서대로 점 1개의 정보를 나타낸다. 

geometry의 attributes.position.array에 접근하여 형태를 변화시키는 예(위 코드와 이어짐)

const positionArray = geometry.attributes.position.array;

for (let i = 0; i < positionArray.length; i += 3) {
    // 정점(Vertex) 한 개의 x, y, z 좌표를 랜덤으로 조정
    positionArray[i] += (Math.random() - 0.5) * 0.2;
    positionArray[i + 1] += (Math.random() - 0.5) * 0.2;
    positionArray[i + 2] += (Math.random() - 0.5) * 0.2;
}

점 1개의 x,y,z의 값을 한 턴에 모두 변경하고, 그 다음 점의 x,y,z값으로 이동하기 위해 for loop에서 i의 값을 3씩 증가시킨다.

그리고 position array에 접근해서 기존의 정점 좌표값에 랜덤으로 -0.5 ~ 0.5 사이의 값을 더해준다.

그 결과 랜덤하게 점이 들어가거나 나오게 만들어서 표면이 울퉁불퉁한 형태가 된다.

position에서 사용되는 값의 기준이 0부터 1이기 때문에 소수점 한자리 기준으로 변화를 줄 시 변화되는 범위가 커서 좀 더 미세한 변화를 주기 위해 여기서는 0.2를 한번 더 곱해준다.

(좌: -0.5~0.5로만 변경한 예 / 우: 0.2를 한번 더 곱해준 예)


Material

물체의 재질

대표적인 Material 종류

  • MashBasicMaterial : 입체감이 없는 평평한 기본 재질, 입체감이 없어서 light의 영향을 받지 않는다. → 가장 성능이 좋다.
  • MeshLambertMaterial :  입체감은 있으나 하이라이트, 반사광이 없는 무광 재질
  • MeshPhongMaterial : 입체감 있고 하이라이트, 반사광이 있는 유광 재질, shininess 옵션으로 유광 강도 설정 가능, MeshStandardMaterial보다 성능이 조금 더 좋다.
  • MeshStandardMaterial : 입체감 있는 유광 재질, MeshPhongMaterial과 다르게 roughness 옵션으로 유광-무광 강도를 설정, metalness 옵션으로 금속 효과도 줄 수 있다.

그 외에도 여러 Material들이 존재
→ 공식 문서: https://threejs.org/docs/index.html#api/en/materials/MeshBasicMaterial

Material 공통 옵션

1. flatShading: boolean
표면을 segment에 따라 각지게 만드는 옵션. lowpoly 스타일로 만들 수 있다.
2. side
물체의 재질을 어느 쪽 면에 적용할 것인지 설정하는 옵션.
바깥쪽, 안쪽, 양쪽 설정 가능.

  • THREE.FrontSide : 바깥쪽에만 설정, 물체 안으로 들어가 보면 재질이 적용되지 않은 것을 확인할 수 있다.
  • THREE.BackSide : 안쪽에만 설정, 물체 밖으로 나가보면 재질이 적용되어 있지 않다.
  • THREE.DoubleSide : 양쪽 표면에 모두 설정.

(두 옵션의 사용 예시는 Geometry 첫 번째 코드 블록에서 확인할 수 있다.)

Texture

이미지를 물체의 표면에 적용할 수 있다.
현실에 존재하는 다양한 재질 및 원하는 이미지들을 이용해 구현하고자 하는 물체의 외형을 만들 수 있는 유용한 속성.

공식 문서: https://threejs.org/docs/index.html?q=tex#api/en/textures/Texture

TextureLoader : 이미지 로드

이미지를 material로 사용하려면 먼저 TextureLoader 객체의 메서드로 이미지를 로드해야 한다. 

new THREE.TextureLoader()
	.load(image, callback, callback, callback)

첫 번째 매개변수로 로드할 이미지 경로를 넣는다.
그 다음 매개변수에는 순서대로 로드 완료, 로드 중, 로드 에러 시에 실행할 콜백 함수를 전달할 수 있다.

[ texture를 이용한 material 적용 예 ]

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
	'/textures/brick.jpg',
	() => {
		console.log('load completed');
	},
	() => {
		console.log('loading..');
	},
	() => {
		console.log('load error');
	},
);

const geometry = new THREE.BoxGeometry(5, 5, 5);
const material = new THREE.MeshStandardMaterial({
    map: texture,
    side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

먼저 TextureLoader 객체의 인스턴스를 생성한 다음 load 메서드로 이미지를 로드한다.

그 다음 material 선언 시에 map 옵션으로 texture를 전달하면 이미지가 적용된 Mesh를 생성할 수 있다.

앞서 설명한 side 옵션을 DoubleSide로 적용하여 Box 내부에서도 벽돌 재질이 적용되도록 설정한다.

이렇게 하면 아래와 같이 Box의 바깥쪽, 안쪽에 모두 벽돌 재질이 설정된 것을 볼 수 있다.
→ side 옵션을 활용하면 물체 뿐 아니라 특정한 공간을 만드는 데에도 이용할 수 있다.

[ 여러 texture를 각각 다른 면에 적용하는 예 ]

큐브의 각 면에 각각 다른 이미지의 Texture를 적용할 수도 있다.

Mesh를 생성할 때 material 속성을 변수가 아닌 배열로 전달할 수 있는데,

이 때 배열 안의 값은 순서대로 [ right, left, top, bottom, front, back ] 위치에 들어갈 material 객체들을 가져야 한다. (texture 변수가 아닌 texture 변수를 매핑한 Material 객체) 

// Texture
const textureLoader = new THREE.TextureLoader(loadingManager);
const rightTexture= textureLoader.load('/textures/mcstyle/right.png');
const leftTexture= textureLoader.load('/textures/mcstyle/left.png');
const topTexture= textureLoader.load('/textures/mcstyle/top.png');
const bottomTexture= textureLoader.load('/textures/mcstyle/bottom.png');
const frontTexture= textureLoader.load('/textures/mcstyle/front.png');
const backTexture= textureLoader.load('/textures/mcstyle/back.png');

// magFilter: 이미지를 확대할 때 적용하는 filter
rightTexture.magFilter = THREE.LinearFilter;
leftTexture.magFilter = THREE.LinearFilter;
topTexture.magFilter = THREE.NearestFilter;
bottomTexture.magFilter = THREE.NearestFilter;
frontTexture.magFilter = THREE.NearestFilter;
backTexture.magFilter = THREE.NearestFilter;

// Mesh
const geometry = new THREE.BoxGeometry(5, 5, 5);
const materials = [
    new THREE.MeshBasicMaterial({ map: rightTexture }),
    new THREE.MeshBasicMaterial({ map: leftTexture }),
    new THREE.MeshBasicMaterial({ map: topTexture }),
    new THREE.MeshBasicMaterial({ map: bottomTexture }),
    new THREE.MeshBasicMaterial({ map: frontTexture }),
    new THREE.MeshBasicMaterial({ map: backTexture })
];
const mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);

+ magFilter)

위에서 사용한 이미지가 16x16의 굉장히 작은 이미지여서 적용 시에 이미지가 깨진다.

magFilter는 이럴 때 사용하는 이미지 확대 보정 속성이다. (Magnification Filter 약자)

공식 문서에서는 어렵게 Texel(=텍스처 단위)가 둘 이상의 Pixel을 덮을 때 텍스처가 샘플링 되는 방법이라고 설명하고 있는데,

쉽게 표현하자면 이미지가 작아서 픽셀이 표면 크기에 비해 부족할 때 부족한 부분을 보간하는 방법이다.

위에서 사용한 NearListFilter는 맨하탄 거리(L1 Distance)를 기준으로 가장 가까운 텍스처 픽셀 요소의 값을 비어있는 텍스처 픽셀 값의 좌표로 반환하여 부족한 픽셀을 채우는 방법이며,

기본값인 LinearFilter는 지정된 texture 좌표에서 가장 가까운 4개의 texture들의 평균치로 보간하는 방법이다. 

이미지에서 선명한 부분이 NearListFilter, 흐릿한 부분이 LinearFilter이다. 
각 면에 각각 다른 이미지들이 적용된 것도 확인할 수 있다.

LoadingManager()

여러 개의 Texture를 로드할 때 로딩 과정을 제어하기 위한 목적으로 사용하는 객체이다.

TextureLoader의 콜백 함수와 유사하게 동작하지만, 각각의 상황 시 실행할 메서드가 내부적으로 존재한다는 차이가 있다.

new THREE.LoadingManager()로 먼저 생성하고, TextureLoader를 생성 시에 생성한 loadingManager를 전달해 주어야 사용 가능하다.

const loadingManager = new THREE.LoadingManager();
loadingManager.onStart = () => {
	console.log('load Start');
};
loadingManager.onProgress = img => {
	console.log(img + ' loading...');
};
loadingManager.onLoad = () => {
	console.log('load completed!');
};
loadingManager.onError = () => {
	console.log('error');
};

const textureLoader = new THREE.TextureLoader(loadingManager); //TextLoader 선언 시에 loadingManager를 전달해야 함

const rightTexture= textureLoader.load('/textures/mcstyle/right.png');
const leftTexture= textureLoader.load('/textures/mcstyle/left.png');
const topTexture= textureLoader.load('/textures/mcstyle/top.png');
const bottomTexture= textureLoader.load('/textures/mcstyle/bottom.png');
const frontTexture= textureLoader.load('/textures/mcstyle/front.png');
const backTexture= textureLoader.load('/textures/mcstyle/back.png');

위와 같이 texture를 로드하는 과정에서 순차적으로 각 메서드들이 실행된다.

profile
빙글빙글 FE 개발자

0개의 댓글