앞선 글에서도 말하였듯이 아래 코드펜 프로젝트를 기반으로 welcome page를 제작하였다.
https://codepen.io/akm2/pen/RwQXLdP
# 조금 더 구체적인 컨셉
그렇다고 저걸 그대로 가져다 쓰기에는 허전하다.
저 파티클은 우주 컨셉의 배경일 뿐, 주인공이 될 수는 없다.
그렇다면 메인 모델은 누가 되어야 할 것인가?
나는 주인공을 혜성으로 정했다.
우주를 자유로이 유영하는 아름다운 천체이자
미지의 세계로 두려움없이, 또 끊임없이 나아가는 낭만을 품은 천체이다.
또한 여러 공부를 하다보면 마주치는 'deep dive'란 용어에 가장 잘 어울리는 존재이며
아름다운 꼬리를 남기는 모습이 끊임없이 탐구하며 발자취를 남겨가는 모습과 닮았다고 느껴졌다.
그럼 이 각설하고,
# 구현
코드펜 프로젝트는 파티클의 x, y, z 값을 양수로 랜덤하게 설정하고 일정한 interval을 두어 z 값이 줄어들도록 하였다.
그러다 z 값이 0 이하가 되는 순간 x, y, z 값을 다시 랜덤하게 설정하여 새로운 파티클이 등장한 것처럼 표현하였다.
또한, canvas의 중심의 x, y 좌표와 코드 상 FL(아마 focal length인 듯)이라 표현된 z 좌표를 가지는 지점을 원점으로 하고 현재 마우스 좌표를 z 값이 0인 지점으로 하여 3차원 공간 상에서의 파티클이 화면에 투영된 좌표를 계산하도록 해 마우스 움직임에 따라 공간이 회전하는 듯 보이게 하였다.
이를 이용하면, 혜성의 핵에 해당하는 좌표를 (0, 0, z)로 정해놓고 그 지점으로부터 혜성의 꼬리 및 파티클이 뿜어나오는 형상을 그려내면 될 것 같다.
작업을 단순화하기 위해 혜성의 꼬리는 진행 방향과 반대되는 하나만 존재한다고 가정하였다. (실제처럼 이온 꼬리, 티끌 꼬리 구현까지 하려면 작업량이 너무 많아진다.)
혜성 본체
z 좌표가 고정된 조금 큰 파티클이라고 생각하면 된다.
기존 프로젝트 코드를 수정하여 사용하였으며, 핵은 원으로 표현하였다.
꼬리는 원에서 마우스 위치를 기준으로 한 두 접점을 이은 삼각형으로 표현하였다.
추가로 조금의 반짝거리는 효과를 넣고자 블러를 넣었는데, 딱히 크게 티는 안난다. 쳇.
let rx, ry;
let f, x, y;
let pf, px, py, pr;
let a, a1, a2;
// draw comet body
rx = canvasWidth / 2 - cx;
ry = canvasHeight / 2 - cy;
pf = FL / COMET_Z;
px = cx + rx * pf;
py = cy + ry * pf;
pr = COMET_RADIUS * pf;
context.fillStyle = COMET_COLOR;
context.shadowColor = COMET_SHADOW_COLOR;
context.shadowBlur = 25;
context.beginPath();
context.arc(px, py, pr, 0, 2 * pi);
context.closePath();
context.fill();
// draw comet tail
f = FL / (COMET_Z - speed * 100);
x = cx + rx * f;
y = cy + ry * f;
a = atan2(py - y, px - x);
a1 = a + pi / 2;
a2 = a - pi / 2;
context.shadowBlur = 40;
context.beginPath();
context.moveTo(px + pr * cos(a1), py + pr * sin(a1));
context.lineTo(mouseX, mouseY);
context.lineTo(px + pr * cos(a2), py + pr * sin(a2));
context.closePath();
context.fill();
파티클 객체
기존 코드는 파티클을 따로 클래스로 정의하지 않았지만, 나는 파티클 종류를 두 가지 생성해야 하므로 객체화 시켰다.
3차원 상의 위치를 표현해주기 위해 x, y, z값을 가지며, 위치를 랜덤하게 설정하기 위한 메서드가 추가되어 있다.
class Particle {
constructor() {
this.x = 0;
this.y = 0;
this.z = 0;
this.pastZ = 0;
}
randomize() {
this.x = Math.random() * canvasWidth;
this.y = Math.random() * canvasHeight;
this.z = Math.random() * 1500 + 500;
}
}
파티클 그리는 로직은 기존 코드를 사용하였으므로 맨 위 코드펜 링크 참고.
혜성에서 뿜어져나오는 파티클
단순히 저 원과 삼각형만으로는 이게 혜성인지 총알 날아가는 궤적인지 분간이 안된다.
따라서 혜성의 핵으로부터 뿜어져나오는 파티클을 추가해주기로 하였다.
기존 배경 파티클과 다른 점은 한 점으로부터 방사되는 형태로 경로가 변경되어야 한다는 것이다.
이를 위해 기존 파티클 객체를 상속하면서 x, y의 변화량을 나타내는 dx, dy 변수를 추가하였고, randomize 함수를 수정하였다.
class CometParticle extends Particle {
constructor() {
super();
this.dx = 0;
this.dy = 0;
}
randomize() {
let r = 0.75 + Math.random() * 0.25;
let theta = Math.random() * 2 * pi;
this.dx = r * cos(theta);
this.dy = r * sin(theta);
this.x = this.dx * COMET_RADIUS + canvasWidth / 2;
this.y = this.dy * COMET_RADIUS + canvasHeight / 2;
this.z = (Math.random() - 1) * 100 + COMET_Z;
}
}
그리고 이를 그려주는 로직은 다음과 같이 기존 파티클 로직과 거의 비슷하다.
다만, 포물선 형태로 x, y 값을 변경해주기 위해 마지막에 p.x, p.y를 재설정해주는 코드를 추가하였다.
let p;
let rx, ry;
let f, x, y, r;
let pf, px, py, pr;
let a, a1, a2;
context.fillStyle = COMET_COLOR;
context.shadowColor = COMET_SHADOW_COLOR;
context.shadowBlur = 20;
context.beginPath();
// draw particles
for (let i = 0; i < PARTICLE_NUM * COMET_RATIO; i++) {
p = cometParticles[i];
p.pastZ = p.z;
p.z -= speed * 2;
if (p.z <= 0) {
p.randomize();
continue;
}
rx = p.x - cx;
ry = p.y - cy;
f = FL / p.z;
x = cx + rx * f;
y = cy + ry * f;
r = PARTICLE_BASE_RADIUS * 1.5 * f;
pf = FL / p.pastZ;
px = cx + rx * pf;
py = cy + ry * pf;
pr = PARTICLE_BASE_RADIUS * 1.5 * pf;
a = atan2(py - y, px - x);
a1 = a + pi / 2;
a2 = a - pi / 2;
context.moveTo(px + pr * cos(a1), py + pr * sin(a1));
context.arc(px, py, pr, a1, a2, true);
context.lineTo(x + r * cos(a2), y + r * sin(a2));
context.arc(x, y, r, a2, a1, true);
context.closePath();
p.x += p.dx * Math.pow(p.z, 3) / Math.pow(10, 8);
p.y += p.dy * Math.pow(p.z, 3) / Math.pow(10, 8);
}
context.fill();
결과물
아직 디테일이 조금 떨어지는 느낌이긴 하지만, 가구현 단계로선 나쁘지 않아 보인다.
프로젝트는 GitHub에 올려두었다.
다만, 현재 제작 중이므로 추후 코드가 대폭 수정될 가능성이 크다.
'Projects > Portfolio' 카테고리의 다른 글
Next.js 포트폴리오 페이지 제작기 - 5. Projects part (0) | 2023.08.05 |
---|---|
Next.js 포트폴리오 페이지 제작기 - 4. About part (0) | 2023.08.04 |
Next.js 포트폴리오 페이지 제작기 - 3. Intro part (0) | 2023.07.28 |
Next.js 포트폴리오 페이지 제작기 - 2. Welcome to Intro (0) | 2023.07.28 |
Next.js 포트폴리오 페이지 제작기 - 0. 프로젝트 구상 (0) | 2023.07.25 |