void transformToTS(in vec3 TSnormal, in mat3 normalMatrix, inout vec3 normal)
{
    // derive the tangent space basis
    // we do this in the pixel shader because we don't have per-vertex normals
    // because of the LOD, we use a normal map
    // tangent is always +x or -z in object space depending on alignment
#ifdef TERRAIN_ALIGN_Y_Z
    vec3 tangent = vec3(0, 0, -1);
#else
    vec3 tangent = vec3(1, 0, 0);
#endif
    normal = normalize(normal);
    tangent = normalize(mul(normalMatrix, tangent));
    vec3 binormal = cross(tangent, normal);
    // note, now we need to re-cross to derive tangent again because it wasn't orthonormal
    tangent = cross(normal, binormal);
    // derive final matrix
    mat3 TBN = mtxFromCols(tangent, binormal, normal);

    normal = mul(TBN, TSnormal);
}

void getShadowFactor(in sampler2D lightmap, in vec2 uv, inout float shadowFactor)
{
    float lmShadow = texture2D(lightmap, uv).x;
    shadowFactor = min(shadowFactor, lmShadow);
}

#define MIN_BLEND_WEIGHT 0.0039 // 1/255

void blendTerrainLayer(in float blendWeight, in f32vec2 uv0, in float uvMul,
#ifdef TERRAIN_PARALLAX_MAPPING
                    in vec3 viewPos, in vec2 scaleBias,
#endif
#ifdef TERRAIN_NORMAL_MAPPING
                    in sampler2D normtex, inout vec3 normal,
#endif
                    in sampler2D difftex, inout vec4 diffuseSpec)
{
    if(blendWeight < MIN_BLEND_WEIGHT)
        return;

    // generate UV
    vec2 uv = mod(uv0 * uvMul, 1.0);

#ifdef TERRAIN_PARALLAX_MAPPING
    SGX_Generate_Parallax_Texcoord(normtex, uv, viewPos, scaleBias, uv);
#endif

    // sample diffuse texture
    vec4 diffuseSpecTex = texture2D(difftex, uv);
    // apply to common
    diffuseSpec = mix(diffuseSpec, diffuseSpecTex, blendWeight);

#ifdef TERRAIN_NORMAL_MAPPING
    vec3 TSnormal;
    // access TS normal map
    SGX_FetchNormal(normtex, uv, TSnormal);
    // Partial Derivative Blending https://blog.selfshadow.com/publications/blending-in-detail/
    normal = normalize(vec3(mix(normal.xy*TSnormal.z, TSnormal.xy*normal.z, blendWeight), TSnormal.z*normal.z));
#endif
}