개발/토이 프로젝트

별 헤는 밤 BHNB 알파 버전 개발 후기

havana723 2022. 9. 28. 02:30

https://bhnb.havana.moe/

 

BHNB

 

bhnb.havana.moe

 

별 헤는 밤(BHNB)을 WebGL을 사용해 개발해보았습니다.

 

별 헤는 밤은 천문 동아리 선배인 PngWnA 선배가 처음 개발한 프로젝트로, 웹 브라우저로 현재 접속 중인 위치에서의 밤하늘이 어떻게 보이는지 구현한 플라네타리움입니다.

 

해당 프로젝트를 나중에 또 다른 천문 동아리 선배인 susemeee 선배가 React로 포팅한 버전인 BHNB-react가 있고, 이를 다시 한 번 WebGL로 구현한 것이 이번 프로젝트입니다.

 

웹에서 구현한 밤하늘

 

기술 스택

사용된 기술 스택은 다음과 같습니다.

  • Next.js, React
  • TypeScript
  • three.js, react-three-fiber, react-three/drei
  • Github Action/Github Pages

 

원래 하던 것들

각 스택을 차근차근 설명해보자면 Next.js를 사용한 이유는 추후 동아리 웹사이트에 사용하기 위해서입니다. 현재 제가 개발 중인 동아리 프론트엔드는 Next.js를 사용하고 있기 때문에, 같은 스택으로 개발하는 것이 좋을 것이라고 생각하였습니다. 익숙한 스택이어서 빠르게 개발할 수 있을 것 같다는 점도 있었습니다. 결과적으로 이번 프로젝트에서 Next.js의 기능을 전부 활용하지는 못했지만, Next.js를 이용한 static site generation(SSG)을 경험해 볼 수 있었고 getStaticProps와 같은 기능에 대해 알아볼 수 있었습니다.

 

TypeScript는 지금까지 TypeScript를 사용하지 않는 개발을 해 본 적이 없기도 하였고, 저 자신을 믿을 수 없어서 썼습니다. 매번 타입 추론에 고통받으면서 괴로워하지만 없으면 어디서 터졌는지도 모르고 터졌을 거라고 생각하면 그나마 다행입니다. 참 좋은 언어인거 같아요.

 

새로 시도해 본 것들

three.js는 WebGL 사용을 용이하게 하는 javascript 라이브러리입니다. 이번 프로젝트를 통해 배우고 싶었던 라이브러리이기도 합니다. react-three-fiber는 이를 React에서 쉽게 사용할 수 있도록 jsx 문법을 three.js 컴포넌트로 렌더해주는 react renderer입니다. 정확히는 React 그 자체는 상태 관리를 주 기능으로 하고 react-dom은 그렇게 관리한 상태를 받아 html 컴포넌트로 렌더해주는 역할을 합니다. react-three-fiber는 react-dom에 대응되는 renderer로 렌더 결과물이 html이 아니라 three.js 기반의 3D 컴포넌트입니다.

 

이번 프로젝트를 진행하면서 React와 renderer의 관계에 대해 알 수 있었습니다. 프로젝트 전에는 React와 react-dom의 차이도 몰랐고, React는 html만 렌더해주는 줄 알았습니다. 또한 React Native도 렌더러의 일종으로 안드로이드와 IOS 컴포넌트를 렌더하는 방식으로 앱을 개발할 수 있다고 합니다. 신기해요.

 

다시 본론으로 돌아와서, three.js를 선택한 이유는 생태계가 가장 커서 (사실상 거의 유일해서) 자료가 많을 것이라고 생각했기 때문입니다. 실제로 진행 과정에서 한국어 자료도 여럿 보았던 만큼 좋은 선택이었던거 같습니다. 이것보다 자료가 적었으면... 꽤 많이 힘들었을거 같아요.

 

CI/CD

