React と Three.js で、ポストプロセス(ポストエフェクト) と カスタムシェーダー を扱う方法をまとめてみました。
僕はシェーダーを扱っていましたが、ウェブで扱うのはほぼ初めてだったのでまとめてみました。
@react-three/postprocessing のビルトインエフェクトの使い方と、
postprocessing の Effect クラスを継承したカスタムシェーダー(ワールドノーマル可視化)の実装までを紹介します。
デモ用リポジトリとテスター用ページへのリンクも掲載しています。(お気軽に遊んでみてください)
この記事でわかること
- @react-three/postprocessing でビルトインエフェクトを使う方法
- カスタムシェーダーの 4 ステップ(Effect 継承 → React でラップ → GLSL 記述 → EffectComposer に追加)
- NormalPass と EffectComposerContext の役割
- ハマりやすいポイント(View Space → World Space の変換、React 19 で動かないビルトインなど)
リポジトリ・デモ
- リポジトリ: react-postprocess-tester
- テスター用ページ: testkun.net/react-postprocess-tester
開発環境・技術スタック
- macOS Sequoia 15.5 / VS Code / Node.js 20+ / npm
- React 19.1.1, Vite 7.1.2, Three.js 0.179.1
- @react-three/fiber, @react-three/drei, @react-three/postprocessing, postprocessing
- TailwindCSS 4, Leva(パラメータ調整 UI)
セットアップ
git clone https://github.com/testkun08080/react-postprocess-tester.git
cd react-postprocess-tester
npm install
npm run devブラウザで http://localhost:5173 にアクセスします。
Leva でエフェクトをリアルタイムに調整したり出来ると思います。

ビルトインエフェクトの使い方
@react-three/postprocessing には 20 種類以上のビルトインエフェクトがあります。
基本的な使い方
import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
function PostEffects() {
return (
<EffectComposer>
<Bloom intensity={1.0} luminanceThreshold={0.9} />
<Vignette offset={0.5} darkness={0.5} />
</EffectComposer>
);
}主なビルトイン: Bloom, DepthOfField, ChromaticAberration, Glitch, Vignette, SSAO / N8AO, ToneMapping などです。
Leva でパラメータをバインドすると調整しやすくなります。
カスタムシェーダーの作り方(ワールドノーマル可視化)
個人的にワールド変換されたノーマルマップをしようしたかったので、
postprocessing の Effect を継承し、法線バッファを読んでワールドノーマルを可視化するカスタムシェーダーを追加します。
1. Effect クラスを継承したクラスを作成
// SimpleCheckNormalEffect.jsx
import { Effect } from "postprocessing";
import { Uniform, Matrix4 } from "three";
import { EffectComposerContext } from "@react-three/postprocessing";
import { checkNormalShader } from "./shaders/index.js";
class SimpleCheckNormalEffectImpl extends Effect {
constructor({ normalBuffer, mode = 0, useWorldSpace = true }) {
super("SimpleCheckNormalEffect", checkNormalShader, {
uniforms: new Map([
["normalBuffer", new Uniform(normalBuffer)],
["uMode", new Uniform(mode)],
["uUseWorldSpace", new Uniform(useWorldSpace)],
["cameraMatrixWorld", new Uniform(new Matrix4())],
["viewMatrix", new Uniform(new Matrix4())],
// ...
]),
});
}
set mode(value) {
this.uniforms.get("uMode").value = value;
}
set useWorldSpace(value) {
this.uniforms.get("uUseWorldSpace").value = value;
}
// camera matrix の setter も同様に定義
}2. React コンポーネントでラップ
forwardRef と useContext(EffectComposerContext) で normalPass と camera を取得し、useMemo で Effect インスタンスを作成。
useEffect でカメラ行列や props を渡します。
export const SimpleCheckNormalEffect = forwardRef((props, ref) => {
const { normalPass, camera } = useContext(EffectComposerContext);
const effect = useMemo(
() =>
new SimpleCheckNormalEffectImpl({
normalBuffer: normalPass?.texture,
...props,
}),
[normalPass, props],
);
useEffect(() => {
if (camera) {
effect.cameraMatrixWorld = camera.matrixWorld;
effect.viewMatrix = camera.matrixWorldInverse;
effect.projectionMatrix = camera.projectionMatrix;
effect.inverseProjectionMatrix = camera.projectionMatrixInverse;
}
}, [effect, camera]);
return <primitive ref={ref} object={effect} dispose={null} />;
});3. GLSL で mainImage を記述
postprocessing では mainImage(inputColor, uv, outputColor) がピクセルごとに呼ばれます。
法線バッファを読んで View Space → World Space に変換し、法線を色で可視化します。
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
vec3 viewSpaceNormal = texture2D(normalBuffer, uv).xyz * 2.0 - 1.0;
vec3 normal = uUseWorldSpace ? viewToWorldNormal(viewSpaceNormal) : viewSpaceNormal;
vec3 normalColor = visualizeNormal(normal, uMode);
outputColor = vec4(normalColor, inputColor.a);
}4. EffectComposer に組み込む
<EffectComposer>
<NormalPass />
{worldNormalControls.enabled && (
<SimpleCheckNormalEffect
mode={worldNormalControls.mode}
useWorldSpace={worldNormalControls.useWorldSpace}
/>
)}
</EffectComposer>法線バッファを使う場合は NormalPass を忘れずに追加します。

