Holy Shit… I think i finally cracked terrain shaders. Its actually alot of shit to learn. But since i could not get anybody to actually contribute any real world terrain shader code for the toolkit… Finally … I said Fuck it… I had to go to the drawing board and figure this whole advanced terrain shader thing out for myself.
1… The Texture Atlas(s) Must be perfect. Can be any filter mode (Point, Bilinear or Trilinear). Each Tile Must be squared (Ex: 1024 x 1024) and the same resolution. DO NOT need to encode the entire mip chain into atlas either.
2… Must use a manual calculation for the desired mip map level in your shader. In a terrain shader. the further out should get more blurry. Have to use texture2DLodExt no way around that and keep it SIMPLE
3… Must use a fract for tiling. Cant get around that at all. The magic is in recalculating the uv after the fract using bilinear filtering.
4… The splatmap atlas also need to be just as perfect as the detail texture atlas. The higher the resolution the better. Up to a point. i like 1024 splatmap and 1024 detail resolution textures.
5… Sample tiles from the splatmap, detail and normal atlas(s) with the correct gamma correction and mip map level selection.
6… Mixing all the texture layer colors can be tricky. Everybody uses the mix glsl shader function. But that sucks for me. I cant get the fine grain detail like from a high res Unity Terrain. So i made my own color blending function that works like fucking charm
All my shader functions are short and sweet and very easy to understand and use
Mackey GLSL Terrain Shader Functions. These four simple functions i wrote are all you need
//////////////////////////////////////////////////////////////////////////////////////
/// CalculateMipmapLevel
//////////////////////////////////////////////////////////////////////////////////////
/// - uvs is the texture uv
/// - size is the texture size
//////////////////////////////////////////////////////////////////////////////////////
float calculateMipmapLevel(const in vec2 uvs, const in vec2 size)
{
vec2 dx = dFdx(uvs * size.x);
vec2 dy = dFdy(uvs * size.y);
float d = max(dot(dx, dx), dot(dy, dy));
return 0.4 * log2(d);
}
//////////////////////////////////////////////////////////////////////////////////////
/// SampleTextureAtlas2D
//////////////////////////////////////////////////////////////////////////////////////
/// - atlas is the texture atlas from which to sample a tile
/// - gamma is the desired gamma corection (NoCorrection = 1.0 - LinearCorrection = 2.2 - GammaCorrection = 0.454545)
/// - tile is the texture tile size in pixels and texture tile bits (ex.: a 1024 tile is 10 bits, a 512 tile is 9 Bits)
/// - rect is the position in atlas and the inverse of the number of horizontal tiles (ex.: 4 tiles -> 0.25 x 2 tiles -> 0.5)
/// - uvs are the texture coordinates of the pixel *inside the tile*
/// - lod is the desired mip map bluring (-1.0 for auto mip mapping bluring)
//////////////////////////////////////////////////////////////////////////////////////
vec4 sampleTextureAtlas2D(const in sampler2D atlas, const in float gamma, const in vec2 tile, const in vec4 rect, in vec2 uvs, in float lod)
{
if (lod < 0.0) lod = clamp(calculateMipmapLevel(uvs, vec2(tile.x, tile.x)), 0.0, tile.y); // Tile Info (tile.xy)
float size = pow(2.0, tile.y - lod); // Tile Bits (tile.y)
float sizex = size / rect.z; // Tile Width (rect.z)
float sizey = size / rect.w; // Tile Height (rect.w)
uvs = fract(uvs); // Perfrom Tiling (fract)
uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x; // Tile Position X (rect.x)
uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y; // Tile Position Y (rect.y)
vec4 color = texture2DLodEXT(atlas, uvs, lod);
if (gamma != 1.0) {
color.r = pow(color.r, gamma);
color.g = pow(color.g, gamma);
color.b = pow(color.b, gamma);
}
return color;
}
//////////////////////////////////////////////////////////////////////////////////////
/// SampleSplatmapAtlas2D
//////////////////////////////////////////////////////////////////////////////////////
/// - atlas is the texture atlas from which to sample a tile
/// - gamma is the desired gamma corection (NoCorrection = 1.0 - LinearCorrection = 2.2 - GammaCorrection = 0.454545)
/// - tile is the texture tile size in pixels and texture tile bits (ex.: a 1024 tile is 10 bits, a 512 tile is 9 Bits)
/// - rect is the position in atlas and the inverse of the number of horizontal tiles (ex.: 4 tiles -> 0.25 x 2 tiles -> 0.5)
/// - uvs are the texture coordinates of the pixel *inside the tile*
//////////////////////////////////////////////////////////////////////////////////////
vec4 sampleSplatmapAtlas2D(const in sampler2D atlas, const in float gamma, const in vec2 tile, const in vec4 rect, in vec2 uvs)
{
float size = pow(2.0, tile.y); // Tile Bits (tile.y)
float sizex = size / rect.z; // Tile Width (rect.z)
float sizey = size / rect.w; // Tile Height (rect.w)
uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x; // Tile Position X (rect.x)
uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y; // Tile Position Y (rect.y)
vec4 color = texture2D(atlas, uvs);
if (gamma != 1.0) {
color.r = pow(color.r, gamma);
color.g = pow(color.g, gamma);
color.b = pow(color.b, gamma);
}
return color;
}
//////////////////////////////////////////////////////////////////////////////////////
/// BlendSplatmapTileColors
//////////////////////////////////////////////////////////////////////////////////////
/// - splatmap is the mix color map
/// - color1 splatmap texture color 1
/// - color2 splatmap texture color 2
/// - color3 splatmap texture color 3
/// - color4 splatmap texture color 4
/// - mixbuffer is the final mixing buffer
//////////////////////////////////////////////////////////////////////////////////////
vec3 blendSplatmapTileColors(const in vec4 splatmap, in vec4 color1, in vec4 color2, in vec4 color3, in vec4 color4, in vec3 mixbuffer)
{
mixbuffer += (color1.rgb * splatmap.r);
mixbuffer += (color2.rgb * splatmap.g);
mixbuffer += (color3.rgb * splatmap.b);
mixbuffer += (color4.rgb * splatmap.a);
return mixbuffer;
}
//////////////////////////////////////////////////////////////////////////////////////
Example Usage:
splatmapBuffer = vec3(0.0, 0.0, 0.0);
// ..
// GLOBAL TILE INFO
// ..
float splatTileSize = 512.0;
float splatTileBits = 9.0;
float atlasTileSize = 1024.0;
float atlasTileBits = 10.0;
// ..
// SPLATMAP TEXTURE 0
// ..
vec4 splatmapRect0 = vec4(0.0, 1.0, 0.5, 0.5);
vec2 splatmapTileUV0 = (vAlbedoUV + uvOffset);
vec4 splatmapAlbedo0 = sampleSplatmapAtlas2D(splatmapTextures, noCorrection, vec2(splatTileSize, splatTileBits), splatmapRect0, splatmapTileUV0);
// ..
vec4 textureRect0 = vec4(0.0, 3.0, 0.25, 0.25);
vec2 textureScale0 = vec2(1.0, 1.0);
vec2 textureOffset0 = vec2(0.0, 0.0);
vec2 textureTileUV0 = ((vAlbedoUV + textureOffset0) * textureScale0);
vec4 textureAlbedo0 = sampleTextureAtlas2D(terrainTextures, linearCorrection, vec2(atlasTileSize, atlasTileBits), textureRect0, textureTileUV0, autoMipLevel);
// ..
vec4 textureRect1 = vec4(1.0, 3.0, 0.25, 0.25);
vec2 textureScale1 = vec2(100.0, 75.0);
vec2 textureOffset1 = vec2(0.0, 0.0);
vec2 textureTileUV1 = ((vAlbedoUV + textureOffset1) * textureScale1);
vec4 textureAlbedo1 = sampleTextureAtlas2D(terrainTextures, linearCorrection, vec2(atlasTileSize, atlasTileBits), textureRect1, textureTileUV1, autoMipLevel);
// ..
vec4 textureRect2 = vec4(2.0, 3.0, 0.25, 0.25);
vec2 textureScale2 = vec2(100.0, 75.0);
vec2 textureOffset2 = vec2(0.0, 0.0);
vec2 textureTileUV2 = ((vAlbedoUV + textureOffset2) * textureScale2);
vec4 textureAlbedo2 = sampleTextureAtlas2D(terrainTextures, linearCorrection, vec2(atlasTileSize, atlasTileBits), textureRect2, textureTileUV2, autoMipLevel);
// ..
vec4 textureRect3 = vec4(3.0, 3.0, 0.25, 0.25);
vec2 textureScale3 = vec2(100.0, 75.0);
vec2 textureOffset3 = vec2(0.0, 0.0);
vec2 textureTileUV3 = ((vAlbedoUV + textureOffset3) * textureScale3);
vec4 textureAlbedo3 = sampleTextureAtlas2D(terrainTextures, linearCorrection, vec2(atlasTileSize, atlasTileBits), textureRect3, textureTileUV3, autoMipLevel);
splatmapBuffer = blendSplatmapTileColors(splatmapAlbedo0, textureAlbedo0, textureAlbedo1, textureAlbedo2, textureAlbedo3, splatmapBuffer);
surfaceAlbedo = splatmapBuffer.rgb;
Eazy Fuckin Peazy
(Yeah Right)