Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terrain Shader updates #516

Merged
merged 4 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Terrain3D.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
<None Include="doc\docs\texture_painting.md" />
<None Include="doc\docs\user_interface.md" />
<None Include="doc\index.rst" />
<None Include="project\addons\terrain_3d\extras\minimum.gdshader" />
<None Include="project\addons\terrain_3d\plugin.cfg" />
<None Include="project\addons\terrain_3d\terrain.gdextension" />
<None Include="README.md" />
Expand Down
3 changes: 3 additions & 0 deletions Terrain3D.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@
<None Include=".github\workflows\web.yml">
<Filter>1. Project Files</Filter>
</None>
<None Include="project\addons\terrain_3d\extras\minimum.gdshader">
<Filter>4. Shaders</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Text Include=".readthedocs.yaml">
Expand Down
6 changes: 3 additions & 3 deletions doc/doc_classes/Terrain3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
70 changes: 70 additions & 0 deletions project/addons/terrain_3d/extras/hex_grid.gdshader
Original file line number Diff line number Diff line change
@@ -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);
*/
127 changes: 60 additions & 67 deletions project/addons/terrain_3d/extras/minimum.gdshader
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -95,32 +78,23 @@ 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;
v_uv2_offset = v_uv_offset * _region_texel_size;
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);
Expand All @@ -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);
}
9 changes: 8 additions & 1 deletion project/demo/Demo.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/auto_shader.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading