diff --git a/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader b/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader new file mode 100644 index 000000000..88c02f1b1 --- /dev/null +++ b/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader @@ -0,0 +1,333 @@ +shader_type spatial; +render_mode blend_mix,depth_draw_opaque,cull_disabled,diffuse_burley,specular_schlick_ggx,skip_vertex_transform; + +/* The terrain depends on this shader to function. Don't change most things in vertex() or + * terrain normal calculations in fragment(). You probably 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. + * + */ + +// Defined Constants +#define SKIP_PASS 0 +#define VERTEX_PASS 1 +#define FRAGMENT_PASS 2 +#define COLOR_MAP_DEF vec4(1.0, 1.0, 1.0, 0.5) +#define DIV_255 0.003921568627450 // 1. / 255. +#define DIV_1024 0.0009765625 // 1. / 1024. +#define TAU_16TH -0.392699081698724 // -TAU / 16. + +// Inline Functions +#define DECODE_BLEND(control) float(control >> 14u & 0xFFu) * DIV_255 +#define DECODE_AUTO(control) bool(control & 0x1u) +#define DECODE_BASE(control) int(control >> 27u & 0x1Fu) +#define DECODE_OVER(control) int(control >> 22u & 0x1Fu) +#define DECODE_ANGLE(control) float(control >>10u & 0xFu) * TAU_16TH +// This math recreates the scale value directly rather than using an 8 float const array. +#define DECODE_SCALE(control) (0.9 - float(((control >>7u & 0x7u) + 3u) % 8u + 1u) * 0.1) +#define DECODE_HOLE(control) bool(control >>2u & 0x1u) + +#define TEXTURE_ID_PROJECTED(id) bool((_texture_vertical_projections >> uint(id)) & 0x1u) + +#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 +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./region_size +uniform int _region_map_size = 32; +uniform int _region_map[1024]; +uniform vec2 _region_locations[1024]; +uniform float _texture_normal_depth_array[32]; +uniform float _texture_ao_strength_array[32]; +uniform float _texture_roughness_mod_array[32]; +uniform float _texture_uv_scale_array[32]; +uniform uint _texture_vertical_projections; +uniform vec2 _texture_detile_array[32]; +uniform vec4 _texture_color_array[32]; +uniform highp sampler2DArray _height_maps : repeat_disable; +uniform highp sampler2DArray _control_maps : repeat_disable; +#define FILTER_LINEAR +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; + +// Public uniforms + +uniform bool hide_me = false; + +group_uniforms general; +uniform bool flat_terrain_normals = false; +uniform float blend_sharpness : hint_range(0, 1) = 0.5; +uniform bool vertical_projection = true; +uniform float projection_threshold : hint_range(0.0, 0.99, 0.01) = 0.8; +group_uniforms; + +#define AUTO_SHADER +group_uniforms auto_shader; +uniform float auto_slope : hint_range(0, 10) = 1.0; +uniform float auto_height_reduction : hint_range(0, 1) = 0.1; +uniform int auto_base_texture : hint_range(0, 31) = 0; +uniform int auto_overlay_texture : hint_range(0, 31) = 1; +group_uniforms; + +group_uniforms dual_scaling; +uniform int dual_scale_texture : hint_range(0,31) = 0; +uniform float dual_scale_reduction : hint_range(0.001,1) = 0.3; +uniform float tri_scale_reduction : hint_range(0.001,1) = 0.3; +uniform float dual_scale_far : hint_range(0,1000) = 170.0; +uniform float dual_scale_near : hint_range(0,1000) = 100.0; +group_uniforms; + +group_uniforms macro_variation; +uniform bool macro_variation = true; +uniform vec3 macro_variation1 : source_color = vec3(1.); +uniform vec3 macro_variation2 : source_color = vec3(1.); +uniform float macro_variation_slope : hint_range(0., 1.) = 0.333; +uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable; + +uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x +uniform float noise1_angle : hint_range(0, 6.283) = 0.; +uniform vec2 noise1_offset = vec2(0.5); +uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x +group_uniforms; + +group_uniforms mipmaps; +uniform float bias_distance : hint_range(0.0, 16384.0, 0.1) = 512.0; +uniform float mipmap_bias : hint_range(0.5, 1.5, 0.01) = 1.0; +uniform float depth_blur : hint_range(0.0, 35.0, 0.1) = 0.0; +group_uniforms; + +group_uniforms world_background_noise; +uniform bool world_noise_fragment_normals = false; +uniform float world_noise_region_blend : hint_range(0.05, 0.95, 0.01) = 0.33; +uniform int world_noise_max_octaves : hint_range(0, 15) = 4; +uniform int world_noise_min_octaves : hint_range(0, 15) = 2; +uniform float world_noise_lod_distance : hint_range(0, 40000, 1) = 7500.; +uniform float world_noise_scale : hint_range(0.25, 20, 0.01) = 5.0; +uniform float world_noise_height : hint_range(0, 1000, 0.1) = 64.0; +uniform vec3 world_noise_offset = vec3(0.0); +group_uniforms; +varying vec2 world_noise_ddxy; + + +// Varyings & Types + +struct material { + vec4 albedo_height; + vec4 normal_rough; + float normal_map_depth; + float ao_strength; + float total_weight; +}; + +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); +} + +// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with: +// XY: (0. to 1.) coordinates within a region +// Z: layer index used for texturearrays, -1 if not in a region +vec3 get_index_uv(const vec2 uv2) { + ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); + int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); + int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; + return vec3(uv2 - _region_locations[layer_index], float(layer_index)); +} + +// World Noise Functions Start + +// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not. +float check_region(const vec2 uv2) { + ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); + int layer_index = 0; + if (uint(pos.x | pos.y) < uint(_region_map_size)) { + layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1; + } + return float(layer_index); +} + +// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions +float region_blend(vec2 uv2) { + uv2 -= 0.5; + const vec2 offset = vec2(0.0,1.0); + float a = check_region(uv2 + offset.xy); + float b = check_region(uv2 + offset.yy); + float c = check_region(uv2 + offset.yx); + float d = check_region(uv2 + offset.xx); + vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2)); + float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); + return 1.0 - blend; +} + +float hashf(float f) { + return fract(sin(f) * 1e4); +} + +float hashv2(vec2 v) { + return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x))))); +} + +// https://iquilezles.org/articles/morenoise/ +vec3 noise2D(vec2 x) { + vec2 f = fract(x); + // Quintic Hermine Curve. Similar to SmoothStep() + vec2 f2 = f * f, f3 = f2 * f, s = f - 1.0, s2 = s * s; + vec2 u = f3 * fma(vec2(6.0), f2, fma(vec2(-15.0), f, vec2(10.0))); + vec2 du = 30.0 * f2 * s2; + + vec2 p = floor(x); + + // Four corners in 2D of a tile + float a = hashv2( p+vec2(0,0) ); + float b = hashv2( p+vec2(1,0) ); + float c = hashv2( p+vec2(0,1) ); + float d = hashv2( p+vec2(1,1) ); + + // Mix 4 corner percentages + float k0 = a; + float k1 = b - a; + float k2 = c - a; + float k3 = d - (b + k2); + return vec3(fma(k2, u.y, fma(u.x, fma(k3, u.y, k1), k0)), + du * fma(vec2(k3), u.yx, vec2(k1, k2))); +} + +float world_noise(vec2 p) { + float a = 0.0; + float b = 1.0; + vec2 d = vec2(0.0); + + int octaves = int( clamp( + float(world_noise_max_octaves) - floor(v_vertex_xz_dist/(world_noise_lod_distance)), + float(world_noise_min_octaves), float(world_noise_max_octaves)) + ); + + for( int i=0; i < octaves; i++ ) { + vec3 n = noise2D(p); + d += n.yz; + a += b * n.x / (1.0 + dot(d,d)); + b *= 0.5; + p = mat2( vec2(0.8, -0.6), vec2(0.6, 0.8) ) * p * 2.0; + } + return a; +} + +float get_noise_height(const vec2 uv) { + float weight = region_blend(uv); + // only calculate world noise when it could be visibile. + if (weight <= 1.0 - world_noise_region_blend) { + return 0.0; + } + //TODO: Offset/scale UVs are semi-dependent upon region size 1024. Base on v_vertex.xz instead + float noise = world_noise((uv + world_noise_offset.xz * 1024. / _region_size) * world_noise_scale * _region_size / 1024. * .1) * + world_noise_height * 10. + world_noise_offset.y * 100.; + weight = smoothstep(1.0 - world_noise_region_blend, 1.0, weight); + return mix(0.0, noise, weight); +} + +// World Noise Functions End + +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); + + // SHADOW MESH snap + shift = round(shift); + vertex_lerp = round(vertex_lerp); + // END + + 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 = DECODE_HOLE(control); + + // 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 == -1))) { + v_vertex.x = 0. / 0.; + } else { + // Set final vertex height. + ivec3 coord_a = get_index_coord(start_pos, VERTEX_PASS); + ivec3 coord_b = get_index_coord(end_pos, VERTEX_PASS); + //float h = mix(texelFetch(_height_maps, coord_a, 0).r,texelFetch(_height_maps, coord_b, 0).r,vertex_lerp); + // SHADOW MESH + ivec3 snapcoord = vertex_lerp < 0.5 ? coord_a : coord_b; + float h = texelFetch(_height_maps, snapcoord, 0).r; + v_vertex.y = h; + } + + + if(hide_me){ + v_vertex = vec3(0.0); + } + + // Convert model space to view space w/ skip_vertex_transform render mode + VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz; +} + diff --git a/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader.uid b/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader.uid new file mode 100644 index 000000000..812d2f869 --- /dev/null +++ b/project/addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader.uid @@ -0,0 +1 @@ +uid://b76jv0pirbfxm diff --git a/project/demo/Demo.tscn b/project/demo/Demo.tscn index 8884d48f8..1662597ea 100644 --- a/project/demo/Demo.tscn +++ b/project/demo/Demo.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=13 format=3 uid="uid://chol2xlfbq7cu"] +[gd_scene load_steps=16 format=4 uid="uid://chol2xlfbq7cu"] [ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_k7qca"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_nqak5"] @@ -6,6 +6,8 @@ [ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="3_ht63y"] [ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="3_kdh0b"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_yqldq"] +[ext_resource type="Shader" uid="uid://bohymbh771gyr" path="res://tmp_terrain_shader.gdshader" id="7_fhs52"] +[ext_resource type="Shader" uid="uid://b76jv0pirbfxm" path="res://addons/terrain_3d/extras/shaders/minimum_shadow_mesh.gdshader" id="7_fwrtk"] [ext_resource type="PackedScene" uid="uid://d3sr0a7dxfkr8" path="res://addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn" id="8_fwrtk"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="8_g2of2"] @@ -48,6 +50,7 @@ _shader_parameters = { &"dual_scaling": null, &"flat_terrain_normals": false, &"general": null, +&"hide_me": false, &"macro_variation": true, "macro_variation1": Color(0.855, 0.8625, 0.9, 1), "macro_variation2": Color(0.9, 0.885, 0.81, 1), @@ -75,6 +78,63 @@ _shader_parameters = { world_background = 2 auto_shader_enabled = true dual_scaling_enabled = true +shader_override_enabled = true +shader_override = ExtResource("7_fhs52") + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_fwrtk"] +render_priority = 0 +shader = ExtResource("7_fwrtk") +shader_parameter/_camera_pos = Vector3(0, 0, 0) +shader_parameter/_mesh_size = 48.0 +shader_parameter/_background_mode = 1 +shader_parameter/_mouse_layer = 2147483648 +shader_parameter/_vertex_spacing = 1.0 +shader_parameter/_vertex_density = 1.0 +shader_parameter/_region_size = 1024.0 +shader_parameter/_region_texel_size = 0.000976563 +shader_parameter/_region_map_size = 32 +shader_parameter/_region_map = PackedInt32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_region_locations = PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_normal_depth_array = PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_ao_strength_array = PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_roughness_mod_array = PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_uv_scale_array = PackedFloat32Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_vertical_projections = 0 +shader_parameter/_texture_detile_array = PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/_texture_color_array = PackedVector4Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +shader_parameter/hide_me = false +shader_parameter/flat_terrain_normals = false +shader_parameter/blend_sharpness = 0.5 +shader_parameter/vertical_projection = true +shader_parameter/projection_threshold = 0.8 +shader_parameter/auto_slope = 1.0 +shader_parameter/auto_height_reduction = 0.1 +shader_parameter/auto_base_texture = 0 +shader_parameter/auto_overlay_texture = 1 +shader_parameter/dual_scale_texture = 0 +shader_parameter/dual_scale_reduction = 0.3 +shader_parameter/tri_scale_reduction = 0.3 +shader_parameter/dual_scale_far = 170.0 +shader_parameter/dual_scale_near = 100.0 +shader_parameter/macro_variation = true +shader_parameter/macro_variation1 = Color(1, 1, 1, 1) +shader_parameter/macro_variation2 = Color(1, 1, 1, 1) +shader_parameter/macro_variation_slope = 0.333 +shader_parameter/noise1_scale = 0.04 +shader_parameter/noise1_angle = 0.0 +shader_parameter/noise1_offset = Vector2(0.5, 0.5) +shader_parameter/noise2_scale = 0.076 +shader_parameter/bias_distance = 512.0 +shader_parameter/mipmap_bias = 1.0 +shader_parameter/depth_blur = 0.0 +shader_parameter/world_noise_fragment_normals = false +shader_parameter/world_noise_region_blend = 0.33 +shader_parameter/world_noise_max_octaves = 4 +shader_parameter/world_noise_min_octaves = 2 +shader_parameter/world_noise_lod_distance = 7500.0 +shader_parameter/world_noise_scale = 5.0 +shader_parameter/world_noise_height = 64.0 +shader_parameter/world_noise_offset = Vector3(0, 0, 0) [node name="Demo" type="Node"] script = ExtResource("1_k7qca") @@ -83,6 +143,10 @@ script = ExtResource("1_k7qca") [node name="Environment" parent="." instance=ExtResource("3_yqldq")] +[node name="DirectionalLight3D" parent="Environment" index="1"] +transform = Transform3D(-0.933581, -0.122569, -0.336755, 0, -0.939692, 0.34202, -0.358368, 0.319303, 0.877278, 0, 0, 0) +directional_shadow_max_distance = 1000.0 + [node name="Borders" parent="." instance=ExtResource("3_cw38j")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, -3, 512) collision_mask = 3 @@ -90,7 +154,7 @@ collision_mask = 3 [node name="Tunnel" parent="." instance=ExtResource("3_kdh0b")] [node name="Player" parent="." instance=ExtResource("3_ht63y")] -transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 223.143, 105.348, -1833.08) +transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 0, 105.348, -1833.08) [node name="Terrain3D" type="Terrain3D" parent="." node_paths=PackedStringArray("collision_target", "clipmap_target")] data_directory = "res://demo/data" @@ -98,9 +162,15 @@ material = SubResource("Terrain3DMaterial_jrc01") assets = ExtResource("8_g2of2") collision_mask = 3 collision_target = NodePath("../Player") +mesh_size = 24 clipmap_target = NodePath("../Player") +cast_shadows = 3 +shadow_material = SubResource("ShaderMaterial_fwrtk") top_level = true metadata/_edit_lock_ = true [node name="Terrain3DParticles" parent="Terrain3D" node_paths=PackedStringArray("terrain") instance=ExtResource("8_fwrtk")] +visible = false terrain = NodePath("..") + +[editable path="Environment"] diff --git a/project/tmp_terrain_shader.gdshader b/project/tmp_terrain_shader.gdshader new file mode 100644 index 000000000..d624bb874 --- /dev/null +++ b/project/tmp_terrain_shader.gdshader @@ -0,0 +1,796 @@ +shader_type spatial; +render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform; + +/* The terrain depends on this shader to function. Don't change most things in vertex() or + * terrain normal calculations in fragment(). You probably 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. + * + */ + +// Defined Constants +#define SKIP_PASS 0 +#define VERTEX_PASS 1 +#define FRAGMENT_PASS 2 +#define COLOR_MAP_DEF vec4(1.0, 1.0, 1.0, 0.5) +#define DIV_255 0.003921568627450 // 1. / 255. +#define DIV_1024 0.0009765625 // 1. / 1024. +#define TAU_16TH -0.392699081698724 // -TAU / 16. + +// Inline Functions +#define DECODE_BLEND(control) float(control >> 14u & 0xFFu) * DIV_255 +#define DECODE_AUTO(control) bool(control & 0x1u) +#define DECODE_BASE(control) int(control >> 27u & 0x1Fu) +#define DECODE_OVER(control) int(control >> 22u & 0x1Fu) +#define DECODE_ANGLE(control) float(control >>10u & 0xFu) * TAU_16TH +// This math recreates the scale value directly rather than using an 8 float const array. +#define DECODE_SCALE(control) (0.9 - float(((control >>7u & 0x7u) + 3u) % 8u + 1u) * 0.1) +#define DECODE_HOLE(control) bool(control >>2u & 0x1u) + +#define TEXTURE_ID_PROJECTED(id) bool((_texture_vertical_projections >> uint(id)) & 0x1u) + +#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 +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./region_size +uniform int _region_map_size = 32; +uniform int _region_map[1024]; +uniform vec2 _region_locations[1024]; +uniform float _texture_normal_depth_array[32]; +uniform float _texture_ao_strength_array[32]; +uniform float _texture_roughness_mod_array[32]; +uniform float _texture_uv_scale_array[32]; +uniform uint _texture_vertical_projections; +uniform vec2 _texture_detile_array[32]; +uniform vec4 _texture_color_array[32]; +uniform highp sampler2DArray _height_maps : repeat_disable; +uniform highp sampler2DArray _control_maps : repeat_disable; +#define FILTER_LINEAR +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; + +// Public uniforms + +uniform bool hide_me = false; + +group_uniforms general; +uniform bool flat_terrain_normals = false; +uniform float blend_sharpness : hint_range(0, 1) = 0.5; +uniform bool vertical_projection = true; +uniform float projection_threshold : hint_range(0.0, 0.99, 0.01) = 0.8; +group_uniforms; + +#define AUTO_SHADER +group_uniforms auto_shader; +uniform float auto_slope : hint_range(0, 10) = 1.0; +uniform float auto_height_reduction : hint_range(0, 1) = 0.1; +uniform int auto_base_texture : hint_range(0, 31) = 0; +uniform int auto_overlay_texture : hint_range(0, 31) = 1; +group_uniforms; + +group_uniforms dual_scaling; +uniform int dual_scale_texture : hint_range(0,31) = 0; +uniform float dual_scale_reduction : hint_range(0.001,1) = 0.3; +uniform float tri_scale_reduction : hint_range(0.001,1) = 0.3; +uniform float dual_scale_far : hint_range(0,1000) = 170.0; +uniform float dual_scale_near : hint_range(0,1000) = 100.0; +group_uniforms; + +group_uniforms macro_variation; +uniform bool macro_variation = true; +uniform vec3 macro_variation1 : source_color = vec3(1.); +uniform vec3 macro_variation2 : source_color = vec3(1.); +uniform float macro_variation_slope : hint_range(0., 1.) = 0.333; +uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable; + +uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x +uniform float noise1_angle : hint_range(0, 6.283) = 0.; +uniform vec2 noise1_offset = vec2(0.5); +uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x +group_uniforms; + +group_uniforms mipmaps; +uniform float bias_distance : hint_range(0.0, 16384.0, 0.1) = 512.0; +uniform float mipmap_bias : hint_range(0.5, 1.5, 0.01) = 1.0; +uniform float depth_blur : hint_range(0.0, 35.0, 0.1) = 0.0; +group_uniforms; + +group_uniforms world_background_noise; +uniform bool world_noise_fragment_normals = false; +uniform float world_noise_region_blend : hint_range(0.05, 0.95, 0.01) = 0.33; +uniform int world_noise_max_octaves : hint_range(0, 15) = 4; +uniform int world_noise_min_octaves : hint_range(0, 15) = 2; +uniform float world_noise_lod_distance : hint_range(0, 40000, 1) = 7500.; +uniform float world_noise_scale : hint_range(0.25, 20, 0.01) = 5.0; +uniform float world_noise_height : hint_range(0, 1000, 0.1) = 64.0; +uniform vec3 world_noise_offset = vec3(0.0); +group_uniforms; +varying vec2 world_noise_ddxy; + + +// Varyings & Types + +struct material { + vec4 albedo_height; + vec4 normal_rough; + float normal_map_depth; + float ao_strength; + float total_weight; +}; + +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); +} + +// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with: +// XY: (0. to 1.) coordinates within a region +// Z: layer index used for texturearrays, -1 if not in a region +vec3 get_index_uv(const vec2 uv2) { + ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); + int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); + int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; + return vec3(uv2 - _region_locations[layer_index], float(layer_index)); +} + +// World Noise Functions Start + +// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not. +float check_region(const vec2 uv2) { + ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); + int layer_index = 0; + if (uint(pos.x | pos.y) < uint(_region_map_size)) { + layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1; + } + return float(layer_index); +} + +// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions +float region_blend(vec2 uv2) { + uv2 -= 0.5; + const vec2 offset = vec2(0.0,1.0); + float a = check_region(uv2 + offset.xy); + float b = check_region(uv2 + offset.yy); + float c = check_region(uv2 + offset.yx); + float d = check_region(uv2 + offset.xx); + vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2)); + float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); + return 1.0 - blend; +} + +float hashf(float f) { + return fract(sin(f) * 1e4); +} + +float hashv2(vec2 v) { + return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x))))); +} + +// https://iquilezles.org/articles/morenoise/ +vec3 noise2D(vec2 x) { + vec2 f = fract(x); + // Quintic Hermine Curve. Similar to SmoothStep() + vec2 f2 = f * f, f3 = f2 * f, s = f - 1.0, s2 = s * s; + vec2 u = f3 * fma(vec2(6.0), f2, fma(vec2(-15.0), f, vec2(10.0))); + vec2 du = 30.0 * f2 * s2; + + vec2 p = floor(x); + + // Four corners in 2D of a tile + float a = hashv2( p+vec2(0,0) ); + float b = hashv2( p+vec2(1,0) ); + float c = hashv2( p+vec2(0,1) ); + float d = hashv2( p+vec2(1,1) ); + + // Mix 4 corner percentages + float k0 = a; + float k1 = b - a; + float k2 = c - a; + float k3 = d - (b + k2); + return vec3(fma(k2, u.y, fma(u.x, fma(k3, u.y, k1), k0)), + du * fma(vec2(k3), u.yx, vec2(k1, k2))); +} + +float world_noise(vec2 p) { + float a = 0.0; + float b = 1.0; + vec2 d = vec2(0.0); + + int octaves = int( clamp( + float(world_noise_max_octaves) - floor(v_vertex_xz_dist/(world_noise_lod_distance)), + float(world_noise_min_octaves), float(world_noise_max_octaves)) + ); + + for( int i=0; i < octaves; i++ ) { + vec3 n = noise2D(p); + d += n.yz; + a += b * n.x / (1.0 + dot(d,d)); + b *= 0.5; + p = mat2( vec2(0.8, -0.6), vec2(0.6, 0.8) ) * p * 2.0; + } + return a; +} + +float get_noise_height(const vec2 uv) { + float weight = region_blend(uv); + // only calculate world noise when it could be visibile. + if (weight <= 1.0 - world_noise_region_blend) { + return 0.0; + } + //TODO: Offset/scale UVs are semi-dependent upon region size 1024. Base on v_vertex.xz instead + float noise = world_noise((uv + world_noise_offset.xz * 1024. / _region_size) * world_noise_scale * _region_size / 1024. * .1) * + world_noise_height * 10. + world_noise_offset.y * 100.; + weight = smoothstep(1.0 - world_noise_region_blend, 1.0, weight); + return mix(0.0, noise, weight); +} + +// World Noise Functions End + +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 = DECODE_HOLE(control); + + // 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 == -1))) { + v_vertex.x = 0. / 0.; + } else { + // Set final vertex height & calculate vertex normals. 3 lookups + ivec3 coord_a = get_index_coord(start_pos, VERTEX_PASS); + ivec3 coord_b = get_index_coord(end_pos, VERTEX_PASS); + float h = mix(texelFetch(_height_maps, coord_a, 0).r,texelFetch(_height_maps, coord_b, 0).r,vertex_lerp); + // World Noise + if (_background_mode == 2u) { + vec2 nuv_a = fma(start_pos, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); + vec2 nuv_b = fma(end_pos, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); + float nh = mix(get_noise_height(nuv_a),get_noise_height(nuv_b),vertex_lerp); + float nu = mix(get_noise_height(nuv_a + vec2(_region_texel_size, 0.0)), + get_noise_height(nuv_b + vec2(_region_texel_size, 0.0)),vertex_lerp); + float nv = mix(get_noise_height(nuv_a + vec2(0.0, _region_texel_size)), + get_noise_height(nuv_b + vec2(0.0, _region_texel_size)),vertex_lerp); + world_noise_ddxy = vec2(nh - nu, nh - nv); + h += nh; + } + + v_vertex.y = h; + } + + if(hide_me){ + v_vertex = vec3(0.0); + } + + // 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 +//////////////////////// + +float random(in vec2 xy) { + return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453); +} + +vec2 rotate_vec2(const vec2 v, const vec2 cs) { + return vec2(fma(cs.x, v.x, cs.y * v.y), fma(cs.x, v.y, -cs.y * v.x)); +} + +// 2-4 lookups ( 2-6 with dual scaling ) +void accumulate_material(vec3 base_ddx, vec3 base_ddy, const float weight, const ivec3 index, const uint control, + const vec2 texture_weight, const ivec2 texture_id, const vec3 i_normal, float h, inout material mat) { + + // Applying scaling before projection reduces the number of multiplys ops required. + vec3 i_vertex = v_vertex; + + // Control map scale + float control_scale = DECODE_SCALE(control); + // tri scaling + if (index.z < 0) { + control_scale *= tri_scale_reduction; + } + base_ddx *= control_scale; + base_ddy *= control_scale; + i_vertex *= control_scale; + h *= control_scale; + + // Index position for detiling. + vec2 i_pos = fma(_region_locations[index.z], vec2(_region_size), vec2(index.xy)); + i_pos *= _vertex_spacing * control_scale; + + // Projection + vec2 i_uv = i_vertex.xz; + vec4 i_dd = vec4(base_ddx.xz, base_ddy.xz); + mat2 p_align = mat2(1.); + vec2 p_uv = i_uv; + vec4 p_dd = i_dd; + vec2 p_pos = i_pos; + if (i_normal.y <= projection_threshold && vertical_projection) { + // Projected normal map alignment matrix + p_align = mat2(vec2(i_normal.z, -i_normal.x), vec2(i_normal.x, i_normal.z)); + // Fast 45 degree snapping https://iquilezles.org/articles/noatan/ + vec2 xz = round(normalize(-i_normal.xz) * 1.3065629648763765); // sqrt(1.0 + sqrt(0.5)) + xz *= abs(xz.x) + abs(xz.y) > 1.5 ? 0.7071067811865475 : 1.0; // sqrt(0.5) + xz = vec2(-xz.y, xz.x); + p_pos = floor(vec2(dot(i_pos, xz), -h)); + p_uv = vec2(dot(i_vertex.xz, xz), -i_vertex.y); + p_dd.xy = vec2(dot(base_ddx.xz, xz), -base_ddx.y); + p_dd.zw = vec2(dot(base_ddy.xz, xz), -base_ddy.y); + } + + // Control map rotation. Must be applied seperatley from detiling to maintain UV continuity. + float c_angle = DECODE_ANGLE(control); + vec2 c_cs_angle = vec2(cos(c_angle), sin(c_angle)); + i_uv = rotate_vec2(i_uv, c_cs_angle); + i_pos = rotate_vec2(i_pos, c_cs_angle); + p_uv = rotate_vec2(p_uv, c_cs_angle); + p_pos = rotate_vec2(p_pos, c_cs_angle); + + // Blend adjustment of Higher ID from Lower ID normal map in world space. + float world_normal = 1.; + vec3 T = normalize(base_ddx), B = -normalize(base_ddy); + // mat3 multiply, reduced to 2x fma and 1x mult. + #define FAST_WORLD_NORMAL(n) fma(T, vec3(n.x), fma(B, vec3(n.z), i_normal * vec3(n.y))) + + float blend = DECODE_BLEND(control); // only used for branching. + float sharpness = fma(56., blend_sharpness, 8.); + + // dual scaling + float far_factor = clamp(smoothstep(dual_scale_near, dual_scale_far, length(v_vertex - _camera_pos)), 0.0, 1.0); + vec4 far_alb = vec4(0.); + vec4 far_nrm = vec4(0.); + if (far_factor > 0. && any(equal(texture_id, ivec2(dual_scale_texture)))) { + bool projected = TEXTURE_ID_PROJECTED(dual_scale_texture); + float far_scale = _texture_uv_scale_array[dual_scale_texture] * dual_scale_reduction; + vec4 far_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * far_scale; + + // Detiling and Control map rotation + vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected))); + vec2 uv_center = floor(fma(id_pos, vec2(far_scale), vec2(0.5))); + vec2 far_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[dual_scale_texture] * TAU; + vec2 far_cs_angle = vec2(cos(far_detile.x), sin(far_detile.x)); + // Apply UV rotation and shift around pivot. + vec2 far_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected))); + far_uv = rotate_vec2(fma(far_uv, vec2(far_scale), -uv_center), far_cs_angle) + uv_center + far_detile.y - 0.5; + // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also + // including control map rotation. avoids extra matrix op, and sin/cos calls. + far_cs_angle = vec2( + fma(far_cs_angle.x, c_cs_angle.x, -far_cs_angle.y * c_cs_angle.y), + fma(far_cs_angle.y, c_cs_angle.x, far_cs_angle.x * c_cs_angle.y)); + // Align derivatives for correct anisotropic filtering + far_dd.xy = rotate_vec2(far_dd.xy, far_cs_angle); + far_dd.zw = rotate_vec2(far_dd.zw, far_cs_angle); + + far_alb = textureGrad(_texture_array_albedo, vec3(far_uv, float(dual_scale_texture)), far_dd.xy, far_dd.zw); + far_nrm = textureGrad(_texture_array_normal, vec3(far_uv, float(dual_scale_texture)), far_dd.xy, far_dd.zw); + far_alb.rgb *= _texture_color_array[dual_scale_texture].rgb; + far_nrm.a = clamp(far_nrm.a + _texture_roughness_mod_array[dual_scale_texture], 0., 1.); + // Unpack and rotate normal map. + far_nrm.xyz = fma(far_nrm.xzy, vec3(2.0), vec3(-1.0)); + far_nrm.xz = rotate_vec2(far_nrm.xz, far_cs_angle); + far_nrm.xz = fma((far_nrm.xz * p_align), vec2(float(projected)), far_nrm.xz * vec2(float(!projected))); + + // apply weighting when far_factor == 1.0 as the later lookup will be skipped. + if (far_factor == 1.0) { + float id_w = texture_id[0] == dual_scale_texture ? texture_weight[0] : texture_weight[1]; + float id_weight = exp2(sharpness * log2(weight + id_w + far_alb.a)) * weight; + world_normal = FAST_WORLD_NORMAL(far_nrm).y; + mat.albedo_height = fma(far_alb, vec4(id_weight), mat.albedo_height); + mat.normal_rough = fma(far_nrm, vec4(id_weight), mat.normal_rough); + mat.normal_map_depth = fma(_texture_normal_depth_array[dual_scale_texture], id_weight, mat.normal_map_depth); + mat.ao_strength = fma(_texture_ao_strength_array[dual_scale_texture], id_weight, mat.ao_strength); + mat.total_weight += id_weight; + } + } + + + // 1st Texture Asset ID + if (blend < 1.0 + && !(far_factor == 1.0 && texture_id[0] == dual_scale_texture) + ) { + int id = texture_id[0]; + bool projected = TEXTURE_ID_PROJECTED(id); + float id_w = texture_weight[0]; + float id_scale = _texture_uv_scale_array[id]; + vec4 id_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * id_scale; + + // Detiling and Control map rotation + vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected))); + vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5))); + vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; + vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); + // Apply UV rotation and shift around pivot. + vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected))); + id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; + // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also + // including control map rotation. avoids extra matrix op, and sin/cos calls. + id_cs_angle = vec2( + fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), + fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); + // Align derivatives for correct anisotropic filtering + id_dd.xy = rotate_vec2(id_dd.xy, id_cs_angle); + id_dd.zw = rotate_vec2(id_dd.zw, id_cs_angle); + + vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); + vec4 nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); + alb.rgb *= _texture_color_array[id].rgb; + nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.); + // Unpack and rotate normal map. + nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); + nrm.xz = rotate_vec2(nrm.xz, id_cs_angle); + nrm.xz = fma((nrm.xz * p_align), vec2(float(projected)), nrm.xz * vec2(float(!projected))); + + // If dual scaling, apply to overlay texture + if (id == dual_scale_texture && far_factor > 0.) { + alb = mix(alb, far_alb, far_factor); + nrm = mix(nrm, far_nrm, far_factor); + } + world_normal = FAST_WORLD_NORMAL(nrm).y; + + float id_weight = exp2(sharpness * log2(weight + id_w + alb.a)) * weight; + mat.albedo_height = fma(alb, vec4(id_weight), mat.albedo_height); + mat.normal_rough = fma(nrm, vec4(id_weight), mat.normal_rough); + mat.normal_map_depth = fma(_texture_normal_depth_array[id], id_weight, mat.normal_map_depth); + mat.ao_strength = fma(_texture_ao_strength_array[id], id_weight, mat.ao_strength); + mat.total_weight += id_weight; + } + + // 2nd Texture Asset ID + if (blend > 0.0 && texture_id[1] != texture_id[0] + && !(far_factor == 1.0 && texture_id[1] == dual_scale_texture) + ) { + int id = texture_id[1]; + bool projected = TEXTURE_ID_PROJECTED(id); + float id_w = texture_weight[1]; + float id_scale = _texture_uv_scale_array[id]; + vec4 id_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * id_scale; + + // Detiling and Control map rotation + vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected))); + vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5))); + vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU; + vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x)); + // Apply UV rotation and shift around pivot. + vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected))); + id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5; + // Manual transpose to rotate derivatives and normals counter to uv rotation whilst also + // including control map rotation. avoids extra matrix op, and sin/cos calls. + id_cs_angle = vec2( + fma(id_cs_angle.x, c_cs_angle.x, -id_cs_angle.y * c_cs_angle.y), + fma(id_cs_angle.y, c_cs_angle.x, id_cs_angle.x * c_cs_angle.y)); + // Align derivatives for correct anisotropic filtering + id_dd.xy = rotate_vec2(id_dd.xy, id_cs_angle); + id_dd.zw = rotate_vec2(id_dd.zw, id_cs_angle); + + vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); + vec4 nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), id_dd.xy, id_dd.zw); + alb.rgb *= _texture_color_array[id].rgb; + nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.); + // Unpack and rotate normal map. + nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0)); + nrm.xz = rotate_vec2(nrm.xz, id_cs_angle); + nrm.xz = fma((nrm.xz * p_align), vec2(float(projected)), nrm.xz * vec2(float(!projected))); + + // If dual scaling, apply to overlay texture + if (id == dual_scale_texture && far_factor > 0.) { + alb = mix(alb, far_alb, far_factor); + nrm = mix(nrm, far_nrm, far_factor); + } + float id_weight = exp2(sharpness * log2(weight + id_w + alb.a * clamp(world_normal, 0., 1.))) * weight; + mat.albedo_height = fma(alb, vec4(id_weight), mat.albedo_height); + mat.normal_rough = fma(nrm, vec4(id_weight), mat.normal_rough); + mat.normal_map_depth = fma(_texture_normal_depth_array[id], id_weight, mat.normal_map_depth); + mat.ao_strength = fma(_texture_ao_strength_array[id], id_weight, mat.ao_strength); + mat.total_weight += id_weight; + } +} + +void fragment() { + // Recover UVs + vec2 uv = UV; + vec2 uv2 = UV2; + + // Lookup offsets, ID and blend weight + vec3 region_uv = get_index_uv(uv2); + 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 + ); + + ivec3 index[4]; + // control map lookups, used for some normal lookups as well + index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS); + index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS); + index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS); + index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS); + + vec3 base_ddx = dFdxCoarse(v_vertex); + vec3 base_ddy = dFdyCoarse(v_vertex); + // Calculate the effective mipmap for regionspace, and when 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 && region_uv.z > -1.; + + // Terrain normals + vec3 index_normal[4]; + float h[4]; + // allows additional derivatives, eg world noise, brush previews etc + float u = 0.0; + float v = 0.0; + + // World Noise + if (_background_mode == 2u && world_noise_fragment_normals) { + float noise_height = get_noise_height(uv2); + u += noise_height - get_noise_height(uv2 + vec2(_region_texel_size, 0.0)); + v += noise_height - get_noise_height(uv2 + vec2(0.0, _region_texel_size)); + } + if (_background_mode == 2u && !world_noise_fragment_normals) { + u += world_noise_ddxy.x; + v += world_noise_ddxy.y; + } + // Re-use index[] for the first lookups, skipping some math. 3 lookups + h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0,0) + h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1,0) + h[0] = texelFetch(_height_maps, index[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 - overwritten if bilerp is true + vec3 w_normal = index_normal[3]; + + // Adjust derivatives for mipmap bias and depth blur effect + float bias = mix(mipmap_bias, + depth_blur + 1., + smoothstep(0.0, 1.0, (v_vertex_xz_dist - bias_distance) * DIV_1024)); + base_ddx *= bias; + base_ddy *= bias; + + // Color map + vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF; + + // Branching smooth normals and manually interpolated color map - fixes cross region artifacts + if (bilerp) { + // 4 lookups if linear filtering, else 1 lookup. + vec4 col_map[4]; + col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF; + #ifdef FILTER_LINEAR + col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF; + col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF; + col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF; + + color_map = + col_map[0] * weights[0] + + col_map[1] * weights[1] + + col_map[2] * weights[2] + + col_map[3] * weights[3] ; + #else + color_map = col_map[3]; + #endif + + // 5 lookups + // Fetch the additional required height values for smooth normals + h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1) + float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2) + float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1) + float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0) + float 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 + if (flat_terrain_normals) { + NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX))); + TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz)); + BINORMAL = normalize(cross(NORMAL, TANGENT)); + } else { + 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; + } + + // Get index control data + // 1 - 4 lookups + uvec4 control = uvec4(floatBitsToUint(texelFetch(_control_maps, index[3], 0).r)); + if (bilerp) { + control = uvec4( + floatBitsToUint(texelFetch(_control_maps, index[0], 0).r), + floatBitsToUint(texelFetch(_control_maps, index[1], 0).r), + floatBitsToUint(texelFetch(_control_maps, index[2], 0).r), + control[3]); + } + + { + // Auto blend calculation + float auto_blend = clamp(fma(auto_slope * 2.0, (w_normal.y - 1.0), 1.0) + - auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0); + // Enable Autoshader if outside regions or painted in regions, otherwise manual painted + uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(uint(region_uv.z < 0.0)); + uint u_auto = + ((uint(auto_base_texture) & 0x1Fu) << 27u) | + ((uint(auto_overlay_texture) & 0x1Fu) << 22u) | + ((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u); + control = control * (1u - is_auto) + u_auto * is_auto; + } + + + // Texture weights + // Vectorised Deocode of all texture IDs, then swizzle to per index mapping. + // Passed to accumulate_material to avoid repeated decoding. + ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)), + ivec4(control >> uvec4(22u) & uvec4(0x1Fu))}; + ivec2 texture_ids[4] = ivec2[4]( + ivec2(t_id[0].x, t_id[1].x), + ivec2(t_id[0].y, t_id[1].y), + ivec2(t_id[0].z, t_id[1].z), + ivec2(t_id[0].w, t_id[1].w)); + + // uninterpolated weights. + vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255; + vec4 weights_id_0 = 1.0 - weights_id_1; + vec2 t_weights[4] = vec2[4]( + vec2(weights_id_0[0], weights_id_1[0]), + vec2(weights_id_0[1], weights_id_1[1]), + vec2(weights_id_0[2], weights_id_1[2]), + vec2(weights_id_0[3], weights_id_1[3])); + // interpolated weights + #if CURRENT_RENDERER == RENDERER_FORWARD_PLUS + if (bilerp) { + t_weights = {vec2(0), vec2(0), vec2(0), vec2(0)}; + weights_id_0 *= weights; + weights_id_1 *= weights; + for (int i = 0; i < 4; i++) { + vec2 w_0 = vec2(weights_id_0[i]); + vec2 w_1 = vec2(weights_id_1[i]); + ivec2 id_0 = texture_ids[i].xx; + ivec2 id_1 = texture_ids[i].yy; + t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1))); + t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1))); + t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1))); + t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1))); + } + } + #endif + + // Struct to accumulate all texture data. + material mat = material(vec4(0.0), vec4(0.0), 0., 0., 0.); + + // 2 - 4 lookups, 2 - 6 if dual scale texture + accumulate_material(base_ddx, base_ddy, weights[3], index[3], control[3], t_weights[3], + texture_ids[3], index_normal[3], h[3], mat); + + // 6 - 12 lookups, 6 - 18 if dual scale texture + if (bilerp) { + accumulate_material(base_ddx, base_ddy, weights[2], index[2], control[2], t_weights[2], + texture_ids[2], index_normal[2], h[2], mat); + accumulate_material(base_ddx, base_ddy, weights[1], index[1], control[1], t_weights[1], + texture_ids[1], index_normal[1], h[1], mat); + accumulate_material(base_ddx, base_ddy, weights[0], index[0], control[0], t_weights[0], + texture_ids[0], index_normal[0], h[0], mat); + } + + // normalize accumulated values back to 0.0 - 1.0 range. + float weight_inv = 1.0 / mat.total_weight; + mat.albedo_height *= weight_inv; + mat.normal_rough *= weight_inv; + mat.normal_map_depth *= weight_inv; + mat.ao_strength *= weight_inv; + + // Macro variation. 2 lookups + vec3 macrov = vec3(1.); + if (macro_variation) { + float noise1 = texture(noise_texture, rotate_vec2(fma(uv, vec2(noise1_scale * .1), noise1_offset) , vec2(cos(noise1_angle), sin(noise1_angle)))).r; + float noise2 = texture(noise_texture, uv * noise2_scale * .1).r; + macrov = mix(macro_variation1, vec3(1.), noise1); + macrov *= mix(macro_variation2, vec3(1.), noise2); + macrov = mix(vec3(1.0), macrov, clamp(w_normal.y + macro_variation_slope, 0., 1.)); + } + + // Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values + float roughness = clamp(fma(color_map.a - 0.5, 2.0, mat.normal_rough.a), 0., 1.); + + // Apply PBR + ALBEDO = mat.albedo_height.rgb * color_map.rgb * macrov; + ROUGHNESS = roughness; + SPECULAR = 1. - mat.normal_rough.a; + // Repack final normal map value. + NORMAL_MAP = fma(normalize(mat.normal_rough.xzy), vec3(0.5), vec3(0.5)); + NORMAL_MAP_DEPTH = mat.normal_map_depth; + + // Higher and/or facing up, less occluded. + float ao = (1. - (mat.albedo_height.a * log(2.1 - mat.ao_strength))) * (1. - mat.normal_rough.y); + AO = clamp(1. - ao * mat.ao_strength, mat.albedo_height.a, 1.0); + AO_LIGHT_AFFECT = (1.0 - mat.albedo_height.a) * clamp(mat.normal_rough.y, 0., 1.); + +} + diff --git a/project/tmp_terrain_shader.gdshader.uid b/project/tmp_terrain_shader.gdshader.uid new file mode 100644 index 000000000..f69646dfa --- /dev/null +++ b/project/tmp_terrain_shader.gdshader.uid @@ -0,0 +1 @@ +uid://bohymbh771gyr diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 9a0bba9ce..35d6bd7c5 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -475,6 +475,9 @@ void Terrain3D::set_plugin(EditorPlugin *p_plugin) { } void Terrain3D::set_camera(Camera3D *p_camera) { + if (p_camera == _camera.get_target()) { + return; + } if (p_camera && p_camera->is_queued_for_deletion()) { LOG(ERROR, "Attempted to set a node queued for deletion"); _camera.clear(); @@ -869,6 +872,13 @@ PackedStringArray Terrain3D::_get_configuration_warnings() const { return psa; } +void Terrain3D::set_shadow_material(const Ref &p_material) { + _shadow_material = p_material; + if (_mesher) { + _mesher->update(); + } +} + /////////////////////////// // Protected Functions /////////////////////////// @@ -1124,6 +1134,10 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mouse_layer"), &Terrain3D::get_mouse_layer); ClassDB::bind_method(D_METHOD("set_cast_shadows", "shadow_casting_setting"), &Terrain3D::set_cast_shadows); ClassDB::bind_method(D_METHOD("get_cast_shadows"), &Terrain3D::get_cast_shadows); + + ClassDB::bind_method(D_METHOD("set_shadow_material", "material"), &Terrain3D::set_shadow_material); + ClassDB::bind_method(D_METHOD("get_shadow_material"), &Terrain3D::get_shadow_material); + ClassDB::bind_method(D_METHOD("set_gi_mode", "gi_mode"), &Terrain3D::set_gi_mode); ClassDB::bind_method(D_METHOD("get_gi_mode"), &Terrain3D::get_gi_mode); ClassDB::bind_method(D_METHOD("set_cull_margin", "margin"), &Terrain3D::set_cull_margin); @@ -1220,6 +1234,9 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "render_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_render_layers", "get_render_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_layer", PROPERTY_HINT_RANGE, "21, 32"), "set_mouse_layer", "get_mouse_layer"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadows", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows", "get_cast_shadows"); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shadow_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_shadow_material", "get_shadow_material"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cull_margin", PROPERTY_HINT_RANGE, "0.0,10000.0,.5,or_greater"), "set_cull_margin", "get_cull_margin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "free_editor_textures"), "set_free_editor_textures", "get_free_editor_textures"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 96f5f36bd..81f36888a 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,7 @@ class Terrain3D : public Node3D { GeometryInstance3D::GIMode _gi_mode = GeometryInstance3D::GI_MODE_STATIC; real_t _cull_margin = 0.0f; bool _free_editor_textures = true; + Ref _shadow_material; // Mouse cursor SubViewport *_mouse_vp = nullptr; @@ -204,6 +206,9 @@ class Terrain3D : public Node3D { uint8_t get_warnings() const { return _warnings; } PackedStringArray _get_configuration_warnings() const override; + void set_shadow_material(const Ref &p_material); + Ref get_shadow_material() const { return _shadow_material; } + // Collision Aliases void set_collision_mode(const CollisionMode p_mode) { _collision ? _collision->set_mode(p_mode) : void(); } CollisionMode get_collision_mode() const { return _collision ? _collision->get_mode() : CollisionMode::DYNAMIC_GAME; } diff --git a/src/terrain_3d_mesher.cpp b/src/terrain_3d_mesher.cpp index 498a35356..092fc1402 100644 --- a/src/terrain_3d_mesher.cpp +++ b/src/terrain_3d_mesher.cpp @@ -12,53 +12,66 @@ // Private Functions /////////////////////////// -void Terrain3DMesher::_generate_mesh_types(const int p_size) { +void Terrain3DMesher::_generate_mesh_types(const int p_size, int p_shadow_scale) { + p_shadow_scale = CLAMP(p_shadow_scale, 1, 4); _clear_mesh_types(); LOG(INFO, "Generating all Mesh segments for clipmap of size ", p_size); // Create initial set of Mesh blocks to build the clipmap // # 0 TILE - mesh_size x mesh_size tiles _mesh_rids.push_back(_generate_mesh(V2I(p_size))); + _shadow_mesh_rids.push_back(_generate_mesh(V2I(p_size), false, p_shadow_scale)); // # 1 EDGE_A - 2 by (mesh_size * 4 + 8) strips to bridge LOD transitions along +-Z axis _mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 8))); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 8), false, p_shadow_scale)); // # 2 EDGE_B - (mesh_size * 4 + 4) by 2 strips to bridge LOD transitions along +-X axis _mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 4, 2))); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 4, 2), false, p_shadow_scale)); // # 3 FILL_A - 4 by mesh_size _mesh_rids.push_back(_generate_mesh(Vector2i(4, p_size))); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(4, p_size), false, p_shadow_scale)); // # 4 FILL_B - mesh_size by 4 _mesh_rids.push_back(_generate_mesh(Vector2i(p_size, 4))); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(p_size, 4), false, p_shadow_scale)); // # 5 STANDARD_TRIM_A - 2 by (mesh_size * 4 + 2) strips for LOD0 +-Z axis edge _mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 2), true)); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 2), true, p_shadow_scale)); // # 6 STANDARD_TRIM_B - (mesh_size * 4 + 2) by 2 strips for LOD0 +-X axis edge _mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 2, 2), true)); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 2, 2), true, p_shadow_scale)); // # 7 STANDARD_TILE - mesh_size x mesh_size tiles _mesh_rids.push_back(_generate_mesh(Vector2i(p_size, p_size), true)); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(p_size, p_size), true, p_shadow_scale)); // # 8 STANDARD_EDGE_A - 2 by (mesh_size * 4 + 8) strips to bridge LOD transitions along +-Z axis _mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 8), true)); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(2, p_size * 4 + 8), true, p_shadow_scale)); // # 9 STANDARD_EDGE_B - (mesh_size * 4 + 4) by 2 strips to bridge LOD transitions along +-X axis _mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 4, 2), true)); + _shadow_mesh_rids.push_back(_generate_mesh(Vector2i(p_size * 4 + 4, 2), true, p_shadow_scale)); return; } -RID Terrain3DMesher::_generate_mesh(const Vector2i &p_size, const bool p_standard_grid) { +RID Terrain3DMesher::_generate_mesh(const Vector2i &p_size, const bool p_standard_grid, int p_scale) { + p_scale = CLAMP(p_scale, 1, 4); + PackedVector3Array vertices; PackedInt32Array indices; AABB aabb = AABB(V3_ZERO, Vector3(p_size.x, 0.1f, p_size.y)); LOG(DEBUG, "Generating verticies and indices for a", p_standard_grid ? " symetric " : " standard ", "grid mesh of width: ", p_size.x, " and height: ", p_size.y); // Generate vertices - for (int y = 0; y <= p_size.y; ++y) { - for (int x = 0; x <= p_size.x; ++x) { + for (int y = 0; y <= p_size.y / p_scale; ++y) { + for (int x = 0; x <= p_size.x / p_scale; ++x) { // Match GDScript vertex definitions - vertices.push_back(Vector3(x, 0.f, y)); // bottom-left + vertices.push_back(Vector3(x * p_scale, 0.f, y * p_scale)); } } // Generate indices for quads with alternating diagonals - for (int y = 0; y < p_size.y; ++y) { - for (int x = 0; x < p_size.x; ++x) { - int bottomLeft = y * (p_size.x + 1) + x; + for (int y = 0; y < p_size.y / p_scale; ++y) { + for (int x = 0; x < p_size.x / p_scale; ++x) { + int bottomLeft = y * ((p_size.x / p_scale) + 1) + x; int bottomRight = bottomLeft + 1; - int topLeft = (y + 1) * (p_size.x + 1) + x; + int topLeft = (y + 1) * ((p_size.x / p_scale) + 1) + x; int topRight = topLeft + 1; if ((x + y) % 2 == 0 || p_standard_grid) { @@ -80,7 +93,6 @@ RID Terrain3DMesher::_generate_mesh(const Vector2i &p_size, const bool p_standar } } } - return _instantiate_mesh(vertices, indices, aabb); } @@ -107,78 +119,107 @@ RID Terrain3DMesher::_instantiate_mesh(const PackedVector3Array &p_vertices, con LOG(DEBUG, "Setting custom aabb: ", p_aabb.position, ", ", p_aabb.size); RS->mesh_set_custom_aabb(mesh, p_aabb); RS->mesh_surface_set_material(mesh, 0, _terrain->get_material()->get_material_rid()); - return mesh; } void Terrain3DMesher::_generate_clipmap(const int p_size, const int p_lods, const RID &p_scenario) { _clear_clipmap(); - _generate_mesh_types(p_size); + _generate_mesh_types(p_size, 2); _generate_offset_data(p_size); LOG(DEBUG, "Creating instances for all mesh segments for clipmap of size ", p_size, " for ", p_lods, " LODs"); for (int level = 0; level < p_lods; level++) { Array lod; + Array shadow_lod; // 12 Tiles LOD1+, 16 for LOD0 Array tile_rids; - int tile_ammount = (level == 0) ? 16 : 12; + Array shadow_tile_rids; + int tile_amount = (level == 0) ? 16 : 12; - for (int i = 0; i < tile_ammount; i++) { + for (int i = 0; i < tile_amount; i++) { RID tile_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_TILE : TILE], p_scenario); + RID shadow_rid = RS->instance_create2(_shadow_mesh_rids[level == 0 ? STANDARD_TILE : TILE], p_scenario); tile_rids.append(tile_rid); + shadow_tile_rids.append(shadow_rid); } lod.append(tile_rids); // index 0 TILE + shadow_lod.append(shadow_tile_rids); // 4 Edges present on all LODs Array edge_a_rids; + Array shadow_edge_a_rids; for (int i = 0; i < 2; i++) { RID edge_a_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_EDGE_A : EDGE_A], p_scenario); + RID shadow_edge_a_rid = RS->instance_create2(_shadow_mesh_rids[level == 0 ? STANDARD_EDGE_A : EDGE_A], p_scenario); edge_a_rids.append(edge_a_rid); + shadow_edge_a_rids.append(shadow_edge_a_rid); } lod.append(edge_a_rids); // index 1 EDGE_A + shadow_lod.append(shadow_edge_a_rids); Array edge_b_rids; + Array shadow_edge_b_rids; for (int i = 0; i < 2; i++) { RID edge_b_rid = RS->instance_create2(_mesh_rids[level == 0 ? STANDARD_EDGE_B : EDGE_B], p_scenario); + RID shadow_edge_b_rid = RS->instance_create2(_shadow_mesh_rids[level == 0 ? STANDARD_EDGE_B : EDGE_B], p_scenario); edge_b_rids.append(edge_b_rid); + shadow_edge_b_rids.append(shadow_edge_b_rid); } lod.append(edge_b_rids); // index 2 EDGE_B + shadow_lod.append(shadow_edge_b_rids); // Fills only present on LODs 1+ if (level > 0) { Array fill_a_rids; + Array shadow_fill_a_rids; for (int i = 0; i < 2; i++) { RID fill_a_rid = RS->instance_create2(_mesh_rids[FILL_A], p_scenario); + RID shadow_fill_a_rid = RS->instance_create2(_shadow_mesh_rids[FILL_A], p_scenario); fill_a_rids.append(fill_a_rid); + shadow_fill_a_rids.append(shadow_fill_a_rid); } lod.append(fill_a_rids); // index 4 FILL_A + shadow_lod.append(shadow_fill_a_rids); Array fill_b_rids; + Array shadow_fill_b_rids; for (int i = 0; i < 2; i++) { RID fill_b_rid = RS->instance_create2(_mesh_rids[FILL_B], p_scenario); + RID shadow_fill_b_rid = RS->instance_create2(_shadow_mesh_rids[FILL_B], p_scenario); fill_b_rids.append(fill_b_rid); + shadow_fill_b_rids.append(shadow_fill_b_rid); } lod.append(fill_b_rids); // index 5 FILL_B + shadow_lod.append(shadow_fill_b_rids); // Trims only on LOD 0 These share the indices of the fills for the offsets. // When snapping LOD 0 Trim a/b positions are looked up instead of Fill a/b } else { Array trim_a_rids; + Array shadow_trim_a_rids; for (int i = 0; i < 2; i++) { RID trim_a_rid = RS->instance_create2(_mesh_rids[STANDARD_TRIM_A], p_scenario); + RID shadow_trim_a_rid = RS->instance_create2(_shadow_mesh_rids[STANDARD_TRIM_A], p_scenario); trim_a_rids.append(trim_a_rid); + shadow_trim_a_rids.append(shadow_trim_a_rid); } lod.append(trim_a_rids); // index 4 TRIM_A + shadow_lod.append(shadow_trim_a_rids); Array trim_b_rids; + Array shadow_trim_b_rids; for (int i = 0; i < 2; i++) { RID trim_b_rid = RS->instance_create2(_mesh_rids[STANDARD_TRIM_B], p_scenario); + RID shadow_trim_b_rid = RS->instance_create2(_shadow_mesh_rids[STANDARD_TRIM_B], p_scenario); trim_b_rids.append(trim_b_rid); + shadow_trim_b_rids.append(shadow_trim_b_rid); } lod.append(trim_b_rids); // index 5 TRIM_B + shadow_lod.append(shadow_trim_b_rids); } // Append LOD to _lod_rids array _clipmap_rids.append(lod); + _clipmap_shadow_rids.append(shadow_lod); } } @@ -255,16 +296,22 @@ void Terrain3DMesher::_clear_clipmap() { LOG(INFO, "Freeing all clipmap instances"); for (int lod = 0; lod < _clipmap_rids.size(); lod++) { Array lod_array = _clipmap_rids[lod]; + Array shadow_lod_array = _clipmap_shadow_rids[lod]; for (int mesh = 0; mesh < lod_array.size(); mesh++) { Array mesh_array = lod_array[mesh]; + Array shadow_mesh_array = shadow_lod_array[mesh]; for (int instance = 0; instance < mesh_array.size(); instance++) { RS->free_rid(mesh_array[instance]); + RS->free_rid(shadow_mesh_array[instance]); } mesh_array.clear(); + shadow_mesh_array.clear(); } lod_array.clear(); + shadow_lod_array.clear(); } _clipmap_rids.clear(); + _clipmap_shadow_rids.clear(); return; } @@ -273,8 +320,10 @@ void Terrain3DMesher::_clear_mesh_types() { LOG(INFO, "Freeing all clipmap meshes"); for (int m = 0; m < _mesh_rids.size(); m++) { RS->free_rid(_mesh_rids[m]); + RS->free_rid(_shadow_mesh_rids[m]); } _mesh_rids.clear(); + _shadow_mesh_rids.clear(); return; } @@ -326,6 +375,9 @@ void Terrain3DMesher::snap() { real_t vertex_spacing = _terrain->get_vertex_spacing(); Vector3 snapped_pos = (target_pos / vertex_spacing).floor() * vertex_spacing; RS->material_set_param(_terrain->get_material()->get_material_rid(), "_camera_pos", snapped_pos); + if (_terrain->get_shadow_material().is_valid()) { + RS->material_set_param(_terrain->get_shadow_material()->get_rid(), "_camera_pos", snapped_pos); + } Vector3 pos = V3_ZERO; for (int lod = 0; lod < _clipmap_rids.size(); ++lod) { @@ -345,8 +397,10 @@ void Terrain3DMesher::snap() { int test_x = CLAMP(int(round((pos.x - next_x) / snap_step)) + 1, 0, 2); int test_z = CLAMP(int(round((pos.z - next_z) / snap_step)) + 1, 0, 2); Array lod_array = _clipmap_rids[lod]; + Array shadow_array = _clipmap_shadow_rids[lod]; for (int mesh = 0; mesh < lod_array.size(); ++mesh) { Array mesh_array = lod_array[mesh]; + Array shadow_mesh_array = shadow_array[mesh]; for (int instance = 0; instance < mesh_array.size(); ++instance) { Transform3D t = Transform3D(); switch (mesh) { @@ -390,9 +444,11 @@ void Terrain3DMesher::snap() { t = t.scaled(lod_scale); t.origin += pos; RS->instance_set_transform(mesh_array[instance], t); + RS->instance_set_transform(shadow_mesh_array[instance], t); // Deprecated Godot 4.5+ #if GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR == 4 RS->instance_reset_physics_interpolation(mesh_array[instance]); + RS->instance_reset_physics_interpolation(shadow_mesh_array[instance]); #endif } } @@ -433,21 +489,97 @@ void Terrain3DMesher::update() { LOG(INFO, "Updating all mesh instances for ", _clipmap_rids.size(), " LODs"); for (int lod = 0; lod < _clipmap_rids.size(); ++lod) { Array lod_array = _clipmap_rids[lod]; + Array lod_shadow_array = _clipmap_shadow_rids[lod]; for (int mesh = 0; mesh < lod_array.size(); ++mesh) { Array mesh_array = lod_array[mesh]; + Array shadow_array = lod_shadow_array[mesh]; for (int instance = 0; instance < mesh_array.size(); ++instance) { RS->instance_set_visible(mesh_array[instance], visible); RS->instance_set_scenario(mesh_array[instance], scenario); RS->instance_set_layer_mask(mesh_array[instance], render_layers); - RS->instance_geometry_set_cast_shadows_setting(mesh_array[instance], cast_shadows); RS->instance_geometry_set_flag(mesh_array[instance], RenderingServer::INSTANCE_FLAG_USE_BAKED_LIGHT, baked_light); RS->instance_geometry_set_flag(mesh_array[instance], RenderingServer::INSTANCE_FLAG_USE_DYNAMIC_GI, dynamic_gi); + RS->instance_geometry_set_cast_shadows_setting(mesh_array[instance], RenderingServer::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF); + + // Shadow imposter + if (!_terrain->get_shadow_material().is_valid()) { + LOG(WARN, "No shadow material assigned to terrain, cannot set material override"); + RS->instance_set_visible(shadow_array[instance], false); + continue; + } + RS->instance_set_visible(shadow_array[instance], visible); + RS->instance_set_scenario(shadow_array[instance], scenario); + RS->instance_set_layer_mask(shadow_array[instance], render_layers); + RS->instance_set_visible(shadow_array[instance], cast_shadows); + RS->instance_geometry_set_cast_shadows_setting(shadow_array[instance], cast_shadows); + RS->instance_geometry_set_material_override(shadow_array[instance], _terrain->get_shadow_material()->get_rid()); } } } + _update_maps(); return; } +void Terrain3DMesher::_update_maps() { + IS_DATA_INIT(VOID); + LOG(EXTREME, "Updating maps in shader"); + + if (!_terrain->get_shadow_material().is_valid()) { + LOG(WARN, "No shadow material assigned to terrain, cannot update maps in shader"); + return; + } + + RID material_rid = _terrain->get_shadow_material()->get_rid(); + + Terrain3DData *data = _terrain->get_data(); + PackedInt32Array region_map = data->get_region_map(); + LOG(EXTREME, "region_map.size(): ", region_map.size()); + if (region_map.size() != Terrain3DData::REGION_MAP_SIZE * Terrain3DData::REGION_MAP_SIZE) { + LOG(ERROR, "Expected region_map.size() of ", Terrain3DData::REGION_MAP_SIZE * Terrain3DData::REGION_MAP_SIZE); + return; + } + RS->material_set_param(material_rid, "_region_map", region_map); + RS->material_set_param(material_rid, "_region_map_size", Terrain3DData::REGION_MAP_SIZE); + if (Terrain3D::debug_level >= EXTREME) { + LOG(EXTREME, "Region map"); + for (int i = 0; i < region_map.size(); i++) { + if (region_map[i]) { + LOG(EXTREME, "Region id: ", region_map[i], " array index: ", i); + } + } + } + + TypedArray region_locations = data->get_region_locations(); + LOG(EXTREME, "Region_locations size: ", region_locations.size(), " ", region_locations); + RS->material_set_param(material_rid, "_region_locations", region_locations); + + real_t region_size = real_t(_terrain->get_region_size()); + LOG(EXTREME, "Setting region size in material: ", region_size); + RS->material_set_param(material_rid, "_region_size", region_size); + RS->material_set_param(material_rid, "_region_texel_size", 1.0f / region_size); + if (RS->material_get_param(material_rid, "_height_maps")) { + LOG(INFO, "Material ", material_rid, " has parameter height maps"); + LOG(INFO, RS->material_get_param(material_rid, "_height_maps")) + } else { + LOG(WARN, "Material ", material_rid, " has no parameter height maps"); + } + RS->material_set_param(material_rid, "_height_maps", data->get_height_maps_rid()); + + RS->material_set_param(material_rid, "_control_maps", data->get_control_maps_rid()); + RS->material_set_param(material_rid, "_color_maps", data->get_color_maps_rid()); + LOG(EXTREME, "Height map RID: ", data->get_height_maps_rid()); + LOG(EXTREME, "Control map RID: ", data->get_control_maps_rid()); + LOG(EXTREME, "Color map RID: ", data->get_color_maps_rid()); + + real_t spacing = _terrain->get_vertex_spacing(); + LOG(EXTREME, "Setting vertex spacing in material: ", spacing); + RS->material_set_param(material_rid, "_vertex_spacing", spacing); + RS->material_set_param(material_rid, "_vertex_density", 1.0f / spacing); + + real_t mesh_size = real_t(_terrain->get_mesh_size()); + RS->material_set_param(material_rid, "_mesh_size", mesh_size); +} + // Iterates over all meshes and updates their AABBs // All instances of each mesh inherit the updated AABB void Terrain3DMesher::update_aabbs() { @@ -459,10 +591,12 @@ void Terrain3DMesher::update_aabbs() { LOG(INFO, "Updating ", _mesh_rids.size(), " meshes AABBs") for (int m = 0; m < _mesh_rids.size(); m++) { RID mesh = _mesh_rids[m]; + RID shadow_mesh = _shadow_mesh_rids[m]; AABB aabb = RS->mesh_get_custom_aabb(mesh); aabb.position.y = height_range.x - cull_margin; aabb.size.y = height_range.y + cull_margin * 2.f; RS->mesh_set_custom_aabb(mesh, aabb); + RS->mesh_set_custom_aabb(shadow_mesh, aabb); } return; } diff --git a/src/terrain_3d_mesher.h b/src/terrain_3d_mesher.h index ed3efc2ee..fc10a0632 100644 --- a/src/terrain_3d_mesher.h +++ b/src/terrain_3d_mesher.h @@ -31,8 +31,10 @@ class Terrain3DMesher { Vector2 _last_target_position = V2_MAX; Array _mesh_rids; + Array _shadow_mesh_rids; // LODs -> MeshTypes -> Instances Array _clipmap_rids; + Array _clipmap_shadow_rids; // Mesh offset data // LOD0 only @@ -49,14 +51,15 @@ class Terrain3DMesher { real_t _offset_c = 0.f; PackedVector3Array _edge_pos; - void _generate_mesh_types(const int p_mesh_size); - RID _generate_mesh(const Vector2i &p_size, const bool p_standard_grid = false); + void _generate_mesh_types(const int p_mesh_size, int p_shadow_scale = 1); + RID _generate_mesh(const Vector2i &p_size, const bool p_standard_grid = false, int p_scale = 1); RID _instantiate_mesh(const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices, const AABB &p_aabb); void _generate_clipmap(const int p_size, const int p_lods, const RID &scenario); void _generate_offset_data(const int p_mesh_size); void _clear_clipmap(); void _clear_mesh_types(); + void _update_maps(); public: Terrain3DMesher() {}