Custom Terrrain Shader With WebGPU

Yo @Evgeni_Popov … Hey guys, i have a situation where I use a PBRCustomMaterial for my terrains. I am trying to go to WebGPU but am getting not found errors:

The docs says

If you decide to write a shader with GLSL, Babylon.js will detect it and will use internal tools to compilte it to WGSL. It will take some times and will download a WASM library to do the conversion.

When and how does this happen. I think it is failing in WebGPU because of my custom terrain shader. So how do still use GLSL Terrain Materials with WebGPU ?

PBRCustomMaterial and CustomMaterial don’t support WGSL, so GLSL code will be translated to WGSL automatically:

You should normally have nothing to do if your shader works in WebGL. Just make sure to not set languageShader = WGSL.

1 Like

Yo @Evgeni_Popov

It must be a timing thing. My custom material is getting set in the GLTF extension. Been working for years with WebGL.

So there must be a timing thing, where my custom shader is trying to get used BEFORE it is converted …

When are the shaders converted ?

Can I manually ensure my custom material is converted before trying to load the gltf file that actually uses my custom material ?

Attached is my babylon-shaders.ts where I define the UniversalTerrainMaterial


/** Babylon Toolkit Namespace */
namespace TOOLKIT {
    /**
     * Babylon universal terrain material pro class
     * @class UniversalTerrainMaterial
     */
    export class UniversalTerrainMaterial extends TOOLKIT.UniversalAlbedoMaterial {
        public constructor(name: string, scene: BABYLON.Scene) {
            super(name, scene);
            this.enableShaderChunks = true;
            // ..
            const instance: TOOLKIT.UniversalTerrainMaterial = this;
            if (instance["awake"]) instance["awake"]();
            this.onBindObservable.add(() => { if (instance["after"]) instance["after"](); });
        }
        public getShaderName(): string {
            return "pbr";
        }
        public getShaderChunk(): string {
            return null;
        }
        protected updateShaderChunks(): void {
            const colorName: string = "surfaceAlbedo";
            const splatmapSampler: string = "splatmapSampler";
            const detailsSampler: string = "detailsSampler";
            const normalsSampler: string = "normalsSampler";
            const colorCorrection: number = TOOLKIT.SceneManager.TerrainColorCorrection; // DEPRECATED: (this.getScene().getEngine().getCaps().supportSRGBBuffers === false) ? TOOLKIT.System.ToLinearSpace : 1.0;
            // ..
            // Vertex Shader Chunks
            // ..
            if (this.materialShaderChunks.Vertex_Definitions != null && this.materialShaderChunks.Vertex_Definitions !== "") {
                this.materialShaderChunks.Vertex_Definitions = this.formatTerrainVertexDefintions() + this.materialShaderChunks.Vertex_Definitions;
            } else {
                this.materialShaderChunks.Vertex_Definitions = this.formatTerrainVertexDefintions();
            }
            if (this.materialShaderChunks.Vertex_MainEnd != null && this.materialShaderChunks.Vertex_MainEnd !== "") {
                this.materialShaderChunks.Vertex_MainEnd = this.formatTerrainVertexMainEnd() + this.materialShaderChunks.Vertex_MainEnd;
            } else {
                this.materialShaderChunks.Vertex_MainEnd = this.formatTerrainVertexMainEnd();
            }
            // ..
            // Fragment Shader Chunks
            // ..
            if (this.materialShaderChunks.Fragment_Definitions != null && this.materialShaderChunks.Fragment_Definitions !== "") {
                this.materialShaderChunks.Fragment_Definitions = this.formatTerrainFragmentDefintions(splatmapSampler, detailsSampler, normalsSampler) + this.materialShaderChunks.Fragment_Definitions;
            } else {
                this.materialShaderChunks.Fragment_Definitions = this.formatTerrainFragmentDefintions(splatmapSampler, detailsSampler, normalsSampler);
            }
            if (this.materialShaderChunks.Fragment_Custom_Albedo != null && this.materialShaderChunks.Fragment_Custom_Albedo !== "") {
                this.materialShaderChunks.Fragment_Custom_Albedo = this.formatTerrainFragmentUpdateColor(this.terrainInfo, colorName, splatmapSampler, detailsSampler, normalsSampler, colorCorrection) + this.materialShaderChunks.Fragment_Custom_Albedo;
            } else {
                this.materialShaderChunks.Fragment_Custom_Albedo = this.formatTerrainFragmentUpdateColor(this.terrainInfo, colorName, splatmapSampler, detailsSampler, normalsSampler, colorCorrection);
            }
        }
        private formatTerrainVertexDefintions(): string {
            return ("\r\n#define TERRAIN_VERTEX_DEFINITIONS\r\n\r\n"
                + "varying vec2 vSplatmapUV;\r\n"
                + "\r\n");
        }
        private formatTerrainVertexMainEnd(): string {
            return ("\r\n#define TERRAIN_VERTEX_MAIN_END\r\n\r\n"
                + "#ifdef UV1\r\n"
                + "vSplatmapUV = uv;\r\n"
                + "#endif\r\n"
                + "\r\n");
        }
        private formatTerrainFragmentDefintions(splatmapSampler: string, detailsSampler: string, normalsSampler: string): string {
            return ("\r\n#define TERRAIN_FRAGMENT_DEFNITIONS\r\n\r\n"
                + "varying vec2 vSplatmapUV;\r\n"
                + "uniform sampler2D " + splatmapSampler + ";\r\n"
                + "uniform sampler2D " + detailsSampler + ";\r\n"
                + "uniform sampler2D " + normalsSampler + ";\r\n"
                + "\r\n"
                + "float calculateMipmapLevel(const in vec2 uvs, const in vec2 size)\r\n"
                + "{\r\n"
                + "    vec2 dx = dFdx(uvs * size.x);\r\n"
                + "    vec2 dy = dFdy(uvs * size.y);\r\n"
                + "    float d = max(dot(dx, dx), dot(dy, dy));\r\n"
                + "    return 0.4 * log2(d);\r\n"
                + "}\r\n"
                + "\r\n"
                + "vec4 sampleTextureAtlas2D(const in sampler2D atlas, const in float gamma, const in vec2 tile, const in vec4 rect, in vec2 uvs, in float lod)\r\n"
                + "{\r\n"
                + "    if (lod < 0.0) lod = clamp(calculateMipmapLevel(uvs, vec2(tile.x, tile.x)), 0.0, tile.y);   // Tile Info (tile.xy)\r\n"
                + "    float size = pow(2.0, tile.y - lod);                                                        // Tile Bits (tile.y)\r\n"
                + "    float sizex = size / rect.z;                                                                // Tile Width (rect.z)\r\n"
                + "    float sizey = size / rect.w;                                                                // Tile Height (rect.w)\r\n"
                + "    uvs = fract(uvs);                                                                           // Perfrom Tiling (fract)\r\n"
                + "    uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x;           // Tile Position X (rect.x)\r\n"
                + "    uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y;           // Tile Position Y (rect.y)\r\n"
                + "    vec4 color = texture2DLodEXT(atlas, uvs, lod);\r\n"
                + "    if (gamma != 1.0) {\r\n"
                + "        color.r = pow(color.r, gamma);\r\n"
                + "        color.g = pow(color.g, gamma);\r\n"
                + "        color.b = pow(color.b, gamma);\r\n"
                + "    }\r\n"
                + "    return color;\r\n"
                + "}\r\n"
                + "\r\n"
                + "vec4 sampleSplatmapAtlas2D(const in sampler2D atlas, const in vec2 tile, const in vec4 rect, in vec2 uvs)\r\n"
                + "{\r\n"
                + "    float size = pow(2.0, tile.y);                                                              // Tile Bits (tile.y)\r\n"
                + "	   float sizex = size / rect.z;                                                                // Tile Width (rect.z)\r\n"
                + "	   float sizey = size / rect.w;                                                                // Tile Height (rect.w)\r\n"
                + "	   uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x;           // Tile Position X (rect.x)\r\n"
                + "	   uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y;           // Tile Position Y (rect.y)\r\n"
                + "    return texture2D(atlas, uvs);\r\n"
                + "}\r\n"
                + "\r\n"
                + "vec3 blendSplatmapAtlasColors(const in vec4 splatmap, in vec4 color1, in vec4 color2, in vec4 color3, in vec4 color4, in vec3 mixbuffer)\r\n"
                + "{\r\n"
                + "    vec3 buffer1 = mix(mixbuffer, color1.rgb, splatmap.r);\r\n"
                + "    vec3 buffer2 = mix(buffer1, color2.rgb, splatmap.g);\r\n"
                + "    vec3 buffer3 = mix(buffer2, color3.rgb, splatmap.b);\r\n"
                + "    return mix(buffer3, color4.rgb, splatmap.a);\r\n"
                + "}\r\n"
                + "\r\n"
                + "vec3 perturbNormalSamplerColor(mat3 cotangentFrame, vec3 samplerColor, float scale)\r\n"
                + "{\r\n"
                + "    vec3 map = samplerColor.xyz;\r\n"
                + "    map = map * 2.00787402 - 1.00787402;\r\n"
                + "    #ifdef NORMALXYSCALE\r\n"
                + "        map = normalize(map * vec3(scale, scale, 1.0));\r\n"
                + "    #endif\r\n"
                + "    return normalize(cotangentFrame * map);\r\n"
                + "}\r\n"
                + "\r\n"
                + "\r\n");
        }
        private formatTerrainFragmentUpdateColor(terrainInfo: any, colorName: string, splatmapSampler: string, detailsSampler: string, normalsSampler: string, colorCorrection: number = 1.0): string {
            let result: string = "";
            if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
                result = ("\r\n#define TERRAIN_FRAGMENT_UPDATE_COLOR\r\n\r\n"
                    + "vec3 normalsColor = vec3(0.5, 0.5, 1.0);\r\n"
                    + "vec3 normalsBuffer = normalW.rgb;\r\n"
                    + "vec3 splatmapBuffer = " + colorName + ".rgb;\r\n"
                    + "float autoMipMapLevel = -1.0;\r\n"
                    + "float normalCorrection = 1.0;\r\n"
                    + "float detailCorrection = " + colorCorrection.toFixed(4) + ";\r\n"
                    + "\r\n"
                    + "#if defined(ALBEDO) && defined(" + splatmapSampler.toUpperCase() + ") && defined(" + detailsSampler.toUpperCase() + ")\r\n"
                    + "\r\n"
                    + "// Reset Normal Values\r\n"
                    + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
                    + "    uvOffset = vec2(0.0, 0.0);\r\n"
                    + "    #ifdef NORMAL\r\n"
                    + "        normalW = normalize(vNormalW);\r\n"
                    + "    #else\r\n"
                    + "        normalW = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;\r\n"
                    + "    #endif\r\n"
                    + "    #ifdef CLEARCOAT\r\n"
                    + "        clearCoatNormalW = normalW;\r\n"
                    + "    #endif\r\n"
                    + "    #if defined(BUMP) || defined(PARALLAX)\r\n"
                    + "        #if defined(TANGENT) && defined(NORMAL)\r\n"
                    + "            TBN = vTBN;\r\n"
                    + "        #else\r\n"
                    + "            TBN = cotangent_frame(normalW, vPositionW, vSplatmapUV);\r\n"
                    + "        #endif\r\n"
                    + "    #elif defined(ANISOTROPIC)\r\n"
                    + "        #if defined(TANGENT) && defined(NORMAL)\r\n"
                    + "            TBN = vTBN;\r\n"
                    + "        #else\r\n"
                    + "            TBN = cotangent_frame(normalW, vPositionW, vSplatmapUV, vec2(1.0, 1.0));\r\n"
                    + "        #endif\r\n"
                    + "    #endif\r\n"
                    + "    #ifdef PARALLAX\r\n"
                    + "        invTBN = transposeMat3(TBN);\r\n"
                    + "    #endif\r\n"
                    + "    normalW = perturbNormalSamplerColor(TBN, normalsColor, 1.0);\r\n"
                    + "#endif\r\n"
                    + "\r\n"
                    + "// Global Atlas Values\r\n"
                    + "float splatTileSize = " + terrainInfo.splatmapAtlas[2].toFixed(4) + ";\r\n"
                    + "float splatTileBits = " + terrainInfo.splatmapAtlas[3].toFixed(4) + ";\r\n"
                    + "float detailTileSize = " + terrainInfo.textureAtlas[2].toFixed(4) + ";\r\n"
                    + "float detailTileBits = " + terrainInfo.textureAtlas[3].toFixed(4) + ";\r\n"
                    + "\r\n"
                    + "// Sample splatmap textures\r\n");
                /////////////////////////////////////////////////////////////////////////////////////////////////////////
                // Sample Each Splatmap Textures
                /////////////////////////////////////////////////////////////////////////////////////////////////////////
                let counter: number = 0;
                for (let index: number = 0; index < terrainInfo.splatmapCount; index++) {
                    counter = (index * 4);
                    const splatmapRect: number[] = terrainInfo["splatmapRect" + index];
                    result += "vec4 splatmapRect" + index + " = vec4(" + splatmapRect[0].toFixed(4) + ", " + splatmapRect[1].toFixed(4) + ", " + splatmapRect[2].toFixed(4) + ", " + splatmapRect[3].toFixed(4) + ");\r\n";
                    result += "vec4 splatmapAlbedo" + index + " = sampleSplatmapAtlas2D(" + splatmapSampler + ", vec2(splatTileSize, splatTileBits), splatmapRect" + index + ", (vSplatmapUV + uvOffset));\r\n";
                    result += "vec4 textureAlbedo" + (counter + 0) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "vec4 textureAlbedo" + (counter + 1) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "vec4 textureAlbedo" + (counter + 2) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "vec4 textureAlbedo" + (counter + 3) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    if (terrainInfo["textureRect" + (counter + 0)]) {
                        const textureRect: number[] = terrainInfo["textureRect" + (counter + 0)];
                        const textureInfo: number[] = terrainInfo["textureInfo" + (counter + 0)];
                        result += "vec4 textureRect" + (counter + 0) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureScale" + (counter + 0) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
                        result += "vec2 textureOffset" + (counter + 0) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureTileUV" + (counter + 0) + " = ((vSplatmapUV + textureOffset" + (counter + 0) + ") * textureScale" + (counter + 0) + ");\r\n";
                        result += "textureAlbedo" + (counter + 0) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 1)]) {
                        const textureRect: number[] = terrainInfo["textureRect" + (counter + 1)];
                        const textureInfo: number[] = terrainInfo["textureInfo" + (counter + 1)];
                        result += "vec4 textureRect" + (counter + 1) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureScale" + (counter + 1) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
                        result += "vec2 textureOffset" + (counter + 1) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureTileUV" + (counter + 1) + " = ((vSplatmapUV + textureOffset" + (counter + 1) + ") * textureScale" + (counter + 1) + ");\r\n";
                        result += "textureAlbedo" + (counter + 1) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 2)]) {
                        const textureRect: number[] = terrainInfo["textureRect" + (counter + 2)];
                        const textureInfo: number[] = terrainInfo["textureInfo" + (counter + 2)];
                        result += "vec4 textureRect" + (counter + 2) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureScale" + (counter + 2) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
                        result += "vec2 textureOffset" + (counter + 2) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureTileUV" + (counter + 2) + " = ((vSplatmapUV + textureOffset" + (counter + 2) + ") * textureScale" + (counter + 2) + ");\r\n";
                        result += "textureAlbedo" + (counter + 2) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 3)]) {
                        const textureRect: number[] = terrainInfo["textureRect" + (counter + 3)];
                        const textureInfo: number[] = terrainInfo["textureInfo" + (counter + 3)];
                        result += "vec4 textureRect" + (counter + 3) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureScale" + (counter + 3) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
                        result += "vec2 textureOffset" + (counter + 3) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
                        result += "vec2 textureTileUV" + (counter + 3) + " = ((vSplatmapUV + textureOffset" + (counter + 3) + ") * textureScale" + (counter + 3) + ");\r\n";
                        result += "textureAlbedo" + (counter + 3) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
                    }
                    result += "splatmapBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", textureAlbedo" + (counter + 0) + ", textureAlbedo" + (counter + 1) + ", textureAlbedo" + (counter + 2) + ", textureAlbedo" + (counter + 3) + ", splatmapBuffer);\r\n";
                    result += "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n";
                    result += "    #if defined(" + normalsSampler.toUpperCase() + ")\r\n";
                    result += "        vec4 normalColor" + (counter + 0) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "        vec4 normalColor" + (counter + 1) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "        vec4 normalColor" + (counter + 2) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    result += "        vec4 normalColor" + (counter + 3) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
                    if (terrainInfo["textureRect" + (counter + 0)]) {
                        const normalScale: number = terrainInfo["normalsScale" + (counter + 0)];
                        result += "        float normalScale" + (counter + 0) + " = " + normalScale.toFixed(4) + ";\r\n";
                        result += "        normalColor" + (counter + 0) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
                        result += "        normalColor" + (counter + 0) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 0) + ".rgb, normalScale" + (counter + 0) + ");\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 1)]) {
                        const normalScale: number = terrainInfo["normalsScale" + (counter + 1)];
                        result += "        float normalScale" + (counter + 1) + " = " + normalScale.toFixed(4) + ";\r\n";
                        result += "        normalColor" + (counter + 1) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
                        result += "        normalColor" + (counter + 1) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 1) + ".rgb, normalScale" + (counter + 1) + ");\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 2)]) {
                        const normalScale: number = terrainInfo["normalsScale" + (counter + 2)];
                        result += "        float normalScale" + (counter + 2) + " = " + normalScale.toFixed(4) + ";\r\n";
                        result += "        normalColor" + (counter + 2) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
                        result += "        normalColor" + (counter + 2) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 2) + ".rgb, normalScale" + (counter + 2) + ");\r\n";
                    }
                    if (terrainInfo["textureRect" + (counter + 3)]) {
                        const normalScale: number = terrainInfo["normalsScale" + (counter + 3)];
                        result += "        float normalScale" + (counter + 3) + " = " + normalScale.toFixed(4) + ";\r\n";
                        result += "        normalColor" + (counter + 3) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
                        result += "        normalColor" + (counter + 3) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 3) + ".rgb, normalScale" + (counter + 3) + ");\r\n";
                    }
                    result += "        normalsBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", normalColor" + (counter + 0) + ", normalColor" + (counter + 1) + ", normalColor" + (counter + 2) + ", normalColor" + (counter + 3) + ", normalsBuffer);\r\n";
                    result += "    #endif\r\n";
                    result += "#endif\r\n";
                    result += "\r\n";
                }
                /////////////////////////////////////////////////////////////////////////////////////////////////////////
                result += ("// Update Color Values\r\n"
                    + colorName + ".rgb = splatmapBuffer.rgb;\r\n"
                    + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
                    + "    #if defined(" + normalsSampler.toUpperCase() + ")\r\n"
                    + "        normalW.rgb = normalsBuffer.rgb;\r\n"
                    + "    #endif\r\n"
                    + "    #if defined(FORCENORMALFORWARD) && defined(NORMAL)\r\n"
                    + "        vec3 faceNormal = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;\r\n"
                    + "        #if defined(TWOSIDEDLIGHTING)\r\n"
                    + "            faceNormal = gl_FrontFacing ? faceNormal : -faceNormal;\r\n"
                    + "        #endif\r\n"
                    + "        normalW *= sign(dot(normalW, faceNormal));\r\n"
                    + "    #endif\r\n"
                    + "    #if defined(TWOSIDEDLIGHTING) && defined(NORMAL)\r\n"
                    + "        normalW = gl_FrontFacing ? normalW : -normalW;\r\n"
                    + "    #endif\r\n"
                    + "#endif\r\n"
                    + "\r\n"
                    + "#endif\r\n"
                    + "\r\n");
            }
            return result;
        }
    }
    BABYLON.RegisterClass("TOOLKIT.UniversalTerrainMaterial", UniversalTerrainMaterial);
}

This my version of Unity Terrain Splatmap Shader that allow you render the a terrain painted up using the Unity Terrain tools

In your screenshot, I don’t understand why it looks for shader code in ShadersWGSL/, because the constructor of PBRCustomMaterial explicitely forces to use GLSL:

(It’s the true parameter which forces GLSL)

Can you set a breakpoint there and check that the parameter is ok?