MMDでサポートされてるけど
まだ対応してなかったスフィアマップをやってみました。
スフィアマップというのは、
テクスチャの貼り方の技法の一つで、
環境マッピングとか呼ばれてます。
擬似的な周囲環境の映り込みを再現する手法で、
金属のような光沢とかを表現できたりします。
環境マッピングの実装方式には、
球状マップ(sphere map)やキューブマップ(cube map)があります。
three.jsのサンプルである
こちらやこちらでその結果をみることができます。
three.jsのサンプルで見れたりするくらいなので、
割りとアッサリ導入できると思ってたんですが、
webGLレンダラーではスフィアマップはまだ実装されてませんでした・・・。
canvasレンダラーでは実装されているのですが、
webGLレンダラーではキューブマップのみとなってました。
ということで、
自前で実装することにしました。
どうやればいいかググってみたら、
これ(その1)やこれ(その2)とかみつかりました。
実現手法は一つでは無いようですね。
どっちがいいんだろ。
2つともやってみることにしました。
なお、
MMDのスフィアマップではマッピング時の演算として
乗算および加算をサポートしています。
今回は乗算のみの対応となっていますが、
fragment shader の最後の乗算を加算にするだけのことですね。たぶん。
GLSLなコードは以下の様な感じになりそうです。
その1)
// vertex shader
varying vec3 vNormal;
varying vec2 vUvSphere;
vNormal = normalMatrix * objectNormal;
vUvSphere.x = vNormal.x * 0.5 + 0.5;
vUvSphere.y = vNormal.y * 0.5 + 0.5;
// fragment shader
varying vec2 vUvSphere;
uniform sampler2D mmdSphereMap;
gl_FragColor = gl_FragColor * texture2D( mmdSphereMap, vUvSphere );
その2)
// vertex shader
varying vec3 vNormal;
varying vec2 vUvSphere;
vNormal = normalMatrix * objectNormal;
vec4 mvPosition = modelViewMatrix * vec4( objectPosition, 1.0 );
vec3 r = reflect( normalize(mvPosition.xyz), vNormal );
float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) );
vUvSphere.x = r.x/m + 0.5;
vUvSphere.y = r.y/m + 0.5;
// fragment shader
varying vec2 vUvSphere;
uniform sampler2D mmdSphereMap;
gl_FragColor = gl_FragColor * texture2D( mmdSphereMap, vUvSphere );
つづいて、three.jsを改造します。
three.jsで使われているGLSLなコードをダンプしてみると
こんな感じになっています。
レンダラーやマテリアルのプロパティに応じて、
例えば
#define USE_SKINNING
とかが、
GLSLのソースコードに付加される仕組みになっています。
ただ、現状の実装では
three.jsが認識しているプロパティでないとうまく行きません。
そこで、マテリアルに
material.vertexShaderPrefix
material.fragmentShaderPrefix
というプロパティを追加して対応することにしました、
改造したtheee.jsのコードは以下のようになります。
独自の #define とかをコードの先頭に追加できるようにしてしまうわけです。
// WegGLRenderer:
this.initMaterial = function ( material, lights, fog, object ) {
.
.
material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, parameters, material.fragmentShaderPrefix, material.vertexShaderPrefix ); // MOD
.
.
};
function buildProgram ( shaderID, fragmentShader, vertexShader, uniforms, attributes, parameters , fragmentShaderPrefix, vertexShaderPrefix) { // MOD
var p, pl, program, code;
var chunks = [];
// Generate code
if ( shaderID ) {
chunks.push( shaderID );
} else {
chunks.push( fragmentShaderPrefix ); // ADD
chunks.push( fragmentShader );
chunks.push( vertexShaderPrefix ); // ADD
chunks.push( vertexShader );
}
for ( p in parameters ) {
chunks.push( p );
chunks.push( parameters[ p ] );
}
code = chunks.join();
// code = md5(code); // to do if possible
.
.
// ADD
if (fragmentShaderPrefix) {
prefix_fragment += fragmentShaderPrefix;
}
if (vertexShaderPrefix) {
prefix_vertex += vertexShaderPrefix;
}
var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader );
var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader );
.
.
}
さて、
実際にやってみた感じでは、
その1)でも、その2)でもそれなりにイイ感じになったのですが、
MMDのスフィアマップ用に使用しているテクスチャとの相性が良さそうなのは
その1)っぽい気がしましたので、そちらを採用してみました。
ということで、
メタルっぽくなったミクさんはこちらです。