My website has been renewaled
※この記事はtkmh.me上で掲載している記事 (2016.07.08 掲載) を転載、加筆・修正したものです。
---------
かねてより企んでいた自身のサイトのリニューアルが完了しました。
自分の実績を載せるページが欲しかったのですが、コンテンツが寂しいと思ったので、今までサブドメインで運用していたブログとブックマークサイトを統合しました。RSSを登録頂いていた方はいかに登録し直していただけると幸いです。
・Blog
https://tkmh.me/blog/feed/
・Bookmarks
https://tkmh.me/bookmarks/feed/
ついでに新設のWorksのRSSも。
・Works
https://tkmh.me/works/feed/
技術的にも盛り込みたいものは盛り込めたし (WebGL、非同期遷移、レスポンシブ etc…) 、デザインも綺麗にまとまって、割と気に入ってます。
このサイトの一番の見せ場はメインビジュアルのトランスフォームです。エンジニアの方向けに少しだけ解説します。
メインビジュアルはWebGL (three.js) で組んでいます。ロゴの形の3Dオブジェクトはモデリングをしたのではなく、プログラムで構築しています。直方体を組み合わせた形なので、そんなに複雑ではないです。
ただし、ポリゴンをバラバラにしてトランスフォームさせたかったので、BoxGeometryを使用せず、BefferGeometryに自分で頂点を追加しています。かつバラバラのキューブ状でも動かしたかったので、ボクセルを構築しています。なので普通にBoxGeometryを使用して同じ形を作るよりも、頂点数は遥かに多いです。
あとはポリゴン単位で動かしたり、キューブ単位で動かしたり、頂点単位で動かしたりしています。
頂点の座標計算はすべてシェーダないで行っています。これはGPGPUという手法です。
GPGPUでトランスフォームをどのように制御しているかというと、頂点シェーダ内にすべてのアニメーションパターンを記述しておき、それを係数によってどのアニメーションを適用するかを決めています。以下に簡単な例を記述します。
// 頂点シェーダ例
uniform mat4 modelViewMatrix;
uniform mat4 modelMatrix;
uniform mat4 projectionMatrix;
uniform float animationParam1;
uniform float animationParam2;
void main() {
vec3 pos = position;
// パターン1
pos.x += animationParam1 * 100;
// パターン2
pos.y += animationParam2 * 100;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
デフォルトの状態ではanimationParam1に1をセット、animationParam2に0をセットし、パターン1のみが適用されている状態で、パターン1からパターン2にトランスフォームする際は、animationParam1を0に、animationParam1を1に値を徐々に変化させる処理を同時行っています。そうすることによって、2つのアニメーションパターン間をシームレスに遷移させることができます。
実際はイージングの処理を入れたり、ノイズを入れたり、時間差処理を入れたりしてるので、これよりもずっと複雑ですが、面倒なので割愛。
各コンテンツのタイトルの形状に変形させていますが、それは予め各コンテンツのタイトルの文字の画像 (Works) などを読み込んで、ピクセル情報を取得し、黒い色が付いているところをランダムでピックアップし、GeometryのAttributeにvec3として渡しているので、その情報を元に座標を決めています。
↑こんな背景透過画像を使用しています。(白の背景色を入れていますが、実際に使用している画像は白い部分が透過になっているものです。)
以下はタイトルの画像のピクセル情報取得部分の抜粋です。 (CoffeeScript)
# 一部抜粋
$img = $('').one 'load', (e)=>
# drawImageでタイトルの文字をcanvasに描画
img = $img.get 0
canvas = document.createElement 'canvas'
context = canvas.getContext '2d'
canvas.width = img.width
canvas.height = img.height
context.drawImage img, 0, 0, img.width, img.height
# ピクセルの色情報を取得
imgData = context.getImageData 0, 0, img.width, img.height
points = []
for i in [0...imgData.data.length - 1] by 4
alpha = imgData.data[i + 3]
index = i / 4
if alpha > 0
# アルファが0より大きい場合に、ピクセルの座標をpointsにpush
points.push {
x: index % img.width - img.width / 2
y: img.height / 2 - Math.floor(index / img.width)
}
# ... 以下、pointsに入っている座標をランダムにピックアップし、シェーダに渡す
.attr 'src', imgPath # imgPathはタイトルの文字の画像パス
色の制御は適当にsimplexNoise等を使用して決めてます。
すごい適当な説明になってしまいましたが、要は結構本気出して実装しました。
ソースコードも公開しています。(readmeに説明がありませんがあしからず。。)
サポートいただければ、レッドブルを飲んでより頑張れると思います。翼を授けてください。