1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SvelteAdvent Calendar 2024

Day 11

Svelte で 3D 表示を行うライブラリ Threlte #6: カスタムシェーダーを適用する

Last updated at Posted at 2024-12-10

シリーズ

静止シェーダーの作成と適用

シェーダーコード

まずはシェーダーを作成する。
配置場所としては、$lib/shaders 以下がいいかもしれない。

root
  └─lib
      └─shaders
          ├─ simple.frag.glsl
          └─ simple.vert.glsl
simple.vertex.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;
}
simple.fragment.glsl
varying vec3 vPosition;

void main() {
  gl_FragColor = vec4(vPosition, 1.);
}

上記は、グローバルポジションの色だけをモデルに描画する処理。

ファイルの拡張子について
実際のところ厳格にファイルの拡張子や命名は決められているわけではない。結局のところ、中身のアスキーを Raw で読み込んでパースしているだけなので。

ただ、一応の定義としては、.glsl か、細かく .vert.frag などを分けられることもある。
これに従って、GLSL のハイライトサポートの恩恵も受けられるので、そこはある程度従っておくべきだろう。
しかしながら、.glsl の場合は、そのファイルが Vertex シェーダー用なのか、Fragment シェーダー用なのかわからなくなるため、.vert.glsl.frag.glsl とファイル名に着けておく方が良いだろう。

適用

シェーダーコードを読み込む際には、パスの末尾に ?raw を付けて読み込む。

Scene.svelte
<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}

image.png

その他にも、 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 を渡してアニメーションしてみた。

output-palette.gif

シェーダーコード

シェーダー側としてのポイントは Fragment シェーダー側で uTime と、任意の uniform パラメーターを定義して、使用しているところ。

animLines.vert.gls
varying vec2 vUv;

void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
animLnies.frag..glsl
+ 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 を使う用に指定されている。

Scene.svelte
<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 から startstopstartedtask を受け取ることができる。

これを利用して、例えば、ボタンを押した際にアニメーションを開始させたり、停止させたりできる。

<script lang="ts">
    const { start, stop, started } = useFrame(
      () => {
        console.log('rendering…')
      },
      {
        autostart: false
      }
    )
    
    const toggleUseFrame = () => {
      if ($started) {
        stop()
      } else {
        start()
      }
    }
</script>

まとめ

静止したシェーダーに関してはそのまま適用で問題なくそのまま <Mesh> コンポーネントの material プロップに渡すなどの対応で良い。

アニメーションするシェーダーに関しては、時間情報を Svelte 側から渡してあげる様にセットアップする必要がある。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?