シリーズ
- Svelte で 3D 表示を行うライブラリ Threlte #1: パッケージの作成とライブラリ概要
- Svelte で 3D 表示を行うライブラリ Threlte #2: エクストラパッケージを使用する
- Svelte で 3D 表示を行うライブラリ Threlte #3: glTF シーンを扱う
- Svelte で 3D 表示を行うライブラリ Threlte #4: シーンコントロールツールセット Theatre.js for Threlte
- Svelte で3D表示を行うライブラリ Threlte #5: XR への対応
- Svelte で 3D 表示を行うライブラリ Threlte #6: カスタムシェーダーを適用する
静止シェーダーの作成と適用
シェーダーコード
まずはシェーダーを作成する。
配置場所としては、$lib/shaders
以下がいいかもしれない。
root
└─lib
└─shaders
├─ simple.frag.glsl
└─ simple.vert.glsl
varying vec3 vPosition;
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
}
varying vec3 vPosition;
void main() {
gl_FragColor = vec4(vPosition, 1.);
}
上記は、グローバルポジションの色だけをモデルに描画する処理。
ファイルの拡張子について
実際のところ厳格にファイルの拡張子や命名は決められているわけではない。結局のところ、中身のアスキーを Raw で読み込んでパースしているだけなので。
ただ、一応の定義としては、.glsl
か、細かく .vert
、.frag
などを分けられることもある。
これに従って、GLSL のハイライトサポートの恩恵も受けられるので、そこはある程度従っておくべきだろう。
しかしながら、.glsl
の場合は、そのファイルが Vertex シェーダー用なのか、Fragment シェーダー用なのかわからなくなるため、.vert.glsl
や .frag.glsl
とファイル名に着けておく方が良いだろう。
適用
シェーダーコードを読み込む際には、パスの末尾に ?raw
を付けて読み込む。
<script lang="ts">
import { T } from '@threlte/core';
import fragmentShader from '$lib/shaders/simple.frag.glsl?raw';
import vertexShader from '$lib/shaders/simple.vert.glsl?raw';
import { ShaderMaterial } from 'three';
const gltf = useGltf('./models/DamagedHelmet.glb', { useDraco: true });
</script>
{#if $gltf}
<T.Mesh
geometry={$gltf.nodes['node_damagedHelmet_-6514'].geometry}
rotation={[1.5, 0, 0]}
position={[0, 0, 0]}
>
<T.ShaderMaterial
{fragmentShader}
{vertexShader}
/>
</T.Mesh>
{/if}
その他にも、 T.Mesh
に対して material
を使用する方法もある。
この場合は直接 Three.js の ShaderMaterial
を使用して、各シェーダーを渡す。
<script lang="ts">
// そのほか省略
import { ShaderMaterial } from 'three';
</script>
{#if $gltf}
<T.Mesh
geometry={$gltf.nodes['node_damagedHelmet_-6514'].geometry}
material={new ShaderMaterial({ vertexShader: vertexShader,
fragmentShader: fragmentShader })}
rotation={[1.5, 0, 0]}
position={[0, 0, 0]}
/>
{/if}
シェーダーアニメーションを適用する
シェーダー側に動きを点けたい場合、シェーダー外部から毎フレーム変わるユニフォームパラメーターを渡す。
サンプルとして、以下の様に、シンプルに Box だけストライプを sin()
アニメーションに uTime
を渡してアニメーションしてみた。
シェーダーコード
シェーダー側としてのポイントは Fragment シェーダー側で uTime
と、任意の uniform パラメーターを定義して、使用しているところ。
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
+ uniform float uTime;
uniform vec3 color;
varying vec2 vUv;
in vec2 inTexCoord;
void main() {
+ vec2 mulvUv = mod(vUv * sin(uTime) * 4.0, 1.0);
float strength = step(0.5, mulvUv.y);
gl_FragColor.rgba = vec4(vec3(strength), 1.0);
}
適用
適用する側で重要なのは、タイムパラメーター処理を Threlte 環境から持ってくる こと。
それには useTask
を使用する
もともと useFrame
という機能があったが、そちらは LEGACY 扱いとなり、現在ではより拡張された useTask
を使う用に指定されている。
<script lang="ts">
- import { T } from '@threlte/core';
+ import { T, useTask } from '@threlte/core';
import { ContactShadows, Float, Grid, OrbitControls } from '@threlte/extras';
import { ShaderMaterial } from 'three';
import simpleAnimFragmentShader from '$lib/shaders/simpleAnim.fragment.glsl?raw';
import simpleAnimVertexShader from '$lib/shaders/simpleAnim.vertex.glsl?raw';
+ let uTime = $state(0);
+ useTask(
+ (delta) => {
+ uTime += delta;
+ },
+ { autoStart: true }
+ );
</script>
<!-- 省略 -->
<Float floatIntensity={1} floatingRange={[0, 1]}>
<T.Mesh
position.y={1.2}
position.z={-0.75}
material={new ShaderMaterial({
vertexShader: simpleAnimVertexShader,
fragmentShader: simpleAnimFragmentShader,
uniforms: {
uTime: { value: { uTime } }
}
})}
>
<T.BoxGeometry />
</T.Mesh>
</Float>
アニメーションタイミングをコントロールする
上記では、useTask
の返り値を指定していないが、本来は useTask
から start
、stop
、started
、task
を受け取ることができる。
これを利用して、例えば、ボタンを押した際にアニメーションを開始させたり、停止させたりできる。
<script lang="ts">
const { start, stop, started } = useFrame(
() => {
console.log('rendering…')
},
{
autostart: false
}
)
const toggleUseFrame = () => {
if ($started) {
stop()
} else {
start()
}
}
</script>
まとめ
静止したシェーダーに関してはそのまま適用で問題なくそのまま <Mesh>
コンポーネントの material
プロップに渡すなどの対応で良い。
アニメーションするシェーダーに関しては、時間情報を Svelte 側から渡してあげる様にセットアップする必要がある。