From bea8ef10c0635914b6704b4022281d0452ece39a Mon Sep 17 00:00:00 2001
From: Xtarsia <69606701+Xtarsia@users.noreply.github.com>
Date: Mon, 7 Oct 2024 23:20:18 +0100
Subject: [PATCH 1/4] Projected UVs texelFetch Normals Derivative adjustment
and Bilerp branch
---
project/demo/Demo.tscn | 9 +-
src/shaders/auto_shader.glsl | 2 +-
src/shaders/dual_scaling.glsl | 31 ++-
src/shaders/main.glsl | 347 +++++++++++++++++++---------------
src/shaders/world_noise.glsl | 31 ++-
src/terrain_3d_material.cpp | 1 +
6 files changed, 245 insertions(+), 176 deletions(-)
diff --git a/project/demo/Demo.tscn b/project/demo/Demo.tscn
index d3afcc039..c42039c99 100644
--- a/project/demo/Demo.tscn
+++ b/project/demo/Demo.tscn
@@ -41,17 +41,24 @@ _shader_parameters = {
"dual_scale_near": 100.0,
"dual_scale_reduction": 0.3,
"dual_scale_texture": 0,
+"enable_macro_variation": true,
+"enable_projection": true,
"height_blending": true,
"macro_variation1": Color(0.878431, 0.862745, 0.901961, 1),
"macro_variation2": Color(0.898039, 0.898039, 0.803922, 1),
+"macro_variation_slope": 0.333,
"noise1_angle": 0.1,
"noise1_offset": Vector2(0.5, 0.5),
"noise1_scale": 0.04,
"noise2_scale": 0.076,
"noise3_scale": 0.225,
"noise_texture": SubResource("NoiseTexture2D_bov7h"),
+"projection_angular_division": 1.436,
+"projection_threshold": 0.8,
+"texture_depth_blur": 0.0,
+"texture_mipmap_bias": 1.0,
"tri_scale_reduction": 0.3,
-"vertex_normals_distance": 128.0,
+"world_noise_fragment_normals": false,
"world_noise_height": 34.0,
"world_noise_lod_distance": 7500.0,
"world_noise_max_octaves": 4,
diff --git a/src/shaders/auto_shader.glsl b/src/shaders/auto_shader.glsl
index 1625892f8..fcc96562f 100644
--- a/src/shaders/auto_shader.glsl
+++ b/src/shaders/auto_shader.glsl
@@ -14,7 +14,7 @@ uniform int auto_overlay_texture : hint_range(0, 31) = 1;
out_mat.base = int(auto_shader) * auto_base_texture + int(!auto_shader) * int(control >>27u & 0x1Fu);
out_mat.over = int(auto_shader) * auto_overlay_texture + int(!auto_shader) * int(control >> 22u & 0x1Fu);
out_mat.blend = float(auto_shader) * clamp(
- dot(vec3(0., 1., 0.), normal * auto_slope * 2. - (auto_slope * 2. - 1.))
+ dot(vec3(0., 1., 0.), auto_slope * 2. * (normal - 1.) + 1.)
- auto_height_reduction * .01 * v_vertex.y // Reduce as vertices get higher
, 0., 1.) +
float(!auto_shader) * float(control >>14u & 0xFFu) * 0.003921568627450; // 1./255.0
diff --git a/src/shaders/dual_scaling.glsl b/src/shaders/dual_scaling.glsl
index 99f58d6ae..92bebc608 100644
--- a/src/shaders/dual_scaling.glsl
+++ b/src/shaders/dual_scaling.glsl
@@ -16,31 +16,29 @@ uniform float dual_scale_near : hint_range(0,1000) = 100.0;
}
//each time we change scale, recalculate antitiling from baseline to maintain continuity.
matUV = detiling(base_uv * mat_scale, uv_center * mat_scale, out_mat.base, normal_angle);
- ddx1 *= mat_scale;
- ddy1 *= mat_scale;
- albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
- normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
+ dd1 *= mat_scale;
+ albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), dd1.xy, dd1.zw);
+ normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), dd1.xy, dd1.zw);
// Unpack & rotate base normal for blending
normal_rg.xz = unpack_normal(normal_rg).xz;
- normal_rg.xz = rotate_normal(normal_rg.xz, normal_angle);
+ normal_rg.xz = rotate_normal(normal_rg.xz, -normal_angle);
- if(out_mat.base == dual_scale_texture || out_mat.over == dual_scale_texture) {
+ float far_factor = clamp(smoothstep(dual_scale_near, dual_scale_far, length(v_vertex - v_camera_pos)), 0.0, 1.0);
+ if (far_factor > 0.f && (out_mat.base == dual_scale_texture || out_mat.over == dual_scale_texture)) {
mat_scale *= dual_scale_reduction;
- ddx1 *= dual_scale_reduction;
- ddy1 *= dual_scale_reduction;
+ dd1 *= dual_scale_reduction;
float dual_scale_normal = uv_rotation; //do not add near & far rotations
// Do not apply detiling if tri-scale reduction occurs.
matUV = region < 0 ? base_uv * mat_scale : detiling(base_uv * mat_scale, uv_center * mat_scale, dual_scale_texture, dual_scale_normal);
- albedo_far = textureGrad(_texture_array_albedo, vec3(matUV, float(dual_scale_texture)), ddx1, ddy1);
- normal_far = textureGrad(_texture_array_normal, vec3(matUV, float(dual_scale_texture)), ddx1, ddy1);
+ albedo_far = textureGrad(_texture_array_albedo, vec3(matUV, float(dual_scale_texture)), dd1.xy, dd1.zw);
+ normal_far = textureGrad(_texture_array_normal, vec3(matUV, float(dual_scale_texture)), dd1.xy, dd1.zw);
// Unpack & rotate dual scale normal for blending
normal_far.xz = unpack_normal(normal_far).xz;
- normal_far.xz = rotate_normal(normal_far.xz, dual_scale_normal);
+ normal_far.xz = rotate_normal(normal_far.xz, -dual_scale_normal);
}
- float far_factor = clamp(smoothstep(dual_scale_near, dual_scale_far, length(v_vertex - v_camera_pos)), 0.0, 1.0);
if(out_mat.base == dual_scale_texture) {
albedo_ht = mix(albedo_ht, albedo_far, far_factor);
normal_rg = mix(normal_rg, normal_far, far_factor);
@@ -48,14 +46,13 @@ uniform float dual_scale_near : hint_range(0,1000) = 100.0;
//INSERT: UNI_SCALING_BASE
matUV = detiling(base_uv * mat_scale, uv_center * mat_scale, out_mat.base, normal_angle);
- ddx1 *= mat_scale;
- ddy1 *= mat_scale;
- albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
- normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
+ dd1 *= mat_scale;
+ albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), dd1.xy, dd1.zw);
+ normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), dd1.xy, dd1.zw);
// Unpack & rotate base normal for blending
normal_rg.xz = unpack_normal(normal_rg).xz;
- normal_rg.xz = rotate_normal(normal_rg.xz, normal_angle);
+ normal_rg.xz = rotate_normal(normal_rg.xz, -normal_angle);
//INSERT: DUAL_SCALING_OVERLAY
// If dual scaling, apply to overlay texture
diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl
index 9add592d8..b58d0430a 100644
--- a/src/shaders/main.glsl
+++ b/src/shaders/main.glsl
@@ -25,38 +25,48 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic
// Private uniforms
-uniform float _region_size = 1024.0;
-uniform float _region_texel_size = 0.0009765625; // = 1/1024
+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 usampler2DArray _control_maps : repeat_disable;
//INSERT: TEXTURE_SAMPLERS_NEAREST
//INSERT: TEXTURE_SAMPLERS_LINEAR
-uniform float _texture_uv_scale_array[32];
-uniform float _texture_detile_array[32];
-uniform vec4 _texture_color_array[32];
-uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
-uniform uint _mouse_layer = 0x80000000u; // Layer 32
// Public uniforms
-uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
-uniform bool height_blending = true;
-uniform float blend_sharpness : hint_range(0, 1) = 0.87;
//INSERT: AUTO_SHADER_UNIFORMS
//INSERT: DUAL_SCALING_UNIFORMS
+uniform bool height_blending = true;
+uniform float blend_sharpness : hint_range(0, 1) = 0.87;
+
+uniform bool enable_projection = true;
+uniform float projection_threshold : hint_range(0.0, 1.0, 0.01) = 0.8;
+uniform float projection_angular_division : hint_range(.85, 16.0, 0.001) = 1.436;
+
+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;
+uniform float bias_distance : hint_range(0.0, 16384.0, 0.1) = 512.0;
+
+uniform bool enable_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;
// Generic noise at 3 scales, which can be used for anything
-uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
+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
-uniform float noise3_scale : hint_range(0.001, 1.) = 0.225; // Used for texture blending edge.
+uniform float noise3_scale : hint_range(0.001, 1.) = 0.225; // Used for texture blending edge.
// Varyings & Types
@@ -68,14 +78,13 @@ struct Material {
float blend;
};
-varying flat vec3 v_vertex; // World coordinate vertex location
varying flat vec3 v_camera_pos;
-varying float v_vertex_xz_dist;
varying flat ivec3 v_region;
varying flat vec2 v_uv_offset;
varying flat vec2 v_uv2_offset;
+varying float v_vertex_xz_dist;
+varying vec3 v_vertex;
varying vec3 v_normal;
-varying float v_region_border_mask;
)"
R"(
@@ -105,17 +114,6 @@ vec3 get_region_uv2(const vec2 uv2) {
}
//INSERT: WORLD_NOISE1
-// 1 lookup
-float get_height(vec2 uv) {
- highp float height = 0.0;
- vec3 region = get_region_uv2(uv);
- if (region.z >= 0.) {
- height = texture(_height_maps, region).r;
- }
-//INSERT: WORLD_NOISE2
- return height;
-}
-
void vertex() {
// Get camera pos in world vertex coords
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
@@ -140,21 +138,17 @@ void vertex() {
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
- VERTEX.x = 0. / 0.;
+ v_vertex.x = 0. / 0.;
} else {
// Set final vertex height & calculate vertex normals. 3 lookups.
- VERTEX.y = get_height(UV2);
- v_vertex.y = VERTEX.y;
- v_normal = vec3(
- v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
- _vertex_spacing,
- v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
- );
- // Due to a bug caused by the GPUs linear interpolation across edges of region maps,
- // mask region edges and use vertex normals only across region boundaries.
- v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
+ float h = texelFetch(_height_maps, v_region, 0).r;
+ float u = texelFetch(_height_maps, get_region_uv(UV + vec2(1,0)), 0).r;
+ float v = texelFetch(_height_maps, get_region_uv(UV + vec2(0,1)), 0).r;
+//INSERT: WORLD_NOISE2
+ v_vertex.y = h;
+ v_normal = vec3(h - u, _vertex_spacing, h - v);
}
-
+
// Transform UVs to local to avoid poor precision during varying interpolation.
v_uv_offset = MODEL_MATRIX[3].xz * _vertex_density;
UV -= v_uv_offset;
@@ -162,8 +156,7 @@ void vertex() {
UV2 -= v_uv2_offset;
// Convert model space to view space w/ skip_vertex_transform render mode
- VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
- VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
+ 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);
@@ -175,31 +168,12 @@ void vertex() {
// Fragment
////////////////////////
-// 0 - 3 lookups
-vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
- float u, v, height;
- vec3 normal;
- // Use vertex normals within radius of vertex_normals_distance, and along region borders.
- if ((v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) && v_region.z >= 0) {
- normal = normalize(v_normal);
- } else {
- height = get_height(uv);
- u = height - get_height(uv + vec2(_region_texel_size, 0));
- v = height - get_height(uv + vec2(0, _region_texel_size));
- normal = normalize(vec3(u, _vertex_spacing, v));
- }
- tangent = normalize(cross(normal, vec3(0, 0, 1)));
- binormal = normalize(cross(normal, tangent));
- return normal;
-}
-
vec3 unpack_normal(vec4 rgba) {
- vec3 n = fma(rgba.xzy, vec3(2.0, 2.0, -2.0), vec3(-1.0, -1.0, 1.0));
- return n;
+ return fma(rgba.xzy, vec3(2.0), vec3(-1.0));
}
-vec4 pack_normal(vec3 n, float a) {
- return vec4(fma(n.xzy, vec3(0.5, -0.5, 0.5), vec3(0.5)), a);
+vec3 pack_normal(vec3 n) {
+ return fma(n.xzy, vec3(0.5), vec3(0.5));
}
float random(in vec2 xy) {
@@ -242,15 +216,14 @@ vec2 detiling(vec2 uv, vec2 uv_center, int mat_id, inout float normal_rotation){
}
vec2 rotate_normal(vec2 normal, float angle) {
+ float new_x = dot(vec2(cos(angle), sin(angle)), normal);
angle = fma(PI, 0.5, angle);
float new_y = dot(vec2(cos(angle), sin(angle)), normal);
- angle = fma(PI, -0.5, angle);
- float new_x = dot(vec2(cos(angle) ,sin(angle)) ,normal);
return vec2(new_x, new_y);
}
// 2-4 lookups
-void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out Material out_mat) {
+void get_material(vec2 base_uv, vec4 ddxy, uint control, ivec3 iuv_center, vec3 normal, out Material out_mat) {
out_mat = Material(vec4(0.), vec4(0.), 0, 0, 0.0);
vec2 uv_center = vec2(iuv_center.xy);
int region = iuv_center.z;
@@ -265,9 +238,8 @@ void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out
float control_scale = scale_array[(control >>7u & 0x7u)];
base_uv *= control_scale;
uv_center *= control_scale;
- // calculate baseline derivatives
- vec2 ddx = dFdxCoarse(base_uv);
- vec2 ddy = dFdyCoarse(base_uv);
+ ddxy *= control_scale;
+
// Apply global uv rotation from control map.
float uv_rotation = float(control >>10u & 0xFu) / 16. * TAU;
base_uv = rotate_around(base_uv, vec2(0), uv_rotation);
@@ -280,8 +252,7 @@ void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out
vec4 normal_far = vec4(0.5f, 0.5f, 1.0f, 1.0f);
float mat_scale = _texture_uv_scale_array[out_mat.base];
float normal_angle = uv_rotation;
- vec2 ddx1 = ddx;
- vec2 ddy1 = ddy;
+ vec4 dd1 = ddxy;
//INSERT: UNI_SCALING_BASE
//INSERT: DUAL_SCALING_BASE
@@ -292,10 +263,9 @@ void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out
float mat_scale2 = _texture_uv_scale_array[out_mat.over];
float normal_angle2 = uv_rotation;
vec2 matUV2 = detiling(base_uv * mat_scale2, uv_center * mat_scale2, out_mat.over, normal_angle2);
- vec2 ddx2 = ddx * mat_scale2;
- vec2 ddy2 = ddy * mat_scale2;
- vec4 albedo_ht2 = textureGrad(_texture_array_albedo, vec3(matUV2, float(out_mat.over)), ddx2, ddy2);
- vec4 normal_rg2 = textureGrad(_texture_array_normal, vec3(matUV2, float(out_mat.over)), ddx2, ddy2);
+ vec4 dd2 = ddxy * mat_scale2;
+ vec4 albedo_ht2 = textureGrad(_texture_array_albedo, vec3(matUV2, float(out_mat.over)), dd2.xy, dd2.zw);
+ vec4 normal_rg2 = textureGrad(_texture_array_normal, vec3(matUV2, float(out_mat.over)), dd2.xy, dd2.zw);
// Though it would seem having the above lookups in this block, or removing the branch would
// be more optimal, the first introduces artifacts #276, and the second is noticably slower.
@@ -303,7 +273,7 @@ void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out
if (out_mat.blend > 0.f) {
// Unpack & rotate overlay normal for blending
normal_rg2.xz = unpack_normal(normal_rg2).xz;
- normal_rg2.xz = rotate_normal(normal_rg2.xz, normal_angle2);
+ normal_rg2.xz = rotate_normal(normal_rg2.xz, -normal_angle2);
//INSERT: DUAL_SCALING_OVERLAY
// Apply color to overlay
@@ -314,13 +284,22 @@ void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out
normal_rg = height_blend(normal_rg, albedo_ht.a, normal_rg2, albedo_ht2.a, out_mat.blend);
}
- // Repack normals and return material
- normal_rg = pack_normal(normal_rg.xyz, normal_rg.a);
out_mat.alb_ht = albedo_ht;
out_mat.nrm_rg = normal_rg;
return;
}
+vec2 project_uv_from_normal(vec3 normal) {
+ if (v_region.z < 0 || normal.y >= projection_threshold || !enable_projection) {
+ return v_vertex.xz;
+ }
+ // Quantize the normal otherwise textures lose continuity across domains
+ vec3 p_normal = normalize(round(normal * projection_angular_division));
+ // Avoid potential singularity
+ vec3 p_tangent = normalize(cross(p_normal, vec3(1e-6, 1e-6, 1.0)));
+ return vec2(dot(v_vertex, p_tangent), dot(v_vertex, normalize(cross(p_tangent, p_normal))));
+}
+
float blend_weights(float weight, float detail) {
weight = smoothstep(0.0, 1.0, weight);
weight = sqrt(weight * 0.5);
@@ -332,100 +311,164 @@ void fragment() {
// Recover UVs
vec2 uv = UV + v_uv_offset;
vec2 uv2 = UV2 + v_uv2_offset;
-
- // Calculate Terrain Normals. 4 lookups
- vec3 w_tangent, w_binormal;
- vec3 w_normal = get_normal(uv2, w_tangent, w_binormal);
+
+ // 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
+ );
+
+ vec4 base_derivatives = vec4(dFdxCoarse(uv), dFdyCoarse(uv)) * _vertex_spacing;
+ // When this exceeds 2.0, as derivatives are across 2x2 fragments it means that
+ // each control map texel is less than 1 pixel in screen space as such we can
+ // skip all extra lookups required for bilinear blend.
+ bool bilerp = length(base_derivatives) <= 2.0;
+
+ // Adjust derivatives for mipmap bias and depth blur effect
+ base_derivatives *= mix(mipmap_bias,
+ depth_blur + 1.,
+ smoothstep(0.0, 1.0, (v_vertex_xz_dist - bias_distance) / bias_distance));
+
+ ivec3 indexUV[4];
+ // control map lookups, used for some normal lookups as well
+ indexUV[0] = get_region_uv(index_id + offsets.xy);
+ indexUV[1] = get_region_uv(index_id + offsets.yy);
+ indexUV[2] = get_region_uv(index_id + offsets.yx);
+ indexUV[3] = get_region_uv(index_id + offsets.xx);
+
+ // 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;
+
+//INSERT: WORLD_NOISE3
+ // Re-use the indexUVs for the first 4 lookups, skipping some math.
+ h[0] = texelFetch(_height_maps, indexUV[3], 0).r; // 0 (0,0)
+ h[1] = texelFetch(_height_maps, indexUV[2], 0).r; // 1 (1,0)
+ h[2] = texelFetch(_height_maps, indexUV[0], 0).r; // 2 (0,1)
+ index_normal[3] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h[2] + 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) {
+ // Fetch the additional required height values for smooth normals
+ h[3] = texelFetch(_height_maps, indexUV[1], 0).r; // 3 (1,1)
+ h[4] = texelFetch(_height_maps, get_region_uv(index_id + offsets.yz), 0).r; // 4 (1,2)
+ h[5] = texelFetch(_height_maps, get_region_uv(index_id + offsets.zy), 0).r; // 5 (2,1)
+ h[6] = texelFetch(_height_maps, get_region_uv(index_id + offsets.zx), 0).r; // 6 (2,0)
+ h[7] = texelFetch(_height_maps, get_region_uv(index_id + offsets.xz), 0).r; // 7 (0,2)
+
+ // Calculate the normal for the remaining index ids.
+ index_normal[0] = normalize(vec3(h[2] - h[3] + u, _vertex_spacing, h[2] - h[7] + v));
+ index_normal[1] = normalize(vec3(h[3] - h[5] + u, _vertex_spacing, h[3] - h[4] + v));
+ index_normal[2] = normalize(vec3(h[1] - h[6] + u, _vertex_spacing, h[1] - h[3] + 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;
- // Idenfity 4 vertices surrounding this pixel
- vec2 texel_pos = uv;
- highp vec2 texel_pos_floor = floor(uv);
-
- // Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY
- vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0);
- // And the opposite grid in ZW
- mirror.zw = vec2(1.0) - mirror.xy;
-
- // Get the region and control map ID for the vertices
- ivec3 indexUV[4] = {
- get_region_uv(texel_pos_floor + mirror.xy),
- get_region_uv(texel_pos_floor + mirror.xw),
- get_region_uv(texel_pos_floor + mirror.zy),
- get_region_uv(texel_pos_floor + mirror.zw)
- };
-
- // Lookup adjacent vertices. 4 lookups
- uint control[4] = {
- texelFetch(_control_maps, indexUV[0], 0).r,
- texelFetch(_control_maps, indexUV[1], 0).r,
- texelFetch(_control_maps, indexUV[2], 0).r,
- texelFetch(_control_maps, indexUV[3], 0).r
- };
-
- // Get the textures for each vertex. 8-16 lookups (2-4 ea)
- Material mat[4];
- get_material(uv, control[0], indexUV[0], w_normal, mat[0]);
- get_material(uv, control[1], indexUV[1], w_normal, mat[1]);
- get_material(uv, control[2], indexUV[2], w_normal, mat[2]);
- get_material(uv, control[3], indexUV[3], w_normal, mat[3]);
-
- // Calculate weight for the pixel position between the vertices
- // Bilinear interpolation of difference of uv and floor(uv)
- vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1);
- weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy);
- vec2 weights0 = vec2(1.0) - weights1;
- // Adjust final weights by texture's height/depth + noise. 1 lookup
- float noise3 = texture(noise_texture, uv*noise3_scale).r;
- vec4 weights;
- weights.x = blend_weights(weights0.x * weights0.y, clamp(mat[0].alb_ht.a + noise3, 0., 1.));
- weights.y = blend_weights(weights0.x * weights1.y, clamp(mat[1].alb_ht.a + noise3, 0., 1.));
- weights.z = blend_weights(weights1.x * weights0.y, clamp(mat[2].alb_ht.a + noise3, 0., 1.));
- weights.w = blend_weights(weights1.x * weights1.y, clamp(mat[3].alb_ht.a + noise3, 0., 1.));
- float weight_sum = weights.x + weights.y + weights.z + weights.w;
- float weight_inv = 1.0 / weight_sum;
-
- // Weighted average of albedo & height
- vec4 albedo_height = weight_inv * (
- mat[0].alb_ht * weights.x +
- mat[1].alb_ht * weights.y +
- mat[2].alb_ht * weights.z +
- mat[3].alb_ht * weights.w );
-
- // Weighted average of normal & rough
- vec4 normal_rough = weight_inv * (
- mat[0].nrm_rg * weights.x +
- mat[1].nrm_rg * weights.y +
- mat[2].nrm_rg * weights.z +
- mat[3].nrm_rg * weights.w );
+ if (enable_projection) {
+ base_derivatives *= 1.0 + (1.0 - w_normal.y);
+ }
+
+ // Minimum amount of lookups for sub fragment sized index domains.
+ uint control[4];
+ control[3] = texelFetch(_control_maps, indexUV[3], 0).r;
+ Material mat[4];
+ get_material(project_uv_from_normal(index_normal[3]), base_derivatives, control[3], indexUV[3], w_normal, mat[3]);
+
+ vec4 albedo_height = mat[3].alb_ht;
+ vec4 normal_rough = mat[3].nrm_rg;
+
+ // Otherwise do full bilinear interpolation
+ if (bilerp) {
+ control[0] = texelFetch(_control_maps, indexUV[0], 0).r;
+ control[1] = texelFetch(_control_maps, indexUV[1], 0).r;
+ control[2] = texelFetch(_control_maps, indexUV[2], 0).r;
+
+ get_material(project_uv_from_normal(index_normal[0]), base_derivatives, control[0], indexUV[0], w_normal, mat[0]);
+ get_material(project_uv_from_normal(index_normal[1]), base_derivatives, control[1], indexUV[1], w_normal, mat[1]);
+ get_material(project_uv_from_normal(index_normal[2]), base_derivatives, control[2], indexUV[2], w_normal, mat[2]);
+
+ // rebuild weights for detail and noise blending
+ float noise3 = texture(noise_texture, uv * noise3_scale).r * blend_sharpness;
+ #define PARABOLA(x) (4.0 * x * (1.0 - x))
+ weights = smoothstep(0, 1, weights);
+ weights = vec4(
+ blend_weights(weights.x + PARABOLA(weights.x) * noise3, mat[0].alb_ht.a),
+ blend_weights(weights.y + PARABOLA(weights.y) * noise3, mat[1].alb_ht.a),
+ blend_weights(weights.z + PARABOLA(weights.z) * noise3, mat[2].alb_ht.a),
+ blend_weights(weights.w + PARABOLA(weights.w) * noise3, mat[3].alb_ht.a)
+ );
+ #undef PARABOLA
+ // renormalize weights
+ weights *= 1.0 / (weights.x + weights.y + weights.z + weights.w);
+
+ // Interpolate Albedo/Height/Normal/Roughness
+ albedo_height =
+ mat[0].alb_ht * weights[0] +
+ mat[1].alb_ht * weights[1] +
+ mat[2].alb_ht * weights[2] +
+ mat[3].alb_ht * weights[3] ;
+
+ normal_rough =
+ mat[0].nrm_rg * weights[0] +
+ mat[1].nrm_rg * weights[1] +
+ mat[2].nrm_rg * weights[2] +
+ mat[3].nrm_rg * weights[3] ;
+ }
+
// Determine if we're in a region or not (region_uv.z>0)
vec3 region_uv = get_region_uv2(uv2);
-
+
// Colormap. 1 lookup
vec4 color_map = vec4(1., 1., 1., .5);
if (region_uv.z >= 0.) {
float lod = textureQueryLod(_color_maps, uv2.xy).y;
color_map = textureLod(_color_maps, region_uv, lod);
}
-
+
// Macro variation. 2 Lookups
- float noise1 = texture(noise_texture, rotate(uv*noise1_scale * .1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r;
- float noise2 = texture(noise_texture, uv*noise2_scale * .1).r;
- vec3 macrov = mix(macro_variation1, vec3(1.), clamp(noise1 + v_vertex_xz_dist * .0002, 0., 1.));
- macrov *= mix(macro_variation2, vec3(1.), clamp(noise2 + v_vertex_xz_dist * .0002, 0., 1.));
-
- // Wetness/roughness modifier, converting 0-1 range to -1 to 1 range
+ vec3 macrov = vec3(1.);
+ if (enable_macro_variation) {
+ float noise1 = texture(noise_texture, rotate(uv * noise1_scale * .1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r;
+ float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
+ macrov = mix(macro_variation1, vec3(1.), clamp(noise1 + v_vertex_xz_dist * .0002, 0., 1.));
+ macrov *= mix(macro_variation2, vec3(1.), clamp(noise2 + v_vertex_xz_dist * .0002, 0., 1.));
+ 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
float roughness = fma(color_map.a - 0.5, 2.0, normal_rough.a);
-
+
// Apply PBR
ALBEDO = albedo_height.rgb * color_map.rgb * macrov;
ROUGHNESS = roughness;
SPECULAR = 1. - normal_rough.a;
- NORMAL_MAP = normal_rough.rgb;
- NORMAL_MAP_DEPTH = 1.0;
+ NORMAL_MAP = pack_normal(normal_rough.rgb);
+ NORMAL_MAP_DEPTH = 1.;
}
diff --git a/src/shaders/world_noise.glsl b/src/shaders/world_noise.glsl
index 02c48cc47..c62a3af68 100644
--- a/src/shaders/world_noise.glsl
+++ b/src/shaders/world_noise.glsl
@@ -4,6 +4,7 @@ R"(
//INSERT: WORLD_NOISE1
// World 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;
@@ -11,6 +12,7 @@ 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);
+varying vec2 world_noise_ddxy;
// 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) {
@@ -73,8 +75,9 @@ float world_noise(vec2 p) {
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)) );
+ 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);
@@ -102,8 +105,26 @@ float get_noise_height(const vec2 uv) {
// World Noise end
//INSERT: WORLD_NOISE2
+ // World Noise
+ if (_background_mode == 2u) {
+ float nh = get_noise_height(UV2);
+ float nu = get_noise_height(UV2 + vec2(_region_texel_size, 0.0));
+ float nv = get_noise_height(UV2 + vec2(0.0, _region_texel_size));
+ world_noise_ddxy = vec2(nh - nu, nh - nv);
+ h += nh;
+ u += nu;
+ v += nv;
+ }
+
+//INSERT: WORLD_NOISE3
// World Noise
- if (_background_mode == 2u) {
- height += get_noise_height(uv);
- }
+ 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;
+ }
)"
diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp
index 362f8002e..006ac78e6 100644
--- a/src/terrain_3d_material.cpp
+++ b/src/terrain_3d_material.cpp
@@ -124,6 +124,7 @@ String Terrain3DMaterial::_generate_shader_code() const {
if (_world_background != NOISE) {
excludes.push_back("WORLD_NOISE1");
excludes.push_back("WORLD_NOISE2");
+ excludes.push_back("WORLD_NOISE3");
}
if (_texture_filtering == LINEAR) {
excludes.push_back("TEXTURE_SAMPLERS_NEAREST");
From 05e0d140156a3017f6dfd0a30b4e7e6b4633983a Mon Sep 17 00:00:00 2001
From: Xtarsia <69606701+Xtarsia@users.noreply.github.com>
Date: Wed, 9 Oct 2024 09:30:20 +0100
Subject: [PATCH 2/4] Update Control map debug views
---
src/shaders/debug_views.glsl | 61 +++++++++++++++++++++++++-----------
1 file changed, 42 insertions(+), 19 deletions(-)
diff --git a/src/shaders/debug_views.glsl b/src/shaders/debug_views.glsl
index 3c949a0bf..f4099e9e9 100644
--- a/src/shaders/debug_views.glsl
+++ b/src/shaders/debug_views.glsl
@@ -45,20 +45,47 @@ R"(
NORMAL_MAP = vec3(0.5, 0.5, 1.0);
//INSERT: DEBUG_CONTROL_TEXTURE
+
// Show control map texture selection
- float __ctrl_base = weight_inv * (
- float(mat[0].base) * weights.x +
- float(mat[1].base) * weights.y +
- float(mat[2].base) * weights.z +
- float(mat[3].base) * weights.w )/96.;
- float __ctrl_over = weight_inv * (
- float(mat[0].over) * weights.x +
- float(mat[1].over) * weights.y +
- float(mat[2].over) * weights.z +
- float(mat[3].over) * weights.w )/96.;
- ALBEDO = vec3(__ctrl_base, __ctrl_over, 0.);
- ROUGHNESS = 1.;
- SPECULAR = 0.;
+ vec3 _t_colors[32];
+ _t_colors[0] = vec3(1.0, 0.0, 0.0);
+ _t_colors[1] = vec3(0.0, 1.0, 0.0);
+ _t_colors[2] = vec3(0.0, 0.0, 1.0);
+ _t_colors[3] = vec3(1.0, 0.0, 1.0);
+ _t_colors[4] = vec3(0.0, 1.0, 1.0);
+ _t_colors[5] = vec3(1.0, 1.0, 0.0);
+ _t_colors[6] = vec3(0.2, 0.0, 0.0);
+ _t_colors[7] = vec3(0.0, 0.2, 0.0);
+ _t_colors[8] = vec3(0.0, 0.0, 0.35);
+ _t_colors[9] = vec3(0.2, 0.0, 0.2);
+ _t_colors[10] = vec3(0.0, 0.2, 0.2);
+ _t_colors[11] = vec3(0.2, 0.2, 0.0);
+ _t_colors[12] = vec3(0.1, 0.0, 0.0);
+ _t_colors[13] = vec3(0.0, 0.1, 0.0);
+ _t_colors[14] = vec3(0.0, 0.0, 0.15);
+ _t_colors[15] = vec3(0.1, 0.0, 0.1);
+ _t_colors[16] = vec3(0.0, 0.1, 0.1);
+ _t_colors[17] = vec3(0.1, 0.1, 0.0);
+ _t_colors[18] = vec3(0.2, 0.05, 0.05);
+ _t_colors[19] = vec3(0.1, 0.3, 0.1);
+ _t_colors[20] = vec3(0.05, 0.05, 0.2);
+ _t_colors[21] = vec3(0.1, 0.05, 0.2);
+ _t_colors[22] = vec3(0.05, 0.15, 0.2);
+ _t_colors[23] = vec3(0.2, 0.2, 0.1);
+ _t_colors[24] = vec3(1.0);
+ _t_colors[25] = vec3(0.5);
+ _t_colors[26] = vec3(0.35);
+ _t_colors[27] = vec3(0.25);
+ _t_colors[28] = vec3(0.15);
+ _t_colors[29] = vec3(0.1);
+ _t_colors[30] = vec3(0.05);
+ _t_colors[31] = vec3(0.0125);
+ vec3 __ctrl_base = _t_colors[mat[3].base];
+ vec3 __ctrl_over = _t_colors[mat[3].over];
+ float base_over = (length(fract(uv) - 0.5) < fma(mat[3].blend, 0.45, 0.1) ? 1.0 : 0.0);
+ ALBEDO = mix(__ctrl_base, __ctrl_over, base_over);
+ ROUGHNESS = 1.0;
+ SPECULAR = 0.0;
NORMAL_MAP = vec3(0.5, 0.5, 1.0);
//INSERT: DEBUG_CONTROL_ANGLE
@@ -91,11 +118,7 @@ R"(
//INSERT: DEBUG_CONTROL_BLEND
// Show control map blend values
- float __ctrl_blend = weight_inv * (
- float(mat[0].blend) * weights.x +
- float(mat[1].blend) * weights.y +
- float(mat[2].blend) * weights.z +
- float(mat[3].blend) * weights.w );
+ float __ctrl_blend = mat[3].blend;
ALBEDO = vec3(__ctrl_blend);
ROUGHNESS = 1.;
SPECULAR = 0.;
@@ -118,7 +141,7 @@ R"(
//INSERT: DEBUG_TEXTURE_NORMAL
// Show normal map textures
- ALBEDO = normal_rough.rgb;
+ ALBEDO = pack_normal(normal_rough.rgb);
ROUGHNESS = 0.7;
SPECULAR = 0.;
NORMAL_MAP = vec3(0.5, 0.5, 1.0);
From afb35dc38f7cb61365e6485860875aba86c6b907 Mon Sep 17 00:00:00 2001
From: Xtarsia <69606701+Xtarsia@users.noreply.github.com>
Date: Tue, 31 Dec 2024 21:11:39 +0000
Subject: [PATCH 3/4] Update minimum.gdshader
---
Terrain3D.vcxproj | 1 +
Terrain3D.vcxproj.filters | 3 +
.../addons/terrain_3d/extras/minimum.gdshader | 127 +++++++++---------
3 files changed, 64 insertions(+), 67 deletions(-)
diff --git a/Terrain3D.vcxproj b/Terrain3D.vcxproj
index c1300db34..dbd8465c1 100644
--- a/Terrain3D.vcxproj
+++ b/Terrain3D.vcxproj
@@ -201,6 +201,7 @@
+
diff --git a/Terrain3D.vcxproj.filters b/Terrain3D.vcxproj.filters
index 25df23fec..f8310fc6c 100644
--- a/Terrain3D.vcxproj.filters
+++ b/Terrain3D.vcxproj.filters
@@ -235,6 +235,9 @@
1. Project Files
+
+ 4. Shaders
+
diff --git a/project/addons/terrain_3d/extras/minimum.gdshader b/project/addons/terrain_3d/extras/minimum.gdshader
index 1eb620711..a438df50d 100644
--- a/project/addons/terrain_3d/extras/minimum.gdshader
+++ b/project/addons/terrain_3d/extras/minimum.gdshader
@@ -5,38 +5,31 @@ shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
// Private uniforms
-uniform float _region_size = 1024.0;
-uniform float _region_texel_size = 0.0009765625; // = 1/1024
+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 sampler2DArray _height_maps : repeat_disable;
-uniform usampler2DArray _control_maps : repeat_disable;
-uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
-uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
-uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
-uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
-
uniform float _texture_uv_scale_array[32];
uniform float _texture_detile_array[32];
uniform vec4 _texture_color_array[32];
-uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
-uniform uint _mouse_layer = 0x80000000u; // Layer 32
-
-// Public uniforms
-uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
+uniform highp sampler2DArray _height_maps : repeat_disable;
+uniform highp usampler2DArray _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
-varying flat vec3 v_vertex; // World coordinate vertex location
varying flat vec3 v_camera_pos;
-varying float v_vertex_xz_dist;
varying flat ivec3 v_region;
varying flat vec2 v_uv_offset;
varying flat vec2 v_uv2_offset;
-varying vec3 v_normal;
-varying float v_region_border_mask;
+varying float v_vertex_xz_dist;
+varying vec3 v_vertex;
////////////////////////
// Vertex
@@ -63,16 +56,6 @@ vec3 get_region_uv2(const vec2 uv2) {
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
}
-// 1 lookup
-float get_height(vec2 uv) {
- highp float height = 0.0;
- vec3 region = get_region_uv2(uv);
- if (region.z >= 0.) {
- height = texture(_height_maps, region).r;
- }
- return height;
-}
-
void vertex() {
// Get camera pos in world vertex coords
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
@@ -95,23 +78,15 @@ void vertex() {
bool hole = bool(control >>2u & 0x1u);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
- if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
+ if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
- VERTEX.x = 0. / 0.;
- } else {
- // Set final vertex height & calculate vertex normals. 3 lookups.
- VERTEX.y = get_height(UV2);
- v_vertex.y = VERTEX.y;
- v_normal = vec3(
- v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
- _vertex_spacing,
- v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
- );
- // Due to a bug caused by the GPUs linear interpolation across edges of region maps,
- // mask region edges and use vertex normals only across region boundaries.
- v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
+ v_vertex.x = 0. / 0.;
+ } else {
+ // Set final vertex height, 1 lookup.
+ float h = texelFetch(_height_maps, v_region, 0).r;
+ v_vertex.y = h;
}
-
+
// Transform UVs to local to avoid poor precision during varying interpolation.
v_uv_offset = MODEL_MATRIX[3].xz * _vertex_density;
UV -= v_uv_offset;
@@ -119,8 +94,7 @@ void vertex() {
UV2 -= v_uv2_offset;
// Convert model space to view space w/ skip_vertex_transform render mode
- VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
- VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
+ 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);
@@ -130,36 +104,55 @@ void vertex() {
// Fragment
////////////////////////
-// 0 - 3 lookups
-vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
- float u, v, height;
- vec3 normal;
- // Use vertex normals within radius of vertex_normals_distance, and along region borders.
- if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
- normal = normalize(v_normal);
- } else {
- height = get_height(uv);
- u = height - get_height(uv + vec2(_region_texel_size, 0));
- v = height - get_height(uv + vec2(0, _region_texel_size));
- normal = normalize(vec3(u, _vertex_spacing, v));
- }
- tangent = cross(normal, vec3(0, 0, 1));
- binormal = cross(normal, tangent);
- return normal;
-}
-
void fragment() {
// Recover UVs
vec2 uv = UV + v_uv_offset;
vec2 uv2 = UV2 + v_uv2_offset;
- // Calculate Terrain Normals. 4 lookups
- vec3 w_tangent, w_binormal;
- vec3 w_normal = get_normal(uv2, w_tangent, w_binormal);
+ // Lookup offsets, ID and bilinear weights
+ 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
+ );
+
+ // Terrain normals, sample height map 8 times, because these are adjacent values
+ // they are cache friendly. texelFetch avoids any cross texture border interpolation errors.
+ float h[8];
+ h[0] = texelFetch(_height_maps, get_region_uv(index_id + offsets.xx), 0).r; // 0 (0,0)
+ h[1] = texelFetch(_height_maps, get_region_uv(index_id + offsets.yx), 0).r; // 1 (1,0)
+ h[2] = texelFetch(_height_maps, get_region_uv(index_id + offsets.xy), 0).r; // 2 (0,1)
+ h[3] = texelFetch(_height_maps, get_region_uv(index_id + offsets.yy), 0).r; // 3 (1,1)
+ h[4] = texelFetch(_height_maps, get_region_uv(index_id + offsets.yz), 0).r; // 4 (1,2)
+ h[5] = texelFetch(_height_maps, get_region_uv(index_id + offsets.zy), 0).r; // 5 (2,1)
+ h[6] = texelFetch(_height_maps, get_region_uv(index_id + offsets.zx), 0).r; // 6 (2,0)
+ h[7] = texelFetch(_height_maps, get_region_uv(index_id + offsets.xz), 0).r; // 7 (0,2)
+
+ // Calculate the normal from height map derivatives at 4 points.
+ vec3 index_normal[4];
+ index_normal[0] = normalize(vec3(h[2] - h[3], _vertex_spacing, h[2] - h[7]));
+ index_normal[1] = normalize(vec3(h[3] - h[5], _vertex_spacing, h[3] - h[4]));
+ index_normal[2] = normalize(vec3(h[1] - h[6], _vertex_spacing, h[1] - h[3]));
+ index_normal[3] = normalize(vec3(h[0] - h[1], _vertex_spacing, h[0] - h[2]));
+
+ // Interpolate
+ vec3 w_normal =
+ index_normal[0] * weights[0] +
+ index_normal[1] * weights[1] +
+ index_normal[2] * weights[2] +
+ index_normal[3] * weights[3];
+
+ 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);
+ ALBEDO = vec3(0.2);
}
From 5b1c0f69b71fef5cf55ec19a85935383fee057cf Mon Sep 17 00:00:00 2001
From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com>
Date: Thu, 2 Jan 2025 18:34:06 +0700
Subject: [PATCH 4/4] Add hexgrid to extras
---
doc/doc_classes/Terrain3D.xml | 6 +-
.../terrain_3d/extras/hex_grid.gdshader | 70 +++++++++++++++++++
2 files changed, 73 insertions(+), 3 deletions(-)
create mode 100644 project/addons/terrain_3d/extras/hex_grid.gdshader
diff --git a/doc/doc_classes/Terrain3D.xml b/doc/doc_classes/Terrain3D.xml
index ccfd5fb2d..dec7bb628 100644
--- a/doc/doc_classes/Terrain3D.xml
+++ b/doc/doc_classes/Terrain3D.xml
@@ -64,11 +64,11 @@
The main caveats of using this mode is that the call to get_intersection() requests a viewport be drawn, but cannot wait for it to finish as there is no "await" in C++ and no force draw function in Godot. So the return value is one frame behind, and invalid on the first call. This also means the function cannot be used more than once per frame. This mode works well when used continuously, once per frame, where one frame of difference won't matter. The editor uses this mode to place the mouse cursor decal.
- This mode can also be used by your plugins and games, such as a space ship firing lasers at the terrain and causing an explosion at the hit point. However if the calls aren't continuous, you'll need to call once to capture the viewport image, wait for it to be drawn, then call again to get the result:
+ This mode can also be used by your plugins and games, such as a space ship firing lasers at the terrain and causing an explosion at the hit point. However if the calls aren't continuous, eg driven by the mouse, you'll need to call once to capture the viewport image, wait for it to be drawn, then call again to get the result:
[codeblock]
- var target_point = terrain.get_intersection(camera_pos, camera_dir)
+ var target_point = terrain.get_intersection(camera_pos, camera_dir, true)
await RenderingServer.frame_post_draw
- target_point = terrain.get_intersection(camera_pos, camera_dir)
+ target_point = terrain.get_intersection(camera_pos, camera_dir, true)
[/codeblock]
Possible return values:
diff --git a/project/addons/terrain_3d/extras/hex_grid.gdshader b/project/addons/terrain_3d/extras/hex_grid.gdshader
new file mode 100644
index 000000000..7fc26a9d5
--- /dev/null
+++ b/project/addons/terrain_3d/extras/hex_grid.gdshader
@@ -0,0 +1,70 @@
+// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
+// This shader snippet draws a hex grid
+// Contributed by Broco
+
+shader_type spatial;
+
+// 1. Uncomment and paste this function outside of any other function block:
+
+/*
+mat2 rotate2d(float _angle) {
+ return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle)));
+}
+*/
+
+// 2. Uncomment and paste this code at the end of the fragment shader, before the last }
+
+/*
+ float hex_size = 0.02;
+ float line_thickness = 0.04;
+
+ vec2 guv = (uv2 - vec2(0.5 * _region_texel_size)) / hex_size;
+
+ // Convert UV to axial hex coordinates
+ float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y);
+ float r = (2.0 / 3.0 * guv.y);
+
+ // Cube coordinates for the hex (q, r, -q-r)
+ float x = q;
+ float z = r;
+ float y = -x - z;
+
+ // Round to the nearest hex center
+ vec3 rounded = round(vec3(x, y, z));
+ vec3 diff = abs(vec3(x, y, z) - rounded);
+
+ // Fix rounding errors
+ if (diff.x > diff.y && diff.x > diff.z) {
+ rounded.x = -rounded.y - rounded.z;
+ } else if (diff.y > diff.z) {
+ rounded.y = -rounded.x - rounded.z;
+ } else {
+ rounded.z = -rounded.x - rounded.y;
+ }
+
+ // Find the hex center in UV space
+ vec2 hex_center = vec2(
+ sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z,
+ 3.0 / 2.0 * rounded.z
+ );
+
+ // Relative position within the hex
+ vec2 local_pos = guv - hex_center;
+ vec2 lines_uv = local_pos;
+ float line = 1.0;
+
+ for (int i = 0; i < 6; i++) {
+ vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0));
+ float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0)));
+ line = min(line, dist);
+ }
+
+ // Filter lines by slope
+ float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement
+ float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (w_normal - 1.) + 1.), 0., 1.);
+
+ // Draw hex grid
+ ALBEDO = mix(ALBEDO, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor);
+ // Draw Hex center dot
+ ALBEDO = mix(ALBEDO, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor);
+*/
\ No newline at end of file