Basic Texture Splatting, need help

Hi everyone,

I am new to Three.js & 3D

My goal is to MIX multiple textures to a single face, with % opacity
texture splatting https://en.wikipedia.org/wiki/Texture_splatting

some questions (beginner)

  1. is there a basic example out there ? with three js; i can’t find :frowning:
    Something very basic, to understand the process

a Plan
2 textures GREEN & WHITE.
<Green - Mix of green & white + White>

dot


the best example i could found is this one :


It’s very nice, but i have 2 majors issues wih this ;

  1. This section,that seems the most important, sounds like magic black voodoo Math

vec4 water = (smoothstep(0.01, 0.25, vAmount) - smoothstep(0.24, 0.26, vAmount)) * texture2D( oceanTexture, vUV * 10.0 );
vec4 sandy = (smoothstep(0.24, 0.27, vAmount) - smoothstep(0.28, 0.31, vAmount)) * texture2D( sandyTexture, vUV * 10.0 );
vec4 grass = (smoothstep(0.28, 0.32, vAmount) - smoothstep(0.35, 0.40, vAmount)) * texture2D( grassTexture, vUV * 20.0 );
vec4 rocky = (smoothstep(0.30, 0.50, vAmount) - smoothstep(0.40, 0.70, vAmount)) * texture2D( rockyTexture, vUV * 20.0 );
vec4 snowy = (smoothstep(0.50, 0.65, vAmount)) * texture2D( snowyTexture, vUV * 10.0 );
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0) + water + sandy + grass + rocky + snowy;

can someone explain me further ?
i Guess some “segmentation” 0.01, 0.25 , 0.24, 0.27 … but its all i can understand.

smoothstep(0.01, 0.25, vAmount) - smoothstep(0.24, 0.26, vAmount)) * texture2D( oceanTexture, vUV * 10.0

// - 0.24 .26 ? why 24-26
// … * 10 .? why * 10
// last segment why does it stop at 50-65 and not 99 ?

I guess the global idea is “this pixel has 5% grass texture + 10% ocean texture + 25% water…” but my brain can make no link between this idea & the math behind…

I ll welcome anyone who can help me :slight_smile: :slight_smile:

  1. Why use Shader ?
    I see Shader as “live - perf - post processing”,
    .

lets say i have a 128*128 2D Array, my LAND & “type”
Array[10][11] = {height: 1 ; type: FOREST }
Array[10][15] = {height:6 ; type: SNOW}

i have a FOREST texture
i have a SNOW texture

at render, i am looking for this kind of result

Array[10][11] => apply FOREST TEXTURE
Array[10][15] => apply SNOW TEXTURE

Array [10][12] => Apply 75% FOREST + 25 % SNOW
Array [10][13] => Apply 50% FOREST + 50 % SNOW
Array [10][14] => Apply 25% FOREST + 75 % SNOW

And it will never change. Am i not supposed to pre-compute a static “RGB” myself at LOADING, instead of postprocessing shader ?

Is the previous example still ok, lets say, if you have 25 differents grounds instead of 5 ?


Also in some other tutorials i ve seen, ppl are explaning

AddTexture (texture1 ) AlphaChannel = "opactity"

AddTexture (texture2 )
AddTexture (texture3 )

Playing with the AlphaChannel to "combine" and there is no mention of shader doing the job... ..i thought texture opacity was enough

I am a bit lost,

Thx for reading and help :slight_smile:

It’s pretty simply basically, you only blend the chain of all layers together. But this approach also as a performance bottleneck if you require a lot layers, and especially when they also have bump/normal maps, what will double or tripple the amount of texture fetches, since this approach will always sample from all of them.

25 is a lot, since you change the texture you sample from with each call. To reduce the context switch you can store 4 weights in one weight-texture instead having 25, as it would mean 50 samples and different textures. You can test and see if it will suffice your needs and target devices with everthing else required in your game.

Another approach i made for a planetary engine with very varying and continuous terrain is a spatial weighted map with a tile atlas. Using a 4 channel texture for 4 weights and another 4 channel for the index of the tiles in the atlas, it allows to use for example up to 64 512x512 tiles/layers in a 4k atlas texture. The weightmap is more tricky to deal with, since not more than 4 layers can be stored in one pixel, what isn’t really a limitation though. It can be avoided if you implment the terrain brushing yoursef, what would be required for this method anyway.

It reduces the fetches to be constantly 6 with access to, in this example 64 different layers. If being scalable isn’t a high priority i would just go with the standard texture splatting approach and see how it works with the amount of layers you need.

Thx for you fast reply

I’ve understand your global idea, nice. // but i am still learning, to high to implement for me yet

for my others questions :

  1. shaders: OK

  2. Math : OK, after some hours & experiments, tilt :slight_smile:

  3. Basic example :slight_smile:

I want to try an “alpha map” basic version, thought this would do the job, but no

// load WATER texture 
var oceanTexture = new THREE.TextureLoader().load( './textures/water.jpg' );
oceanTexture.wrapS = oceanTexture.wrapT = THREE.RepeatWrapping; 

// load WATER Alpha MAP   = % of water at this postion   ->  [  BLACK  GREY  WHITE ]  like
var alphaOcean = new THREE.TextureLoader().load( './hm.png' );
alphaOcean.wrapS = alphaOcean.wrapT = THREE.RepeatWrapping; 
    
// load SAND texture
var sandyTexture = new THREE.TextureLoader().load( './textures/sand.jpg' );
sandyTexture.wrapS = sandyTexture.wrapT = THREE.RepeatWrapping;

// load SAND ALPHA MAP  :  % of sand at x,y :     the opposite of hm1   [  WHITE GREY  BLACK ]  like
var alphaSand = new THREE.TextureLoader().load( './hm2.png' );
alphaSand.wrapS = alphaSand.wrapT = THREE.RepeatWrapping; 

// create 2 distincts materials  
//WATER
 var planeMaterial = new THREE.MeshPhongMaterial(
            {
                alphaMap : alphaOcean,
                map : oceanTexture,
                transparent : true
            }
        );

// SAND
var planeMaterial2 = new THREE.MeshPhongMaterial({
                alphaMap : alphaSand,
                map : sandyTexture,
                transparent : true
            }
        );

// Render

// TEST 1
var plane = new THREE.Mesh(planeGeometry, planeMaterial);  // THIS IS OK  -> half WATER & nothing

// TEST 2
var plane = new THREE.Mesh(planeGeometry, planeMaterial2);  // THIS IS OK ->  nothing & half sand

// WANTED 
var plane = new THREE.Mesh(planeGeometry, [ planeMaterial, planeMaterial2 ]);

//  KO  -> it only display the first texture  < with right amount according to his alpha map>, 
//  we pass an **ARRAY** of textures, it doesn't combine the two ?
//  something is wrong

}

And, according this is possible, what is the right way to go : , this way (easier to write), or shader way ?

Best regards,

Been there… =]

Passing multiple materials will not superimpose them or combine them. It allows a mesh to have a different material on each face (via Geometry.faces[i].materialIndex), not on the same face.

I think it actually is possible, i think i’ve seen an example some where. You add multiple groups with the same range.

oh, i forgot a 3d object has multiple faces
This is a good point

while now afterreading many others docs, i am convinced shader is the way to go
(https://www.khronos.org/opengl/wiki/Texture_Combiners )
i am kinda sure it’s an opengl “core” possibility

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

const float partOf1 = 0.5f;

glBindTexture(GL_TEXTURE_2D, texture1);
glColor4f(1.0f, 1.0f, 1.0f, partOf1 );
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f( -1.0f, -1.0f, 0.0);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0);
glTexCoord2f(0.0f, 1.0f); glVertex3f( -1.0f, 1.0f, 0.0);
glEnd();

glBindTexture(GL_TEXTURE_2D, texture2);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f - partOf1);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f( -1.0f, -1.0f, 0.0);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0);
glTexCoord2f(0.0f, 1.0f); glVertex3f( -1.0f, 1.0f, 0.0);
glEnd();

so render 2 or more texture on the same face with an alpha transparaent is not a basic feature of three ?
Transparency is just to make a whole object transparent ?

I d like to learn how to do it.
not saying its > shader, just curious

EDIT : its work perfecty with 2 Mesh at same position… but i doubt its the way to go ?
res


Hum so let me sum up the situation :, correct me if i am wrong … this is my conclusion
:
All the texture splattering litterature with an alpha transparence texture map , is obsolete ?
The new and 100% better way to do is :

  • setup textures as “global variables” for shaders
  • use some extra fake-textures, not as a visual information, but to pass “physical-logical” informations for x,y, so the shader can compute – whatever –

for exemple, lets say i want altitude, longitude, % humidty, age, surface type … to determine influence the final apparence, TODO :

  • must create a “fake texture”
  • fakeTexture.r = “% humidty”,
  • fakeTexture.g = type
  • fakeTexture.b = “% water”
  • fakeTexture.a = “% ground”…

    more than 4 attributes ?no problem juste pass another “fake texture” as a second information layer

push all this to the shader then math cookbook to combine, “pixel view”

Correct ?

Using a mesh per layer isn’t a good solution, you also will experience z-fighting. The first approach you mentionend with each layer blending in the shader code would be the way to go.

You pass for each layer a weight texture which goes from black to white defining the contribution of each layer. To do this more efficiently than using 25 weightmaps for 25 tiles, you can use 1 RGBA weightmap for 4 layers. But just go with 1 weightmap per layer at first.

This example uses the height instead weightmaps, that means it isn’t brushed manually, while you still could add/combine this.
http://stemkoski.github.io/Three.js/Shader-Heightmap-Textures.html

I could make it work… but hardware limit is 16 textures.

< THREE.WebGLRenderer: Trying to use 16 texture units while this GPU supports only 16>

ok so lets start again with a texture atlas

EDIT : Problem sovled !

to correctly bind my 1 ROW atlas
“index 8” :

xmin = (8.0 * sizeOfTexture) / totalPixelX;
xmax = ((8.0 * sizeOfTexture) + sizeOfTexture) / totalPixelX;
vec4 paintSavanne =  texture2D( textureAtlas,vec2( mix( xmin, xmax, vUV.x),vUV.y  ) ) ;