마지막으로 배포를 위해서는 Github Action와 Github Pages를 사용하였습니다. 이 둘은 이전에도 사용해 본 만큼 큰 부담 없이 적용할 수 있었습니다. Github Action을 사용하면 매번 수동으로 배포해주는 대신 main 브랜치에 push하는 것으로 배포를 대신할 수 있었습니다. 또한 Github Pages를 이용하며 무료로, 그리고 빠르게 static site를 배포할 수 있습니다. Github Action과 연동하기도 편합니다. 지난번 aws ec2에서 클라이언트 빌드를 할 때 영겁의 시간이 걸리고 메모리가 터지는 일이 있었기 때문에 aws에는 올리고 싶지 않았습니다. 로컬에서 빌드할 수 있지 않겠냐고 하겠지만.. Github Action은 그것도 대신 해줍니다!

 

이번에 Next.js 프로젝트를 Github Action을 통해 배포하면서, SSG에 대해 알 수 있었습니다. Next.js를 Github Action을 통해 어떻게 배포하는지 찾아보다가 next export를 해야 한다는 것을 찾을 수 있었고, 이는 Next.js에서 static site로 변환시키는 과정이라는 것을 알게 되었습니다. 사실 동아리 프론트엔드에서 Next.js를 쓰는 것도 그냥 좋아보여서였는데 static site와 server side rendering, 그리고 Next.js를 통해 빌드 시 데이터를 fetch하는 방법(getStaticProps)을 알게 되니 왜 Next.js를 쓰는게 좋은지 체감할 수 있었습니다.

 

개발 과정

개발을 하려면 먼저 프로젝트 초기 세팅을 해야 합니다. create next app을 통해 빈 Next.js 프로젝트를 생성하고, three.js와 기타 라이브러리들을 설치해줍니다. 밤하늘의 별을 그리기 위한 기초 준비가 끝났습니다. 저는 사실 아직도 이게 매번 헷갈리기 때문에 항상 다시 찾아봅니다... 너무 어려워요.

 

three.js 시작하기

다음으로는 three.js를 먼저 만져봅니다. 인터넷에서 찾은 기본 예제들은 화면에 큐브를 띄우는 것이었는데, 저도 따라서 띄워봤습니다.

 

(대충 아무것도 안 보이는 이미지)

 

개발 과정에서 찍은 이미지가 남아있다면 올렸을텐데 애석하게도 개발할 때 쓰던 컴퓨터가 죽은 관계로 (오늘 새 맥북을 사왔습니다... 너무 비싸요 슬퍼요) 남은 이미지가 없습니다. 앞으로 저런 이미지(였던 것)들을 자주 보게 될 거예요. 제 블로그를 참고 견디세요. 아무튼 빈 Scene을 만들어서 CubeMesh를 넣으니 아무 것도 뜨지 않았습니다. 왜 보이지 않느냐 하면 빛이 없어서였습니다. 현실에서야 자연스럽게 빛이 있지만, 컴퓨터 속에는 그렇지 않습니다. 빛이 있으라! Scene에 빛을 넣어주면 작고 귀여운 큐브가 화면에 나옵니다.

 

데이터베이스

이제 three.js의 기능을 대충 익혀 봤으니 다시 별을 그리러 갈 때입니다. 별을 그리기 위해서는 어느 별이, 어느 위치에 있는지에 대한 정보가 필요합니다. 그러한 데이터베이스가 여럿 있는 것으로 아는데 이번 프로젝트에서는 선배들이 프로젝트에 사용한 HYG-Database를 가공하여 사용하기로 하였습니다.

 

HYG-Database는 깃헙에 올라온 프로젝트로 해당 데이터베이스를 깃에 submodule로 넣을 수 있습니다. submodule이라는 개념을 이번 프로젝트를 하면서 처음 알게 되었는데, 좋은 기능인거 같습니다. 이번 프로젝트를 하면서 새로 배우는 것들이 많네요.

 

HYG-Database는 매우 크고, 많은 정보들이 담겨 있습니다. 모든 별을 다 그리면 제 컴퓨터가 비행기 이륙하는 소리를 내는 것에서 끝나지 않을 것이기 때문에, 7.9등급 이상의 별만 보여주기로 합니다. 또한 별을 그리기 위해서 그 많은 정보가 다 필요한 것은 아니므로, 별의 이름과 cartesian 좌표, 겉보기 등급, 그리고 color index만 추출합니다. 이는 PngWnA 선배가 작성해놓은 python script를 통해 json 파일로 추출했습니다.

 

