It’s a bit silly to keep replying to my own post, but I think this may be helpful to others.

I now have a demo with working lighting and normal mapping. I took a different approach to get here. Instead of creating a custom material with my own shader, I now use onBeforeCompile to replace some shader chunks to suit my needs, leaving all others intact. This allows me to start with any material.

I then replace the mapping chunks and the normalmapping chunks for the fragment shader - leaving the vertex shader as it is.

The result is seen here: Terrain tiles blending demo #4

Navigate using W (forward), S (backward), A (turn left) and D (turn right) keys.

My shader codes:

`

```
<script id="tileblend_pars_fragment" type="x-shader/x-vertex">
#define USE_MAP
uniform sampler2D texBlendMap;
uniform sampler2D texBaseColor;
uniform sampler2D texTile1Color;
uniform sampler2D texTile2Color;
uniform sampler2D texTile3Color;
//uniform sampler2D texTile4Color;
uniform float repeatBase;
uniform float repeatTile1;
uniform float repeatTile2;
uniform float repeatTile3;
//uniform float repeatTile4;
</script>
<script id="tileblend_fragment" type="x-shader/x-vertex">
vec4 tbBlend=texture2D( texBlendMap, vUv );
float tbBaseWeight=1.0 - max(tbBlend.r, max(tbBlend.g, tbBlend.b));
vec4 base = tbBaseWeight * texture2D( texBaseColor, vUv * repeatBase );
vec4 color1 = tbBlend.r * texture2D( texTile1Color, vUv * repeatTile1 );
vec4 color2 = tbBlend.g * texture2D( texTile2Color, vUv * repeatTile2 );
vec4 color3 = tbBlend.b * texture2D( texTile3Color, vUv * repeatTile3 );
vec4 newColor = (vec4(0.0, 0.0, 0.0, 1.0) + base + color1 + color2 + color3) / (tbBaseWeight+tbBlend.r+tbBlend.g+tbBlend.b);
diffuseColor = newColor;
</script>
<script id="normalmap_pars_fragment" type="x-shader/x-vertex">
#ifdef USE_NORMALMAP
uniform sampler2D normalMap;
uniform vec2 normalScale;
uniform sampler2D texBaseBump;
uniform sampler2D texTile1Bump;
uniform sampler2D texTile2Bump;
uniform sampler2D texTile3Bump;
uniform sampler2D texTile4Bump;
#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 ) {
// 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 );
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;}
vec3 mapN = texture2D( texBaseBump, 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) mapN = texture2D( texTile2Bump, vUv * repeatTile2 ).xyz * 3.0 - 1.0;
else if (foundIdx==3.0) mapN = texture2D( texTile3Bump, vUv * repeatTile3 ).xyz * 3.0 - 1.0;
mapN.xy *= normalScale;
mapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );
return normalize( tsn * mapN );
}
#endif
#endif
```

`

And the onBeforeCompile bit:

```
// --------------------------------------------------------
// Customizing Material
terrainMaterial.userData.myValue = { type:"f",value: 2.0 } //this will be our input, the system will just reference it
terrainMaterial.onBeforeCompile = shader => {
shader.uniforms.myValue = terrainMaterial.userData.myValue //pass this input by reference
shader.uniforms.texBlendMap= { type: "t", value: blendTexture };
shader.uniforms.texBaseColor= { type: "t", value: baseTexture };
shader.uniforms.texBaseBump = { type: "t", value: baseBump };
shader.uniforms.repeatBase= { type: "f", value: 50};
shader.uniforms.texTile1Color= { type: "t", value: grassTexture };
shader.uniforms.texTile1Bump = { type: "t", value: grassBump };
shader.uniforms.repeatTile1= { type: "f", value: 20};
shader.uniforms.texTile2Color= { type: "t", value: snowyTexture };
shader.uniforms.texTile2Bump = { type: "t", value: snowyBump };
shader.uniforms.repeatTile2= { type: "f", value: 35};
shader.uniforms.texTile3Color= { type: "t", value: rockyTexture };
shader.uniforms.texTile3Bump = { type: "t", value: rockyBump };
shader.uniforms.repeatTile3= { type: "f", value: 19};
//shader.uniforms.texTile4Color= { type: "t", value: sandyTexture };
//shader.uniforms.texTile4Bump = { type: "t", value: sandyBump };
//shader.uniforms.repeatTile4= { type: "f", value: 20};
shader.fragmentShader = document.getElementById( 'tileblend_pars_fragment' ).textContent + shader.fragmentShader
shader.fragmentShader =
shader.fragmentShader.replace(
'#include <map_fragment>',
document.getElementById( 'tileblend_fragment' ).textContent
)
shader.fragmentShader =
shader.fragmentShader.replace(
'#include <normalmap_pars_fragment>',
document.getElementById( 'normalmap_pars_fragment' ).textContent
)
}
```

`

I am not happy with the shadows, but those are weird even when the material is not modified, so I’ll start a different topic for it.