Looks great!
Yea I ran into a texture unit issue when I added normal and specular mapping (with shadows). Had to workaround by passing blendmap into the normal map perturbnormal function instead of re-fetching it. Also instead of using the āBaseā diffuse,normal and specular maps as uniforms I just use those as the material map, normalMap and specularMaps.
Just for the heck of it hereās the shader code:
// SPLAT
terrainSplatTexture: {
tileblend_pars_fragment: [
'#define USE_MAP',
'uniform sampler2D texBlendMap;',
//'uniform sampler2D texBaseColor;',
'uniform sampler2D texTile1Color;',
'uniform sampler2D texTile2Color;',
'uniform sampler2D texTile3Color;',
'uniform float repeatBase;',
'uniform float repeatTile1;',
'uniform float repeatTile2;',
'uniform float repeatTile3;',
'uniform int tileCount;'
].join('\n'),
tileblend_fragment: [
'vec4 tbBlend;',
'float tbBaseWeight;',
'if ( tileCount > 1 ){',
'tbBlend = texture2D( texBlendMap, vUv );',
'tbBaseWeight = 1.0 - max(tbBlend.r, max(tbBlend.g, tbBlend.b));',
'vec4 texelColor = vec4(0.0, 0.0, 0.0, 1.0);',
'texelColor += tbBaseWeight * texture2D( map, vUv * repeatBase );',
'texelColor += tbBlend.r * texture2D( texTile1Color, vUv * repeatTile1 );',
'tbBaseWeight += tbBlend.r;',
'if( tileCount > 2 ) {',
'texelColor += tbBlend.g * texture2D( texTile2Color, vUv * repeatTile2 );',
'tbBaseWeight += tbBlend.g;',
'}',
'if( tileCount > 3 ) {',
'texelColor += tbBlend.b * texture2D( texTile3Color, vUv * repeatTile3 );',
'tbBaseWeight += tbBlend.b;',
'}',
'texelColor = texelColor / tbBaseWeight;',
'texelColor.a = tbBlend.a;',
//'diffuseColor = mapTexelToLinear( texelColor );',
'diffuseColor = texelColor;',
'} else {',
'vec4 texelColor = texture2D( map, vUv );',
'texelColor = mapTexelToLinear( texelColor );',
'diffuseColor *= texelColor;',
'}',
].join('\n'),
normalmap_pars_fragment: [
'#ifdef USE_NORMALMAP',
'uniform sampler2D normalMap;',
'uniform vec2 normalScale;',
//'uniform sampler2D texBaseBump;',
'uniform sampler2D texTile1Bump;',
'uniform sampler2D texTile2Bump;',
'uniform sampler2D texTile3Bump;',
'#ifdef OBJECTSPACE_NORMALMAP',
'uniform mat3 normalMatrix;',
'#else',
'// Per-Pixel Tangent Space Normal Mapping',
'// http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html',
'vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec4 tbBlend ) {',
'// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988',
'vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );',
'vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );',
'vec2 st0 = dFdx( vUv.st );',
'vec2 st1 = dFdy( vUv.st );',
'float scale = sign( st1.t * st0.s - st0.t * st1.s ); // we do not care about the magnitude',
'vec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );',
'vec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );',
'vec3 N = normalize( surf_norm );',
'mat3 tsn = mat3( S, T, N );',
'vec3 mapN;',
'if( tileCount > 1 ){',
//'vec4 tbBlend=texture2D( texBlendMap, vUv );',
'float tbBaseWeight=1.0 - max(tbBlend.r, max(tbBlend.g, tbBlend.b));',
'float foundIdx=0.0;',
'float foundVal=tbBaseWeight;',
'if (tbBlend.r>foundVal) { foundIdx=1.0; foundVal=tbBlend.r;}',
'if (tbBlend.g>foundVal) { foundIdx=2.0; foundVal=tbBlend.g;}',
'if (tbBlend.b>foundVal) { foundIdx=3.0; foundVal=tbBlend.b;}',
'mapN = texture2D( normalMap, vUv * repeatBase ).xyz * 2.0 - 1.0;',
'if (foundIdx==1.0) mapN = texture2D( texTile1Bump, vUv * repeatTile1 ).xyz * 2.0 - 1.0;',
'else if (foundIdx==2.0 && tileCount > 2) mapN = texture2D( texTile2Bump, vUv * repeatTile2 ).xyz * 2.0 - 1.0;',
'else if (foundIdx==3.0 && tileCount > 3) mapN = texture2D( texTile3Bump, vUv * repeatTile3 ).xyz * 2.0 - 1.0;',
'} else {',
'mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;',
'}',
'mapN.xy *= normalScale;',
'mapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );',
'return normalize( tsn * mapN );',
'}',
'#endif',
'#endif'
].join('\n'),
normalmap_fragment_maps: [
'#ifdef USE_NORMALMAP',
'#ifdef OBJECTSPACE_NORMALMAP',
'normal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;',
'#ifdef FLIP_SIDED',
'normal = - normal;',
'#endif',
'#ifdef DOUBLE_SIDED',
'normal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );',
'#endif',
'normal = normalize( normalMatrix * normal );',
'#else',
'#ifdef USE_TANGENT',
'mat3 vTBN = mat3( tangent, bitangent, normal );',
'vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;',
'mapN.xy = normalScale * mapN.xy;',
'normal = normalize( vTBN * mapN );',
'#else',
'normal = perturbNormal2Arb( -vViewPosition, normal, tbBlend );',
'#endif',
'#endif',
'#elif defined( USE_BUMPMAP )',
'normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );',
'#endif',
].join('\n'),
specularmap_pars_fragment: [
'#ifdef USE_SPECULARMAP',
'uniform sampler2D specularMap;',
//'uniform sampler2D texBaseSpecular;',
'uniform sampler2D texTile1Specular;',
'uniform sampler2D texTile2Specular;',
'uniform sampler2D texTile3Specular;',
'#endif'
].join('\n'),
specularmap_fragment: [
'float specularStrength;',
'#ifdef USE_SPECULARMAP',
'vec4 texelSpecular;',
'if( tileCount > 1 ){',
//'vec4 tbBlend=texture2D( texBlendMap, vUv );',
//'float tbBaseWeight=1.0 - max(tbBlend.r, max(tbBlend.g, tbBlend.b));',
'float foundIdx=0.0;',
'float foundVal = tbBaseWeight;',
'if (tbBlend.r > foundVal) { foundIdx=1.0; foundVal=tbBlend.r; }',
'if (tbBlend.g > foundVal) { foundIdx=2.0; foundVal=tbBlend.g; }',
'if (tbBlend.b > foundVal) { foundIdx=3.0; foundVal=tbBlend.b; }',
'vec4 texelSpecular = texture2D( specularMap, vUv * repeatBase );',
'if (foundIdx==1.0) texelSpecular = texture2D( texTile1Specular, vUv * repeatTile1 );',
'else if (foundIdx==2.0) texelSpecular = texture2D( texTile2Specular, vUv * repeatTile2 );',
'else if (foundIdx==3.0) texelSpecular = texture2D( texTile3Specular, vUv * repeatTile3 );',
'}else{',
'texelSpecular = texture2D( specularMap, vUv );',
'}',
'specularStrength = texelSpecular.r;',
'#else',
'specularStrength = 1.0;',
'#endif'
].join('\n')
}