[three.js][typescript] - Material(3)
[three.js][typescript] - Material(2)[three.js][typescript] - Material(1)Three.js 의 Object3D의 파생클래스는 위와 같다. Point는 Geometry를 구성하는 좌표 하나 하나를 렌더링하며 Line은 좌표를 통해 선을 만들어 렌더
yoohwanihn.tistory.com
이전 글에서 MeshMaterial의 MeshBasicMaterial과 MeshLambertMaterial을 알아봤었다.
이어서 MeshPhongMaterial을 알아보자
MeshPhongMaterial
MeshPhongMaterial은 Mesh가 렌더링 되는 픽셀 단위로 광원의 영향을 계산한다.
private setupModels(){
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
emissive: 0x00000, // 광원의 영향을 받지 않고 재질이 방출하는 color. default black
specular: 0xffff00, // 광원에 의해 반사되는 color값. default black
shininess: 5, // specular로 반사되는 정도
flatShading: false, // Mesh를 이루는 면을 평평하게 표현할지 여부
wireframe: false, // wireframe 옵션
visible: true, // 렌더링 시 모델이 보일지 안보일지
transparent: false, // opacity 옵션을 사용할지 여부
opacity: 1, // material의 불투명 (0~1)
depthTest: true, // 렌더링 되고있는 Mesh를 표현하는 픽셀의 z값과 depth버퍼에 저장된 동일한 위치의 z값을 비교 검사할지 여부
depthWrite: true, // 렌더링 되고있는 Mesh의 픽셀에 대한 z값을 depth버퍼에 저장할 것인지 여부
side: THREE.FrontSide
})
const geomCylinder = new THREE.CylinderGeometry(0.6, 0.9, 1.2, 64, 1)
const cylinder = new THREE.Mesh(geomCylinder, material)
cylinder.position.x = -1
this.scene.add(cylinder)
const geomTorusknot = new THREE.TorusKnotGeometry(0.4 ,0.18, 128, 64)
const torusknot = new THREE.Mesh(geomTorusknot,material)
torusknot.position.x = 1
this.scene.add(torusknot)
}
기존의 코드에서 setupModels를 위와 같이 수정해 준다.
MeshPhongMaterial에서 제공하는 옵션들은 emissive, specular, shininess, flatShading 등이 있다.
emissive는 광원의 영향을 받지 않고 재질이 방출하는 color 옵션이다. 우리는 이를 검은색(없음)으로 지정해 주었다.
specular는 광원에 의해 반사되는 color 옵션이다. 우리는 이를 노란색으로 지정해 주었다.
specular로 광원에 의해 반사되는 정도를 shininess로 설정해 줄 수 있다. 우리는 이를 128로 지정해 주었다.
결과를 통해 확인해 보자

광원에 의해 반사되는 노란색 값과 기존의 material color값인 붉은색이 조화를 이루는 것을 확인할 수 있다.
이를 shininess를 통해 조절할 수 있으며, 128에서 500으로 수정해 보았다.


노란색의 비중이 줄어든 것을 확인할 수 있다.
이번엔 flatShading을 true로 변경시켜 보자.


Mesh를 이루는 면이 평평하게 표현되는 것을 확인할 수 있다.
PBR(물리 기반 렌더링)

지금까지 MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial을 학습했다.
이제부터 알아볼 재질들은 PBR(물리 기반 렌더링)을 위한 재질들이다.
PBR을 위한 재질들로는 MeshStandardMaterial과 그를 상속받는 MeshPhysicalMaterial이 있다.
이 두 개의 재질은 물리 기반 렌더링이기 때문에 속도면에서 이전의 3개의 재질에 비해 상대적으로 느리지만,
광원의 영향을 더 사실적으로 계산해서 더 높은 품질의 렌더링 결과를 만들 수 있다.
그러면 이를 위해서 Models를 만들기 전에 광원 코드부터 수정해 보자.

기존의 setupLight 메서드를 새로 작성해 보자.
우선, HDRI 데이터를 광원으로 사용할 것이다.
HDRI
HDRIs • Poly Haven
Previously known as HDRI Haven. Hundreds of free HDRI environments, ready to use for any purpose. No login required.
polyhaven.com
Poly Haven 사이트에서 원하는 분위기의 환경을 선택해서

우측 상단의 다운로드 버튼을 통해 다운로드하자. (EXR을 HDR로 변경해야 함)

그 후 프로젝트의 public 폴더로 업로드해 준다.

