Skip to content

Commit

Permalink
Reorganize and document shader. Remove texture offset
Browse files Browse the repository at this point in the history
  • Loading branch information
TokisanGames committed Oct 31, 2023
1 parent 8a1ff54 commit 7240ecc
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 70 deletions.
184 changes: 114 additions & 70 deletions src/shaders/main.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,24 @@
R"(shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;

/* This shader is generated based upon the debug views you have selected.
* The terrain function depends on this shader. So don't change:
* - vertex positioning in vertex()
* - terrain normal calculation in fragment()
*
* Most will only want to customize the material calculation and PBR application in fragment()
*
* Uniforms that begin with _ are private and will not display in the inspector. However,
* you can set them via code. You are welcome to create more of your own hidden uniforms.
*
* This system only supports albedo, height, normal, roughness. Most textures don't need the other
* PBR channels. Height can be used as an approximation for AO. For the rare textures do need
* additional channels, you can add maps for that one texture. e.g. an emissive map for lava.
*
*/

uniform float _region_size = 1024.0;
uniform float _region_pixel_size = 0.0009765625; // 1.0 / 1024.0
uniform float _region_texel_size = 0.0009765625; // = 1./1024.
uniform int _region_map_size = 16;
uniform int _region_uv_limit = 8;
uniform int _region_map[256];
Expand All @@ -19,45 +35,91 @@ uniform float _texture_uv_scale_array[32];
uniform float _texture_uv_rotation_array[32];
uniform vec4 _texture_color_array[32];

//INSERT: WORLD_NOISE1
varying vec3 v_vertex; // World coordinate vertex location

vec3 unpack_normal(vec4 rgba) {
vec3 n = rgba.xzy * 2.0 - vec3(1.0);
n.z *= -1.0;
return n;
}
////////////////////////
// Vertex
////////////////////////

vec4 pack_normal(vec3 n, float a) {
n.z *= -1.0;
return vec4((n.xzy + vec3(1.0)) * 0.5, a);
}

// Takes location in world space coordinates, returns ivec3 with:
// XY: (0-_region_size) coordinates within the region
// Takes in UV world space coordinates, returns ivec3 with:
// XY: (0 to _region_size) coordinates within a region
// Z: region index used for texturearrays, -1 if not in a region
ivec3 get_region(vec2 uv) {
ivec3 get_region_uv(vec2 uv) {
uv *= _region_texel_size;
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
int index = _region_map[ pos.y*_region_map_size + pos.x ] - 1;
int index = _region_map[ pos.y * _region_map_size + pos.x ] - 1;
return ivec3(ivec2((uv - _region_offsets[index]) * _region_size), index);
}

// vec3 form of get_region. Same return values
vec3 get_regionf(vec2 uv) {
// Takes in UV2 region space coordinates, returns vec3 with:
// XY: (0 to 1) coordinates within a region
// Z: region index used for texturearrays, -1 if not in a region
vec3 get_region_uv2(vec2 uv) {
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
int index = _region_map[ pos.y*_region_map_size + pos.x ] - 1;
int index = _region_map[ pos.y * _region_map_size + pos.x ] - 1;
return vec3(uv - _region_offsets[index], float(index));
}

//INSERT: WORLD_NOISE1
// 1 lookup
float get_height(vec2 uv) {
float height = 0.0;
vec3 region = get_regionf(uv);
vec3 region = get_region_uv2(uv);
if (region.z >= 0. && abs(uv.x) < float(_region_uv_limit) && abs(uv.y) < float(_region_uv_limit)) {
height = texture(_height_maps, region).r;
}
//INSERT: WORLD_NOISE2
return height;
}

void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;

// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz;

// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = (UV + vec2(0.5)) * _region_texel_size;

// Get final vertex location and save it
VERTEX.y = get_height(UV2);
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;

// Flatten normal to be calculated in fragment()
NORMAL = vec3(0, 1, 0);
}

////////////////////////
// Fragment
////////////////////////

// 4 lookups
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
float left = get_height(uv + vec2(-_region_texel_size, 0));
float right = get_height(uv + vec2(_region_texel_size, 0));
float back = get_height(uv + vec2(0, -_region_texel_size));
float front = get_height(uv + vec2(0, _region_texel_size));
vec3 horizontal = vec3(2.0, right - left, 0.0);
vec3 vertical = vec3(0.0, back - front, 2.0);
vec3 normal = normalize(cross(vertical, horizontal));
normal.z *= -1.0;
tangent = cross(normal, vec3(0, 0, 1));
binormal = cross(normal, tangent);
return normal;
}

