THREE.ShaderMaterialを試すための環境を作り、試す。
アイデアを実現するための検証環境を作って試行錯誤した。結果、所望の動作をさせることができた。テクスチャが伸縮することなく、ExtrudeBufferGeometry
のamountに応じてテクスチャが貼り付けることができている。
階数とテクスチャー番号のアトリビュートを用意して、それを元にフラグメントシェーダーで階数に応じてうまくテクスチャを貼り付けるようにした。ただテクスチャを貼り付けるところ以外はMeshPhongMaterial
のままで動いてほしい。ライティングとか座標変換とかね。こういうのを一から実装するのは骨が折れるし、Three.jsを使う意味があまりなくなってしまう。私の場合さらに3Dビギナーであるため、phong
シェーディングをいちから実装することはすぐにできない。一からシェーダーを全部書くには私にはヘビーすぎる。何とか最低限コードを書くだけで所望の処理を実現できないだろうか。
調べた結果、ShaderLib
とShaderChunk
を使えばできそうだということが分かった。そして実際にそれを使ってコードを書いた。
ShaderLibとShaderChunk、UniformLib
シェーダーはマテリアルを指定するときに自動的に決まる。three.jsは描画時にマテリアルに適したシェーダーをShaderLib
から選択してレンダリングを行う。
ShaderLib
はいろいろなシェーダーのライブラリ、ShaderChunk
はシェーダーコードの断片、UniformsLib
はuniform
変数のテンプレートである。ShaderLibはShaderChunkとUniformsLibを組み合わせること+αで作られている。
ShaderLibはシェーダー名のプロパティを持つ。その中身はuniforms
・vertexShader
・fragmentShader
の3つのプロパティである。例えばphong
シェーダーは以下の定義となっている。
var ShaderLib = {
.
.
.
phong: {
uniforms: UniformsUtils.merge( [
UniformsLib.common,
UniformsLib.aomap,
UniformsLib.lightmap,
UniformsLib.emissivemap,
UniformsLib.bumpmap,
UniformsLib.normalmap,
UniformsLib.displacementmap,
UniformsLib.gradientmap,
UniformsLib.fog,
UniformsLib.lights,
{
emissive: { value: new Color( 0x000000 ) },
specular: { value: new Color( 0x111111 ) },
shininess: { value: 30 }
}
] ),
vertexShader: ShaderChunk.meshphong_vert,
fragmentShader: ShaderChunk.meshphong_frag
},
.
.
uniforms
プロパティにはそのシェーダーで使用するuniform
変数を定義する。この変数定義はUniformsLib
でテンプレート化されており、これにより定義の簡略化と名称の共通化は果たしている。
たとえばUniformsLib.common
の内容は以下となっている。
var UniformsLib = {
common: {
diffuse: { value: new Color( 0xeeeeee ) },
opacity: { value: 1.0 },
map: { value: null },
offsetRepeat: { value: new Vector4( 0, 0, 1, 1 ) },
specularMap: { value: null },
alphaMap: { value: null },
envMap: { value: null },
flipEnvMap: { value: - 1 },
reflectivity: { value: 1.0 },
refractionRatio: { value: 0.98 }
},
.
.
.
オリジナルのシェーダーを実装する場合はできる限りUniformsLib
で提供されているuniform
変数を使うべきだろう。上のコードではUniformUtils.merge()
を使って、UniformsLib
を1つにまとめている。
vertexShader
には頂点シェーダーを指定する。上のphong
シェーダーではShaderChunk.meshbasic_vert
シェーダーが指定されている。このシェーダーの内容は以下のとおりである。
#define PHONG
varying vec3 vViewPosition;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
vNormal = normalize( transformedNormal );
#endif
#include <begin_vertex>
#include <displacementmap_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
vViewPosition = - mvPosition.xyz;
#include <worldpos_vertex>
#include <envmap_vertex>
#include <shadowmap_vertex>
#include <fog_vertex>
}
なんとコードの中身は#include
の塊である。#include
で指定されている値はShaderChunk
のプロパティである。
うむむ。。なんだこの#include
、こんなディレクティブWebGLのシェーダー言語の仕様にあったっけ??としばし悩む。
実は「ない」。これはthree.jsだけで使える。描画時に#include
はShaderChunk
のコード片が展開される。
そのコードはWebGLProgram.jsにあるparseIncludes()
関数である。
function parseIncludes( string ) {
var pattern = /^[ \t]*#include +<([\w\d.]+)>/gm;
function replace( match, include ) {
var replace = ShaderChunk[ include ];
if ( replace === undefined ) {
throw new Error( 'Can not resolve #include <' + include + '>' );
}
return parseIncludes( replace );
}
return string.replace( pattern, replace );
}
string
引数にはシェーダーコード文字列を渡すと展開されたコード文字列を返すという簡単な関数である。
でこのShaderChunk
の中身を理解すれば、コードの構築が端折れそうだが、この中身をすべて理解するのは大変である。カスタマイズする部分のみに着目するのがよさそうだ。今回はattribute
変数をフラグメントシェーダーに渡す部分を頂点シェーダーに書き、それを受けてテクスチャーマッピングする部分をフラグメントシェーダーに追加すればよい。
頂点シェーダーについては既存のコードはそのままおいておいて、attribute
変数の追加と、それをフラグメントシェーダーに引き渡すコードだけを書けばよさそうである。
実際に書いたコードは以下である。
vertex shader:
#define PHONG
varying vec3 vViewPosition;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
varying vec3 vNormalView;
// attributeの追加
attribute float texIndex;
attribute float amount;
// フラグメントシェーダーに引き渡す値の定義
varying float vTexIndex;
varying float vAmount;
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
vNormal = normalize( transformedNormal );
#endif
#include <begin_vertex>
#include <displacementmap_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
vViewPosition = - mvPosition.xyz;
#include <worldpos_vertex>
#include <envmap_vertex>
#include <shadowmap_vertex>
#include <fog_vertex>
// コードの追加
vTexIndex = texIndex;
vAmount = amount;
vNormalView = normal;
}
fragment shader:
#define PHONG
uniform vec3 diffuse;
uniform vec3 emissive;
uniform vec3 specular;
uniform float shininess;
uniform float opacity;
#include <common>
#include <packing>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <envmap_pars_fragment>
#include <gradientmap_pars_fragment>
#include <fog_pars_fragment>
#include <bsdfs>
#include <lights_pars>
#include <lights_phong_pars_fragment>
#include <shadowmap_pars_fragment>
#include <bumpmap_pars_fragment>
#include <normalmap_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
varying float vTexIndex;
varying float vAmount;
varying vec3 vNormalView;
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
vec3 totalEmissiveRadiance = emissive;
#include <logdepthbuf_fragment>
// シェーダーのカスタマイズ
//#include <map_fragment>
//float ycomp = dot(vec3(0.,0.,1.),vNormalView);
//テクスチャ中のセル番号
float texIdx;
// 法線のz成分があれば底面か上面とする
if(vNormalView.z != 0.0){
//上面・底面用のテクスチャのセル番号
texIdx = MAX_TEX_NUM - vTexIndex - 8.0 - 1.0;
} else {
//側面用のテクスチャのセル番号を求める
texIdx = MAX_TEX_NUM - vTexIndex - 1.0;
}
vec2 uv;
// texIdxからテクスチャ上のuv開始座標を求める
uv.y = floor(texIdx / TEX_DIV) * TEX_DIV_R;
uv.x = mod(texIdx,TEX_DIV ) * TEX_DIV_R ;
vec2 vuv;
// 上面・底面と側面とで処理を分ける。
if(vNormalView.z == 0.0){
//側面用
vuv = vec2(vUv.x * TEX_DIV_R,mod(vUv.y , 1.0 / vAmount) * vAmount * TEX_DIV_R / 8.0);
} else {
//上面・底面用
vuv = vUv * TEX_DIV_R;
}
vec4 texelColor = texture2D(map, vuv + uv);
//vec4 texelColor = texture2D( map, vUv );
texelColor = mapTexelToLinear( texelColor );
diffuseColor *= texelColor;
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
#include <normal_flip>
#include <normal_fragment>
#include <emissivemap_fragment>
// accumulation
#include <lights_phong_fragment>
#include <lights_template>
// modulation
#include <aomap_fragment>
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
#include <envmap_fragment>
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
2つのシェーダーとも、追加コードは最低限で所望の処理を実現することができている。あとはShaderMaterialにこのシェーダーを引き渡して描画するだけである。
const baseShader = THREE.ShaderLib['phong'];
const mat = new THREE.ShaderMaterial({
uniforms:THREE.UniformsUtils.clone(baseShader.uniforms),
defines: {
MAX_TEX_NUM: toFloatString(MAX_TEX_NUM),
TEX_DIV: toFloatString(TEX_DIV),
TEX_DIV_R: toFloatString(TEX_DIV_R),
USE_MAP:''
},
lights:true,
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
mat.uniforms.emissive.value = new THREE.Color(0x000000);
mat.uniforms.ambientLightColor.value = new THREE.Color(0x303030);
mat.uniforms.map.value = buildingsTextures[0];
mesh.add(new THREE.Mesh(
new THREE.Geometry(),
mat
));
とはいってもまだコードには不具合があり、その原因を探っているところである。
この話は続く。
デモ&ソースコード