カスタムエフェクト実装のポイント
- Effect は
new Effect(name, fragmentShader, { uniforms, blendFunction, attributes })で作成。 - mainImage のシグネチャは
(inputColor, uv, outputColor)。 - NormalPass を入れたうえで、EffectComposerContext の
normalPassをカスタムエフェクトに渡す。
シェーダー一覧(サンプル)
このプロジェクトでは次のようなビルトイン・カスタムを試せます。スライダーでの比較はテスター用ページで確認してください。以下は元記事(Zenn)と同じく、エフェクトごとに小分けした一覧です(画像はローカル src/assets/react-postshader/ を参照)。
SMAA
絶妙な違いですけど、やっぱ有無では違いますね。


Auto Focus
マニュアルでもマウスで試すことも可能です。

SSAO
パラメーターを更新してもリアルタイムに反映されないバグがあります。そして、激重。

n8ao (SSAO)
SSAO 使うなら、こちらを推奨します。使いやすいし、バグはないかなと思います。ソースレポ

Bloom(ブルーム)

Chromatic Aberration(色収差)

Wave Distortion(波状歪みエフェクト)

RGB Split(色収差)

Kaleidoscope(万華鏡エフェクト)
万華鏡っぽいやつです。

Fractal Noise
定番ノイズで歪みを出せるやつです。

Glitch
壊れたモニターでよく使われるやつです。

Pixelation(ドット化)
ドット風モザイクです。

Dot Screen
漫画・アメコミ風です。

Grid

Scanline

Outline
選択しているオブジェクトのみにアウトラインをつけたり、隠れていても見えるようにする UX/UI 用エフェクトです。(※内部では固定したオブジェクトを渡しています)

Edge Outline(エッジ検出エフェクト)
深度バッファと法線バッファを使用してエッジを検出し、アウトラインを描画します。トゥーンシェーディングやセル画風の表現に使えます。

Sepia

Brightness Contrast

Color Dot

Average Color

Color Shift

Tilt Shift

Tilt Shift 2

Water

View Depth Visualization(デプスバッファ可視化)
カメラからの距離(深度)を白黒で視覚化します。デバッグやアート表現に有用で、近いほど黒く、遠いほど白く表示されます。

Simple Check Normal(法線ベクトル可視化)
ノーマル可視化用のデバッグ用です。ビューノーマルと、ワールド空間用のノーマルを切り替えて見れます。

Noise
雰囲気を与えるのにノイズは便利です。

Vignette

Tonemap

LUT
アセットは引用元などから。地味に LUT テクスチャのインポートに手こずりました。

Ascii エフェクト
文字を使ってサイバーっぽくするやつです。

シーン設定について
背景の非表示やライトの色などの簡易設定ができます。

ハマったポイント
1. View Space → World Space の変換
カメラの viewMatrix を使って法線をワールド空間に変換します。
vec4 worldNormal = vec4(viewNormal, 1.0) * viewMatrix;2. React 19 で一部ビルトインが動作しない
Godrays, Lensflare, FXAA などは React 19 では正しく動かなかったため、今回のデモでは省いています。
まとめ
- @react-three/postprocessing と postprocessing を使うと、ビルトインで 20 種類以上のポストエフェクトを手軽に使え、Effect を継承すれば カスタム GLSL シェーダー も組み込めます。
- カスタムシェーダーは少し手間がかかりますが、このリポジトリのコードとテスター用ページをあわせて参考にして頂ければと思います。