본문 바로가기
Three.js/Intro

[threejs-journey 1-11] Material

by SL123 2025. 6. 27.

개요

머터리얼(Material)3D 객체의 겉 표면을 어떻게 보이게 할지를 결정하는 요소입니다. 각 픽셀에 색을 어떻게 입힐지, 질감은 어떤지, 빛에 어떻게 반응할지를 담당합니다.

 

이전에 했던 작업들을 복습하고, 해당 작업과 머터리얼을 합쳐 이용하는 방법을 알아봅시다. Three.js의 기본 머터리얼부터 텍스처 활용, 그리고 다양한 옵션까지 한 번에 이해할 수 있도록 구성했습니다.

 

- 기본 빌트인 머터리얼

Three.js에서는 여러 가지 빌트인 머터리얼을 제공합니다. 추후에 커스텀 쉐이더를 써서 직접 머터리얼을 만들어볼 수도 있지만, 지금은 준비될 걸로도 충분히 만들 수 있습니다. 먼저 가장 기본적인 예제를 보겠습니다.

/**
 * Objects
 */

// MeshBasicMaterial
const material = new THREE.MeshBasicMaterial({});

// Sphere (left) - 왼쪽에 구체
const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), material);
sphere.position.x = -1.5;

// Plane (center) - 가운데에 평면
const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
plane.position.x = 0;

// Torus (right) - 오른쪽에 토러스
const torus = new THREE.Mesh(new THREE.TorusGeometry(0.4, 0.15, 8, 16), material);
torus.position.x = 1.5;

scene.add(sphere, plane, torus);

이렇게 하면 왼쪽 구, 가운데에 평면 오른쪽에 도넛 형태가 만들어집니다. 기본 컬러로는 하얀식("white")을 적용했습니다.

 

 

- 회전 애니메이션 넣기

3D 정적으로 멈춰두지 말고, 회전 애니메이션을 주어 테스트를 편하게 해보죠.

const tick = () => {
  const elapsedTime = clock.getElapsedTime();

  // Update objects
  sphere.rotation.y = 0.1 * elapsedTime;
  plane.rotation.y = 0.1 * elapsedTime;
  torus.rotation.y = 0.1 * elapsedTime;

  sphere.rotation.x = 0.1 * elapsedTime;
  plane.rotation.x = 0.1 * elapsedTime;
  torus.rotation.x = 0.1 * elapsedTime;

  // Update controls
  controls.update();

  // Render
  renderer.render(scene, camera);

  // Call tick again on the next frame
  window.requestAnimationFrame(tick);
};

tick();

clock.getElapsedTime()을 사용해서 시간에 따라 조금씩 회전하도록 구현했습니다.

이 부분은 Three.js에서 애니메이션을 넣을 때 자주 사용하고, 이전에 Animation 파트에서도 다뤘으니 기억해주세요.

 

- Texture 적용

이제 단순 색만 입히는 게 아니라 텍스처(Texture)를 적용해보겠습니다. 텍스처를 불러오려면 TextureLoader를 사용합니다.

const textureLoader = new THREE.TextureLoader();
const doorColorTexture = textureLoader.load("./textures/door/color.jpg");
doorColorTexture.colorSpace = THREE.SRGBColorSpace;

colorSpaceTHREE.sRGBColorSpace로 맞춰서 인코딩하는 것은 필수입니다. 인 맞추면 색이 이상하게 보입니다.(실수 많이함)

 

const material = new THREE.MeshBasicMaterial({ map: doorColorTexture });

여기서 map 옵션은 "어떤 텍스처를 표면해 입힐까/'를 정해줍니다. 이렇게 하면 구체, 평면, 도넛 전부 문 텍스처가 입혀집니다.

 

텍스처를 모두 넣어보면서 경로와 확장자명이 문제있는 지 한 번씩 확인해보세요.

 

색상(Color)

 

컬러만을 변경하고 싶다고 한다면 어떻게 하면 됩니다.

단순히 material.color = "green"이라고 쓰면 안됩니다. 반드시 THREE.Color 객체를 사용해주세요.

material.color = new THREE.Color("green");

 

 

텍스처와 컬러를 동시에 처리할 수도 있습니다. 실제로 두 값이 곱연산(Multiply) 처리가 되죠.