vec3 unpack_normal(vec4 rgba) {
vec3 n = rgba.xzy * 2.0 - vec3(1.0);
n.z *= -1.0;
return n;
}

vec4 pack_normal(vec3 n, float a) {
n.z *= -1.0;
return vec4((n.xzy + vec3(1.0)) * 0.5, a);
}

float random(in vec2 xy) {
return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453);
}
Expand All @@ -68,7 +130,7 @@ float blend_weights(float weight, float detail) {
return result;
}

vec4 depth_blend(vec4 a_value, float a_bump, vec4 b_value, float b_bump, float t) {
vec4 height_blend(vec4 a_value, float a_bump, vec4 b_value, float b_bump, float t) {
float ma = max(a_bump + (1.0 - t), b_bump + t) - 0.1;
float ba = max(a_bump + (1.0 - t) - ma, 0.0);
float bb = max(b_bump + t - ma, 0.0);
Expand All @@ -79,29 +141,13 @@ vec2 rotate(vec2 v, float cosa, float sina) {
return vec2(cosa * v.x - sina * v.y, sina * v.x + cosa * v.y);
}

vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
// Normal calc
// Control map is also sampled 4 times, so in theory we could reduce the region samples to 4 from 8,
// but control map sampling is slightly different with the mirroring and doesn't work here.
// The region map is very, very small, so maybe the performance cost isn't too high
float left = get_height(uv + vec2(-_region_pixel_size, 0));
float right = get_height(uv + vec2(_region_pixel_size, 0));
float back = get_height(uv + vec2(0, -_region_pixel_size));
float fore = get_height(uv + vec2(0, _region_pixel_size));
vec3 horizontal = vec3(2.0, right - left, 0.0);
vec3 vertical = vec3(0.0, back - fore, 2.0);
vec3 normal = normalize(cross(vertical, horizontal));
normal.z *= -1.0;
tangent = cross(normal, vec3(0, 0, 1));
binormal = cross(normal, tangent);
return normal;
}

// 2-4 lookups
vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float total_weight, inout vec4 out_normal) {
float material = index.r * 255.0;
float r = random(uv_center) * PI;
float rand = r * _texture_uv_rotation_array[int(material)];
vec2 rot = vec2(cos(rand), sin(rand));
uv *= .5; // Allow larger numbers on uv scale array - move to C++
vec2 matUV = rotate(uv, rot.x, rot.y) * _texture_uv_scale_array[int(material)];

vec4 albedo = texture(_texture_array_albedo, vec3(matUV, material));
Expand All @@ -121,8 +167,8 @@ vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float
n = unpack_normal(normal2);
normal2.xz = rotate(n.xz, rot2.x, -rot2.y);

albedo = depth_blend(albedo, albedo.a, albedo2, albedo2.a, index.b);
normal = depth_blend(normal, albedo.a, normal2, albedo2.a, index.b);
albedo = height_blend(albedo, albedo.a, albedo2, albedo2.a, index.b);
normal = height_blend(normal, albedo.a, normal2, albedo2.a, index.b);
}

normal = pack_normal(normal.xyz, normal.a);
Expand All @@ -132,58 +178,56 @@ vec4 get_material(vec2 uv, vec4 index, vec2 uv_center, float weight, inout float
return albedo * weight;
}

void vertex() {
vec3 world_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
UV = world_vertex.xz * 0.5; // Without this, individual material UV needs to be very small.
UV2 = ((world_vertex.xz + vec2(0.5)) / vec2(_region_size));
VERTEX.y = get_height(UV2);
NORMAL = vec3(0, 1, 0);
}

