結論にたどり着くまでに半日くらいかかったのでメモ。
three.jsでなにかをつくる場合、ShaderMaterialもしくはRawShaderMaterialを使って全部自前で書いてもいいけどライブラリで用意されているマテリアルをちょこっとだけ改造すれば事足りることが多いと思う。というわけで自分がたどり着いた結論をご紹介。
この公式サンプルを見ればOK
https://threejs.org/examples/#webgl_materials_modified
色々調べたが灯台もと暗しで公式にサンプルがあったという結論。
一応一通りみたつもりだったけど多すぎてどれが目的のサンプルか判断できないあるある。。
Exampleを見ろ!ではあまりにも内容がないのでかんたんに自分が書いたコードを紹介して解説しておく。
コード例
import * as THREE from 'three'
class CustomPhongMaterial extends THREE.MeshPhongMaterial{
constructor(param){
super(param);
this.onBeforeCompile = this.overwriteShader;
}
overwriteShader(shader){
shader.uniforms.customUniform = {type: 'f', value: 1};
// 追加したいコード行
const insertUniform = `
uniform float customUniformValue;
`
// シェーダー内部のどこに追加するかは文字列で指定
shader.fragmentShader = shader.fragmentShader.replace('#define PHONG', insertUniform);
const insertMain = `
outgoingLight.r = customUniformValue;
`
shader.fragmentShader = shader.fragmentShader.replace('#include <envmap_fragment>', insertMain);
// userDataに格納
this.userData.shader = shader;
}
}
export default CustomPhongMaterial
uniformの更新はサンプルを見ながら適当にやればいい。(以下の部分)
注意点として、レンダリングの最初のフレームでonCompileBeforeが呼ばれるのでuserData.shaderのnullチェックをしないとエラーになる。最初のuniform値を指定したい場合はmaterialのコンストラクタなどで渡すなどしなくてはならない。
const shader = child.material.userData.shader;
if ( shader ) {
shader.uniforms.time.value = performance.now() / 1000;
}
補足: customProgramCacheKeyについて
customProgramCacheKey関数内ではシェーダーにバリエーションが有る場合に別のシェーダーとしてコンパイルするかという条件を書いておく場所。
文章で説明が難しいがthree.jsのサンプルの使われ方だとonBeforeCompile内でシェーダーコードの書き換えをする際にamountが定数で埋め込まれている。その場合amountの値が違うだけの複数のシェーダーバリエーションが必要なのでcustomProgramCacheKeyの中でバリエーションを作るかどうかの判定を行う。自分が書いた方のコードは特にそういったバリエーションを必要としないのでcustomProgramCacheKeyは使わなかった。
ドキュメントを読んだだけの知識なので本当かどうかは確認していないが。
別の方法でやっている例をみつけてた最初に試してみたが、うごいたもののおすすめはしない。
ただこのサンプルの絵結構きれい。
https://jsfiddle.net/gsynuh/bc1q9prL