const material = new THREE.MeshBasicMaterial();
material.map = doorColorTexture;
material.color = new THREE.Color("green");

잘 합쳐지는 것을 확인 할 수 있습니다.

 

- 와이어 프레임(Wire Frame)

주로 디버깅 목적으로 사용되며, 물처의 윤곽과 구조를 파악하는데 유용합니다. thickness 속성과 함께 사용될 때 더욱 명확하게 보입니다, 해당 속성을 true로 설정하면 Mesh가 WireFrame형태로 표시됩니다,

material.wireframe = true

초록색이 너무 안보이는 것 같아 빨간색으로 지정했습니다. 여러분도 한번 바꿔보세요,

 

- 투명도(Opacity) 및 불투명도(Transparent)

투명도(Opactiy)만 조정한다면 내부적으로 막아놔 적용이 안되기 때문에, 반드시 transparent = true의 속성을 이용해서 적용해야 합니다. opacity는 0.0(완전 투명)부터 1.0(완전 불투명) 사이의 값을 사용하여 불투명도를 조절합니다.

material.transparent = true;
material.opacity = 0.2;

투명도를 적용할 때 색상이 회색으로 보일 수 있는데, 이는 텍스처의 알파(alpha) 값을 통해 투명도를 조절하기 때문입니다,

 

알파맵(Alpha Map)

알파 맵은 텍스처를 기반으로 메시의 투명도를 조절하는 데 사용됩니다. 알파 맵의 검은색 부분은 완전히 투명하게 렌더링되어 해당 부분이 보이지 않게 되고, 흰색 부분은 불투명하게 렌더링됩니다. 이를 통해 특정 이미지 영역만 투명하게 처리하여 복잡한 모양의 오브젝트를 만들 수 있습니다.

material.alphaMap = doorAlphaTexture;

 

- 사이드(Side)

side 속성은 메시의 어느 면을 렌더링할지 결정합니다. 기본적으로 Three.js는 성능 최적화를 위해 카메라를 향하는 면(FrontSide)만 렌더링합니다.

  • THREE.FrontSide: 카메라를 향하는 앞면만 렌더링합니다.(기본값)
  • THREE.BackSide 카메라 반대편의 뒷면만 렌더링합니다.
  • THREE.DoubleSide: 앞면과 뒷면 모드 렌더링합니다.
material.side = THREE.DoubleSide;

DoubleSide는 잔디나 나뭇잎처럼 양면이 모두 보여야 하는 오브젝트에 유용하지만, 양쪽 면을 모두 렌더링하므로 성능 저하가 발생할 수 있습니다.

 

- 노말(Normal)과 플랫 쉐이딩(Flat Shading)

노말(Normal)

노말은 메시의 각 정점에 인코딩된 정보로, 해당 면이 어느 방향을 향하고 있는지 나타냅니다 이는 빛 계산, 굴절 등 다양한 렌더링 효과에 사용됩니다. MeshNormalMaterial은 카메라와 관련된 노말 방향에 따라 메시의 색상이 결정됩니다. 오브젝트를 회전해도 색상이 유지되는 것이 특징입니다.

const material = new THREE.MeshNormalMaterial();

 

 

플랫 쉐이딩(Flat Shading)

플랫 쉐이딩은 메시의 각 면을 평평하게 렌더링하여 면과 면 사이의 노말 보간을 없앱니다. 이는 메시의 각진 형태를 명확하게 보여주기 때문에 디버깅에 편리합니다.

material.flatShading = true;

 

 

- 매트캡 머터리얼(MeshMatcapMaterial)

MeshMatcapMaterial성능이 뛰어난 머터리얼입니다. 조명 없이도 조명을 받는 듯한 시각적 효과를 낼 수 있으며, 텍스처의 색상과 빛이 미리 구워져(baked) 있기 때문에 카메라 방향에 관계없이 결과가 나타납니다. 이는 웹 환경에서 퍼포먼스를 유지하면서 시각적 품질을 높이는 데 효과적인 '속임수' 기술입니다. Matcap 텍스처는 블렌더나 포토샵 같은 2D/3D 프로그램에서도 활용될 수 있습니다.

 

const material = new THREE.MeshMatcapMaterial();
material.matcap = matcapTexture;

