Three.js 프로젝트를 통해 웹의 새로운 가능성을 탐구하고, 사용자에게 혁신적인 온라인 경험을 제공하고자 합니다.
“3D 인터랙티브 웹 경험 (3D Interactive Web Experience)”
Three.js를 사용하여 개발된 인터랙티브 3D 우주 시뮬레이션입니다.
메인 프로젝트 전 three.js를 학습하고자 만든 페이지 입니다.
지구와 달, 주변의 별들로 이루어진 간단한 태양계를 시뮬레이션화 했습니다.
지옥에서 온 고양이 캐릭터와 함께 다양한 카메라 컨트롤을 통해 사용자 상호작용 요소를 포함하고 있습니다.

현재 프로젝트 구조
project_root/
│
├── dist/
├── node_modules/
│── src/
│ ├── texture/
│ │ ├── cat_eyes.png
│ │ └── fur.jpg
│ ├── cat.js
│ ├── index.html
│ ├── main.css
│ ├── main.js
│ ├── planet.js
│ └── spaceProject.js
├── babel.config.js
├── package-lock.json
├── package.json
├── README.md
└── webpack.config.js
[모듈화 부재]
spaceProject.js 에 scene, camera, 객체 생성 등 너무 많은 다양한 기능들이 혼재되어 있음.cat, planet만 분리되어있고, 다른 주요 컴포넌트들은 개별 모듈로 분리되어있지 않다.→ 가독성 및 유지보수성 떨어짐
컴포넌트 기반 구조화
project_root/
│
├── dist/
├── node_modules/
└── src/
├── components/
│ ├── Planet.js
│ ├── Star.js
│ └── Cat.js
├── scenes/
│ ├── MainScene.js
├── controllers/
│ └── OrbitController.js
├── assets/
│ ├── textures/
│ └── models/
├── styles/
│ └── main.css
├── index.html
└── main.js
components: 각 3D 객체를 개별 컴포넌트로 분리하여 재사용성과 유지보수성을 높일 수 있다.scenes: 여러 씬을 관리할 수 있게 되어 추후 로딩 화면, 메인 화면 등을 별도로 구현할 수 있다.controllers: 입력 처리와 관련된 로직을 한 곳에서 관리할 수 있다. 현재는 카메라 관련 컨트롤러만 넣어 놓은 상태다.assets : 모든 외부 리소스를 관리한다.styles: CSS 파일을 별도 폴더에서 관리한다
Geometry & Material만 가지고 만든
“지옥에서 온 고양이..”
핵심 컴포넌트로 createPlanet 를 통해 지구 및 달을 생성한다.
const surfaceGeometry = new THREE.SphereGeometry(surface.size, 32, 32);
const surfaceMaterial = new THREE.MeshPhongMaterial({
map: new THREE.TextureLoader().load(surface.textures.map),
bumpMap: new THREE.TextureLoader().load(surface.textures.bumpMap),
bumpScale: surface.material.bumpScale,
specularMap: new THREE.TextureLoader().load(surface.textures.specularMap),
specular: surface.material.specular,
shininess: surface.material.shininess,
});
const planetSurface = new THREE.Mesh(surfaceGeometry, surfaceMaterial);
planetGroup.add(planetSurface);
SphereGeometry로 구 모양을 만든 뒤 . surface.size로 크기를 정한다.MeshPhongMaterial로 행성 표면의 재질을 만든다.map: 행성 표면의 기본 이미지bumpMap: 표면에 울퉁불퉁한 느낌을 주는 이미지specularMap: 빛이 반사되는 부분Mesh로 구 모양과 재질을 합쳐 행성 표면을 만든다planetGroup에 추가한다.const surfaceGeometry = new THREE.SphereGeometry(surface.size, 32, 32);
const surfaceMaterial = new THREE.MeshPhongMaterial({
map: new THREE.TextureLoader().load(surface.textures.map),
bumpMap: new THREE.TextureLoader().load(surface.textures.bumpMap),
bumpScale: surface.material.bumpScale,
specularMap: new THREE.TextureLoader().load(surface.textures.specularMap),
specular: surface.material.specular,
shininess: surface.material.shininess,
});
const planetSurface = new THREE.Mesh(surfaceGeometry, surfaceMaterial);
planetGroup.add(planetSurface);
SphereGeometry로 구 모양을 만든 뒤 . surface.size로 크기를 정한다.MeshPhongMaterial로 행성 표면의 재질을 만든다.map: 행성 표면의 기본 이미지bumpMap: 표면에 울퉁불퉁한 느낌을 주는 이미지specularMap: 빛이 반사되는 부분Mesh로 구 모양과 재질을 합쳐 행성 표면을 만든다planetGroup에 추가한다.
Shader사용
Shader를 사용하기 전에는 단순한 Mesh만을 이용한 구를 사용해 대기를 표현했다.
이 방식을 사용하니 실제 대기와 같은 자연스러운 발광 효과를 만들기 어려웠고, 그라데이션 효과를 구현하는 것도 한계가 있었다.
Shader의 경우 픽셀 단위로 색상과 투명도를 정밀하게 제어할 수 있고, 카메라 위치에 따라서 대기의 모습을 동적으로 변경할 수 있었다.
또한, 수학적인 계산을 통해 더 사실적인 발광 효과를 만들 수 있어서 Shader를 사용하게 되었다.
const atmosphereMaterial = new THREE.ShaderMaterial({
vertexShader: `
...
`,
fragmentShader: `
uniform vec3 glowColor;
uniform float coefficient;
uniform float power;
void main() {
float intensity = pow(coefficient - dot(vNormal, vec3(0, 0, 1.0)), power);
gl_FragColor = vec4(glowColor, 1.0) * intensity;
}
`,
uniforms: {
...
},
...
});
[코드 설명]
GLSL과Shader사용
Shader은 픽셀 단위로 색상을 정의하기 때문에 GLSL를 사용했다.
shader에게 명령을 전달하기 위해서는 shader 언어(GLSL, HLSL 등)으로 작성된 작성된 코드가 필요하기 때문이다.
GLSL은 Three.js의 기본 material로는 구현하기 어려운 복잡한 시각 효과를 만들 수 있고, vec3, dot() 와 같은 함수를 사용해서 그래픽에 필요한 함수를 제공하기 때문이다.
사실 GLSL은 C 기반 언어라서 C/C++를 사용할 수 있다면 쉽게 작성할 수 있겠지만, 나는 C언어에 대한 지식이 부족하여 공부 후 더 정리하도록 하겠다.
uniform vec3 glowColor;
uniform float coefficient;
uniform float power;
uniform vec3 glowColor;
uniform은 셰이더 프로그램 전체에서 동일한 값을 가지는 변수를 선언할 때 사용한다. vec3는 RGB 색상을 나타낸다. glowColor는 발광 효과의 색상을 지정하는 변수로, 색상 코드를 사용해 대기에 빛나는 부분을 설정한다.
`uniform float coefficient;
float는 소수점이 있는 숫자 타입, 그리고 coefficient`으로, 위에 설정한 발광 효과의 강도를 조절하는 것이다.
`uniform float power;
power`는 발광 효과가 얼마나 강한지 약한지를 제어해주는 값.
[계산식]
void main() {
float intensity = pow(coefficient - dot(vNormal, vec3(0, 0, 1.0)), power);
gl_FragColor = vec4(glowColor, 1.0) * intensity;
}
float intensity = pow(coefficient - dot(vNormal, vec3(0, 0, 1.0)), power);
coefficient를 빼는 이유:
Shader 사용 전 후Shader 사용 전/후 비교를 위해 임시적으로 발광하는 부분의 크기를 변경해서 비교했다.
그라데이션 효과가 없다.
부자연스럽고 지구위에 막이 덮힌 느낌이라 지구의 색이 선명하지 못함
그라데이션 효과
지구 위에 막이 덮힌 느낌이 없어 지구의 색이 선명함.
질감 효과를 준 것에 영향을 끼치지 않아 더욱 더 사실감 있게 표현 가능
해당 시뮬레이션을 만들면서 가장 힘들었던점은 에러 로깅.
기존 React나 Next.js 의 경우 콘솔에 에러 내용이 나왔는데 three.js는 어떠한 로그도 찍혀있지 않은 경우가 꽤나 있었다.
scene에 등록을 하지 않는다던가, 컨트롤끼리 충돌이 났다던가, 혹은 뭔가 설정이 잘 못 되어있어서 그냥 흰 바탕만 나오는 경우가 있어서 뭐가 문제인지 알아내기가 조금 힘들었다.
이에 관련해서 에러 로깅이라던지 다른 방법이 있는지 알아보고난 후 적용시켜 본 프로젝트를 시작하면 좋을 것 같다.
그라데이션 효과 기능 신기해요..! 잘 보고 갑니다!(지옥에서 온 고양이 너무 귀여워요 🐈⬛)