Three.js × Shader でネオン×グリッチ風の演出を試してみた

Three.jsの勉強、最近少し行き詰まり中です。

やってみたい表現はたくさんあるのに、技術が追いつかなくてもどかしい…!

そんな中で今回は、ChatGPTに相談しながら、

「ネオンっぽい色ズレ」

「グリッチっぽい揺れ」

「波のようにビビビっと走る演出」

この3つを組み合わせたアニメーションをつくってみました。

「こういうことをやりたい!」と伝えると、

参考になるコードを出してくれてありがたい。

そのままでは使えないことも多いけど、

ヒントをもらえるだけでも大きな助けになります✨❤️‍🔥

それでは、今回やってみたことを簡単にまとめていきたいと思います📝

完成したもの

上からビビビっと波を走らせています。

さらに、RBGがずれて少しネオンっぽい感じに🩷💚

揺れる幅や速さは細かく調整できそうです◎

使用した技術・環境

私はviteで環境構築したものを使っています。

コードを変更したときの反映スピードが速くて◎

シェーダーはGLSLという言語を使うのですが、

JavaScriptにベタがきすると少々読みづらい…

そこでvite-plugin-glsl をnpmでインストールして使ってみました。

そうするとvertex.glslとfragment.glslというファイルを別で作って、

それをJavaScriptで読み込むことができます。

これだけで使うことができます👇✨!

vite.config.js
import glsl from 'vite-plugin-glsl'

export default defineConfig({
  plugins: [
    glsl()
  ]
})

公式のURLも貼っておきますね◎

https://www.npmjs.com/package/vite-plugin-glsl

方法

まず簡単な流れを書きますと、

①Three.jsのPlaneGeometryを準備

②ShaderMaterialで画像を貼り付け

③シェーダーで画像をゆらゆら

という感じです。

TextureLoaderで画像を読み込み、

読み込み終了してから諸々準備するという流れです。

JavaScript
const textureLoader = new Three.TextureLoader()
textureLoader.load(
  'image.jpg',
  (texture) => {
    //ここでカメラ、シーン、オブジェクトを用意
  },
  (error) => {
    console.log(error)
  }
)

始めは頂点シェーダーを調整します。modelPositionのxとzをuTime(経過時間)に合わせて変更しました。

物理なんて覚えていなくて、三角関数が難しい。sin(サイン)とcos(コサイン)とか本当に分からなすぎるのですが、sinを組み合わせるとランダムな感じのゆらゆらが作れるみたい👀‼️

数値は色々調整したら好きなようにできそうです。

vertex.glsl
// グリッチタイミングに高さ(y)によるディレイを加える
float glitchTime = uTime * 0.003 - (1.0 - modelPosition.y) * 5.0;

// グリッチの強さ(揺らぎ波 × 強弱制御)
float baseWave = (sin(glitchTime) + sin(glitchTime * 3.45) + sin(glitchTime * 8.76)) / 7.0;

出てきた数値をmodelPosition.xとmodelPosition.zにプラスすることで、上から下に向けて波がいってる感じになりました🌊

次はフラグメントシェーダーでネオンっぽい動きをつけます。

fragment.glsl
// 揺れ量(時間と位置でノイズっぽく)× 強さ
float delayedTime = (uTime - (1.0 - vUv.y) * 1000.0) * 0.001;

float offset1 = (sin(delayedTime) + sin(delayedTime * 3.45) +  sin(delayedTime * 8.76)) * 0.01 * waveStrength;
float offset2 = (cos(delayedTime) + cos(delayedTime * 3.45) +  cos(delayedTime * 8.76)) * 0.0097 * waveStrength;
float offset3 = (sin(delayedTime) + sin(delayedTime * 3.45)) * 0.0095 * waveStrength;

RGBの3つをそれぞれ少しずつずらしていきます。これもさっきと同じでsin(サイン)とcos(コサイン)を使ってずらしました。

fragment.glsl
// それぞれUVをずらす(RGBで色分け)
vec2 uvR = vUv + vec2(offset1, 0.0);
vec2 uvG = vUv + vec2(offset2, 0.0);
vec2 uvB = vUv + vec2(offset3, 0.0);

それぞれ、uvをずらします。

fragment.glsl
// 各チャンネル取得
float r = texture2D(uTexture, uvR).r;
float g = texture2D(uTexture, uvG).g;
float b = texture2D(uTexture, uvB).b;

gl_FragColor = vec4(r, g, b, 1.0);

これをgl_FragColorに入れると、ネオンっぽくなりました。ずれる量とか速さとかは数値を色々いじると調整できます◎

覚えておきたいポイント

◎texture2Dはuvが必要💡

最初うっかりuvを渡し忘れてしまい、真っ白になってしまいました。

glsl
// 正しくはこう!
vec3 color = texture2D(uTexture, vUv).rgb;

texture2D() は「どこを表示するか」を uv で指定してあげないと、何も描画されないんですね。Fragment Shaderで画像を表示したい時は、まずここを確認!

◎ノイズ表現はrandom()だけじゃない

random() を使いたかったけど、GLSLにはビルトインの random() 関数がないので、擬似的に自分で関数を書く必要があります。

たとえばこんな感じ👇

glsl
float random2D(vec2 st) {
  return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123);
}

でも、random() だけだと単調になりがち。

そこで sin() や cos() を組み合わせてみたところ、自然で面白いノイズっぽい揺れが作れました!

glsl
float wave = sin(uTime + uv.y * 10.0) + cos(uTime * 0.5 + uv.y * 5.0);

こうやって波を混ぜると、「ピリピリ感」や「ざぶーん感」が出てきて楽しいです!

今後やってみたいこと

◎ホバーした時だけこの演出をかける

◎複数画像にこの演出をかけて、スライダーにしてみる

まとめ

Three.jsとGLSLの組み合わせでイメージ通りのものができると嬉しいですね。

これからも色々な表現を試していきたいです。