그리고 RGBELoader를 import 해준다. (HDRI를 사용하기 위해 로더 객체 import)
이제 setupCamera 코드를 수정하자
private setupLight(){
/** HDRI */
const rgbeLoader = new RGBELoader() //HDRI를 사용하기 위한 로더 객체
rgbeLoader.load('./red_hill_cloudy_4k.hdr',(environmentMap)=>{
environmentMap.mapping = THREE.EquirectangularRefractionMapping
this.scene.background = environmentMap
this.scene.environment = environmentMap
})
}
HDRI를 사용하기 위해 rgbeLoader 객체를 import 하여 우리가 다운로드한 HDR을 로드하여 광원을 수정하였다.
결과를 통해 확인하자

우리가 다운로드한 HDR 데이터의 배경 그대로 광원이 나오는 결과를 볼 수 있다.
그러나 우리가 Models에서 설정한 color와 specular 옵션등이 적용되지 않고 검은색으로
렌더링 되는 것을 확인할 수 있다.
이는 모든 Material이 HDRI 광원을 사용할 수 없기 때문이다.
이제 HDRI 광원을 지원하는 Material에 대해 학습해 보자.
MeshStandardMaterial

MeshStandardMaterial이 지원하는 option을 확인하기 위해 GUI를 import 한다.
private setupModels() {
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
emissive: 0x00000, // 광원의 영향을 받지 않고 재질이 방출하는 color. default black
roughness: 0, // 표면 거칠기 속성 (0~1 사이)
metalness: 0, // 금속성 (0~1). 값1은 완전한 금속
flatShading: false, // Mesh를 이루는 면을 평평하게 표현할지 여부
wireframe: false, // wireframe 옵션
visible: true, // 렌더링 시 모델이 보일지 안보일지
transparent: false, // opacity 옵션을 사용할지 여부
opacity: 1, // material의 불투명 (0~1)
depthTest: true, // 렌더링 되고있는 Mesh를 표현하는 픽셀의 z값과 depth버퍼에 저장된 동일한 위치의 z값을 비교 검사할지 여부
depthWrite: true, // 렌더링 되고있는 Mesh의 픽셀에 대한 z값을 depth버퍼에 저장할 것인지 여부
side: THREE.FrontSide
})
const gui = new GUI()
gui.addColor(material, "color").onChange(v => material.color = v)
gui.addColor(material, "emissive").onChange(v => material.color = v)
gui.add(material, "roughness", 0, 1, 0.01)
gui.add(material, "metalness", 0, 1, 0.01)
const geomCylinder = new THREE.CylinderGeometry(0.6, 0.9, 1.2, 64, 1)
const cylinder = new THREE.Mesh(geomCylinder, material)
cylinder.position.x = -1
this.scene.add(cylinder)
const geomTorusknot = new THREE.TorusKnotGeometry(0.4, 0.18, 128, 64)
const torusknot = new THREE.Mesh(geomTorusknot, material)
torusknot.position.x = 1
this.scene.add(torusknot)
}
그 뒤, 위와 같이 setupModels 코드를 설정해 준다.
각 option에 대해서는 GUI를 통해 알아보고 주석으로 생략하고자 한다.
결과는 아래와 같다.

