Godot Version
4.5.stable
Question
What causes the white spots in the top right side of the scene:
Here’s the setup:
The scene is lit by a DirectionalLight3D.
Each hexagon is made up of a MeshInstance3D.
In the shader for the hexagons, for areas of the mesh that are unrevealed, the vertex shader flattens the terrain into VERTEX.y = 1.0;
Additionally the fragment shader performs the following operations if the pixel is unrevealed:
// This if statement is basically asking "is this pixel unexplored"
if (abs(visibility_properties[0] - VISIBILITY_EFFECT_UNEXPLORED) < 0.0001) {
ALBEDO = sample_unexplored_texture(UV);
NORMAL_MAP = vec3(0, 1, 0);
NORMAL = vec3(0, 1, 0);
SPECULAR = 0.0;
}
(Note - the shader only ever sets ALBEDO, NORMAL_MAP, NORMAL and SPECULAR - no other builtins are touched by any part of it).
My issue is that wherever there’s mountains in the terrain (or really areas with aggressive vertical changes) these white spots show up.
This is related to lighting somehow - see the same scene with the DirectionalLight3D set to non-visible, or with its Energy set to 0:
No white spots.
Here’s some things I’ve tried:
- Reimplement Godot’s default light() function and just disable bits
- No effect, but might have messed this up
- Changed any and all settings on
DirectionalLight3D
- No effect
- Tried setting
NORMAL in vertex shader
- No effect
- Tried changing the underlying albedo/textures used in the mountainous regions where this effect happens the most
- No effect - this isn’t unexpected, the final
ALBEDOvalue is done off the unexplored_texture anyhow
Please let me know if any other information can help - I’ve been fighting against these spots for some time now with no meaningful progress
Which “white spots” are you referring to? Can you mark them on the screenshot?
Post the shader code that draws the blue background.
Do you have any world environment effects enabled?
Shader:
vec3 sample_unexplored_texture(vec2 uv) {
return texture(unexplored_texture, uv*5.0).rgb + vec3(0.05, 0.05, 0.25);
}
void fragment() {
vec2 mapwide_uv = UV; // Goes from 0->1 across the whole map - used for noise mostly, to ensure cross-hexagon mesh stitching
vec2 triangle_uv = UV2; // Goes from 0->1 across each hexagon triangle (y being the height, x the width of the triangle) - used for shaping cross-hexagons
vec2 hexagon_uv = vec2(UV.x * float(map_size_x), UV.y * float(map_size_y)); // Goes from 0-> across each full hexagon - mostly used in textures
vec2 visibility_properties = visibility(mapwide_uv, triangle_uv);
// Only compute terrain colour if we're revealed
if (visibility_properties[0] >= VISIBILITY_EFFECT_UNEXPLORED) {
vec3 normal = normalize(INV_VIEW_MATRIX * vec4(NORMAL.xyz, 0.0)).xyz;
// ALBEDO and NORMAL_MAP
vec3 base_properties[2] = get_combined_terrain_colour(normal, mapwide_uv, triangle_uv, hexagon_uv);
ALBEDO = base_properties[0];
NORMAL_MAP = base_properties[1];
}
// Deal with any colour changes due to fog of war/unrevealed land
// For underwater terrain, the colouring is done on the water
if (abs(visibility_properties[0] - VISIBILITY_EFFECT_UNEXPLORED) < 0.0001) {
ALBEDO = sample_unexplored_texture(mapwide_uv);
NORMAL_MAP = vec3(0, 1, 0);
NORMAL = vec3(0, 1, 0);
SPECULAR = 0.0;
} else if (abs(visibility_properties[0] - VISIBILITY_EFFECT_FOGOFWAR_MAINBODY) < 0.0001) {
ALBEDO = apply_fog(ALBEDO, mapwide_uv);
} else if (abs(visibility_properties[0] - VISIBILITY_EFFECT_FOGOFWAR_BOUNDARY) < 0.0001) {
ALBEDO = apply_fog(ALBEDO, mapwide_uv);
ALBEDO = mix(fog_of_war_outline_colour.rgb, ALBEDO, smoothstep(0.15, 0.4, visibility_properties[1]));
}
}
Note - you might think there’s something wrong with the logic that decides if a pixel is unexplored or not. I don’t think that’s the case since:
-
The effect goes away if the DirectionalLight3D is disabled
-
If you zoom into one of these whitespots, you can see its strange illumination on the texture:
Here’s the setup for my WorldEnvironment, and the underlying/suspect DirectionalLight3D
It must be the shader. Who wrote the shader? It also doesn’t look like what you posted is the entire shader code.
Comment out line by line to determine which piece of code is causing it.
At a first glance it looks like it’s normal map related. So start by commenting out normal map stuff.
I wrote the code, which is partially why I’m confident its not up to much shenanigans.
I’ve reduced the shader to just:
vec3 sample_unexplored_texture(vec2 uv) {
return texture(unexplored_texture, uv*5.0).rgb + vec3(0.05, 0.05, 0.25);
}
void fragment() {
ALBEDO = sample_unexplored_texture(UV);
NORMAL_MAP = vec3(0, 1, 0);
NORMAL = vec3(0, 1, 0);
SPECULAR = 0.0;
}
I’ve also removed the vertex shader, meaning the terrain doesn’t go through vertex displacement if its unexplored.
The effect still persists - anyhow if it was shader related, why would disabling the DirectionalLight3D resolve the spots?
Does it persist if you just assign the standard material instead of your shader? If not, then it cannot be anything other than the shader.
Can you make a minimal reproducible example?
Same effect with StandardMaterial3D
Changes I made to the StandardMaterial3D:
Set up the texture - same as the one I’m using
Set Specular to 0.0 - to mimic what I do in my shader
Created a curve texture to replicate the entire NORMAL_MAP being set to vec3(0, 1, 0);
On the minimap reproduction - I will try but this is challenging. The terrain is procedurally generated, and I haven’t implemented a way of exporting the meshes yet. I’ll look into that in the morning
Does it happen with a “static” mesh made in a modeling app?
Could be your mesh normals. Are they correct and properly normalized?
Oh. It’s NORMAL_MAP. Don’t touch it. Just set NORMAL to (0.0, 1.0, 0.0) if you want it to look up. NORMAL_MAP is expected to be in tangent space. Assigning a constant to it will not override vertex normals with a constant value like you seem to be expecting.
Also, you need to move normal assignment to vertex() function because in fragment() function NORMAL is expected to be in camera space, while in vertex() it’s in object space. Alternatively you can transform it to object or world space using inverse camera matrix.
I’ll check what effect not writing NORMAL_MAP has in the AM - can you elaborate why writing a constant into NORMAL_MAP is a bad idea?
Because it won’t set the effective normal to that constant value. Normal map perturbs the normal coming from the vertex data. It’s “relative” to it, so to speak.
As a quick test, just eliminate writing to NORMAL_MAP from the shader and see if the problem disappears.
So to nullify the normal map and set the normal to world up, instead of:
NORMAL_MAP = vec3(0, 1, 0);
NORMAL = vec3(0, 1, 0);
Try:
NORMAL = mat3(VIEW_MATRIX) * vec3(0.0, 1.0, 0.0);
NORMAL_MAP = vec3(0.5, 0.5, 1.0);
1 Like
Can confirm, this fixes it!
I’ll go read up on the format that NORMAL_MAPexpects, thank you for the help!
1 Like
It expects the normal values to be exactly like they appear in a standard normal map. That is - in tangent space with components normalized to 0-1 range so:
normalmap_normal = actual_normal_in_tangent_space * 0.5 + 0.5
The non-perturbed normal in tangent space is (0.0, 0.0, 1.0). This value won’t alter the existing vertex normal. When you “encode” it in the mentioned way you get (0.5, 0.5, 1.0). If you look at standard normal maps, it is precisely that dominant light-blue/violet color which signifies no change in vertex normal:
