That’s a great article, wonderfully presented too, with all the before-and-after animations. I hadn’t heard of albedo textures before, had to look it up.
One thing I do when texturing terrain which makes an enormous difference, but is quite a bit simpler to implement than these next-gen techniques, is texture blending between two textures (I guess it’s kind of similar to the “splat map” technique discussed in the article).
You need two textures that are around the same scale (not hard, as lots of tileable textures tend to be 512x512 or 1024x1024 anyway), as this method uses the same texCoords for both textures, using the tileable texCoord technique.
How you generate the “splat map” is up to you.
Here, I use a noise map, to create organic patches:
https://youtu.be/JPqP_nz-GdQ
It really helps to disguise the fact that the same texture is repeating over-and-over.
In the below video, the splat map is related to height, as the textures are meant to reflect above/ below the tree-line:
https://youtu.be/G_j6mYks8Sc
Here’s the shader. You can put the texture blend variable in its own vertex attribute float, or, to save memory and the hassle of setting up a custom vertex attribute, you can hijack an unused value, such as the alpha of the colour attribute (as a vertex attribute float takes up as much memory as a vec4).
I think if you added a third texture to the mix, it would look really cool.
Remember that when setting up the shader, the second texture has to be assigned a little differently, as it is not in the “built-in” slot:
m.texture = "Dropbox:Barren Reds"
m.shader.texture2 = "Dropbox:Crystal Mountain"
The texture blending is done with GLES’s mix
command:
lowp vec4 pixel = mix(texture2D( texture, vec2(fract(vTexCoord.x), fract(vTexCoord.y))), texture2D( texture2, vec2(fract(vTexCoord.x), fract(vTexCoord.y))), vColor.a); //use alpha to mix two textures
the shader:
GroundShader = { --texture blending!
vert = [[
uniform mat4 modelViewProjection;
uniform mat4 modelMatrix;
uniform vec4 lightColor; //--directional light colour
uniform vec4 light; //--directional light direction xyz0
attribute vec4 position;
// attribute lowp float texBlend; //0=100% texture1, 1=100% texture2...
attribute vec4 color; // ...or put the texBlend value into the alpha
attribute vec2 texCoord; //=mesh width / texture width
attribute vec3 normal;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying vec4 vDirectDiffuse;
void main()
{
vTexCoord = texCoord;
vPosition = position * modelMatrix;
vec4 norm = normalize(modelMatrix * vec4(normal,0.0));
vDirectDiffuse = lightColor * max( 0.0, dot( norm, light )) * 1.5; //last is direct strength
vColor = color;
gl_Position = modelViewProjection * position;
}
]],
frag=[[
precision highp float;
uniform lowp sampler2D texture;
uniform lowp sampler2D texture2; //second texture
uniform lowp vec4 aerial; //colour of fog/aerial perspective
uniform float ambient; //power of ambient light
uniform vec4 eye; //xyz1
uniform float fogRadius;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying vec4 vDirectDiffuse;
void main()
{
lowp vec4 pixel = mix(texture2D( texture, vec2(fract(vTexCoord.x), fract(vTexCoord.y))), texture2D( texture2, vec2(fract(vTexCoord.x), fract(vTexCoord.y))), vColor.a); //use alpha to mix two textures
lowp vec4 ambLight = pixel * aerial * ambient; //vColor was here
// ambLight.a = 1.;
lowp vec4 directional = pixel * vDirectDiffuse;
float dist = clamp(1.0-distance(vPosition.xyz, eye.xyz)/fogRadius+0.1, 0.0, 1.1); // calculate distance from eye
vec4 totalColor = mix(aerial, clamp(ambLight + directional,0.,1.), dist * dist); //fog varies by distance squared
totalColor.a = 1.;
gl_FragColor = totalColor;
}
]]
}