MeshPhysicalMaterial
MeshPhysicalMaterial은 MeshStandardMaterial을 상속받아 더 다양한 옵션을 줄 수 있다.
재질 표면에 코팅 효과를 줄 수 있으며, 다른 재질처럼 단순한 투명도 처리가 아닌
실제 유리와 같은 처리를 하는 등 다양한 옵션을 갖고 있다.
private setupModels() {
const material = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
emissive: 0x00000, // 광원의 영향을 받지 않고 재질이 방출하는 color. default black
roughness: 0, // 표면 거칠기 속성 (0~1 사이)
metalness: 0, // 금속성 (0~1). 값1은 완전한 금속
clearcoat: 0, // 모델 표면의 코팅 효과 (0~1 사이)
clearcoatRoughness: 0, // 코팅의 거칠기 값 (0~1 사이)
transmission: 0, // 투명한 정도
ior: 1.5, // 굴절률
thickness: 0.1, // 유리의 두께
sheen: 0, // 비단이나 fabric 원단의 실루엣처럼 빛나는 효과
sheenRoughness: 0, // 실루엣이 빛나는 정도 조절
sheenColor: 0xfffff, // 실루엣의 색상
iridescence: 0, // 무지개색 효과의 강도 조절
iridescenceIOR: 0, // 무지개색 효과가 발생하는 굴절률 조절
iridescenceThicknessRange: [100, 800], // 무지개색 효과가 나타나는 두께 범위
flatShading: false, // Mesh를 이루는 면을 평평하게 표현할지 여부
wireframe: false, // wireframe 옵션
visible: true, // 렌더링 시 모델이 보일지 안보일지
transparent: false, // opacity 옵션을 사용할지 여부
opacity: 1, // material의 불투명 (0~1)
depthTest: true, // 렌더링 되고있는 Mesh를 표현하는 픽셀의 z값과 depth버퍼에 저장된 동일한 위치의 z값을 비교 검사할지 여부
depthWrite: true, // 렌더링 되고있는 Mesh의 픽셀에 대한 z값을 depth버퍼에 저장할 것인지 여부
side: THREE.FrontSide // 앞면 or 뒷면 or 양면 렌더링 옵션 (default FrontSide)
})
const gui = new GUI()
gui.addColor(material, "color").onChange(v => material.color = v)
gui.addColor(material, "emissive").onChange(v => material.color = v)
gui.add(material, "roughness", 0, 1, 0.01)
gui.add(material, "metalness", 0, 1, 0.01)
gui.add(material, "clearcoat", 0, 1, 0.01)
gui.add(material, "clearcoatRoughness", 0, 1, 0.01)
gui.add(material, "transmission", 0, 1, 0.01)
gui.add(material, "ior", 1, 2.333, 0.01)
gui.add(material, "thickness", 0, 10, 0.01)
gui.add(material, "sheen", 0, 1, 0.01)
gui.add(material, "sheenRoughness", 0, 1, 0.01)
gui.addColor(material, "sheenColor").onChange(v => material.sheenColor = new THREE.Color(v))
gui.add(material, "iridescence", 0, 1, 0.01)
gui.add(material, "iridescenceIOR", 1, 2.333, 0.01)
gui.add(material.iridescenceThicknessRange, "0", 1, 1000, 1)
gui.add(material.iridescenceThicknessRange, "1", 1, 1000, 1)
const geomCylinder = new THREE.CylinderGeometry(0.6, 0.9, 1.2, 64, 1)
const cylinder = new THREE.Mesh(geomCylinder, material)
cylinder.position.x = -1
this.scene.add(cylinder)
const geomTorusknot = new THREE.TorusKnotGeometry(0.4, 0.18, 128, 64)
const torusknot = new THREE.Mesh(geomTorusknot, material)
torusknot.position.x = 1
this.scene.add(torusknot)
}
위의 코드로 변경시켜 보자
새로 추가된 option은 clearcoat, clearcoatRoughness, transmission, ior, thickness 등이 있으며
각 option에 대한 기능은 주석을 달아놓았고 GUI환경에서 테스트해 보면 될 것이다.

GUI 환경에서 option 값들을 변경시킬 수 있다.

지금까지 Mesh Material을 정리하면 위와 같다.

