this is a id colors ready for make real mask rgb value and the real mask rgb the blender plugin make materias.cs and hlsl files config how many canal r b & g have rule of split here 3 by canal r Mat 1 Mat 2 Mat 3 g Mat 4 Mat 5 Mat 6 b Mat 7 Mat 8 Mat 9 --- Post updated --- Hi everyone, I’m currently working on a workflow to generate large-scale terrains from OSM data (and other GIS sources) and bring them into BeamNG as static mesh terrain, with an ID-mask driven material system. My goal is to have a single static mesh terrain (DAE) exported from Blender/UPBGE, and drive all the material distribution using a RGB mask texture and a custom HLSL shader via ShaderData / CustomMaterial. Right now, the geometry loads fine in the World Editor, the material name is correctly mapped, but my HLSL logic is not actually being used – BeamNG seems to fall back to a regular material instead of my CustomMaterial + ShaderData. I’d really appreciate some guidance from the BeamNG team or anyone who has already done this with custom shaders. 1. Context / Intent I’m generating terrain from OSM / GIS data and baking: a high-resolution static mesh (exported as .dae from Blender), a RGB “ID mask” texture where each channel encodes multiple terrain classes (forest, arid, rock, water, road, etc.). The idea is similar to an “ID Color map” used in Substance Painter: the mask is not a classic splatmap, but a quantized ID map with several “slices” per channel. On the BeamNG side, I want a custom shader that: reads the RGB mask, picks the correct texture per “slice” inside each channel (R/G/B), blends the final color by channel weights. This would be very useful for OSM-based maps where the terrain material distribution can be autogenerated from real-world data (landuse, roads, rivers, etc.), instead of hand-painting everything. 2. Current setup (files & structure) Right now I have something like this (example paths): levels/myMap/art/shapes/TerrainMesh/ ├─ terrainMesh.dae ├─ terrainBlendV.hlsl ├─ terrainBlendP.hlsl ├─ terrain_maskid.dds ├─ grass_rocky_d.dds ├─ grass_rocky_n.dds ├─ desert_rocky_d.dds ├─ desert_rocky_n.dds ├─ mntn_white_d.dds ├─ mntn_x2_n.dds ├─ desert_mntn_d.dds ├─ desert_mntn_n.dds ├─ ground_mud_d.dds ├─ ground_mud_n.dds ├─ jungle_mntn_d.dds ├─ jungle_mntn_n.dds └─ materials.cs In Blender, the material on the mesh is just a normal Principled BSDF, and its name is: TerrainMesh The DAE therefore has a material called TerrainMesh, and that’s what I use in mapTo in the script. 3. materials.cs (ShaderData + CustomMaterial) Here is the script I’m currently using: // materials.cs // Auto-generated by my Terrain Material Generator singleton ShaderData(TerrainMesh_Shader) { DXVertexShaderFile = "./terrainBlendV.hlsl"; DXPixelShaderFile = "./terrainBlendP.hlsl"; pixVersion = 3.0; }; singleton CustomMaterial(TerrainMesh_Mat) { mapTo = "TerrainMesh"; shader = TerrainMesh_Shader; sampler["maskTex"] = "terrain_maskid.dds"; // R channel – example mapping sampler["R0_diffuseTex"] = "grass_rocky_d.dds"; sampler["R0_normalTex"] = "grass_rocky_n.dds"; sampler["R1_diffuseTex"] = "desert_rocky_d.dds"; sampler["R1_normalTex"] = "desert_rocky_n.dds"; // G channel sampler["G0_diffuseTex"] = "mntn_white_d.dds"; sampler["G0_normalTex"] = "mntn_x2_n.dds"; sampler["G1_diffuseTex"] = "desert_mntn_d.dds"; sampler["G1_normalTex"] = "desert_mntn_n.dds"; // B channel sampler["B0_diffuseTex"] = "ground_mud_d.dds"; sampler["B0_normalTex"] = "ground_mud_n.dds"; sampler["B1_diffuseTex"] = "jungle_mntn_d.dds"; sampler["B1_normalTex"] = "jungle_mntn_n.dds"; // NOTE: eventually there can be up to 3+ slices per channel (R2/G2/B2, etc.) version = 2.0; }; From what I understand, this should: load terrainBlendV.hlsl / terrainBlendP.hlsl, bind all the samplers to the shader, and apply the CustomMaterial to any mesh material named TerrainMesh. 4. HLSL shaders (simplified) Vertex shader (terrainBlendV.hlsl) // terrainBlendV.hlsl // Vertex shader for terrain mask (Torque3D / BeamNG, SM3) float4x4 WorldViewProj; struct VS_INPUT { float4 position : POSITION0; float3 normal : NORMAL0; float2 uv : TEXCOORD0; }; struct VS_OUTPUT { float4 position : POSITION0; float3 normal : TEXCOORD1; float2 uv : TEXCOORD0; }; VS_OUTPUT main( VS_INPUT IN ) { VS_OUTPUT OUT; OUT.position = mul(IN.position, WorldViewProj); OUT.normal = IN.normal; OUT.uv = IN.uv; return OUT; } Pixel shader (terrainBlendP.hlsl – mask logic) For now I’m focusing only on the diffuse logic. Conceptually: I read the RGB mask (maskTex). Each channel R/G/B is quantized into 3 “slices”. Each slice selects one texture (R0/R1/R2, G0/G1/G2, B0/B1/B2). The final color is a weighted mix of R/G/B contributions. Simplified Shader Model 3 version: // terrainBlendP.hlsl // Pixel shader : blend terrain using RGB mask + 3 slices per channel // Target: Shader Model 3.0 (Torque3D / BeamNG) // Mask sampler2D maskTex; // R channel (3 slices) sampler2D R0_diffuseTex; sampler2D R1_diffuseTex; sampler2D R2_diffuseTex; // G channel (3 slices) sampler2D G0_diffuseTex; sampler2D G1_diffuseTex; sampler2D G2_diffuseTex; // B channel (3 slices) sampler2D B0_diffuseTex; sampler2D B1_diffuseTex; sampler2D B2_diffuseTex; struct PS_INPUT { float4 position : POSITION0; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; }; // Simple 3-slice quantization of a 0..1 value int getSliceIndex( float v ) { if (v < 0.3333f) return 0; else if (v < 0.6667f) return 1; else return 2; } float4 main( PS_INPUT IN ) : COLOR0 { float3 m = tex2D( maskTex, IN.uv ).rgb; int idxR = getSliceIndex( m.r ); int idxG = getSliceIndex( m.g ); int idxB = getSliceIndex( m.b ); float4 colR, colG, colB; // R if (idxR == 0) colR = tex2D( R0_diffuseTex, IN.uv ); else if (idxR == 1) colR = tex2D( R1_diffuseTex, IN.uv ); else colR = tex2D( R2_diffuseTex, IN.uv ); // G if (idxG == 0) colG = tex2D( G0_diffuseTex, IN.uv ); else if (idxG == 1) colG = tex2D( G1_diffuseTex, IN.uv ); else colG = tex2D( G2_diffuseTex, IN.uv ); // B if (idxB == 0) colB = tex2D( B0_diffuseTex, IN.uv ); else if (idxB == 1) colB = tex2D( B1_diffuseTex, IN.uv ); else colB = tex2D( B2_diffuseTex, IN.uv ); float wR = m.r; float wG = m.g; float wB = m.b; float total = max(wR + wG + wB, 0.0001f); float4 outColor = (colR * wR + colG * wG + colB * wB) / total; return outColor; } (I plan to later add proper normal mapping and lighting once the basic mask logic works.) 5. What actually happens in BeamNG When I: Launch the map, Open the World Editor, Create a Mesh / Static Object and point it to terrainMesh.dae, … the mesh appears, but: BeamNG is not using my HLSL logic (no ID mask behavior), It looks like a regular material is used (standard texture behaviour), In previous tests with a more “modern” HLSL syntax (Texture2D, SamplerState, .Sample() and SV_POSITION), nothing happened either → so I rewrote it for SM3, but the result stays the same. In other words: the engine loads the DAE and a material, but it behaves like a basic material, not like my CustomMaterial(TerrainMesh_Mat) using TerrainMesh_Shader. I suspect I’m either: placing the materials.cs in the wrong place, missing some required syntax / naming convention for CustomMaterial, or missing some BeamNG-specific step for custom HLSL shaders on static meshes. 6. What I’d like to ask the devs / community Is this CustomMaterial + ShaderData approach still supported for map objects / static mesh terrain in the current BeamNG versions? Is there a recommended path / location for materials.cs when using a custom shader on a static mesh (.dae) in a level? For example: is levels/myMap/art/shapes/TerrainMesh/materials.cs the right place? Are there any special requirements for CustomMaterial to take precedence over a default Material or auto-generated material for mapTo = "TerrainMesh"? Do I need to avoid having any other Material with the same mapTo? For Shader Model 3.0 (pixVersion = 3.0), is the sampler2D / tex2D approach and the semantics (POSITION0, COLOR0, etc.) correct for BeamNG? Is there maybe a better / more modern way (using the built-in terrain system, or a more recent material system) to implement an ID-mask-driven terrain shader for static mesh terrain? My main objective is to have a robust, automatable mask-driven terrain material system for huge OSM-based maps, so any advice, best practices, or example of a working CustomMaterial + HLSL setup for BeamNG would be incredibly helpful. Thanks a lot for reading, and for any pointers you can share! If needed, I can provide a minimal test mod (DAE + textures + materials.cs + HLSL) to reproduce the issue. Best regards,