이 json 파일이... 정말 참 많은 일들이 있었습니다. 일단은 json 파일의 크기가 너무 컸습니다. 약 4MB였는데 너무 커서 typescript가 resolve를 하지 못했습니다. 그래서 import를 하려고 해도 module로 인식하지 못하고... 겨우 any를 통해 로컬에서 돌아가게 만들었더니 배포할 때 또 터졌습니다. 그래서 결국 github에 해당 파일을 올리고 이를 fetch해오는 방식으로 구현했습니다. 해당 과정에서 getStaticProps를 알게 되긴 했는데... json이 좀 더 작았으면 좋을 거 같아요. 다음에는 별을 줄여도 좋을 것 같습니다.

 

실제로 그려주기

별의 정보도 전부 가지고 있으니, 이제 실제로 별을 그려줄 차례입니다. 별을 그리기 위해서 다음 두 가지 방법 중 고민을 했습니다.

  • Instanced Mesh
  • Point

 

Instanced Mesh는 geometry와 material은 같지만 world transformation이 다른 오브젝트를 많이 그려야 할 때 사용합니다. Point는 점들을 찍어주는 오브젝트입니다. Instanced Mesh를 사용하면 단순한 점이 아니라 다양한 형태의 오브젝트를 그릴 수 있다는 장점이 있습니다. 이게 추후에 기획하고 있는 기능들을 구현할 때 도움이 될 것이라고 생각했습니다. 반면 Point는 점만을 그려주기 때문에 Isntanced Mesh보다 가볍고, 성능상의 이슈가 적을 것이라고 생각했습니다. 결국 약 3만개의 오브젝트를 그려야 하는 점을 고려해 Point를 사용하기로 했습니다.

 

그렇게 Point를 찍었는데... 문제가 있었습니다. 바로 별이 동그랗지 않고 네모난 픽셀로 보인다는 점입니다. 찾아보니 Point는 원래 그렇다는 것 같습니다. 이를 해결하기 위해서는 크게 세 가지 방법이 있습니다.

 

첫 번째는 Instanced Mesh로 변경한 뒤 SphereGeometry를 사용하는 것입니다. 이는 정말정말정말 무거워질 것이기 때문에 보류했습니다. 두 번째는 texture를 씌우는 것입니다. 동그란 원 모양의 텍스쳐를 적용하면 동그랗게 보입니다. 이 방법을 텍스쳐를 만들기 귀찮아서 보류했습니다. 마지막으로는 fragment shader를 커스텀하는 것입니다. shader라는 이름을 그래픽스 수업 외에서 다시 보게 될 줄은 몰랐는데... 그래픽스 수업에서 배운 것들을 잘 써먹어서 커스텀 shader를 만들었습니다.

 

const fragmentShader = `
  uniform vec3 color;

  varying vec3 vColor;

  void main() {
    if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;
    gl_FragColor = vec4( color * vColor, 1.0 );
  }
`;

 

중심으로부터의 거리가 0.475 이상이면 discard 하는, 즉 원 범위만 남기는 shader 코드입니다. 그래픽스 다시는 안 할거라고 생각했는데 왜 하게 되는 걸까요...

 

별의  크기와 색

별을 다 그렸습니다! 그렇지만 뭔가 단조롭지 않나요? 사진이 있다면 뭐가 단조로운지 보여드렸을텐데 안타깝네요. 여기까지 그렸을 때 단조로운 이유는, 별이 전부 새하얀 색이기 때문입니다. 우리가 보는 별은 전부 밝게 빛나서 하얀색이라고 착각하기 쉽지만 사실 별마다 모두 다른 색으로 빛나고 있습니다. 물리 시간에 배웠던 별의 온도에 따라서 말이죠. 따라서 별마다 다른 색을 적용해줘야 합니다.

 

