🚀

突拍子もないWebサイトの作り方 技術編

2022/10/06に公開

はじめに

こんにちはウコンパワーです。
今回は私が所属しておりますジュニにて一つWebサイトを公開しまして、ちょっとトリッキーなことをやったりしましたので、いくつか紹介できればなと思いこちらの記事を書いております。

制作全体に関しても記事をまとめたりもしてますのでぜひ参照ください。
https://zenn.dev/ukonpower/articles/3a62a247a033db

というわけで今回公開したサイトは以下です。↓↓
https://next.junni.co.jp/

GPUがアツアツですね!!

すべてをさらけ出す

そして今回はただのTips紹介じゃないぞ!の気持ちです。
なぜなら、リポジトリが公開されてるからです。
https://github.com/junni-inc/next.junni.co.jp

こんな記事読まなくても git cloneすれば済む話なのでこの記事はおまけ程度に見てもらえますとです。

ライブラリ / ツール

主に使用したライブラリやツールは以下です。

ライブラリ

  • Threejs
  • cannon.js
  • ore-three

ツール

  • VSCode
  • Bender
  • Fgma
  • Miro

フレームワーク

無数のWebフレームワークが溢れてる昨今、今回はgulp & webpackの伝統的構成で挑戦しました。

なぜかって??

ちょっぱやで使えるテンプレがそれだったからです。🙄
https://github.com/ukonpower/three-template-ts

シーンづくり

今回のサイト、全セクションをThree.jsを用いた3Dで表現してます。
シーンづくりをscene.add(new THREE.Mesh(geo,mat))でやっていたらなかなかつらいものがあります。そこでBlenderの出番です。

https://github.com/junni-inc/next.junni.co.jp/tree/master/blend-files
Blenderプロジェクトはすべてここにまとめて入っています。

基本的なシーンのモデリングは概ねBlender上で行います。
Blenderで作成したシーンをglTFでWebに持ってきて、各オブジェクトを'scene.getObjectByName()'で取得しそれぞれのクラスに渡していくというのが基本の流れです。

/*-------------------------------
	Logo
-------------------------------*/

this.logo = new Logo( scene.getObjectByName( 'Logo' ) as THREE.Object3D, this.commonUniforms );
this.logo.switchVisibility( this.sectionVisibility );

リンク

以下のようなモデリングでは難しいような形状や演出がされるオブジェクトに関してはThree.js上でメッシュを作ってます。

アニメーション


Blender側で作成するアニメーションはこのバクのキャラクターの動きのみです。
セクションごとにActionを作りTHREE.AnimationMixerなどを用いてアニメーションを切り替えてます

その他のオブジェクトの動きなどはすべてスクリプト制御してます。
ORE.AnimatorというDurationと値を渡すとDuration分時間をかけて値をアニメーションしてくれるクラスを用意してあり、その値をシェーダーに突っ込むことが多いです。
https://github.com/junni-inc/next.junni.co.jp/blob/master/src/ts/MainScene/World/Baku/index.ts#L59-L68

いくつかピックアップ

ちょっと変わった表現をいくつかピックアップします。

屈折


このガラス的な表現を行うにはちょっと手間がかかります。
ガラスのオブジェクトを描画する前に、その奥に存在しているオブジェクトのみが描画されているフレームバッファを用意しないといけないからですね。

THREE.MeshクラスなどにはonBeforeRenderという、そのメッシュが描画される寸前に呼ばれるコールバックが用意されています。

今回はそれを用いて背景のみの描画結果を別フレームバッファに転写しています。

https://github.com/junni-inc/next.junni.co.jp/blob/master/src/ts/MainScene/World/Baku/index.ts#L147-L167

そうして用意したフレームバッファとメッシュの法線を用いてUVを歪ませながら背景色を取得することでガラスの屈折を表現することができます。

こちらが屈折のシェーダー箇所です。
forで歪みの強さを徐々に強くすることでにじむようにし、RGBごとに歪みのつよさをかえることで色ズレの表現をしてます。

// refract

vec3 refractCol = vec3( 0.0 );
vec2 screenUv = gl_FragCoord.xy / winResolution.xy;
vec2 refractUv = screenUv;

float slide;
vec2 refractUvR;
vec2 refractUvG;
vec2 refractUvB;
float refractPower = 0.3;
vec2 refractNormal = geo.normal.xy * ( 1.0 - geo.normal.z * 0.85 );

