WebGPUでThree.JSを描画できるようになり、実務でもテストで導入したところです。
導入にあたり、仕様のほうも以前のWebGLRendererから大幅に変わったところがあります。以前までShaderMaterialで使用していた箇所がNodeMaterialに代わり、vertex.glsl、fragment.glslをTSL(Three.js Shading Language)に書き換える必要が出てきました。
そこで、ちょうど仕様が定まってきたので、備忘録をかねて、コードを変換する方法をサンプルコード付きで紹介します。
Three.js Shading LanguageJavaScript 3D Library. Contribute to mrdoob/three.js development by creating an account on GitHub.
webgpu_tsl_transpiler (GLSLコードをTSLに変換してくれるサンプルサイト)
*精度がいまいちですが、これをベースに書き換えをおこなえば大丈夫! three.js examples
WebGPU 対象のブラウザは以下より確認できます
WebGPU | Can I use... Support tables for HTML5, CSS3, etc"Can I use" provides up-to-date browser support tables for support of front-end web technologies on desktop and mobile w...
WebGPUの基本コード
Three.JSをWebGPUに対応させていきます。以下のCubeを表示させていきます。

typescriptになりますが、npmでthree.jsを読み込んで使用できるようにします。
npm install three --save-dev
npm install @types/three --save-devHTMLに以下のスクリプトを挿入してください。WebGPUで必須のようです。
<script type="importmap">
{
"imports": {
"three": "/build/three.webgpu.js",
"three/webgpu": "/build/three.webgpu.js",
"three/tsl": "/build/three.tsl.js",
"three/addons/": "/jsm/"
}
}
</script>Typescriptですが、以下をコピペしていただければ描画できると思います
import * as THREE from 'three/webgpu'
import {
color
} from 'three/tsl'/* 基本構文 */
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
100
)
camera.position.x = 0
camera.position.y = 0
camera.position.z = 4
// WebGPURenderer
const renderer = new THREE.WebGPURenderer({
antialias: true,
alpha: true,
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2.0))
renderer.setSize( window.innerWidth, window.innerHeight )
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement)
// renderer.shadowMap.enabled = true
// renderer.toneMapping = THREE.NoToneMapping
// renderer.toneMappingExposure = 1.0
// レンダリングループの設定
renderer.setAnimationLoop(renderLoop)
// OrbitControls
// const controls = new OrbitControls(camera, renderer.domElement)
// controls.enableDamping = true
// Helper
const axesHelper = new THREE.AxesHelper( 1 );
scene.add( axesHelper );
/* TSL */
/*
NodeMaterial()
MeshBasicNodeMaterial
MeshStandardNodeMaterial()
MeshPhysicalNodeMaterial()
*/
const material = new THREE.NodeMaterial();
material.fragmentNode = color('crimson')
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material)
scene.add(mesh)
/* Animation */
function renderLoop() {
//controls.update()
renderer.renderAsync(scene, camera)
//stats.update()
}
/* ユーティリティ */
window.addEventListener('resize', function () {
let currentWidth = window.innerWidth;
let currentHeight = window.innerHeight;
camera.aspect = currentWidth / currentHeight
camera.updateProjectionMatrix()
renderer.setSize(currentWidth, currentHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2.0))
})以上で、WebGPUの基本的なコードが完了です。余談ですが、ブラウザがWebGPUに非対応の場合は、WebGLRendererに自動的に変換されますので、ご安心ください。
こちらの記事が、非常に参考になりました
Getting Started - Three.js Shading Language Tutorials
TSL(Three.js Shading Language)
時間ごとに色が変わるCubeをshaderMaterialで書きました。そのコードをTSLに書き換えてみます。
shaderMaterial のコード (WebGL)

