はじめに
毎回Shader環境を作成するのが面倒だったので、
R3F+Vite+Typescript+GLSLShaderテンプレート作成しました。
今回作成した成果物は以下です。
Githubは以下です。
環境構築
Viteでプロジェクト作成
[Project name]» r3f-ts-shader-starter
[Select a framework:] » React
[Select a variant] » TypeScript + SWC
依存関係をインストール
cd r3f-ts-shader-starter
pnpm install
pnpm install three @types/three @react-three/fiber @react-three/drei
pnpm install --save-dev vite-plugin-glsl
Shader環境構築(GLSL)
vite.config.ts
import { defineConfig } from 'vite'
import glsl from "vite-plugin-glsl"
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), glsl(
{
include: [
'**/*.glsl', '**/*.wgsl',
'**/*.vert', '**/*.frag',
'**/*.vs', '**/*.fs'
],
exclude: undefined, // Glob pattern, or array of glob patterns to ignore
warnDuplicatedImports: true, // Warn if the same chunk was imported multiple times
defaultExtension: 'glsl', // Shader suffix when no extension is specified
compress: false, // Compress output shader code
watch: true, // Recompile shader on change
root: '/' // Directory for root imports
}
)],
})
起動
pnpm dev
**http://localhost:5173/**で確認しましょう。
実装
WebGLのCanvasを実装
App.tsx
import { Canvas } from "@react-three/fiber";
import { OrbitControls, GizmoHelper, GizmoViewport } from "@react-three/drei";
function App() {
return (
<div style={{ height: "100dvh", width: "100dvw" }}>
<Canvas shadows>
<ambientLight intensity={1}/>
<pointLight position={[3, 3, 3]}/>
<directionalLight position={[-2, 3, 5]}/>
<mesh>
<boxGeometry args={[1, 1, 1]}/>
<meshStandardMaterial color="hotpink"/>
</mesh>
<OrbitControls/>
<GizmoHelper alignment="top-right" margin={[75, 75]}>
<GizmoViewport labelColor="white" axisHeadScale={1} />
</GizmoHelper>
</Canvas>
</div>
)
}
export default App
画面をぐりぐりするとCanvasの視点が動くはずです。
Shaderを実装
srcフォルダ以下にglslフォルダを作成し、
hello.vertとhello.fragを作成
└ src
+ └ glsl
+ ├ hello.frag
+ └ hello.vert
FragmentShaderの記述
テスト用なので、
なんかいい感じのShaderを作成します。
hello.frag
varying vec2 vUv;
uniform float u_time;
/**
* Line関数
* @params uv 位置データ
* @params speed 波の速さ
* @params height 周波数の高さ
* @params col 色データ
*/
vec4 Line(vec2 uv, float speed, float height, vec3 col){
uv.y += smoothstep(1., 0., abs(uv.x)) * sin(u_time * speed + uv.x * height) * .2;
return vec4(
smoothstep(
.06 * smoothstep(.2, .9, abs(uv.x)),
0.,
abs(uv.y) - .004
) * col,
1.0) * smoothstep(1., .3, abs(uv.x));
}
void main() {
vec2 uv = vUv;
uv -= 0.5; // 中心
uv.x *= 1.6; // [0:1.6]区画にスケール
vec4 color = vec4(0.);
float lattice = 5.0;
for (float i = 0.; i <= lattice; i += 1.) {
float lattice = 5.0;
float t = i / lattice; // [0, 0.25, 0.50, 0.75, 1.0]
color += Line(
uv,
1. + t,
4. + t,
vec3(
.2 + t * .7,
.2 + t * .4,
0.3
)
);
}
gl_FragColor = vec4(color);
}
VertexShader作成
Vertexは面倒なので初期値で。
hello.vert
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
R3F側で実装
App.tsx
+ import React from "react";
+ import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls, GizmoHelper, GizmoViewport } from "@react-three/drei";
+ import vertexShader from "./glsl/hello.vert";
+ import fragmentShader from "./glsl/hello.frag";
+ import { ShaderMaterial } from "three";
function App() {
return (
<div style={{ height: "100dvh", width: "100dvw" }}>
<Canvas shadows>
<ambientLight intensity={1}/>
<pointLight position={[3, 3, 3]}/>
<directionalLight position={[-2, 3, 5]}/>
- <mesh>
- <boxGeometry args={[1, 1, 1]}/>
- <meshStandardMaterial color="hotpink"/>
- </mesh>
+ <ShaderPlane/>
<OrbitControls/>
<GizmoHelper alignment="top-right" margin={[75, 75]}>
<GizmoViewport labelColor="white" axisHeadScale={1} />
</GizmoHelper>
</Canvas>
</div>
)
}
+ const ShaderPlane = () => {
+
+ const ref = React.useRef<ShaderMaterial>(null);
+ useFrame((_, delta) => {
+ if (ref.current) {
+ ref.current.uniforms.u_time.value += + delta;
+ }
+ });
+
+ return (
+ <mesh scale={3}>
+ <planeGeometry args={[1, 1, 1]}/>
+ <shaderMaterial
+ ref={ref}
+ vertexShader={vertexShader}
+ fragmentShader={fragmentShader}
+ uniforms={{
+ u_time: { value: 0.0 }
+ }}
+ />
+ </mesh>
+ )
+ };
export default App
[実行結果]
おわりに
以前書いたCRAを使ったShader環境構築は微妙なので、これからはこっちでやろうと思ってます。
PS: 本当にReactThreeFiberは楽しいです。