209 lines
8.4 KiB
Plaintext
209 lines
8.4 KiB
Plaintext
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
|
// This shader is the minimum needed to allow the terrain to function, without any texturing.
|
|
|
|
shader_type spatial;
|
|
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
|
|
|
|
// Defined Constants
|
|
#define SKIP_PASS 0
|
|
#define VERTEX_PASS 1
|
|
#define FRAGMENT_PASS 2
|
|
|
|
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
|
|
#define fma(a, b, c) ((a) * (b) + (c))
|
|
#define dFdxCoarse(a) dFdx(a)
|
|
#define dFdyCoarse(a) dFdy(a)
|
|
#endif
|
|
|
|
// Private uniforms
|
|
// Commented uniforms aren't needed for this shader, but are available for your own needs.
|
|
uniform vec3 _camera_pos = vec3(0.f);
|
|
uniform float _mesh_size = 48.f;
|
|
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
|
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
|
uniform float _vertex_spacing = 1.0;
|
|
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
|
|
uniform float _region_size = 1024.0;
|
|
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
|
uniform int _region_map_size = 32;
|
|
uniform int _region_map[1024];
|
|
uniform vec2 _region_locations[1024];
|
|
//uniform float _texture_uv_scale_array[32];
|
|
//uniform float _texture_detile_array[32];
|
|
//uniform vec4 _texture_color_array[32];
|
|
uniform highp sampler2DArray _height_maps : repeat_disable;
|
|
uniform highp sampler2DArray _control_maps : repeat_disable;
|
|
//uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
|
//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
|
//uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
|
|
|
// Varyings & Types
|
|
// Some are required for editor functions
|
|
varying float v_vertex_xz_dist;
|
|
varying vec3 v_vertex;
|
|
|
|
////////////////////////
|
|
// Vertex
|
|
////////////////////////
|
|
|
|
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
|
|
// Returns ivec3 with:
|
|
// XY: (0 to _region_size - 1) coordinates within a region
|
|
// Z: layer index used for texturearrays, -1 if not in a region
|
|
ivec3 get_index_coord(const vec2 uv, const int search) {
|
|
vec2 r_uv = round(uv);
|
|
vec2 o_uv = mod(r_uv,_region_size);
|
|
ivec2 pos;
|
|
int bounds, layer_index = -1;
|
|
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
|
|
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
|
|
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
|
|
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
|
|
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
|
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
|
|
}
|
|
}
|
|
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
|
|
}
|
|
|
|
void vertex() {
|
|
// Get vertex of flat plane in world coordinates and set world UV
|
|
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
|
|
// Camera distance to vertex on flat plane
|
|
v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
|
|
|
|
// Geomorph vertex, set end and start for linear height interpolate
|
|
float scale = MODEL_MATRIX[0][0];
|
|
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
|
|
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
|
|
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
|
|
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
|
|
// Shift from regular to symetric
|
|
mix(v_fract, vec2(v_fract.x, -v_fract.y),
|
|
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
|
|
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
|
|
) :
|
|
// Symetric shift
|
|
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
|
|
vec2 start_pos = v_vertex.xz * _vertex_density;
|
|
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
|
|
v_vertex.xz -= shift * scale * vertex_lerp;
|
|
|
|
// UV coordinates in world space. Values are 0 to _region_size within regions
|
|
UV = v_vertex.xz * _vertex_density;
|
|
|
|
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
|
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
|
|
|
|
// Discard vertices for Holes. 1 lookup
|
|
ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
|
|
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
|
|
bool hole = bool(control >>2u & 0x1u);
|
|
|
|
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
|
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
|
(hole || (_background_mode == 0u && v_region.z < 0))) {
|
|
v_vertex.x = 0. / 0.;
|
|
} else {
|
|
// Interpolate Geomorph Start & End, set height. 2 Lookups.
|
|
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
|
|
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
|
|
float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
|
|
v_vertex.y = h;
|
|
}
|
|
|
|
// Convert model space to view space w/ skip_vertex_transform render mode
|
|
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
|
|
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
|
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
|
|
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
|
|
}
|
|
|
|
////////////////////////
|
|
// Fragment
|
|
////////////////////////
|
|
|
|
void fragment() {
|
|
// Recover UVs
|
|
vec2 uv = UV;
|
|
vec2 uv2 = UV2;
|
|
|
|
// Lookup offsets, ID and blend weight
|
|
const vec3 offsets = vec3(0, 1, 2);
|
|
vec2 index_id = floor(uv);
|
|
vec2 weight = fract(uv);
|
|
vec2 invert = 1.0 - weight;
|
|
vec4 weights = vec4(
|
|
invert.x * weight.y, // 0
|
|
weight.x * weight.y, // 1
|
|
weight.x * invert.y, // 2
|
|
invert.x * invert.y // 3
|
|
);
|
|
|
|
vec3 base_ddx = dFdxCoarse(v_vertex);
|
|
vec3 base_ddy = dFdyCoarse(v_vertex);
|
|
vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
|
|
// Calculate the effective mipmap for regionspace, and if less than 0,
|
|
// skip all extra lookups required for bilinear blend.
|
|
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
|
|
bool bilerp = region_mip < 0.0;
|
|
|
|
ivec3 indexUV[4];
|
|
// control map lookups, used for some normal lookups as well
|
|
indexUV[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
|
|
indexUV[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
|
|
indexUV[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
|
|
indexUV[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
|
|
|
|
// Terrain normals
|
|
vec3 index_normal[4];
|
|
float h[8];
|
|
// allows additional derivatives, eg world noise, brush previews etc
|
|
float u = 0.0;
|
|
float v = 0.0;
|
|
|
|
// Re-use the indexUVs for the first lookups, skipping some math. 3 lookups
|
|
h[3] = texelFetch(_height_maps, indexUV[3], 0).r; // 0 (0,0)
|
|
h[2] = texelFetch(_height_maps, indexUV[2], 0).r; // 1 (1,0)
|
|
h[0] = texelFetch(_height_maps, indexUV[0], 0).r; // 2 (0,1)
|
|
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
|
|
|
|
// Set flat world normal - overriden if bilerp is true
|
|
vec3 w_normal = index_normal[3];
|
|
|
|
// Branching smooth normals must be done seperatley for correct normals at all 4 index ids
|
|
if (bilerp) {
|
|
// 5 lookups
|
|
// Fetch the additional required height values for smooth normals
|
|
h[1] = texelFetch(_height_maps, indexUV[1], 0).r; // 3 (1,1)
|
|
h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
|
|
h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
|
|
h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
|
|
h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
|
|
|
|
// Calculate the normal for the remaining index ids.
|
|
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h[7] + v));
|
|
index_normal[1] = normalize(vec3(h[1] - h[5] + u, _vertex_spacing, h[1] - h[4] + v));
|
|
index_normal[2] = normalize(vec3(h[2] - h[6] + u, _vertex_spacing, h[2] - h[1] + v));
|
|
|
|
// Set interpolated world normal
|
|
w_normal =
|
|
index_normal[0] * weights[0] +
|
|
index_normal[1] * weights[1] +
|
|
index_normal[2] * weights[2] +
|
|
index_normal[3] * weights[3] ;
|
|
}
|
|
|
|
// Apply terrain normals
|
|
vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
|
|
vec3 w_binormal = normalize(cross(w_normal, w_tangent));
|
|
NORMAL = mat3(VIEW_MATRIX) * w_normal;
|
|
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
|
|
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
|
|
|
|
// Apply PBR
|
|
ALBEDO = vec3(.2);
|
|
ROUGHNESS = .7;
|
|
}
|