material.matcap 속성에 Matcap 텍스처를 할당해야 합니다. material.map에 할당하면 일반 텍스처처럼 원래 색상 그대로 나타납니다. Matcap 텍스처를 외부에서 가져와 사용할 때는 라이센스를 확인해야 합니다. 

 

아래는 matcap 관련된 링크들입니다.

https://www.kchapelier.com/matcap-studio/

 

Matcap Tweaker

 

www.kchapelier.com

 

https://github.com/nidorx/matcaps

 

GitHub - nidorx/matcaps: Huge library of matcap PNG textures organized by color

Huge library of matcap PNG textures organized by color - nidorx/matcaps

github.com

 

https://github.com/kchapelier

 

kchapelier - Overview

A cell, provably dying. kchapelier has 93 repositories available. Follow their code on GitHub.

github.com

 

 

 

- 뎁스 머터리얼(MeshDepthMaterial)

MeshDepthMaterial은 카메라로부터 거리에 따라 메시를 렌더링합니다. 카메라에 가까운 부분은 흰색으로, 멀어질수록 어두운색으로 칠해집니다. 이는 나중에 그림자 처리와 같은 복잡한 계산에 활용될 수 있는 깊이 정보를 저장하는 데 유용합니다. 

 

 

 

- 램버트 머터리얼(MeshLambertMaterial)

MeshLambertMaterial조명이 필요한 머터리얼입니다. 조명이 추가되면 메시가 빛을 받아 밝게 보이며, 현재까지 소개된 머터리얼 중 빛을 사용할 때 가장 좋은 성능을 보여줍니다.

 

/**
 * Lights
 */
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);

기본적으로 AmbientLight로 표현해 회색으로 표현됩니다. 이후 추가적으로 PointLight를 달아주겠습니다. 

const pointLight = new THREE.PointLight(0xffffff, 30);
pointLight.position.x = 2;
pointLight.position.y = 3;
pointLight.position.z = 4;
scene.add(pointLight);

AmbientLight는 전역적으로 모든 오브젝트에 동일한 빛을 비추고, PointLight는 특정 지점에서 사방으로 빛을 발산합니다. 

 

주의사항

MeshLamertMaterial은 특정 형태(예: 구체)에서 이상한 패턴을 보일 수 있는 단점이 있습니다. 쉐이더를 배울 때 더 자세히 다루게 될 내용입니다.

 

- 퐁 쉐이더 (MeshPhongMaterial)

MeshPhongMaterial은 빛을 받았을 때 반사광(Specular)을 표현하여 물체의 광택을 나타내는 데 사용됩니다. shininess 속성은 반사광의 강도를, specular 속성은 반사광의 색상을 조절합니다. 하지만 퐁 쉐이더는 현실적인 빛 계산과는 거리가 있어 현재는 많이 오래된 렌더링 기술입니다.(자세한 블린-퐁 쉐이딩 내용은 해당 블로그 참고)

// MeshPhongMaterial
const material = new THREE.MeshPhongMaterial();
material.shininess = 100;
material.specular = new THREE.Color(0x1188ff);

 

- 툰 쉐이더 (MeshToonMaterial)

MeshToonMaterial은 비현실적이지만, 만화 같은 셀 쉐이딩(Cell Shading) 효과를 구현하는 데 매우 효과적입니다. 빛과 그림자를 두 가지 색상으로만 구분하여 표현하며, 이를 통해 게임 등에서 독특한 만화적 미학을 연출할 수 있습니다. 퍼포먼스 측면에서도 나쁘지 않은 편입니다.

const material = new THREE.MeshToonMaterial();
material.gradientMap = gradientTexture;

툰 쉐이딩을 위해 gradientMap을 사용하는데, 이는 일반적으로 3x1 픽셀과 같은 작은 이미지를 사용합니다. 이 때 중요한 점은 이 이미지가 자동으로 그라데이션 되지 않는다는 것입니다. GPU가 이미지를 늘리거나 줄일 때 발생하는 문제를 해결하기 위해 minFiltermagFilter 속성을 THREE.NearestFilter로 설정하여 픽셀이 보간되지 않고 명확하게 나타나도록 해야 합니다. 

const material = new THREE.MeshToonMaterial();
gradientTexture.minFilter = THREE.NearestFilter;
gradientTexture.magFilter = THREE.NearestFilter;
material.gradientMap = gradientTexture;

