シェーダ・ポストプロセス
標準マテリアルでは届かない表現に行くための脱出口。ShaderMaterial で自作シェーダ、 EffectComposer でレンダリング後の画面処理(Bloom 等)。
シェーダの基本
シェーダは頂点シェーダ(vertex)とフラグメントシェーダ(fragment)の2段。
- vertex shader: 各頂点の位置を
gl_Positionに書き出す。属性(attribute)を varying として fragment へ渡す。 - 頂点が3つ集まると三角形になり、ラスタライズされる(三角形の中の各ピクセルへ補間)。
- fragment shader: 各ピクセル の色を
gl_FragColor(またはout vec4 outColor)に書く。
3 種類の変数
- uniform — JS から渡す。全頂点・全ピクセルで同じ値。
- attribute — 頂点ごとの値(position, normal, uv)。vertex shader で読む。
- varying — vertex から fragment へ渡す。三角形内で補間される。
ShaderMaterial
Three.js が共通の uniforms(カメラ行列、モデル行列、ライト等)を自動で渡してくれる。
RawShaderMaterial はこれを渡さない素のバージョン。
const mat = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color("#7dd3fc") },
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
uniform float uTime;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
float wave = sin(vUv.x * 10.0 + uTime) * 0.5 + 0.5;
gl_FragColor = vec4(uColor * wave, 1.0);
}
`,
})
renderer.setAnimationLoop(() => {
mat.uniforms.uTime.value += 0.016
renderer.render(scene, camera)
})
Three.js が自動で渡す uniforms の主なもの:
modelMatrix/viewMatrix/projectionMatrixmodelViewMatrix(model × view)normalMatrixcameraPosition
頂点 attribute: position / normal / uv など(自動)。
典型パターン: 画面いっぱいの shader
フルスクリーンでシェーダ遊びをするなら、画面いっぱいの板(PlaneGeometry)に
Orthographic カメラで ShaderMaterial を貼るのが定番。
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
const geom = new THREE.PlaneGeometry(2, 2)
const quad = new THREE.Mesh(geom, mat)
scene.add(quad)
onBeforeCompile — 既存マテリアルに「足す」
MeshStandardMaterial の挙動を全部書き直したくないけど、少しだけシェーダを差し込みたい時。
const mat = new THREE.MeshStandardMaterial({ color: "white" })
mat.userData.uTime = { value: 0 }
mat.onBeforeCompile = (shader) => {
shader.uniforms.uTime = mat.userData.uTime
// 既存コードに前付け
shader.vertexShader = shader.vertexShader.replace(
"#include <common>",
`#include <common>
uniform float uTime;`
)
// 頂点を波打たせる
shader.vertexShader = shader.vertexShader.replace(
"#include <begin_vertex>",
`#include <begin_vertex>
transformed.y += sin(transformed.x * 4.0 + uTime) * 0.1;`
)
}
renderer.setAnimationLoop(() => {
mat.userData.uTime.value += 0.016
renderer.render(scene, camera)
})
chunk 名(#include <...>)は Three.js のソース(renderers/shaders/ShaderChunk/)を参考にする。
よく使うのは:
common— 共通変数の宣言begin_vertex—transformed(頂点座標)の初期化直後project_vertex— projection 適用直後map_fragment— base color 適用
テクスチャを使う
const mat = new THREE.ShaderMaterial({
uniforms: {
uTex: { value: texture },
},
vertexShader: `varying vec2 vUv; void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`,
fragmentShader: `uniform sampler2D uTex; varying vec2 vUv;
void main() {
gl_FragColor = texture2D(uTex, vUv);
}`,
})
頂点カラーや属性を渡す
const colors = new Float32Array(N * 3)
geometry.setAttribute("aColor", new THREE.BufferAttribute(colors, 3))
// shader で
// attribute vec3 aColor;
// varying vec3 vColor;
// void main() { vColor = aColor; ... }
ポストプロセス(EffectComposer)
画面全体に対するエフェクト(Bloom、DoF、SSAO、Outline、FXAA 等)。
EffectComposer がレンダラの代わりに走り、パスの連鎖を実行する。
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"
import { RenderPass } from "three/addons/postprocessing/RenderPass.js"
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"
import { OutputPass } from "three/addons/postprocessing/OutputPass.js"
const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(new UnrealBloomPass(
new THREE.Vector2(innerWidth, innerHeight),
0.6, // strength
0.4, // radius
0.85, // threshold
))
composer.addPass(new OutputPass()) // tonemap + sRGB を最終に
renderer.setAnimationLoop(() => {
composer.render() // ← renderer.render の代わり
})
window.addEventListener("resize", () => {
composer.setSize(innerWidth, innerHeight)
})
OutputPass
ポストプロセスを使う時、最終 pass で OutputPass を入れないと
tonemap や sRGB 変換が漏れて色がおかしくなる。
主な addon パス
| パス | 用途 |
|---|---|
RenderPass | シーンを描画する。最初に必須 |
UnrealBloomPass | ブルーム(光が滲む) |
OutlinePass | 選択中のオブジェクトを縁取り |
SSAOPass / SSAARenderPass | 環境遮蔽 / アンチエイリアス |
BokehPass | 被写界深度(DoF) |
GlitchPass | グリッチ演出 |
FilmPass | フィルム調(ノイズ・スキャンライン) |
ShaderPass(shader) | 自作シェーダを 1 パスとして差し込む |
OutputPass | 最終のトーンマップ・sRGB変換(必須) |
自作 ShaderPass
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"
const myShader = {
uniforms: {
tDiffuse: { value: null }, // 前パスの出力が自動で入る
uStrength: { value: 0.5 },
},
vertexShader: `varying vec2 vUv;
void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
fragmentShader: `uniform sampler2D tDiffuse;
uniform float uStrength;
varying vec2 vUv;
void main() {
vec4 c = texture2D(tDiffuse, vUv);
// モノクロ寄せ
float l = dot(c.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(mix(c.rgb, vec3(l), uStrength), c.a);
}`,
}
composer.addPass(new ShaderPass(myShader))
postprocessing パッケージ(高機能)
addons の EffectComposer より高機能で速い実装が
postprocessing パッケージにある。
Effect 単位で合成するため、複数エフェクトを 1 パスにマージできて速い。
R3F なら @react-three/postprocessing。
レンダーターゲット(FBO)
画面以外の場所(テクスチャ)にレンダリングして、それを別 mesh のテクスチャに使う。 ミニマップ・反射・カスタム post effect等に必須。
const target = new THREE.WebGLRenderTarget(512, 512, {
colorSpace: THREE.SRGBColorSpace,
})
renderer.setRenderTarget(target)
renderer.render(sceneA, cameraA)
renderer.setRenderTarget(null) // 元に戻す
// target.texture を別マテリアルの map に
displayMaterial.map = target.texture
シェーダ学習リソース
- The Book of Shaders — フラグメントシェーダの教科書(無料)
- Shadertoy — シェーダの作品集と play ground
- Three.js Journey — Three.js + シェーダの体系的なコース(有料)
- Three.js 公式の
examples/jsm/shaders/をブラウズ
最近の Three.js は WebGPURenderer と TSL(Three Shading Language)を持っている。
GLSL ではなく JS で書ける式ベースの shader DSL で、WebGL/WebGPU 両対応。
まだ実験的な部分があるので、本番採用は機能をピンポイントで確認してから。