Edited at

Three.jsでトゥーンシェーダ(とShaderMaterial)

More than 5 years have passed since last update.

Three.jsのShaderMaterialを使って、自分で書いたシェーダを適用させたときのメモ。

トゥーンシェーダについては、こちらの「トゥーンレンダリング | wgld.org」を参考にさせていただきました。

ちなみに今回のサンプルはjsdo.it(Three.jsでトゥーンシェーダの実装の実験)に上げてあります。


レンダリング結果

gepr.jpg


ShaderMaterial

Three.jsではいわゆる普通のマテリアルに関してはすぐに使えるように準備がしてあります。(e.g. THREE.MeshPhongMaterial)

これはThree.js側でシェーダのコード断片を組み合わせて自動でシェーダを生成してくれる仕組みです。

なので、簡単なものであればこれらのマテリアルを使うだけである程度のことはできると思います。

しかし、今回の例のようなトゥーンシェーダや、もっと複雑な質感を表そうとするともともと準備されているものでは実現できません。

そこで使用するのがShaderMaterialです。

これは、自分で書いたシェーダをマテリアルとしてgeometryに適用させる手段です。


ShaderMaterialの例

var material = new THREE.ShaderMaterial({

fragmentShader: document.getElementById('fs').innerHTML,
vertexShader : document.getElementById('vs').innerHTML,
attributes: {},
uniforms: {
edgeColor: {
type: 'v4',
value: new THREE.Vector4(0, 0, 0, 0)
},
edge: {
type: 'i',
value: true
},
lightDirection: {
type: 'v3',
value: globalLight.position
},
texture: {
type: 't',
value: THREE.ImageUtils.loadTexture('textures/toon.png')
}
}
});


fragmentShader, vertexShader

THREE.ShaderMaterialはいくつかのパラメータを受け取ります。

fragmentShadervertexShaderは言わずもがなで、それぞれのシェーダとして使用するソースコードを文字列で渡します。(今回の例ではscriptタグの中身をそのまま適用しています)


attributes

今回は使用していませんが、attribute変数に渡す値を設定します。


uniforms

シェーダのuniform変数に値を渡すために使用します。

シェーダ内のuniform変数で使用している変数名をプロパティに指定し、値としてtypeとvalueをセットにしたオブジェクトを渡します。

typeに関してはuniform変数で定義しているのに合った型を指定します。(指定できるtypeについては「Three.jsでWebGLを触ってみる」にリスト化しているので参照ください)


トゥーンシェーダ

今回の例では冒頭のサイトで紹介されているコードを元に、Three.js向けに書いてみたものです。

ので、シェーダ自体の解説はそちらの記事を参照ください。


Three.jsで背面のレンダリング

ここはまだ曖昧な部分ではあるんですが、今回ハマったポイントなので備忘録として残しておきます。

上記記事ではレンダリングする面の設定をgl.cullFace(gl.FRONT);で背面をレンダリングさせていますが、Three.jsでは内部的にスイッチしているのかこれでは背面がレンダリングされません。


背面レンダリングはmaterial.side = **

レンダリング面の設定はmaterial側で行うようです。

material.sideにはTHREE.DoubleSideTHREE.FrontSideTHREE.BackSideという3つの定数が用意されていて、これを設定することでレンダリングされる面が決定するようです。


Materialの共有

Meshを作成する場合、var mesh = new THREE.Mesh(geometry, material);のように生成しますが、ここで指定したmaterialmesh.materialを通じてアクセスすることができます。

実はこれ、Mesh生成時に渡したmaterialそのものの参照でしかなく、このmaterialを使った別のMeshを生成しても、同じ値が参照されます。

なので、レンダリング時にmaterialの値を書き換えるだけで今回のトゥーンシェーダを実現しています。

//render front face.

meshEdge.material.side = THREE.FrontSide;
mesh.material.uniforms.edgeColor.value = new THREE.Vector4(0, 0, 0, 0);
mesh.material.uniforms.edge.value = false;
renderer.render(scene, camera);

//render back face as edge.
meshEdge.material.side = THREE.BackSide;
meshEdge.material.uniforms.edgeColor.value = new THREE.Vector4(0, 0, 0, 1);
meshEdge.material.uniforms.edge.value = true;
renderer.render(sceneEdge, camera);

本当はもっとスマートなやり方があると思いますが、とりあえず今の段階で実現できたことをメモっています。


Materialの動的変更

新しく「シェーダの切り替え」ボタンを追加したのでメモ。

Three.js r59のDocumentを見ると、Meshクラスには「setMaterial」というメソッドがあるみたいだったのだけど、なぜか未定義。

ただ、上記Materialのところでも書いたように、Mesh生成時に指定したMaterialはmesh.materialに参照として紐付いているだけなので、mesh.material = otherMaterial;と直接参照を切り替えてあげればレンダリング結果も変更された。