더 많은 픽셀을 가진 그라디언트 텍스처를 사용하면 더욱 부드러운 그라데이션 효과를 얻을 수 있습니다.

 

 

- 스탠다드 머터리얼(MeshStandardMaterial)

MeshStandardMaterial물리 기반 렌더링(PBR: Physically Based Rendering) 원칙을 따르는 머터리얼입니다. 이는 빛의 상호작용을 현실적인 알고리즘으로 시뮬레이션하여 더욱 실제 같은 재질을 표현할 수 있게 해줍니다. MeshStandardMaterial은 특히 roughness(거칠기)와 metalness(금속성) 속성을 지원합니다. 

MeshStandardMaterial이 "스탠다드"라고 불리는 이유는 블렌더(Blender), 언리얼(Unreal Engine)과 같은 다양한 3D 소프트웨어 및 게임 엔진에서 PBR의 개념과 유사한 파라미터(roughness, metalness 등)를 사용하기 떄문입니다. 하지만 Three.js는 자체적으로 레이트레이싱(Ray Tracing)을 지원하지 않으므로, 완전히 동일한 결과는 아니지만 비슷한 렌더링 품질을 제공합니다.

 

- 디버그 UI (Debug UI) 활용

lil-gui와 같은 디버그 UI 라이브러리를 추가하면 런타임에 머터리얼 파라미터(예: metalness, roughness)를 실시간으로 조정하며 그 변화를 확인할 수 있습니다. 이는 각 파라미터가 3D 객체에 어떤 영향을 미치는 지 직관적으로 이해하고 디버깅하는 데 매우 유용합니다.

import GUI from "lil-gui";

/**
 * Debug
 */
const gui = new GUI();

// ...

gui.add(material, "metalness").min(0).max(1).step(0.0001);
gui.add(material, "roughness").min(0).max(1).step(0.0001);

 

 

- 환경맵(Environment Map)

환경맵은 360도 서라운드 이미지로, 마치 3D Scene 전체를 하나의 이미지로 감싸는 듯한 효과를 줍니다. 이는 RGBLoader를 사용하여 .hdr과 같은 고품질 환경 이미지를 로드하여 구현합니다.

import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";

/**
 * Enviorment map
 */
const rgbeLoader = new RGBELoader();
rgbeLoader.load("./textures/environmentMap/2k.hdr", environmentMap => {
    environmentMap.mapping = THREE.EquirectangularReflectionMapping;

    scene.background = environmentMap;
    scene.environment = environmentMap;
});

환경맵을 로드한 후에는 .mapping 속성을 THREE.EquirectangularReflectionMapping으로 설정해야 올바르게 투영됩니다. 이 환경맵을 scene.backgroundscene.environment에 할당하면 Scene의 배경으로 설정될 뿐만 아니라, 오브젝트들이 이 환경맵의 빛과 색상을 반사하게 되어 더욱 사실적인 렌터링이 가능해집니다.

환경맵이 존재할 때는 엠비언트 라이트나 포인트 라이트 같은 일반적인 조명을 꺼두는 것이 좋습니다. 환경 맵 자체가 빛을 제공하기 때문에, 추가적인 조명은 Scene을 과도하게 밝게 만들 수 있습니다. 환경맵에 대한 자세한 내용은 추후 별도로 다룰 예저입니다.

 

디버그 UI에서 opacity, metalness를 조정해보시면 느낌을 잡을 수 있습니다.

 

 

- 앰비언트 오클루전 맵(Ambient Occlusion Map / aoMap)

aoMap(Ambient Occlusion Map)은 엠비언트 라이트가 있을 때 그림자를 더욱 사실적으로 표현하는 데 사용됩니다. 오브젝트의 틈새나 접히는 부분처럼 빛이 잘 닿지 않아 어두워지는 영역을 미리 계산된 텍스처로 표현하여, 비교적 적은 비용으로 현실적인 그림자 효과를 낼 수 있습니다.

material.map = doorColorTexture;
material.aoMap = doorAmbientOcclusionTexture;
material.aoMapIntensity = 1;

