やり方+結論だけ知りたい人向けの本記事のまとめ。
「8th Wallでポストプロセス的にルックを調整したい」場合
⇒「カメラ」に「shader-postprocessing.js」などをコンポーネントとして追加する
ということで以下経緯や詳細。
経緯
Three.jsやりたい。
でも。
Blenderもまだまだ把握できてない機能が山ほどあるし手を付けられていないアドオンも積みあがってるし、UE5もいまだに初心者から抜け出し切れていないし、Unityも業務で使うから検証しないといけないこといっぱいあるし、AIもClaude CodeがーGemini-CLIと仲良くなりたいーDevinもCursorも、あ、Replitも課金してるから使い倒さないといけないし動画生成も要把握でGoogle ColabもPro課金しちゃったし・・・
時間無い………!!!!!
もう学習に割く時間が、ない.....!!!
(注記:AIの件がこうなる前から3DCG従事者は割と同じ状況だった)
ああでもThree.js、使えたら,,,というかシェーダーも理解できてた方が、、、どうやって学習を進めれば、、、と悩んでいたのですが、ふと。
「あれ?8th WallのコンポーネントはJavaScript系で実装していく・・・ということはthree.jsもいけるよね?これでシェーダーも作れるよね?というか8th Wallでルックの調整(toon調で表現したり)ってどうやるんだ?」
⇒「助けてClaude先生!!」
調べてわかったこと
……ということでClaudeにいろいろ教えてもらって、カスタムシェーダーを作ってもらいました。
また、8th Wallで作品作り・開発していれば、自然とthree.jsやシェーダーのスクリプトにも触れていくことになるので、学習時間の確保はこれでできるかな、となりました。
まだ検証しきれていませんが、8th Wallでは
Unityでよくやるようなオブジェクトのマテリアルごとのシェーダー適用
UE5のポストプロセスやBlenderでのコンポジットのような、シーン全体にシェーダーを適用
の両方ができるようです。
今回はその「後者」のポストプロセス式のシェーダーを利用するケースの話です。
筆者はこの辺の専門知識は独学・ゆっくり学習を進めています。
専門用語が怪しい場合はご容赦を+置換してお読みください🙏
作ったシェーダーサンプル
もとい、Claudeに作ってもらった「toon-postprocess.js」はこちら。
検証用に出力してもらった最低限のものになっていると思います。
このまま流用していただいて問題ありません。
何度か「エラー出てるよー、直してたもれ!」としているので修正が重なったものです。「UnitychanToonShaderみたいの作って!」とプロンプトしました。
また、こちらのGithubにも(自分のメモ用として)公開しておきます。
(Githubのほうが最新版になっているかもしれない)
// toon-postprocess.js の修正版(テクスチャ対応)
import * as ecs from '@8thwall/ecs'
let isInitialized = false
ecs.registerComponent({
name: 'toon-postprocess',
schema: {
intensity: ecs.f32,
steps: ecs.f32
},
schemaDefaults: {
intensity: 0.8,
steps: 4.0
},
add: (world, component) => {
console.log('Toon postprocess component added!')
setTimeout(() => {
if (!isInitialized) {
setupToonEffectWithTextures(world, component)
isInitialized = true
}
}, 2000)
}
})
function setupToonEffectWithTextures(world, component) {
console.log('Setting up toon effect with texture support...')
const intensity = component.schema.intensity
const steps = component.schema.steps
const scene = world.scene
if (scene) {
scene.traverse((child) => {
if (child.isMesh && child.material && child.name) {
if (child.name.includes('Camera') ||
child.name.includes('Light') ||
child.name.includes('Helper') ||
child.name.includes('Gizmo')) {
return
}
console.log('Applying texture-aware toon shader to:', child.name)
try {
const originalMaterial = child.material
// テクスチャ対応のカスタムシェーダー
const toonShader = new THREE.ShaderMaterial({
uniforms: {
baseColor: { value: originalMaterial.color || new THREE.Color(0x888888) },
colorMap: { value: originalMaterial.map || null },
lightDirection: { value: new THREE.Vector3(1, 1, 1).normalize() },
intensity: { value: intensity },
steps: { value: steps },
hasTexture: { value: originalMaterial.map ? 1.0 : 0.0 }
},
vertexShader: `
varying vec3 vNormal;
varying vec2 vUv;
void main() {
vNormal = normalize(normalMatrix * normal);
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 baseColor;
uniform sampler2D colorMap;
uniform vec3 lightDirection;
uniform float intensity;
uniform float steps;
uniform float hasTexture;
varying vec3 vNormal;
varying vec2 vUv;
void main() {
// テクスチャ色を取得
vec3 textureColor = baseColor;
if (hasTexture > 0.5) {
vec4 texColor = texture2D(colorMap, vUv);
textureColor = texColor.rgb * baseColor;
}
// ライティング計算
float lightIntensity = dot(vNormal, lightDirection);
lightIntensity = (lightIntensity + 1.0) * 0.5; // -1〜1 を 0〜1 に変換
// ステップ化
float stepSize = 1.0 / steps;
float steppedIntensity = ceil(lightIntensity / stepSize) * stepSize;
// intensityで効果の強さを調整
float finalIntensity = mix(lightIntensity, steppedIntensity, intensity);
finalIntensity = max(finalIntensity, 0.3); // 最低限の明るさ
// 最終色 = テクスチャ色 × ライティング
vec3 finalColor = textureColor * finalIntensity;
gl_FragColor = vec4(finalColor, 1.0);
}
`,
transparent: originalMaterial.transparent,
side: originalMaterial.side || THREE.FrontSide
})
child.material = toonShader
console.log('Applied texture-aware toon shader to:', child.name)
} catch (error) {
console.error('Failed to apply shader to:', child.name, error)
}
}
})
console.log('Texture-aware toon effect setup completed!')
}
}
8th Wallにこのスクリプトを適用する
このスクリプトを以下のいずれかの方法で8th Wallのプロジェクトにアップロードします。
上述の「toon-postprocess.js」をローカルに保存して、プロジェクトで「アップロード」
プロジェクト内で新規にコンポーネント・ファイルを作成し、上述のスクリプトをコピペ(やり方は過去記事を参照してください)
続いて、「カメラ」にこのコンポーネントを適用します
こちらも過去記事の「ボタンにコンポーネントを追加」のやり方と同じです。
すると、「カメラ」選択時にインスペクターに以下の表示が現れます。
設定可能パラメータ
現在(この記事公開時)、調整可能なパラメータは以下の2点。
スクリプトを出力したCluadeによる説明。
▼試した感じ。
中央の青いキャラクターはDiffuse(albedo、ベースカラー)のみなので綺麗に出ています。
(後ろに置いている板ポリの透過ができてない+PBRのテクスチャも出てませんね…こうやってシェーダーの理解が深まっていくのか…と言う感覚)





▼Claudeが勉強用にまとめてくれたメモ。
ということで。
8th Wallであれば、
作品展示
Vibe Codingで開発
WebGL/three.js/シェーダー周りの学習
AR開発
などいろいろできて「一石2+n鳥」行けそうだな、と思っています。
おまけ:今回の配布GLB
以下はサブスクしてくださっている向けのコンテンツです。
今回は上の図に出てきているこちらのローテーブル(コーヒーテーブル)。
Boothでも販売中です。
Keep reading with a 7-day free trial
Subscribe to toya desk to keep reading this post and get 7 days of free access to the full post archives.