公開日
- 10 分で読めます
第10話 「シェーダーへの秘密のメモ! uniform変数の正体を暴け」
Three.js ってなんだろう? カエル君と学ぶ、ブラウザ3Dの不思議な世界
注
この物語は、Three.jsを楽しく学ぶことを目的に、生成AIを活用して執筆されています。 技術的な情報の正確性には細心の注意を払っていますが、その内容がすべて真実であることを保証するものではありません。 あくまで学習の補助ツールとして、肩の力を抜いてお楽しみください。
登場人物紹介
- カエル君: 最近プログラミングを学び始めた元気なカエル。まだ知らないことばかりだけど、好奇心は人一倍。「〜ケロ」という口調が特徴。
- 三郎先生: 3DグラフィックスやWeb技術にとても詳しい物知り博士。どんな質問にも優しく答えてくれる。
第10話:シェーダーへの秘密のメモ! uniform変数の正体を暴け
カエル君: 「先生! 光の情報がuniform
変数でシェーダーに渡されるって話、やっぱりもやもやするんだケロ…。『情報を渡す』って、一体どうやってるんだケロ? 魔法の呪文で送っているのか、それとも手紙でも書いてるんだケロか? もっと具体的にイメージしたいんだケロ!」
三郎先生: 「カエル君、最高の質問だ。そこは誰もが一度はつまずく、CPUの世界とGPUの世界を繋ぐ、最も重要で、最も見えにくい『架け橋』だ。よし、今日はその架け橋の正体を、究極のたとえ話で解き明かしてあげよう。」
カエル君: 「究極のたとえ話! ワクワクするんだケロ!」
GPUはシェフ、シェーダーはレシピ、uniformは秘密のメモ
三郎先生: 「まず、こう想像してごらん。」
- GPU は… とてつもないスピードで料理(ピクセル描画)を作るが、レシピ以外のことは一切できない、超専門家の**『シェフ』**だ。
- シェーダー は… そのシェフが使う**『レシピ』そのもの。実はこのレシピ、頂点シェーダーとフラグメントシェーダー**という2冊で1セットになっている。
- 頂点シェーダー (Vertex Shader): 素材(頂点)をどこに配置するかを決める、下ごしらえのレシピだ。
- フラグメントシェーダー (Fragment Shader): 配置された素材に、最終的にどんな色を塗るかを決める、仕上げのレシピだね。
- ジオメトリ(頂点たち) は… 料理の**『素材』**。ジャガイモやニンジンだ。
- そして
uniform
変数 は… レシピには書かれていない、その日ごとの**『特別な指示書』または『秘密のメモ』**なんだ。
カエル君: 「秘密のメモ!?」
三郎先生: 「そう。例えばレシピには『ソースを作る』としか書かれていない。そこに、我々(CPU)が『今日のソースは少しスパイシーに』とか『オーブンの温度は200度で』といった指示をメモで渡す。この指示は、一つの料理(一つのオブジェクト)を作っている間は、**全体に共通(uniform)**で適用されるだろう? だからuniform
変数と呼ぶんだ。」
カエル君: 「なるほど! 頂点ごと、ピクセルごとに指示が変わるわけじゃないからuniform
なんだケロね! ちなみに、素材一つ一つに違う指示を出す方法もあるんだケロか?」
三郎先生: 「鋭いね、カエル君! まさにその通り。各頂点の位置座標や法線(面の向き)のように、素材一つ一つが持つ固有の情報は**attribute
(属性)** という、別の方法でシェフに渡される。uniform
が料理全体への指示なら、attribute
はジャガイモ一つ一つの形や大きさ、といったイメージだ。この二つを使い分けるのがキモなんだよ。」
『情報を渡す』プロセスの具体像
三郎先生: 「では、このたとえ話で、光の情報を渡すプロセスを見てみよう。」
-
我々(CPU)の指示: 我々はThree.jsで
light.color.set(0xff0000);
と書く。これは人間が分かる言葉で「今日の料理に使う光の色は赤で!」と指示を出すようなものだ。 -
レンダラー(現場監督)の翻訳:
renderer.render()
が呼ばれると、現場監督であるレンダラーが、この「赤い光」という指示を、シェフ(GPU)が読める形式の『秘密のメモ』に書き起こす。 -
シェーダー(レシピ)の準備: シェーダーというレシピには、あらかじめ
uniform vec3 uLightColor;
のように、『光の色指示:____』という空欄が用意されているんだ。 -
シェフ(GPU)へのメモ渡し: レンダラーは、描画が始まる直前に、この
uLightColor
という空欄に、赤色の情報、つまりvec3(1.0, 0.0, 0.0)
という値をサッと書き込む。これが『情報を渡す』という行為の正体だ。 -
シェフ(GPU)の調理: シェフ(GPU)は、レシピ(シェーダー)と素材(頂点)と、そして渡された秘密のメモ(
uniform
変数)を見て、猛スピードで調理(ピクセル描画)を始める。メモに『赤色』と書いてあるから、光の反射を計算して、オブジェクトを赤っぽく照らすことができる、というわけさ。
カエル君: 「な、なるほどーーーっ! 『情報を渡す』って、レンダラーがシェーダーに用意された空欄に、値を書き込んであげることだったんだケロね! これなら完璧にイメージできるんだケロ!」
自分でメモを渡してみよう!
三郎先生: 「その通りだ。そして、Three.jsのMeshStandardMaterial
などは、これらのメモのやり取りを全て自動でやってくれる。だが、我々が自分でレシピ(シェーダー)を書き、自分でメモ(uniform)を渡すこともできるんだ。ShaderMaterial
を使えばね。」
我々が書くコード(Three.jsの世界):
「時間」という情報をuTime
という名前のメモで渡してみよう。
const material = new THREE.ShaderMaterial({
uniforms: {
// uTimeという名前のメモを用意し、初期値は0.0とする
uTime: { value: 0.0 }
},
vertexShader: `...`,
fragmentShader: `...`
});
// アニメーションループの中で、毎フレーム、メモの値を更新する
function animate(time) {
requestAnimationFrame(animate);
// timeはミリ秒なので、秒に変換して渡す
material.uniforms.uTime.value = time * 0.001;
renderer.render(scene, camera);
}
シェーダーが受け取る秘密のメモ(GLSLという言語の世界):
uTime
というメモを受け取り、それを使って頂点を波のように動かしてみる。
// uniform変数の宣言(uTimeという名前のメモを受け取るための空欄)
uniform float uTime;
void main() {
// ...頂点座標(position)を、時間の経過(uTime)と共にsin波で動かす...
vec3 pos = position;
pos.z += sin(pos.x * 10.0 + uTime) * 0.1;
// 計算後の頂点位置をgl_Positionにセットする
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
カエル君: 「うわー! こうして見比べると、一目瞭然だケロ! 僕たちが書いた簡単なコードが、レンダラーのおかげで、シェーダーへの具体的な指示書に変わっていたんだケロね! しかも、自分で好きな情報を渡せるなんて、可能性は無限大だケロ!」
三郎先生: 「その通りだ、カエル君。uniform
変数こそ、我々がいる人間的な3Dの世界と、GPUが支配する純粋な計算の世界とを繋ぐ、最も重要な架け橋なんだ。この架け橋の存在を理解できた君は、もう初心者ではない。一人の立派な3Dデベロッパーだよ。」
カエル君: 「先生…! 最高の講義だったんだケロ! 秘密のメモの渡し方、完璧にマスターしたんだケロ! ピョンピョンピョーーーン!」
(第11話へつづく)