이제 다음글에서 MeshToonMaterial에 대해 알아보자
전체 코드(main.ts)
import * as THREE from 'three'
import { OrbitControls, RGBELoader } from 'three/examples/jsm/Addons.js'
import './style.css'
import GUI from 'three/examples/jsm/libs/lil-gui.module.min.js'
class App {
private renderer: THREE.WebGLRenderer //Renderer Field 추가
private domApp: Element
private scene: THREE.Scene
private camera?: THREE.PerspectiveCamera //?를 붙이면 PerspectiveCamera Type이나 Undefined Type을 가질 수 있음.(Optional Properties)
constructor() {
console.log("YooHwanIhn");
this.renderer = new THREE.WebGLRenderer({ antialias: true }) // 안티-알리아스 : 높은 렌더링 결과를 얻기 위해 픽셀 사이에 계단 현상을 방지하는 효과 추가
this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)) // 고해상도에서 깨짐 방지를 위해 픽셀 비율 지정
this.domApp = document.querySelector('#app')!
this.domApp.appendChild(this.renderer.domElement) // renderer.domeElement : canvas Type의 DOM 객체
this.scene = new THREE.Scene()
this.setupCamera()
this.setupLight()
this.setupModels()
this.setupEvents()
}
private setupCamera() {
const width = this.domApp.clientWidth
const height = this.domApp.clientHeight
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100)
this.camera.position.z = 2 // (0, 0, 2)
new OrbitControls(this.camera, this.domApp as HTMLElement)
}
private setupLight() {
/** HDRI */
const rgbeLoader = new RGBELoader() //HDRI를 사용하기 위한 로더 객체
rgbeLoader.load('./red_hill_cloudy_4k.hdr', (environmentMap) => {
environmentMap.mapping = THREE.EquirectangularRefractionMapping
this.scene.background = environmentMap
this.scene.environment = environmentMap
})
}
private setupModels() {
const material = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
emissive: 0x00000, // 광원의 영향을 받지 않고 재질이 방출하는 color. default black
roughness: 0, // 표면 거칠기 속성 (0~1 사이)
metalness: 0, // 금속성 (0~1). 값1은 완전한 금속
clearcoat: 0, // 모델 표면의 코팅 효과 (0~1 사이)
clearcoatRoughness: 0, // 코팅의 거칠기 값 (0~1 사이)
transmission: 0, // 투명한 정도
ior: 1.5, // 굴절률
thickness: 0.1, // 유리의 두께
sheen: 0, // 비단이나 fabric 원단의 실루엣처럼 빛나는 효과
sheenRoughness: 0, // 실루엣이 빛나는 정도 조절
sheenColor: 0xfffff, // 실루엣의 색상
iridescence: 0, // 무지개색 효과의 강도 조절
iridescenceIOR: 0, // 무지개색 효과가 발생하는 굴절률 조절
iridescenceThicknessRange: [100, 800], // 무지개색 효과가 나타나는 두께 범위
flatShading: false, // Mesh를 이루는 면을 평평하게 표현할지 여부
wireframe: false, // wireframe 옵션
visible: true, // 렌더링 시 모델이 보일지 안보일지
transparent: false, // opacity 옵션을 사용할지 여부
opacity: 1, // material의 불투명 (0~1)
depthTest: true, // 렌더링 되고있는 Mesh를 표현하는 픽셀의 z값과 depth버퍼에 저장된 동일한 위치의 z값을 비교 검사할지 여부
depthWrite: true, // 렌더링 되고있는 Mesh의 픽셀에 대한 z값을 depth버퍼에 저장할 것인지 여부
side: THREE.FrontSide // 앞면 or 뒷면 or 양면 렌더링 옵션 (default FrontSide)
})
const gui = new GUI()
gui.addColor(material, "color").onChange(v => material.color = v)
gui.addColor(material, "emissive").onChange(v => material.color = v)
gui.add(material, "roughness", 0, 1, 0.01)
gui.add(material, "metalness", 0, 1, 0.01)
gui.add(material, "clearcoat", 0, 1, 0.01)
gui.add(material, "clearcoatRoughness", 0, 1, 0.01)
gui.add(material, "transmission", 0, 1, 0.01)
gui.add(material, "ior", 1, 2.333, 0.01)
gui.add(material, "thickness", 0, 10, 0.01)
gui.add(material, "sheen", 0, 1, 0.01)
gui.add(material, "sheenRoughness", 0, 1, 0.01)
gui.addColor(material, "sheenColor").onChange(v => material.sheenColor = new THREE.Color(v))
gui.add(material, "iridescence", 0, 1, 0.01)
gui.add(material, "iridescenceIOR", 1, 2.333, 0.01)
gui.add(material.iridescenceThicknessRange, "0", 1, 1000, 1)
gui.add(material.iridescenceThicknessRange, "1", 1, 1000, 1)
const geomCylinder = new THREE.CylinderGeometry(0.6, 0.9, 1.2, 64, 1)
const cylinder = new THREE.Mesh(geomCylinder, material)
cylinder.position.x = -1
this.scene.add(cylinder)
const geomTorusknot = new THREE.TorusKnotGeometry(0.4, 0.18, 128, 64)
const torusknot = new THREE.Mesh(geomTorusknot, material)
torusknot.position.x = 1
this.scene.add(torusknot)
}
//실제 이벤트와 렌더링 처리를 다룰 메서드
private setupEvents() {
window.onresize = this.resize.bind(this); // html의 size가 변경될 때마다 호출되는 함수(addEventListener 느낌??)
this.resize();
this.renderer.setAnimationLoop(this.render.bind(this)) // 연속적으로 render 메서드 호출(모니터 Frame에 맞게)
}
// html창 resize시 호출할 함수
private resize() {
const width = this.domApp.clientWidth
const height = this.domApp.clientHeight
//앞선 setUpCamera에서 설정한 camera 정보를 다시 수정해줌.
const camera = this.camera
if (camera) {
camera.aspect = width / height
camera.updateProjectionMatrix() // 카메라의 값이 변경되었다면 수정하도록 함.
}
//renderer도 마찬가지로 사이즈 수정함
this.renderer.setSize(width, height)
}
private update(time: number) {
time *= 0.001 // ms -> s 단위로 변경
}
private render(time: number) {
// time : setAnimationLoop의 값에 의해서 결정되는데 단위는 ms
this.update(time)
this.renderer.render(this.scene, this.camera!)
}
}
new App()'three.js' 카테고리의 다른 글
| [three.js][typescript] - Material(6) (0) | 2024.07.11 |
|---|---|
| [three.js][typescript] - Material(5) (0) | 2024.07.10 |
| [three.js][typescript] - Material(3) (0) | 2024.07.08 |
| [three.js][typescript] - Material(2) (0) | 2024.07.05 |
| [three.js][typescript] - Material(1) (0) | 2024.07.05 |