25
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React Three Fiber】Three.jsでShaderを使う準備をする

Last updated at Posted at 2021-12-12

概要

Three.js(React Three Fiber)でShader使い始めるための基本的な情報をまとめました。
Shaderを使いこなせれば、もう一段階上の表現力を手に入れることができます。

output(video-cutter-js.com).gif

コード
.tsx
import React, { Suspense, VFC } from 'react';
import * as THREE from 'three';
import { Environment, OrbitControls, Stats } from '@react-three/drei';
import { Canvas, useFrame } from '@react-three/fiber';

export const ShaderMesh: VFC = () => {
	return (
		<Canvas
			camera={{
				position: [15, 15, 15],
				fov: 50,
				aspect: window.innerWidth / window.innerHeight,
				near: 0.1,
				far: 2000
			}}
			dpr={window.devicePixelRatio}
			shadows>
			{/* canvas color */}
			<color attach="background" args={['#1e1e1e']} />
			{/* fps */}
			<Stats />
			{/* camera controller */}
			<OrbitControls attach="orbitControls" />
			{/* lights */}
			<ambientLight intensity={0.1} />
			<directionalLight position={[20, 20, 20]} castShadow />
			{/* objects */}
			<Suspense fallback={null}>
				<Environment preset="city" />
				<Objects />
			</Suspense>
			{/* helper */}
			<axesHelper />
		</Canvas>
	)
}

const Objects: VFC = () => {
	const material = new THREE.MeshStandardMaterial({ color: '#22a7f2', metalness: 1, roughness: 0.2, wireframe: false })
	material.onBeforeCompile = shader => {
		shader.uniforms.u_time = { value: 0 }
		shader.uniforms.u_radius = { value: 10 }
		// vertex
		shader.vertexShader = vertexShaderDefine + shader.vertexShader
		shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>', beginnormal_vertex)
		shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', begin_vertex)
		// fragment
		shader.fragmentShader = fragmentShaderDefine + shader.fragmentShader
		shader.fragmentShader = shader.fragmentShader.replace('#include <color_fragment>', color_fragment)

		material.userData.shader = shader
		// debug
		console.log('vertexShader', shader.vertexShader)
		console.log('fragmentShader', shader.fragmentShader)
	}

	const geometry = new THREE.BoxBufferGeometry(10, 10, 10, 12, 12, 12)

	useFrame(({ clock }) => {
		const shader = material.userData.shader
		if (shader) {
			;(shader as THREE.Shader).uniforms.u_time.value = clock.getElapsedTime()
		}
	})

	return <mesh geometry={geometry} material={material} receiveShadow />
}

const vertexShaderDefine = `
uniform float u_time;
uniform float u_radius;
varying vec2 v_uv;
`

const beginnormal_vertex = `
float delta = (sin(u_time) + 1.0) / 2.0;
vec3 objectNormal = delta * normal + (1.0 - delta) * normalize(position);
`

const begin_vertex = `
v_uv = uv;
vec3 v = normalize(position) * u_radius;
vec3 transformed = delta * position + (1.0 - delta) * v;
`

const fragmentShaderDefine = `
uniform float u_time;
varying vec2 v_uv;
`

const color_fragment = `
float rnd = fract(sin(dot(v_uv.xy, vec2(12.9898, 78.233)) + u_time) * 43758.5453123);
float r = (sin(u_time) + 1.0) / 2.0;
float g = (sin(PI * 2.0 / 3.0 + u_time) + 1.0) / 2.0;
float b = (sin(PI * 4.0 / 3.0 + u_time) + 1.0) / 2.0;
vec3 color = vec3(r, g, b) * rnd;
diffuseColor = vec4(color, 1.0);
`

私もまだ学習を始めたばかりなので、認識が正しくない情報があるかもしれませんがご了承ください。

GLSLをざっと知る

GLSLとはOpenGL Shading Languageの略で、WwbGLでShaderを扱うためのC言語ライクなプログラミング言語です。
この記事では、GLSLの基本的な記述方法は記載しませんが、以下のサイトを軽く読んでいただければ概ね理解できると思います。

wgld.org

まず概要を掴むために、以下のサイトを読むことをおすすめします。
ただ、ものすごく長く(情報量が多く)、Three.jsを対象としているわけでもないので、まずは頂点バッファの基礎まで読むといいと思います。

The Book of Shaders

多くの記事でGLSLの学習サイトとして取り上げられています。
Shaderを実際に書きながら学習することができますが、コーディングに関しては説明が飛ばし飛ばしなので、こちらも最初のうちはざっと確認する程度でいいと思います。

[Three.js] Three.jsでシェーダー入門

GLSLの基本的な記述方法がまとめられている記事です。一読しましょう!

ドキュメントで組み込み変数・関数を確認する

GLSL (OpenGL ES2.0)リファレンス

GLSLで使用できるデータ型スウィズル演算子組み込み変数・関数を確認することができます。
関数の説明には使用例もあるので、かなり親切なドキュメントです。

Three.js - WebGLProgram

Three.jsでGLSLを扱う場合、GLSL自体の組み込み変数にプラスして、Three.js上でさらに使用できる組み込み変数があります。