#pragma unroll_loop_start
for ( int i = 0; i < 16; i ++ ) {

	slide = float( UNROLLED_LOOP_INDEX ) / 16.0 * 0.1 + random( screenUv ) * 0.03;

	refractUvR = refractUv - refractNormal * ( refractPower + slide * 1.0 ) * uTransparent;
	refractUvG = refractUv - refractNormal * ( refractPower + slide * 1.5 ) * uTransparent;
	refractUvB = refractUv - refractNormal * ( refractPower + slide * 2.0 ) * uTransparent;

	refractCol.x += texture2D( uSceneTex, refractUvR ).x;
	refractCol.y += texture2D( uSceneTex, refractUvG ).y;
	refractCol.z += texture2D( uSceneTex, refractUvB ).z;

}
#pragma unroll_loop_end
refractCol /= float( 16 );

リンク

トレイル

今回は前提としてミドルレンジ後半~ハイエンドあたりの端末で見られればOKということもあり、せっかくなのでGPGPUを行いました。

GPGPUを行った演出の一つが以下のトレイルです。

こちらはPC時のみ見れる演出で、マウスカーソルの後ろをなぞるように一本の線がビヨンと伸びるものです。

コードはこちら

トレイルははるか昔に解説を書いていたりして基本的な考え方はこちらと同じですが、今回はORE.GPUComputationControllerというコンピュートシェーダー(役のフラグメントシェーダー)を渡すとよしなにフレームバッファをやりくりしてくれるやつを用意してます。

以下がコンピュートシェーダーです。128x1pxのフレームバッファに対してシェーディングが行われます。
uCursorPosにマウスカーソルの位置(をトレイルのローカル座標に変換したもの)が送られてくるのでそれを一番左のピクセルに描画します。他のピクセルはそれぞれの一個左のピクセルの値を描画することでトレイルの動作を作ります。

uniform vec2 dataSize;
uniform sampler2D uPosDataTex;
uniform sampler2D uNoiseTex;
uniform float time;
uniform float uMaterial[6];
uniform vec3 uCursorPos;

#pragma glslify: rotate = require('./rotate.glsl' )

void main() {

    if( gl_FragCoord.x <= 1.0 ) {
		
        vec2 uv = gl_FragCoord.xy / dataSize.xy;
        gl_FragColor = vec4( uCursorPos, 1.0 );
        
    } else {

        vec2 uv = gl_FragCoord.xy / dataSize.xy;
        vec2 bUV = ( gl_FragCoord.xy - vec2( 1.0, 0.0 ) ) / dataSize.xy;

        vec3 pos = texture2D( uPosDataTex, uv ).xyz;
        vec3 beforePos = texture2D( uPosDataTex, bUV ).xyz;
		
		float blend = 0.0;
		blend += uMaterial[0] * 0.4;
		blend += uMaterial[1] * 0.2;
		blend += uMaterial[2] * 0.3;
		blend += uMaterial[4] * 0.1;
		blend += uMaterial[5] * 0.2;

		vec3 newPos = mix(beforePos, pos, blend);

		// sec4 床むりやりやぞ
		
		newPos -= -12.0;
		newPos.y *= 1.0 - 0.1 * uMaterial[3];
		newPos += -12.0;

		// sec5 ノイズ
		
		newPos.xyz += ( texture2D( uNoiseTex, pos.xy * 0.02 ).xyz - 0.50 ) * 0.05 * uMaterial[4];

		// sec6 進むやつ

		newPos += uMaterial[5] * vec3( 0.1, -.06, 0.1 );

        gl_FragColor = vec4(newPos,1.0);
    }
}

左のピクセルの値を描画する際に、自身のピクセルの値とブレンドすることでトレイルが引っ張られるような動きにしています。

ブレンドあり

ブレンドなし

まとめ

あ、あれも!それも!と今までやってみたかった実装をたくさんツメツメしたWebサイトになりました。

パフォーマンスもギリギリ(アウト?)くらいのところまで行き、なにやらiOS Safariでスクショを撮ると100%固まる模様です。

....またそれも一興。

今回は我々のありのまま(主に自分の)をさらけ出すという意味でリポジトリも全て公開しています。

こんな有様も全て公開しています。
シンパシーを感じた方、ぜひお待ちしておりますよ😎

JUNNI

Discussion