typscript
import vertexShader from './glsl/vertex.glsl';
import fragmentShader from './glsl/fragment.glsl';
~~
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
})
~~
const clock = new THREE.Clock();
function renderLoop() {
const elapsedTime = clock.getElapsedTime()
// シェーダーのtime uniformを更新
material.uniforms.time.value = elapsedTime
material.needsUpdate = true;
//renderer.renderAsync(scene, camera)
renderer.render(scene, camera)
requestAnimationFrame(renderLoop)
}
renderLoop()vertex.glsl
precision mediump float;
uniform float time;
uniform vec2 resolution;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// UVをフラグメントシェーダーに渡す
vUv = uv;
// 法線をワールド座標系に変換してフラグメントシェーダーに渡す
vNormal = normalMatrix * normal;
// 頂点位置をワールド座標系に変換してフラグメントシェーダーに渡す
vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
// 最終的な頂点位置を計算(クリップ座標系)
//vec4 worldPosition = modelMatrix * vec4(position, 1.0);
//vec4 mvPosition = viewMatrix * worldPosition;
//gl_Position = projectionMatrix * mvPosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}fragment.glsl
precision mediump float;
uniform float time;
uniform vec2 resolution;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
// UVをフラグメントシェーダーに渡す
vUv = uv;
// 法線をワールド座標系に変換してフラグメントシェーダーに渡す
vNormal = normalMatrix * normal;
// 頂点位置をワールド座標系に変換してフラグメントシェーダーに渡す
vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
// 最終的な頂点位置を計算(クリップ座標系)
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}TSLで書き換えたコード (WebGPU)
TSLを使うには、使うプロパティをおのおの読み込ませる必要があります。必要があるものを追加していく感じです。
import * as THREE from 'three/webgpu'
import {
positionLocal,
positionGeometry,
Fn,
time,
uv,
vec3,
color,
sin,
add,
mul,
cameraProjectionMatrix,
modelViewMatrix,
vec4,
uniform,
varying,
vec2,
normalLocal,
modelNormalMatrix,
} from 'three/tsl'vertexとfragmentに相当するものを別々のファイルでなく、関数として定義します。NodeMaterialに関数を付与することで実現していきます。今回、uTimeは使わずに、TSLにあるtimeを使って時間変化を与えています。
const utime = uniform( 'float' );
const resolution = uniform( 'vec2' );
const vUv = varying( vec2(), 'vUv' );
const vNormal = varying( vec3(), 'vNormal' );
const vPosition = varying( vec3(), 'vPosition' );
const vertexShader = Fn(( ) => {
// UVをフラグメントシェーダーに渡す
vUv.assign( uv() );
// 法線をワールド座標系に変換してフラグメントシェーダーに渡す
vNormal.assign( modelNormalMatrix.mul( normalLocal ) );
// 頂点位置をワールド座標系に変換してフラグメントシェーダーに渡す
vPosition.assign( modelViewMatrix.mul( vec4( positionGeometry, 1.0 ) ).xyz );
// 最終的な頂点位置を計算(クリップ座標系)
return cameraProjectionMatrix.mul(modelViewMatrix).mul( vec4(positionGeometry, 1.0));
});
const fragmentShader = Fn(( ) => {
// 基本的な色の設定
const baseColor = vec3( 1.0, 0.5, 0.2 );
// オレンジ色
// 時間を使ったアニメーション
const timeEffect = sin( time ).mul( 0.5 ).add( 0.5 );
// UVを使ったグラデーション
const finalColor = baseColor.mul( timeEffect ).add( vec3( vUv, 0.0 ).mul( 0.3 ) );
return vec4( finalColor, 1.0 );
});
const material = new THREE.NodeMaterial();
material.vertexNode = vertexShader();
material.fragmentNode = fragmentShader();以上が、基本的な書き換えになります。
リンクのwebgpu_tsl_transpilerでglslのコードをペーストして結果をベースに書き換えています。そのままのコードだとエラーします。gl_FragColorなどのプロパティはmaterial.fragmentNodeに変わっていますので、公式リファレンスで検索して、おのおのを置き換えていく必要があります。

varyingで渡した変数は、fragmentShaderの関数内で参照されて使用できます。
*はmul()、/ はdiv()、+はadd()、- はsub()などに置き換えなければなりません。読みずらいコードですが、ノードベースなので仕方ないかもです。glslを書けるのあれば、上記のやり方で変換できると思います。
uniformで定義した変数は、utime.value = ○○ に値を入れれば問題ありません。以下のコードはtimeをutimeに置き換えた場合のサンプルで、ループ内でutime.value = elapsedTime することで時間変化を与えられます。
/* Animation */
const clock = new THREE.Clock();
function renderLoop() {
//controls.update()
const elapsedTime = clock.getElapsedTime()
//@ts-ignore
utime.value = elapsedTime;
// シェーダーのtime uniformを更新
//material.uniforms.time.value = elapsedTime
renderer.renderAsync(scene, camera)
//renderer.render(scene, camera)
//requestAnimationFrame(renderLoop)
//stats.update()
}Part1はここまでになります。
まだまだリファレンスが少ないですが、以下のリンクが参考になります。ぜひご覧ください
Getting to grips with ThreeJS Shading Language (TSL) – NiksCourses PositionLocal - Three.js Shading Language Tutorials