aoMapAmibentLightHemisphereLight와 함께 사용될 때 효과적이며, PointLight에서는 직접적으로 적용되지 않습니다. aoMapIntensity 속성을 통해 aoMap의 강도를 조절할 수 있습니다. 

 

- MeshPhysicalMaterial

MeshPhysicalMaterial이 PBR(물리 기반 렌더링)의 표준이라면, MeshPhysicalMaterial은 이를 한 단계 더 확장하여 극사실적인 재질을 표현할 수 있게 해주는 머터리얼입니다.

 

이번 포스팅에서는 MeshPhysicalMaterial만이 제공하는 고급 속성들(Clearcoat, Sheen, lridescence, Transmission)을 lil-gui를 활용하여 하나씩 파헤쳐 보겠습니다.

 

- 클리어코트(ClearCoat)

https://threejs.org/examples/#webgl_materials_physical_clearcoat

 

three.js examples

 

threejs.org

물체 표면 위에 얇고 투명한 광택 코팅층을 추가하는 효과입니다. 자동차 도색의 마감 코팅이나 카본 파이버, 니스 칠한 목재처럼 반짝이는 표면을 표현하는 데 이상적입니다. 두 개의 다른 머터리얼 속성을 하나로 합친 것과 같이 사실적인 표현이 가능하지만, 그만큼 성능 비용이 발생합니다.

 

// Clearcoat
material.clearcoat = 1;
material.clearcoatRoughness = 0;

gui.add(material, "clearcoat").min(0).max(1).step(0.0001);
gui.add(material, "clearcoatRoughness").min(0).max(1).step(0.0001);

- clearcoat: 클리어코트 층의 강도를 조절합니다.(0:없음, 1: 최대)

- clearcoatRoughness: 클리어코트 층의 거칠기를 조절합니다. 값이 낮을수록 매끄러운 거울처럼 반사됩니다.

 

 

- 쉰(Sheen)

https://threejs.org/examples/?q=sheen#webgl_loader_gltf_sheen

 

three.js examples

 

threejs.org

천, 벨벳, 새틴과 같은 직물(fabric) 재질이 빛을 받았을 때 표면 가장자리에서 부드럽게 빛나는 효과(Fresnel-like-effect)를 시뮬레이션합니다. 값이 0에 가까우면 플라스틱처럼 보이고, 1에 가까워질수록 원단 같은 느낌이 강해집니다.

 

// Sheen
material.sheen = 1;
material.sheenRoughness = 0.25;
material.sheenColor.set(1, 1, 1);

gui.add(material, "sheen").min(0).max(1).step(0.0001);
gui.add(material, "sheenRoughness").min(0).max(1).step(0.0001);
gui.addColor(material, "sheenColor");

- sheen: 광택 효과의 강도입니다.

- sheenRoughness: 광택의 퍼지는 정도를 조절합니다.

- sheenColor: 광택의 색상을 설정합니다.

 

 

- 아이리데선스(Iridescence)

https://threejs.org/examples/?q=anis#webgl_loader_gltf_anisotropy

 

three.js examples

 

threejs.org

표면을 보는 각도에 따라 색상이 무지개처럼 변하는 효과입니다. 비눗방울, 기름 막, CD 뒷면 등에서 볼 수 있는 아름다운 현상을 재현할 수 있습니다.

// Iridescence
material.iridescence = 1;
material.iridescenceIOR = 1;
material.iridescenceThicknessRange = [100, 800];

gui.add(material, "iridescence").min(0).max(1).step(0.0001);
gui.add(material, "iridescenceIOR").min(0).max(2.333).step(0.0001);

// 배열 값은 아래와 같이 제어할 수 있습니다.
gui.add(material.iridescenceThicknessRange, "0").min(0).max(1000).step(0.0001);
gui.add(material.iridescenceThicknessRange, "1").min(0).max(1000).step(0.0001);

- iridescence: 효과의 강도

- iridescenceIOR: 무지개 빛깔의 굴절률(Index of Refraction)입니다.

- iridescenceThicknessRage: 얇은 막의 최소/최대 두께를 [min, max] 배열로 설정하여 색상 변화 제어

 

 

- 트랜스미션(Transmission)

https://threejs.org/examples/?q=physica#webgl_materials_physical_transmission_alpha

 

three.js examples

 

threejs.org