void fragment() {
// Calculate Terrain World Normals
// Calculate Terrain Normals. 4 lookups
vec3 w_tangent, w_binormal;
vec3 w_normal = get_normal(UV2, w_tangent, w_binormal);
NORMAL = mat3(VIEW_MATRIX) * w_normal;
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;

// Calculated Weighted Material
// source : https://github.com/cdxntchou/IndexMapTerrain
// black magic which I don't understand at all. Seems simple but what and why?
vec2 pos_texel = UV2 * _region_size + 0.5;
vec2 pos_texel00 = floor(pos_texel);
vec4 mirror = vec4(fract(pos_texel00 * 0.5) * 2.0, 1.0, 1.0);
// https://github.com/cdxntchou/IndexMapTerrain/blob/master/Assets/Terrain/Shaders/IndexedTerrainShader.shader
vec2 texel_pos = UV;
vec2 texel_pos_floor = floor(UV);

// Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY
vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0);
// And the opposite grid in ZW
mirror.zw = vec2(1.0) - mirror.xy;

ivec3 index00UV = get_region((pos_texel00 + mirror.xy) * _region_pixel_size);
ivec3 index01UV = get_region((pos_texel00 + mirror.xw) * _region_pixel_size);
ivec3 index10UV = get_region((pos_texel00 + mirror.zy) * _region_pixel_size);
ivec3 index11UV = get_region((pos_texel00 + mirror.zw) * _region_pixel_size);
// Get the region and control map ID of four vertices surrounding this pixel
ivec3 index00UV = get_region_uv(texel_pos_floor + mirror.xy);
ivec3 index01UV = get_region_uv(texel_pos_floor + mirror.xw);
ivec3 index10UV = get_region_uv(texel_pos_floor + mirror.zy);
ivec3 index11UV = get_region_uv(texel_pos_floor + mirror.zw);

vec4 index00 = texelFetch(_control_maps, index00UV, 0);
vec4 index01 = texelFetch(_control_maps, index01UV, 0);
vec4 index10 = texelFetch(_control_maps, index10UV, 0);
vec4 index11 = texelFetch(_control_maps, index11UV, 0);
vec4 control00 = texelFetch(_control_maps, index00UV, 0);
vec4 control01 = texelFetch(_control_maps, index01UV, 0);
vec4 control10 = texelFetch(_control_maps, index10UV, 0);
vec4 control11 = texelFetch(_control_maps, index11UV, 0);

vec2 weights1 = clamp(pos_texel - pos_texel00, 0, 1);
// Calculate weight for the pixel position between the vertices
// Bilinear interpolate difference of UV and floor UV
vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1);
weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy);
vec2 weights0 = vec2(1.0) - weights1;

float total_weight = 0.0;
vec4 normal = vec4(0.0);
vec4 color = vec4(0.0);

color = get_material(UV, index00, vec2(index00UV.xy), weights0.x * weights0.y, total_weight, normal);
color += get_material(UV, index01, vec2(index01UV.xy), weights0.x * weights1.y, total_weight, normal);
color += get_material(UV, index10, vec2(index10UV.xy), weights1.x * weights0.y, total_weight, normal);
color += get_material(UV, index11, vec2(index11UV.xy), weights1.x * weights1.y, total_weight, normal);
// Accumulate material. 8-16 lookups
color = get_material(UV, control00, vec2(index00UV.xy), weights0.x * weights0.y, total_weight, normal);
color += get_material(UV, control01, vec2(index01UV.xy), weights0.x * weights1.y, total_weight, normal);
color += get_material(UV, control10, vec2(index10UV.xy), weights1.x * weights0.y, total_weight, normal);
color += get_material(UV, control11, vec2(index11UV.xy), weights1.x * weights1.y, total_weight, normal);
total_weight = 1.0 / total_weight;
normal *= total_weight;
color *= total_weight;

// Apply Colormap
vec3 ruv = get_regionf(UV2);
// Apply Colormap. 1 lookup
vec3 ruv = get_region_uv2(UV2);
vec4 color_tex = vec4(1., 1., 1., .5);
if (ruv.z >= 0.) {
color_tex = texture(_color_maps, ruv);
Expand Down
1 change: 1 addition & 0 deletions src/shaders/world_noise.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ float noise2D(vec2 st) {
}

// World Noise end

//INSERT: WORLD_NOISE2
// World Noise
float weight = texture(_region_blend_map, (uv/float(_region_map_size))+0.5).r;
Expand Down

0 comments on commit 7240ecc

Please sign in to comment.