はじめに
もうCreateReactAppは非推奨なため、以下のVite環境で構築する方がおすすめです。
最近Shaderにさわり始めたのですが、
glslファイルを直接文字列といれるのは面倒くさく感じたので、
GLSLManagerというクラスをuseContextで使えるようにしました。
ReactThreeFiber(R3F)で記述
ディレクトリ構成
─ public
└ glsls
├ testfrag,glsl
└ testvertex.glsl
- src
├ App.tsx
└ components
├ TestShaders.tsx
└ GLSLManager.ts
App.tsxの記述
import { Canvas } from "@react-three/fiber";
import { Environment } from "@react-three/drei";
import { TestShaderComponent } from "./components/TestShaders";
return (
<div style={{ height: "90vh" }}>
<Canvas shadows>
<TestShaderComponent/>
<Environment preset="dawn" background blur={0.7} />
<directionalLight position={[100, 100, 100]} intensity={0.8} castShadow />
</Canvas>
</div>
)
GLSLManager.tsの記述
import { createContext } from "react";
export class GLSLManager {
Ready : boolean = false;
dirName : string = "./glsls/";
fileList : string[] = [];
GLSLList : {[key: string]: string} = {};
constructor(){}
/**
* GLSLファイルをロードする
* @param fileList
* @returns
*/
async load(fileList: string[]){
this.Ready = false;
this.fileList = fileList;
await (async () => {
await Promise.all(fileList.map(async file => {
await fetch(this.dirName+file)
.then(response => response.text())
.then(text => {
this.GLSLList[file] = text;
});
}))
})()
this.Ready = true;
}
/**
* 特定のファイルのGLSLファイルの中身を取得
* @param fileName
* @returns
*/
getGLSLText(fileName: string): string {
return this.GLSLList[fileName]? this.GLSLList[fileName]: null;
}
/**
* 新しいGLSLファイルをセットする
*/
async setGLSLFile(fileName: string){
fetch(this.dirName+fileName)
.then(response => response.text())
.then(text => {
this.GLSLList[fileName] = text;
});
}
}
export const GLSLManagerContext = createContext<GLSLManager>(null);
TestShaders.tsxの記述
import { OrbitControls } from "@react-three/drei";
import { useEffect, useState, useContext } from "react";
import { GLSLManager, GLSLManagerContext } from "./GLSLManager";
const Cube = () => {
const glsl = useContext(GLSLManagerContext);
return (
<mesh castShadow>
<boxGeometry args={[1, 1, 1]} />
<shaderMaterial
fragmentShader={glsl.getGLSLText("testfrag.glsl")}
vertexShader={glsl.getGLSLText("testvertex.glsl")}
/>
</mesh>
);
};
export const TestShaderComponent = () => {
// 読み込むGLSLファイルリスト
const fileList: string[] = [
"testfrag.glsl",
"testvertex.glsl"
];
const [glsl, setGLSL] = useState<GLSLManager>(null);
useEffect(() => {
(async () => {
const _glsl = new GLSLManager();
await _glsl.load(fileList);
setGLSL(_glsl);
})()
}, []);
return (
<>
{glsl &&
<GLSLManagerContext.Provider value={glsl}>
<Cube/>
</GLSLManagerContext.Provider>
}
<OrbitControls/>
</>
)
}
GLSLの中身
testvertex.glsl
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.y += sin(modelPosition.x * 4.0) * 0.2;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
}
testfrag.glsl
varying vec2 vUv;
vec3 colorA = vec3(0.912,0.191,0.652);
vec3 colorB = vec3(1.000,0.777,0.052);
void main() {
vec2 normalizedPixel = gl_FragCoord.xy/600.0;
vec3 color = mix(colorA, colorB, normalizedPixel.x);
gl_FragColor = vec4(color,1.0);
}
実行結果
おわりに
webpackでGLSLLoaderを使う方法もあるようですが、
webpackを使うほどじゃないかなと感じたので、useContextで済ませました。
今後、Yuri Artiukhさんの動画をみつつ、
ごりごり勉強していきたいなと思います。
おわり。