유리, 물, 투명 플라스틱처럼 빛이 물체를 통과(투과)하는 효과를 시뮬레이션합니다. 이 속성을 사용하려면 metalness는 0에 가깝게, roughness는 낮게 설정하는 것이 좋습니다. 

// Transmission
material.transmission = 1;
material.ior = 1.5;
material.thickness = 0.5;

gui.add(material, "transmission").min(0).max(1).step(0.0001);
gui.add(material, "ior").min(1).max(2.333).step(0.0001);
gui.add(material, "thickness").min(0).max(1).step(0.0001);

- transmission:빛의 투과율입니다. 1로 설정하면 완전히 투명햊ㅂ니다.

- ior: 굴절률(Index of Refraction)입니다. 빛이 재질을 통과할 때 얼마나 꺾이는지를 나타냅니다.(공기 1, 물: 1.333, 다이아몬드: 2.417)

- thickness: 물체의 두께를 시뮬레이션하여 굴절에 영향을 줍니다.

완전 투명한 유리를 만들 수 있습니다. 

다른 모든 프로퍼티도 확인해보세요. 다른 속성과는 관계 없습니다.

 

- 주의사항

MeshPhysicalMaterial은 매우 사실적인 표현이 가능한 만큼, Three.js의 빌트인 머터리얼 중 성능 비용이 가장 비싼 머터리얼 중 하나라는 점을 반드시 기억해야 합니다. 

 

특히 유리나 액체를 표현하는 transmission과 매끄러운 코팅을 위한 clearcoat같은 고급 효과들은 렌더링 시 복잡한 계산을 요구하여 사용자의 GPU에 상당한 부담을 줄 수 있습니다. 따라서 이 머터리얼은 화면의 중심이 되는 핵심 오브젝트에만 제한적으로 사용하여 시각적 효과를 극대화하고, 그 외의 부분에는 MeshStandardMaterial 같은 더 가벼운 대안을 고려하는 지혜가 필요합니다.

 

특히, 다양한 사양의 모바일 환경까지 고려한다면, 압도적인 리얼리즘과 쾨적한 사용자 경험 사이의 균형점을 찾는 것이야말로 프로젝트의 완성도를 결정짓는 중요한 과정이 될 것 입니다.

 

 

결론

Three.js에서 3D 객체의 얼굴을 담당하는 머터리얼(Material)에 대해 깊이 있게 탐험했습니다. 가장 단순한 MeshBasicMaterial에서 출발하여, 빛과 상호작용하기 시작하는 MeshLambertMaterialMeshPhongMaterial을 거쳐, 현대적인 PBR 렌더링의 표준인 MeshStandardMaterial의 핵심 속성(roughness, matalness)을 배웠습니다.

 

그리고 MeshPhysicalMaterial을 통해 자동차 도색, 유리, 직물, 비눗방울과 같은 극사실적인 재질을 표현하는 고급 기법까지 살펴보았습니다. 이 과정에서 환경맵(Environment Map)이 현실적인 반사와 굴절에 얼마나 중요한지, 그리고 디버그 UI가 각 속성의 변화를 직관저긍로 이해하는 데 얼마나 강력한 도구인지 직접 확인했습니다.

 

결국 머터리얼은 3D 모델의 생명을 불어넣는 과 같습니다. 어떤 머터리얼을 어떻게 조합하여 사용하느냐에 따라 결과물은 완전히 다른 인상을 줍니다.

 

물론 파티클을 위한 PointsMaterial이나 GLSL 코드로 자신만의 머터리얼을 창초하는 ShaderMaterial 등 아직 다루지 않은 더 깊은 세계가 남아있습니다. 하지만 지금까지 배운 내용만으로도 원하는 3D 재질을 자신 있게 구현하고, 시각적을 뛰어나면서도 최적화된 3D 웹 경험을 만들어 낸 튼튼한 기초를 다지게 된 것입니다.

 

'Three.js > Intro' 카테고리의 다른 글

[threejs-journey 1-12] 3D Text  (0) 2025.07.04
[threejs-journey 1-10] Texture  (0) 2025.06.25
[threejs-journey 1-9] Debug UI  (0) 2025.06.22
[threejs-journey 1-8] Geometries  (0) 2025.06.16
[threejs-journey 1-7] Resizing & FullScreen  (0) 2025.06.12