初めまして!FIXER 新入社員の谷口です。 FIXER tech blog デビュー! ということで、本題を後回しにして自己紹介します。
プロフィール
- 生年月日:2000年3月11日 20歳
- 出身地:富山県
- 出身校:富山高専 電子情報工学科
- 趣味:ゲーム、アニメ、ゆるいプログラミング
- 好きな食べ物:甘いもの
- 苦手なもの:朝
- 言語:shaderlab(HLSL/Cg),GLSL,C#(Unity,WPF)
初の引っ越し&一人暮らしで東京生活に悪戦苦闘しています。料理経験は皆無ですが自炊をはじめてみました。最近はもやしと豆腐と冷凍うどんで命を繋いでいます。
クラウドはほぼ未経験です。よろしくお願いします。
趣味
ゲームはSplatoon2やチェス、ゴッドフィールドが好きです。
アニメはジャンル問わず何でも見ます。最近見たのは四畳半神話大系です。
趣味でゆるくプログラミングをしています。高専5年のときは主にシェーダーを書いていました。最近はSonic Pi,Blender,TouchDesignerなどを少しずつ触っています。
本題
ReactでGLSLを使った描画をします。
私はフロントエンドをほとんど触ったことがなく(授業でhtmlとjsを少しだけ使ったくらい。Reactは初)、知識も乏しいので間違っていたらご指摘ください。
環境
Reactの環境はcreate-react-appで構築したものを用います。
node.jsのバージョンは v12.16.1 です。
手順
GLSLを使うために以下のコマンドで REACT-VFX をインストールします。REACT-VFX は、多分テキストや画像にシェーダーを当てて見た目をいい感じにしてくれるものです。 REACT-VFX はプリセットのシェーダーを適応する機能とカスタムシェーダーを適応する機能があります。
npm i -S react-vfx
使いたい画像をpublicフォルダに投げます。今回使う画像の名前は”cctlogo.png”です。
srcフォルダ内のApp.jsを編集します。今回はREACT-VFX内蔵のプリセットシェーダーの"rgbshift"をテックブログの画像に適応させました。ついでに最近若者に人気上昇中の技術であるレイマーチングを用いて簡単にメンガーのスポンジを描画しました。
import React from 'react';
import './App.css';
import * as VFX from 'react-vfx';
import { VFXSpan } from 'react-vfx';
const glsl =`
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
const float sphereSize = 1.0;
const vec3 lightDir = vec3(-0.577, 0.577, 0.577);
float sdCross(vec3 p, float c) {
p = abs(p);
float dxy = max(p.x, p.y);
float dyz = max(p.y, p.z);
float dxz = max(p.x, p.z);
return min(dxy, min(dyz, dxz)) - c;
}
float sdBox(vec3 p, vec3 b) {
p = abs(p) - b;
return length(max(p, 0.0)) + min(max(p.x, max(p.y, p.z)), 0.0);
}
#define ITERATIONS 5
float deMengerSponge1(vec3 p, float scale, float width) {
float d = sdBox(p, vec3(1.0));
float s = 1.0;
for (int i = 0; i < ITERATIONS; i++) {
vec3 a = mod(p * s, 2.0) - 1.0;
s *= scale;
vec3 r = 1.0 - scale * abs(a);
float c = sdCross(r, width) / s;
d = max(d, c);
}
return d;
}
float distanceFunc(vec3 p){
return deMengerSponge1(p, 3.0, 1.0);
}
vec3 getNormal(vec3 p){
float d = 0.0001;
return normalize(vec3(
distanceFunc(p + vec3( d, 0.0, 0.0)) - distanceFunc(p + vec3( -d, 0.0, 0.0)),
distanceFunc(p + vec3(0.0, d, 0.0)) - distanceFunc(p + vec3(0.0, -d, 0.0)),
distanceFunc(p + vec3(0.0, 0.0, d)) - distanceFunc(p + vec3(0.0, 0.0, -d))
));
}
void main(void){
// fragment position
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
// camera
vec3 cPos = vec3(0.0, 0.0, 2.2);
vec3 cDir = vec3(0.0, 0.0, -1.0);
vec3 cUp = vec3(0.0, 1.0, 0.0);
vec3 cSide = cross(cDir, cUp);
float targetDepth = 1.0;
// ray
vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);
// marching loop
float distance = 0.0;
float rLen = 0.0;
vec3 rPos = cPos;
for(int i = 0; i < 16; i++){
distance = distanceFunc(rPos);
rLen += distance;
rPos = cPos + ray * rLen;
}
// hit check
if(abs(distance) < 0.001){
vec3 normal = getNormal(rPos);
float diff = clamp(dot(lightDir, normal), 0.1, 1.0);
gl_FragColor = vec4(vec3(diff), 1.0);
}else{
gl_FragColor = vec4(vec3(0.0), 1.0);
}
}`
class App extends React.Component{
render()
{
return (
<div className="vfx">
<VFX.VFXProvider>
<VFX.VFXImg className="tex" src="logo192.png" shader={glsl}/>
<br></br>
<VFX.VFXImg className="tex" src="cctlogo.png" alt="image" shader="rgbShift"/>
</VFX.VFXProvider>
</div>
);
}
}
export default App;
結果
こうなります。
ちょっとだけ解説
REACT-VFXは全体をCanvasで囲っているらしいです。詳しいことは知らないです。<VFX.VFXProvider>の部分のことです。
<VRF.VFXImg> で画像にシェーダーを適用します。カスタムシェーダーを使う場合はシェーダー指定部分を{}で囲います。
class App extends React.Component{
render()
{
return (
<div className="vfx">
<VFX.VFXProvider>
<VFX.VFXImg className="tex" src="logo192.png" shader={glsl}/>
<br></br>
<VFX.VFXImg className="tex" src="cctlogo.png" alt="image" shader="rgbShift"/>
</VFX.VFXProvider>
</div>
);
}
}
おまけ
GPUアーキテクチャ上if文は効率が悪いらしいです。簡単に言うとGLSLのfor文の中でif文を多く回すとGPUが暖房になります。
おわりに
ReactでGLSLを使う方法の1つを紹介しました。業務で使うことはほぼないと思いますがシェーダーは書いていて楽しいですね。
次にブログを書くことがあれば、Unityのシェーダーについて書こうと思います。