TSL conversion
GLSLシェーダーを使用したThreejs Materialを、TSLノードマテリアルに変換する必要があるときに使用できます。
description の原文を見る
Use it when you need to convert a Threejs Material using GLSL shaders to TSL node materials
SKILL.md 本文
My Skill
あなたは世界最高の TSL 開発者です
TSL (Three.js Shading Language) ルール for AI
常にドキュメントを確認してください! @.cursor/skills/tsl/references/TSL-DOC.md と @.cursor/skills/tsl/references/TSL-WIKI.md
一度止まってください。このセクションを最初にお読みください。これらはエラーや警告を引き起こします。
| ❌ 使用しないでください | ✅ 代わりに使用してください |
|---|---|
timerGlobal | time |
timerLocal | time |
timerDelta | deltaTime |
import from 'three/nodes' | import from 'three/tsl' |
import * as THREE from 'three' | import * as THREE from 'three/webgpu' |
oscSine(timerGlobal) | oscSine(time) または oscSine() |
oscSquare(timerGlobal) | oscSquare(time) または oscSquare() |
oscTriangle(timerGlobal) | oscTriangle(time) または oscTriangle() |
oscSawtooth(timerGlobal) | oscSawtooth(time) または oscSawtooth() |
CRITICAL: TSL とは何か
TSL は、シェーダー ノード グラフを構築する JavaScript です。コードは 2 つの時刻に実行されます:
-
ビルド時:JavaScript が実行され、ノード グラフが構築されます
-
実行時:コンパイルされた WGSL/GLSL が GPU 上で実行されます
// BUILD TIME: JavaScript 条件付き(シェーダーコンパイル時に 1 回実行) if (material.transparent) { return transparent_shader; }
// RUN TIME: TSL 条件付き(GPU 上のすべてのピクセル/頂点で実行) If(value.greaterThan(0.5), () => { result.assign(1.0); });
TSL 変換
-
今のところ、シャドウ キャスティングに関連するすべてのコメントを付けてください
-
変換後に
npm run devを実行して、JS エラーをチェックしてください。 -
このエラーを避けるために:「THREE.TSL: NodeError: THREE.TSL:
texture( value )関数は THREE.Texture() の有効なインスタンスを期待しています。」テクスチャを uniform() で渡してください。
インポート
NPM (推奨)
import * as THREE from 'three/webgpu';
import { Fn, vec3, float, uniform, /* ... */ } from 'three/tsl';
間違ったインポート パターン
// 間違い:古いパス
import { vec3 } from 'three/nodes';
// 正しい:
import { vec3 } from 'three/tsl';
// 間違い:WebGL レンダラーで TSL を使用
import * as THREE from 'three';
// 正しい:WebGPU レンダラー
import * as THREE from 'three/webgpu';
レンダラーの初期化
CRITICAL:最初のレンダリングまたはコンピュート前に常に renderer.init() を待つ必要があります。
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// レンダリング前に必須
await renderer.init();
// 安全にレンダリング/コンピュート できるようになりました
renderer.render(scene, camera);
型コンストラクタ
| コンストラクタ | 入力 | 出力 |
|---|---|---|
float(x) | number、node | float |
int(x) | number、node | int |
uint(x) | number、node | uint |
bool(x) | boolean、node | bool |
vec2(x,y) | numbers、nodes、Vector2 | vec2 |
vec3(x,y,z) | numbers、nodes、Vector3、Color | vec3 |
vec4(x,y,z,w) | numbers、nodes、Vector4 | vec4 |
color(hex) | hex number | vec3 |
color(r,g,b) | numbers 0-1 | vec3 |
ivec2/3/4 | integers | signed int vector |
uvec2/3/4 | integers | unsigned int vector |
mat2/3/4 | numbers、Matrix | matrix |
型変換
node.toFloat() node.toInt() node.toUint() node.toBool()
node.toVec2() node.toVec3() node.toVec4() node.toColor()
演算子
算術(メソッド チェーン)
a.add(b) // a + b(複数対応:a.add(b, c, d))
a.sub(b) // a - b
a.mul(b) // a * b
a.div(b) // a / b
a.mod(b) // a % b
a.negate() // -a
割り当て(変更可能な変数の場合)
v.assign(x) // v = x
v.addAssign(x) // v += x
v.subAssign(x) // v -= x
v.mulAssign(x) // v *= x
v.divAssign(x) // v /= x
比較(bool ノードを返す)
a.equal(b) // a == b
a.notEqual(b) // a != b
a.lessThan(b) // a < b
a.greaterThan(b) // a > b
a.lessThanEqual(b) // a <= b
a.greaterThanEqual(b)// a >= b
論理
a.and(b) a.or(b) a.not() a.xor(b)
ビット単位
a.bitAnd(b) a.bitOr(b) a.bitXor(b) a.bitNot()
a.shiftLeft(n) a.shiftRight(n)
スウィズル
v.x v.y v.z v.w // 単一コンポーネント
v.xy v.xyz v.xyzw // 複数コンポーネント
v.zyx v.bgr // 並び替え
v.xxx // 複製
// エイリアス:xyzw = rgba = stpq
変数
ルール:TSL ノードはデフォルトで不変です
// 間違い:不変ノードを変更できません
const pos = positionLocal;
pos.y = pos.y.add(1); // エラー
// 正しい:変更可能な変数に .toVar() を使用
const pos = positionLocal.toVar();
pos.y.assign(pos.y.add(1)); // OK
変数型
const v = expr.toVar(); // 変更可能な変数
const v = expr.toVar('name'); // 名前付き変更可能変数
const c = expr.toConst(); // インライン定数
const p = property('float'); // 初期化されていないプロパティ
ユニフォーム
// 作成
const u = uniform(initialValue);
const u = uniform(new THREE.Color(0xff0000));
const u = uniform(new THREE.Vector3(1, 2, 3));
const u = uniform(0.5);
// JS から更新
u.value = newValue;
// 自動更新コールバック
u.onFrameUpdate(() => value); // フレームごとに 1 回
u.onRenderUpdate(({ camera }) => value); // レンダリングごとに 1 回
u.onObjectUpdate(({ object }) => object.position.y); // オブジェクトごと
関数
Fn() 構文
// 配列パラメータ
const myFn = Fn(([a, b, c]) => { return a.add(b).mul(c); });
// オブジェクト パラメータ
const myFn = Fn(({ color = vec3(1), intensity = 1.0 }) => {
return color.mul(intensity);
});
// デフォルト値付き
const myFn = Fn(([t = time]) => { return t.sin(); });
// ビルド コンテキストにアクセス(第 2 パラメータまたは入力がない場合は最初)
const myFn = Fn(([input], { material, geometry, object, camera }) => {
// JS 条件付きはここでビルド時に実行されます
if (material.transparent) { return input.mul(0.5); }
return input;
});
関数の呼び出し
myFn(a, b, c) // 配列パラメータ
myFn({ color: red }) // オブジェクト パラメータ
myFn() // デフォルト値を使用
インライン関数(Fn ラッパーなし)
// OK:単純な式の場合、変数/条件なし
const simple = (t) => t.sin().mul(0.5).add(0.5);
条件付き
If/ElseIf/Else(大文字 I)
// 間違い
if(condition, () => {}) // 小文字の 'if' は JavaScript
// 正しい(Fn() 内)
If(a.greaterThan(b), () => {
result.assign(a);
}).ElseIf(a.lessThan(c), () => {
result.assign(c);
}).Else(() => {
result.assign(b);
});
Switch/Case
Switch(mode)
.Case(0, () => { out.assign(red); })
.Case(1, () => { out.assign(green); })
.Case(2, 3, () => { out.assign(blue); }) // 複数の値
.Default(() => { out.assign(white); });
// 注:フォールスルーなし、暗黙的な break
select() - 三項(推奨)
// Fn() 外で動作し、値を直接返す
const result = select(condition, valueIfTrue, valueIfFalse);
// 同等:condition ? valueIfTrue : valueIfFalse
// 例:カスタム ロジックで値をクランプ
const clamped = select(x.greaterThan(max), max, x);
数学ベース(パフォーマンスのため推奨)
step(edge, x) // x < edge ? 0 : 1
mix(a, b, t) // a*(1-t) + b*t
smoothstep(e0, e1, x) // スムーズな 0→1 遷移
clamp(x, min, max) // 範囲を制限
saturate(x) // clamp(x, 0, 1)
// パターン:分岐なしで条件付き選択
mix(valueA, valueB, step(threshold, selector))
ループ
// 基本
Loop(count, ({ i }) => { /* i はループ インデックス */ });
// オプション付き
Loop({ start: int(0), end: int(10), type: 'int', condition: '<' }, ({ i }) => {});
// ネストされた
Loop(10, 5, ({ i, j }) => {});
// 逆向き
Loop({ start: 10 }, ({ i }) => {}); // カウント ダウン
// While スタイル
Loop(value.lessThan(10), () => { value.addAssign(1); });
// 制御
Break(); // ループを終了
Continue(); // イテレーションをスキップ
数学関数
// すべて以下として利用可能:func(x) または x.func()
// 基本
abs(x) sign(x) floor(x) ceil(x) round(x) trunc(x) fract(x)
mod(x,y) min(x,y) max(x,y) clamp(x,min,max) saturate(x)
// 補間
mix(a,b,t) step(edge,x) smoothstep(e0,e1,x)
// 三角関数
sin(x) cos(x) tan(x) asin(x) acos(x) atan(y,x)
// 指数
pow(x,y) exp(x) exp2(x) log(x) log2(x) sqrt(x) inverseSqrt(x)
// ベクトル
length(v) distance(a,b) dot(a,b) cross(a,b) normalize(v)
reflect(I,N) refract(I,N,eta) faceforward(N,I,Nref)
// 導関数(フラグメント のみ)
dFdx(x) dFdy(x) fwidth(x)
// TSL エキストラ(GLSL にはない)
oneMinus(x) // 1 - x
negate(x) // -x
saturate(x) // clamp(x, 0, 1)
reciprocal(x) // 1/x
cbrt(x) // 立方根
lengthSq(x) // 2 乗の長さ(sqrt なし)
difference(x,y) // abs(x - y)
equals(x,y) // x == y
pow2(x) pow3(x) pow4(x) // x^2、x^3、x^4
オシレータ
oscSine(t = time) // サイン波 0→1→0
oscSquare(t = time) // 矩形波 0/1
oscTriangle(t = time) // 三角波
oscSawtooth(t = time) // のこぎり波
ブレンド モード
blendBurn(a, b) // カラー バーン
blendDodge(a, b) // カラー ドッジ
blendScreen(a, b) // スクリーン
blendOverlay(a, b) // オーバーレイ
blendColor(a, b) // 標準ブレンド
UV ユーティリティ
uv() // デフォルト UV 座標(vec2、0-1)
uv(index) // 特定の UV チャネル
matcapUV // matcap テクスチャ座標
rotateUV(uv, rotation, center = vec2(0.5)) // UV を回転
spherizeUV(uv, strength, center = vec2(0.5))// 球面歪み
spritesheetUV(count, uv = uv(), frame = 0) // スプライト アニメーション
equirectUV(direction = positionWorldDirection) // 正距円筒図法マッピング
リフレクション
reflectView // ビュー空間での反射
reflectVector // ワールド空間での反射
補間ヘルパー
remap(node, inLow, inHigh, outLow = 0, outHigh = 1) // 範囲をリマップ
remapClamp(node, inLow, inHigh, outLow = 0, outHigh = 1) // リマップ + クランプ
ランダム
hash(seed) // 疑似ランダム float [0,1]
range(min, max) // インスタンスごとのランダム属性
配列
// 定数配列
const arr = array([vec3(1,0,0), vec3(0,1,0), vec3(0,0,1)]);
arr.element(i) // 動的インデックス
arr[0] // 定数インデックスのみ
// ユニフォーム配列(JS から更新可能)
const arr = uniformArray([new THREE.Color(0xff0000)], 'color');
arr.array[0] = new THREE.Color(0x00ff00); // 更新
バリアティングス
// 頂点で計算、フラグメントに補間
const v = varying(expression, 'name');
// 最適化:頂点計算を強制
const v = vertexStage(expression);
テクスチャ
texture(tex) // デフォルト UV でサンプル
texture(tex, uv) // UV でサンプル
texture(tex, uv, level) // LOD でサンプル
cubeTexture(tex, direction) // キューブマップ
triplanarTexture(texX, texY, texZ, scale, pos, normal)
シェーダー入力
ポジション
positionGeometry // 生の属性
positionLocal // スキニング/モルフィング後
positionWorld // ワールド空間
positionView // カメラ空間
positionWorldDirection // 正規化
positionViewDirection // 正規化
法線
normalGeometry normalLocal normalView normalWorld
カメラ
cameraPosition cameraNear cameraFar
cameraViewMatrix cameraProjectionMatrix cameraNormalMatrix
スクリーン
screenUV // 正規化 [0,1]
screenCoordinate // ピクセル
screenSize // ピクセル
viewportUV viewport viewportCoordinate viewportSize
時間
time // 経過時間(秒)(float)
deltaTime // 前フレーム以降の時間(float)
モデル
modelDirection // vec3
modelViewMatrix // mat4
modelNormalMatrix // mat3
modelWorldMatrix // mat4
modelPosition // vec3
modelScale // vec3
modelViewPosition // vec3
modelWorldMatrixInverse // mat4
その他
uv() uv(index) // テクスチャ座標
vertexColor() // 頂点色
attribute('name', 'type') // カスタム属性
instanceIndex // インスタンス/スレッド ID(インスタンシングおよびコンピュート用)
NodeMaterial タイプ
利用可能なマテリアル
MeshBasicNodeMaterial // ライト なし、最速
MeshStandardNodeMaterial // PBR(粗さ/金属性)
MeshPhysicalNodeMaterial // PBR + クリアコート、透過など
MeshPhongNodeMaterial // Blinn-Phong シェーディング
MeshLambertNodeMaterial // Lambert 拡散
MeshToonNodeMaterial // セル シェーディング
MeshMatcapNodeMaterial // matcap シェーディング
MeshNormalNodeMaterial // 法線を可視化
SpriteNodeMaterial // ビルボード クワッド
PointsNodeMaterial // ポイント クラウド
LineBasicNodeMaterial // ソリッド ライン
LineDashedNodeMaterial // ダッシュ ライン
すべてのマテリアル - 共通プロパティ
.colorNode // vec4 - ベースカラー
.opacityNode // float - 不透明度
.positionNode // vec3 - 頂点ポジション(ローカル空間)
.normalNode // vec3 - サーフェス法線
.outputNode // vec4 - 最終出力
.fragmentNode // vec4 - フラグメント ステージ全体を置き換え
.vertexNode // vec4 - 頂点ステージ全体を置き換え
MeshStandardNodeMaterial
.roughnessNode // float
.metalnessNode // float
.emissiveNode // vec3 color
.aoNode // float
.envNode // vec3 color
MeshPhysicalNodeMaterial(Standard を拡張)
.clearcoatNode .clearcoatRoughnessNode .clearcoatNormalNode
.sheenNode .transmissionNode .thicknessNode
.iorNode .iridescenceNode .iridescenceThicknessNode
.anisotropyNode .specularColorNode .specularIntensityNode
SpriteNodeMaterial
.positionNode // vec3 - スプライト中心のワールド ポジション
.colorNode // vec4 - カラーとアルファ
.scaleNode // float - スプライト サイズ(vec2 の非一様)
.rotationNode // float - ラジアン単位の回転
PointsNodeMaterial
.positionNode // vec3 - ポイント ポジション
.colorNode // vec4 - カラーとアルファ
.sizeNode // float - ピクセル単位のポイント サイズ
コンピュート シェーダー
基本的なコンピュート(スタンドアロン)
import { Fn, instanceIndex, storage } from 'three/tsl';
// ストレージ バッファを作成
const count = 1024;
const array = new Float32Array(count * 4);
const bufferAttribute = new THREE.StorageBufferAttribute(array, 4);
const buffer = storage(bufferAttribute, 'vec4', count);
// コンピュート シェーダーを定義
const computeShader = Fn(() => {
const idx = instanceIndex;
const data = buffer.element(idx);
buffer.element(idx).assign(data.mul(2));
})().compute(count);
// 実行
renderer.compute(computeShader); // 同期(フレームごと)
await renderer.computeAsync(computeShader); // 非同期(重いワンショット タスク)
コンピュート → レンダー パイプライン
コンピュート シェーダー出力をレンダリング(例:シミュレーション、手続き型ジオメトリ)する場合、StorageInstancedBufferAttribute で storage() を使用して書き込み、attribute() を使用して読み込みます。
import { Fn, instanceIndex, storage, attribute, vec4 } from 'three/tsl';
const COUNT = 1000;
// 1. 型付き配列とストレージ属性を作成
const dataArray = new Float32Array(COUNT * 4);
const dataAttribute = new THREE.StorageInstancedBufferAttribute(dataArray, 4);
// 2. コンピュート シェーダー用のストレージ ノードを作成(書き込みアクセス)
const dataStorage = storage(dataAttribute, 'vec4', COUNT);
// 3. コンピュート シェーダーを定義
const computeShader = Fn(() => {
const idx = instanceIndex;
const current = dataStorage.element(idx);
// データを変更...
const newValue = current.xyz.add(vec3(0.01, 0, 0));
dataStorage.element(idx).assign(vec4(newValue, current.w));
})().compute(COUNT);
// 4. 属性をジオメトリにアタッチしてレンダリング
const geometry = new THREE.BufferGeometry();
// ... ベース ジオメトリを設定 ...
geometry.setAttribute('instanceData', dataAttribute);
// 5. マテリアルで attribute() を使用して読み込み
const material = new THREE.MeshBasicNodeMaterial();
material.positionNode = Fn(() => {
const data = attribute('instanceData', 'vec4');
return positionLocal.add(data.xyz);
})();
// 6. メッシュを作成
const mesh = new THREE.InstancedMesh(geometry, material, COUNT);
scene.add(mesh);
// 7. アニメーション ループ
await renderer.init();
function animate() {
renderer.compute(computeShader);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
JavaScript からのバッファ更新
// 基本配列を変更
for (let i = 0; i < COUNT; i++) {
dataArray[i * 4] = Math.random();
}
// GPU アップロード用のフラグ
dataAttribute.needsUpdate = true;
例:基本的なマテリアル シェーダー
import * as THREE from 'three/webgpu';
import { Fn, uniform, vec3, vec4, float, uv, time,
normalWorld, positionWorld, cameraPosition,
mix, pow, dot, normalize, max } from 'three/tsl';
// ユニフォーム
const baseColor = uniform(new THREE.Color(0x4488ff));
const fresnelPower = uniform(3.0);
// マテリアルを作成
const material = new THREE.MeshStandardNodeMaterial();
// フレネル リム ライティング付きのカスタム カラー
material.colorNode = Fn(() => {
// フレネルを計算
const viewDir = normalize(cameraPosition.sub(positionWorld));
const NdotV = max(dot(normalWorld, viewDir), 0.0);
const fresnel = pow(float(1.0).sub(NdotV), fresnelPower);
// ベース カラーと白いリムを混合
const rimColor = vec3(1.0, 1.0, 1.0);
const finalColor = mix(baseColor, rimColor, fresnel);
return vec4(finalColor, 1.0);
})();
// アニメーション化した頂点変位
material.positionNode = Fn(() => {
const pos = positionLocal.toVar();
const wave = sin(pos.x.mul(4.0).add(time.mul(2.0))).mul(0.1);
pos.y.addAssign(wave);
return pos;
})();
例:コンピュート シェーダー構造
import * as THREE from 'three/webgpu';
import { Fn, instanceIndex, storage, uniform, vec4, float, sin, time } from 'three/tsl';
const COUNT = 10000;
// ストレージ バッファ
const dataArray = new Float32Array(COUNT * 4);
const dataAttribute = new THREE.StorageBufferAttribute(dataArray, 4);
const dataBuffer = storage(dataAttribute, 'vec4', COUNT);
// コンピュート用ユニフォーム
const speed = uniform(1.0);
// コンピュート シェーダー
const updateCompute = Fn(() => {
const idx = instanceIndex;
const data = dataBuffer.element(idx);
// 現在の値を読み込み
const position = data.xyz.toVar();
const phase = data.w;
// 更新ロジック
const offset = sin(time.mul(speed).add(phase)).mul(0.1);
position.y.addAssign(offset);
// 書き込み
dataBuffer.element(idx).assign(vec4(position, phase));
});
const computeNode = updateCompute().compute(COUNT);
// アニメーション ループ内:
// renderer.compute(computeNode);
一般的なエラー パターン
エラー:「If is not defined」
// 間違い
if(condition, () => {})
// 正しい
If(condition, () => {}) // 大文字 I
エラー:割り当てができない
// 間違い
const v = vec3(1,2,3);
v.x = 5;
// 正しい
const v = vec3(1,2,3).toVar();
v.x.assign(5);
エラー:型の不一致
// 間違い
sqrt(intValue)
// 正しい
sqrt(intValue.toFloat())
エラー:ユニフォームが変わらない
// 間違い
myUniform = newValue;
// 正しい
myUniform.value = newValue;
エラー:インポートが見つからない
// 間違い
import { vec3 } from 'three/nodes';
import * as THREE from 'three';
// 正しい
import { vec3 } from 'three/tsl';
import * as THREE from 'three/webgpu';
エラー:コンピュート データがレンダリングに見えない
// 間違い:レンダー マテリアルで storage() を使用
material.positionNode = storage(attr, 'vec4', count).element(idx).xyz;
// 正しい:レンダー シェーダーで attribute() を使用して読み込み
geometry.setAttribute('myData', attr);
material.positionNode = attribute('myData', 'vec4').xyz;
エラー:何もレンダリングされない
// 間違い:init 前にレンダリング
renderer.render(scene, camera);
// 正しい:常に init を最初に待つ
await renderer.init();
renderer.render(scene, camera);
クイック パターン
フレネル
const fresnel = Fn(() => {
const NdotV = normalize(cameraPosition.sub(positionWorld)).dot(normalWorld).max(0);
return pow(float(1).sub(NdotV), 5);
});
波の変位
material.positionNode = Fn(() => {
const p = positionLocal.toVar();
p.y.addAssign(sin(p.x.mul(5).add(time)).mul(0.2));
return p;
})();
UV スクロール
material.colorNode = texture(map, uv().add(vec2(time.mul(0.1), 0)));
条件付き値
const result = select(value.greaterThan(0.5), valueA, valueB);
// または分岐なし:
const result = mix(valueB, valueA, step(0.5, value));
グラデーション マッピング
const t = smoothstep(float(0.0), float(1.0), inputValue);
const colorA = vec3(0.1, 0.2, 0.8);
const colorB = vec3(1.0, 0.5, 0.2);
const gradient = mix(colorA, colorB, t);
ソフト フォールオフ
// 指数関数的フォールオフ(グロー、減衰に適切)
const falloff = exp(distance.negate().mul(rate));
// 逆二乗フォールオフ
const attenuation = float(1.0).div(distance.mul(distance).add(1.0));
円形マスク(スプライト/ポイント用)
const uvCentered = uv().sub(0.5).mul(2.0); // -1 から 1
const dist = length(uvCentered);
const circle = smoothstep(float(1.0), float(0.8), dist);
OceanHeightMap — 海の表面との要素の同期
プロジェクトは、レンダー ツー テクスチャ ハイトマップ(OceanHeightMap)を使用して、ライブ海面の高さをエンコードします。海に従う必要がある要素(波、フォーム、浮体オブジェクトなど)は、頂点シェーダーでこのテクスチャをサンプリングする必要があります。
HeightMap の仕組み
- 正射影カメラが
PlaneGeometry(1, 1, 200, 200)をレンダリングします。SCALE_OCEAN(3000)で XY 平面にスケール されます。 - 頂点シェーダーは、サイン波の合計表面関数から波の
depthを計算し、バリアティングスにエンコードします。 - フラグメント シェーダーは
WebGLRenderTargetに書き込みます:- R =
(depth + yStrength) / (2 * yStrength)— 正規化された高さ [0,1] - G = R と同じ(平均、将来の使用用)
- B =
yStrength / 100— 強度スケーリング係数 - A = 1.0
- R =
フレームごとに更新されるユニフォーム(Ocean update() から)
OceanHeightMap.uTimeWave.value = this.uTimeWave.value
OceanHeightMap.uDirTex.value = GridManager.offsetUV
OceanHeightMap.uYScale.value = yScale
OceanHeightMap.uYStrength.value = yStrength
TSL positionNode でのハイトマップのサンプリング
1. ハイトマップ テクスチャ参照を取得(ビルド時)
import OceanHeightMap from '../Ocean/OceanHeightMap'
const heightMapTex = OceanHeightMap.heightMap?.texture // WebGLRenderTarget からの THREE.Texture
2. ワールド X,Z をハイトマップ UV にマップ
ハイトマップはワールド X と Z で [-SCALE_OCEAN/2, SCALE_OCEAN/2] をカバーします。
// 通常のメッシュまたはポイント用:
const wPos = modelWorldMatrix.mul(vec4(positionLocal, 1.0))
// InstancedMesh 用 — インスタンス CENTER でサンプル、頂点ごとではなく:
const wCenter = modelWorldMatrix.mul(vec4(0.0, 0.0, 0.0, 1.0))
const uScaleOcean = uniform(SCALE_OCEAN)
const uvGrid = vec2(
float(0.5).add(wCenter.x.div(uScaleOcean)),
float(0.5).sub(wCenter.z.div(uScaleOcean)),
)
InstancedMesh の CRITICAL:vec4(0,0,0,1)(インスタンス原点)をサンプリング ポイントとして使用してください。positionLocal を使用すると、スケール後の各頂点コーナー(±0.5)でサンプリングされ、クワッドが変形の代わりに均一に変位します。元の GLSL Points シェーダーはポイント中心である position を使用しました — インスタンス化されたジオメトリの場合、vec4(0,0,0,1) が同等です。
3. 5-tap クロス平均(ちらつきを減らします)
const off = float(0.01)
const hmC = texture(heightMapTex, uvGrid)
const hm1A = texture(heightMapTex, vec2(uvGrid.x.add(off), uvGrid.y))
const hm1B = texture(heightMapTex, vec2(uvGrid.x, uvGrid.y.add(off)))
const hm2A = texture(heightMapTex, vec2(uvGrid.x.sub(off), uvGrid.y))
const hm2B = texture(heightMapTex, vec2(uvGrid.x, uvGrid.y.sub(off)))
const avgH = hmC.r.add(hm1A.r).add(hm1B.r).add(hm2A.r).add(hm2B.r).div(5.0)
4. ワールド空間 Y 変位を計算
// デコード:(avgH - 0.5) * 2 * yStrength = depth(実際の波高)
// B チャネル * 100 で yStrength を回復
const disp = avgH.sub(0.5).mul(2.0).mul(hmC.b.mul(100.0))
注:元の GLSL
waves.vertは クリップ空間 で変位を適用したため(gl_Position.y +=)、後続の*2は透視除算により自然に減衰されました。ワールド空間では、後続の*2を省略してください。
5. 変位を適用
通常のメッシュ用(ローカル Y ≈ ワールド Y):
const pos = positionLocal.toVar()
pos.y.addAssign(disp)
return pos
ビルボード化された InstancedMesh 用(ビルボード回転によるローカル Y ≠ ワールド Y):
// ワールド Y 変位をインスタンス ローカル空間に変換
const worldDispVec = vec4(0.0, disp, 0.0, 0.0) // w=0(方向の場合)
const localDisp = modelWorldMatrixInverse.mul(worldDispVec)
return positionLocal.add(localDisp.xyz)
このパターンで必要なインポート:
import { positionLocal, modelWorldMatrix, modelWorldMatrixInverse } from 'three/tsl'
完全な positionNode 例(ビルボード付き InstancedMesh)
const positionNodeFn = Fn(() => {
const pos = positionLocal
const wCenter = modelWorldMatrix.mul(vec4(0.0, 0.0, 0.0, 1.0))
const uvGrid = vec2(
float(0.5).add(wCenter.x.div(uScaleOcean)),
float(0.5).sub(wCenter.z.div(uScaleOcean)),
)
const off = float(0.01)
const hmC = texture(heightMapTex, uvGrid)
const hm1A = texture(heightMapTex, vec2(uvGrid.x.add(off), uvGrid.y))
const hm1B = texture(heightMapTex, vec2(uvGrid.x, uvGrid.y.add(off)))
const hm2A = texture(heightMapTex, vec2(uvGrid.x.sub(off), uvGrid.y))
const hm2B = texture(heightMapTex, vec2(uvGrid.x, uvGrid.y.sub(off)))
const avgH = hmC.r.add(hm1A.r).add(hm1B.r).add(hm2A.r).add(hm2B.r).div(5.0)
const disp = avgH.sub(0.5).mul(2.0).mul(hmC.b.mul(100.0))
const worldDispVec = vec4(0.0, disp, 0.0, 0.0)
const localDisp = modelWorldMatrixInverse.mul(worldDispVec)
return pos.add(localDisp.xyz)
})
material.positionNode = positionNodeFn()
Points → InstancedMesh 移行に関する注記
WebGPU は WebGL のような gl_PointCoord または可変 gl_PointSize をサポートしていません。Points ベースのエフェクトを変換するとき:
PointsをPlaneGeometry(1, 1)を使用したInstancedMeshに置き換えます。gl_PointCoordをuv()に置き換えます。- カスタム属性の代わりに、インスタンスごとのバリエーション用に
instanceIndexを使用します(WebGPU でより信頼性が高い)。インスタンスごとの疑似ランダム値用にhash(instanceIndex)を使用します。 - ビルボード回転は CPU で実行する必要があります(毎フレーム
lookAtを使用してインスタンス マトリックスを更新)。 - CPU でビルボード化するときは、UV Y を反転する必要があるかもしれません:
float(1).sub(uv().y)。 - 透明インスタンスの場合、毎フレーム カメラ深度により背面から前面にソートします。
gl_PointSize からワールド空間スケールへの変換
元の gl_PointSize 式は、距離とともに縮小するピクセル サイズを提供します:
gl_PointSize = uSize * (perspectiveFactor / -mvPosition.z)
InstancedMesh の場合、クワッド スケールは ワールド ユニット にあり、透視はカメラ投影により処理されます。gl_PointSize とワールド空間オブジェクトは両方とも 1/distance としてスケール されるため、変換は定数係数です。
式:
SPRITE_SCALE = uSize * perspectiveFactor * 2 * tan(fov / 2) / screenHeight
fov と screenHeight は実行時値のため、1 つの既知の良好な変換から変換係数 C を導き出し、他に適用します:
C = knownWorldScale / (knownUSize * knownPerspectiveFactor)
newWorldScale = newUSize * newPerspectiveFactor * C
このプロジェクトの参照(FOV = 50°):
| コンポーネント | 元の uSize | 透視係数 | K = uSize × factor | ワールド スケール(SPRITE_SCALE) |
|---|---|---|---|---|
| Waves | 450 | 100 | 45,000 | 15 |
| Lightnings | 1000 | 400 | 400,000 | 133 |
| Stars | 50 | 100 | 5,000 | ~2 |
C = 15 / 45000 = 1/3000 → SPRITE_SCALE = K / 3000
GLSL → TSL 移行
| GLSL | TSL |
|---|---|
position | positionGeometry |
transformed | positionLocal |
transformedNormal | normalLocal |
vWorldPosition | positionWorld |
vColor | vertexColor() |
vUv / uv | uv() |
vNormal | normalView |
viewMatrix | cameraViewMatrix |
modelMatrix | modelWorldMatrix |
modelViewMatrix | modelViewMatrix |
projectionMatrix | cameraProjectionMatrix |
diffuseColor | material.colorNode |
gl_FragColor | material.fragmentNode |
texture2D(tex, uv) | texture(tex, uv) |
textureCube(tex, dir) | cubeTexture(tex, dir) |
gl_FragCoord | screenCoordinate |
gl_PointCoord | uv()(SpriteNodeMaterial/PointsNodeMaterial) |
gl_InstanceID | instanceIndex |
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- Robpayot
- リポジトリ
- Robpayot/tslda
- ライセンス
- MIT
- 最終更新
- 2026/3/23
Source: https://github.com/Robpayot/tslda / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。