1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
shader_type spatial;
#include "include/heightmap.gdshaderinc"
uniform sampler2D u_terrain_heightmap;
uniform sampler2D u_terrain_normalmap;
// I had to remove source_color` from colormap in Godot 3 because it makes sRGB conversion kick in,
// which snowballs to black when doing GPU painting on that texture...
uniform sampler2D u_terrain_colormap;
uniform sampler2D u_terrain_splat_index_map;
uniform sampler2D u_terrain_splat_weight_map;
uniform sampler2D u_terrain_globalmap : source_color;
uniform mat4 u_terrain_inverse_transform;
uniform mat3 u_terrain_normal_basis;
uniform sampler2DArray u_ground_albedo_bump_array : source_color;
uniform sampler2DArray u_ground_normal_roughness_array;
// TODO Have UV scales for each texture in an array?
uniform float u_ground_uv_scale;
uniform float u_globalmap_blend_start;
uniform float u_globalmap_blend_distance;
uniform bool u_depth_blending = true;
varying float v_hole;
varying vec3 v_tint;
varying vec2 v_ground_uv;
varying float v_distance_to_camera;
vec3 unpack_normal(vec4 rgba) {
vec3 n = rgba.xzy * 2.0 - vec3(1.0);
// Had to negate Z because it comes from Y in the normal map,
// and OpenGL-style normal maps are Y-up.
n.z *= -1.0;
return n;
}
vec3 get_depth_blended_weights(vec3 splat, vec3 bumps) {
float dh = 0.2;
vec3 h = bumps + splat;
// TODO Keep improving multilayer blending, there are still some edge cases...
// Mitigation: nullify layers with near-zero splat
h *= smoothstep(0, 0.05, splat);
vec3 d = h + dh;
d.r -= max(h.g, h.b);
d.g -= max(h.r, h.b);
d.b -= max(h.g, h.r);
vec3 w = clamp(d, 0, 1);
// Had to normalize, since this approach does not preserve components summing to 1
return w / (w.x + w.y + w.z);
}
void vertex() {
vec4 wpos = MODEL_MATRIX * vec4(VERTEX, 1);
vec2 cell_coords = (u_terrain_inverse_transform * wpos).xz;
// Must add a half-offset so that we sample the center of pixels,
// otherwise bilinear filtering of the textures will give us mixed results (#183)
cell_coords += vec2(0.5);
// Normalized UV
UV = cell_coords / vec2(textureSize(u_terrain_heightmap, 0));
// Height displacement
float h = sample_heightmap(u_terrain_heightmap, UV);
VERTEX.y = h;
wpos.y = h;
vec3 base_ground_uv = vec3(cell_coords.x, h * MODEL_MATRIX[1][1], cell_coords.y);
v_ground_uv = base_ground_uv.xz / u_ground_uv_scale;
// Putting this in vertex saves 2 fetches from the fragment shader,
// which is good for performance at a negligible quality cost,
// provided that geometry is a regular grid that decimates with LOD.
// (downside is LOD will also decimate tint and splat, but it's not bad overall)
vec4 tint = texture(u_terrain_colormap, UV);
v_hole = tint.a;
v_tint = tint.rgb;
// Need to use u_terrain_normal_basis to handle scaling.
NORMAL = u_terrain_normal_basis * unpack_normal(texture(u_terrain_normalmap, UV));
v_distance_to_camera = distance(wpos.xyz, CAMERA_POSITION_WORLD);
}
void fragment() {
if (v_hole < 0.5) {
// TODO Add option to use vertex discarding instead, using NaNs
discard;
}
vec3 terrain_normal_world =
u_terrain_normal_basis * unpack_normal(texture(u_terrain_normalmap, UV));
terrain_normal_world = normalize(terrain_normal_world);
vec3 normal = terrain_normal_world;
float globalmap_factor =
clamp((v_distance_to_camera - u_globalmap_blend_start) * u_globalmap_blend_distance, 0.0, 1.0);
globalmap_factor *= globalmap_factor; // slower start, faster transition but far away
vec3 global_albedo = texture(u_terrain_globalmap, UV).rgb;
ALBEDO = global_albedo;
// Doing this branch allows to spare a bunch of texture fetches for distant pixels.
// Eventually, there could be a split between near and far shaders in the future,
// if relevant on high-end GPUs
if (globalmap_factor < 1.0) {
vec4 tex_splat_indexes = texture(u_terrain_splat_index_map, UV);
vec4 tex_splat_weights = texture(u_terrain_splat_weight_map, UV);
// TODO Can't use texelFetch!
// https://github.com/godotengine/godot/issues/31732
vec3 splat_indexes = tex_splat_indexes.rgb * 255.0;
vec3 splat_weights = vec3(
tex_splat_weights.r,
tex_splat_weights.g,
1.0 - tex_splat_weights.r - tex_splat_weights.g
);
vec4 ab0 = texture(u_ground_albedo_bump_array, vec3(v_ground_uv, splat_indexes.x));
vec4 ab1 = texture(u_ground_albedo_bump_array, vec3(v_ground_uv, splat_indexes.y));
vec4 ab2 = texture(u_ground_albedo_bump_array, vec3(v_ground_uv, splat_indexes.z));
vec4 nr0 = texture(u_ground_normal_roughness_array, vec3(v_ground_uv, splat_indexes.x));
vec4 nr1 = texture(u_ground_normal_roughness_array, vec3(v_ground_uv, splat_indexes.y));
vec4 nr2 = texture(u_ground_normal_roughness_array, vec3(v_ground_uv, splat_indexes.z));
// TODO An #ifdef macro would be nice! Or copy/paste everything in a different shader...
if (u_depth_blending) {
splat_weights = get_depth_blended_weights(splat_weights, vec3(ab0.a, ab1.a, ab2.a));
}
ALBEDO = v_tint * (
ab0.rgb * splat_weights.x
+ ab1.rgb * splat_weights.y
+ ab2.rgb * splat_weights.z
);
ROUGHNESS =
nr0.a * splat_weights.x
+ nr1.a * splat_weights.y
+ nr2.a * splat_weights.z;
vec3 normal0 = unpack_normal(nr0);
vec3 normal1 = unpack_normal(nr1);
vec3 normal2 = unpack_normal(nr2);
vec3 ground_normal =
normal0 * splat_weights.x
+ normal1 * splat_weights.y
+ normal2 * splat_weights.z;
// Combine terrain normals with detail normals (not sure if correct but looks ok)
normal = normalize(vec3(
terrain_normal_world.x + ground_normal.x,
terrain_normal_world.y,
terrain_normal_world.z + ground_normal.z));
normal = mix(normal, terrain_normal_world, globalmap_factor);
ALBEDO = mix(ALBEDO, global_albedo, globalmap_factor);
//ALBEDO = vec3(splat_weight0, splat_weight1, splat_weight2);
ROUGHNESS = mix(ROUGHNESS, 1.0, globalmap_factor);
}
NORMAL = (VIEW_MATRIX * (vec4(normal, 0.0))).xyz;
}
|