Three.js - ShaderMaterial

MeshにShaderを割り当てる方法を確認することができます。

Three.js - EffectComposer

こちらは、Post-Processingで画面全体にエフェクトをかける場合のドキュメントです。

環境を整える

Three.jsでGLSLを使用する場合、コーディングした内容はString型(文字列)として渡します。
でも、コーディングを文字列で行うのは大変です。VSCodeを使用している場合、以下の拡張機能を入れることで少しだけコーディングがしやすくなります。

WebGL GLSL Editor

GLSLを記述するファイル名を.glsl(.vert .vs .frag .fs)にすることで、組み込み関数や定義した変数のスニペット(予測変換)が表示されるようになります。.glslなどのファイルを認識してくれるので、コードが色付けされます。
また、コードフォーマット機能もあります。

glsl-canvas

GLSLを書いただけでその結果を確認することができる拡張機能です。
Three.jsの組み込み関数は認識されませんが、FragmentShaderを記述する際に使用すると便利です。
こちらにもコードフォーマット機能があり、個人的にはWebGL GLSL Editorよりフォーマットルールが好きです。

TypeScript環境で.glslファイルを扱う

TypeScriptを使用している場合、そのままでは.glslファイルを文字列ファイルとして扱ってくれません。
型定義をして、文字列ファイルとして認識させる必要があります。

型定義ファイルの導入方法については、以下を参照してください。

便利なツール・サイトを使う

コーディング時に活用できそうなサイトです。

graphtoy

GLSLで使用できる組み込み関数が揃った関数電卓のサイトです。ただし、GLSLでは使用できない関数もあります。
偉いのは、時間変数(t)が使用できるところです。

The Book of Shaders Editor

FragmentShaderを記述して確認できるサイトです。glsl-canvasのWebサイト版みたいな感じです。

vertexshaderart.com

多くのShader作品がアップロードされており、そのコードを編集することもできます。

既存のMaterialを継承したShaderを実装する

MaterialにShaderを適用させる場合、通常ShaderMaterialを使用しますが、ライトや影、Fogの影響を自前で実装するのは面倒です。
既存のStandardMaterialなどのMaterialの特性は残しつつ、Shaderでカスタマイズしたい特性だけを上書きすることもできます。

ただしShaderの上書きは、MaterialのパラメーターとShaderの理解が必要です。

実装例

以下の質問の回答として、回答者が既存のMaterialを上書きするサンプルコードを提示しています。
Custom GLSL bug, ‘vIndirectFront’ : undeclared identifier
> コード

また、公式サンプルにも実装例があります。
webgl materials modified
> コード

サンプルコードでは、[...].join('\n') で改行をしていますが、バッククォートを使用することで、改行コードなしで簡潔に記述できます。

.ts
shader.vertexShader = shader.vertexShader.replace(
	'#include <begin_vertex>',
	[
		`float theta = sin( time + position.y ) / ${ amount.toFixed( 1 ) };`,
		'float c = cos( theta );',
		'float s = sin( theta );',
		'mat3 m = mat3( c, 0, s, 0, 1, 0, -s, 0, c );',
		'vec3 transformed = vec3( position ) * m;',
		'vNormal = vNormal * m;'
	].join( '\n' )
);
.ts
shader.vertexShader = shader.vertexShader.replace(
	'#include <begin_vertex>',
	`float theta = sin( time + position.y ) / ${ amount.toFixed( 1 ) };
	float c = cos( theta );
	float s = sin( theta );
	mat3 m = mat3( c, 0, s, 0, 1, 0, -s, 0, c );
	vec3 transformed = vec3( position ) * m;
	vNormal = vNormal * m;`
);

組み込みShader

Shader内で#include <color_fragment>のような形式で定義されているcolor_fragmentは、Three.jsに組み込まれているShaderです。これらShaderのコードは以下で確認できます。
Three.js - ShaderChunk
GitHub - ShaderChunk

参考

three.jsのマテリアルを継承してシェーダーを追記する
three.jsのデフォルトのマテリアルを拡張する

ShaderChunkを定義する

【既存のMaterialを継承したShader】では、Three.jsに組み込まれているShaderChunkを紹介しましたが、自分で定義したShaderを組み込むこともできます。

以下の例では、customFunctionで定義したrandom関数を、Fragment Shader内で読み込んで利用している例です。
customFunctionの名前はなんでも大丈夫です。(組み込みShaderと競合する名前はダメかもしれないです)

MyShaderChunk.ts
import * as THREE from 'three';

THREE.ShaderChunk.customFunction = `
float random (vec2 st, float seed) {
	return fract(sin(dot(st.xy, vec2(12.9898, 78.233)) + seed) * 43758.5453123);
}
`
fragment.glsl
#include <customFunction>

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main() {
	vec2 st = gl_FragCoord.xy / u_resolution.xy;
	float rnd = random(st, u_time);
	gl_FragColor = vec4(vec3(rnd), 1.0);
}

その他の参考サイト

まとめ

Shaderは、トライ&エラーで試しながら少しずつ表現の幅を広げていく技術だと実感しました。
焦らずじっくり理解していくことにします。

25
13
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
25
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?