概要
three.js
で,スクリーンセーバーをイメージしてローポリな海を作りました.ソースコードはこちらで公開しています.
また,作ったページをGitHub Actions
でGitHub Pages
にビルド & デプロイし,以下のページで公開しています.
海を作る
開発環境
- Vite
- React + TypeScript
- Three.js r174
一部解説
格子状に点群を設置:
const rows = 50;
const cols = 100;
const spacing = 10;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const x = (j - cols / 2) * spacing;
const y = (i - rows / 2) * spacing;
const z = 0;
points.push(x, y, z);
}
}
geometry.setAttribute("position", new THREE.Float32BufferAttribute(points, 3));
const pointCloud = new THREE.Points(geometry, material);
// pointCloud.visible = false;
scene.add(pointCloud);
格子状の点群を描画し,それらの座標をsin関数とcos関数を組み合わせて変化させ,海面の揺らぎを表現:
requestAnimationFrame(animate);
const positions = geometry.attributes.position.array as Float32Array;
const time = performance.now() * 0.001;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const index = (i * cols + j) * 3 + 2;
positions[index] = (Math.sin(time + i * 0.2) * Math.cos(time * 0.3 + j * 0.2) +
Math.sin(time * 0.5 + i * 0.4) * Math.cos(time * 0.6 + j * 0.4)) * 3;
}
}
geometry.attributes.position.needsUpdate = true;
各点から隣り合う縦・横・(ひとつの)斜め方向に線をつなぎ,海面を表現:
function updateLines() {
linePositions.length = 0;
const positions = geometry.attributes.position.array as Float32Array;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const index = (i * cols + j) * 3;
const x = positions[index];
const y = positions[index + 1];
const z = positions[index + 2];
if (j < cols - 1) {
linePositions.push(x, y, z, positions[index + 3], positions[index + 4], positions[index + 5]);
}
if (i < rows - 1) {
const belowIndex = ((i + 1) * cols + j) * 3;
linePositions.push(x, y, z, positions[belowIndex], positions[belowIndex + 1], positions[belowIndex + 2]);
}
if (i < rows - 1 && j < cols - 1) {
const diagonalIndex = ((i + 1) * cols + (j + 1)) * 3;
linePositions.push(x, y, z, positions[diagonalIndex], positions[diagonalIndex + 1], positions[diagonalIndex + 2]);
}
}
}
lineGeometry.setAttribute("position", new THREE.Float32BufferAttribute(linePositions, 3));
lineGeometry.attributes.position.needsUpdate = true;
}
const animate = () => {
updateLines();
};
点群は消すかどうか迷いましたが,光を反射しているみたいできれいなので残すようにしました.月とかを設置するようにしたい.
ビルド & デプロイ
色々試したせいで余計なコードが含まれているかもしれないです.
手元でビルドするのではなく,リポジトリにpushしたとき,GitHub Actions
内の仮想環境(ubuntu-latest)上でビルドするようにしました.手動でビルドするのが面倒だからこうしてみたけど,時間かかるしエラーが出るかもしれないことを考えるとあんまりよくないのかもしれない.
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
env:
VITE_BASE: '/Ocean/'
GITHUB_PAGES: true
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
おわりに
わざわざコンポーネント化したのは今後これを何かのページでインポートしたかったからです.ChatGPTに提案されたからuseEffect
を使ったけど,useEffect
を完全に理解できていないから今後勉強する.