이는 위의 데이터베이스에서 받아온 color index를 통해 구할 수 있습니다. color index를 rgb로 변환하는 방식은 인터넷을 찾아오면 잘 나옵니다. 해당 공식을 계산하는 utils 함수를 하나 만든 뒤 각 별마다 다른 색을 지정해주면 조금 더 다채로운 우주가 됩니다.

 

또한 별이 모두 같은 크기인게 마음에 들지 않습니다. 지구에서 보는 별은, 밝을수록 크게 보이게 됩니다. 이는 별의 겉보기 등급을 통해 알 수 있습니다. 데이터베이스의 겉보기 등급을 적절한 함수(PngWnA 선배의 식을 살짝 커스텀 해서 사용했습니다)에 넣어 크기를 정해줍니다. 좀 더 실제 밤하늘과 비슷해졌습니다.

 

스스로 빛나는 별

여기까지 왔으면 실제 밤하늘과 꽤 비슷한 무언가를 볼 수 있습니다. 이제 우주를 좀 더 아름답게 만들어봅시다.

 

밤하늘의 별은 반짝반짝 빛나고 있으므로 우리도 별마다 빛나는 효과를 넣으면 예쁠 것 같습니다. three.js에는 unreal bloom이라는 아주 좋은 postprocess 이펙트가 있습니다. 이걸 사용하면 별이 반짝반짝 빛나게 할 수 있습니다. 들어가는 인자 중 threshold는 rgb 값의 합이 몇 이상일 때부터 빛나는지, strength는 얼마나 밝게 빛나는지, radius는 얼마나 넓게 빛나는지를 결정하는 인자입니다.

 

이걸 설정할 때 아무것도 안 보이거나 정말 극단적인 이펙트만 보이거나 해서 엄청 고생했던 기억이 있는데... 어떻게 막 만지다가 고쳐진 이후로 다 까먹었습니다. 이런걸 기록하라고 있는 블로그일텐데 개발하다보면 문제를 해결하는 것에 너무 심취해서 기록을 남기지 않고는 합니다. 좀 더 열심히 기록해야 할텐데 말이에요. 아무튼 아무 생각 없이 적용하면 안 됐던 기억이 있습니다.

 

카메라 이동

여기까지 만들었으면 뭔가 우주를 돌아보고 싶습니다. 지금 상태로는 밤하늘이 제대로 만들어졌는지 확인하기도 어려울 뿐더러 굳이 3D로 만든 보람이 없기도 합니다. 그래서 카메라 움직임을 추가했습니다. three.js에는 TrackballController라는 좋은 기능이 있습니다. 가져다 쓰면 됩니다.

 

최대로 줌을 확대하면 하면 지구 중심에서 바라보는 별을 볼 수 있습니다. 반대로 축소할 경우 우주 밖에서 별을 관측하는 듯한 느낌을 받을 수 있습니다. 되게 예쁘니까 꼭 해보시기를 추천드려요.

 

테스트를 통과한 모습

 

최대로 줌을 확대해 찍은 스크린샷의 경우, 동아리 선배가 진행한 테스트를 통과했습니다. 밤하늘 사진을 넣으면 어떤 별자리인지 알려주는 프로그램이 있는데, 해당 프로그램에 성공적으로 인식되는 것을 알 수 있습니다. 실제 밤하늘을 구현하는데 성공했다는 뜻이기도 합니다.

 

더 해보고 싶은 것들

별 헤는 밤(BHNB)이 아직 알파 버전인 이유는, 아직 해보고 싶은 것들이 많기 때문입니다. 해보고 싶은 것들은 아래와 같습니다.

 

언제가 될 지는 모르겠지만, 전부 구현할 수 있으면 좋겠습니다. 어렵지만 재밌지 않을까요?

 

깃헙 레포지터리

https://github.com/havana723/bhnb-opengl

 

GitHub - havana723/bhnb-opengl

Contribute to havana723/bhnb-opengl development by creating an account on GitHub.

github.com

 

사용된 코드는 전부 위 레포에 있으므로, 관심 있으신 분들은 한 번 씩 확인해주시면 감사하겠습니다.

 

+) 아래는 동아리 내부 스터디에서 발표 자료로 사용했던 슬라이드입니다.

 

012345678
스터디 발표 자료