Zylann / godot_heightmap_plugin

HeightMap terrain for Godot implemented in GDScript
Other
1.73k stars 159 forks source link

Using a custom grass shader,the colour is black and it doesnt conform to the terrain. No shaders work fine. #390

Open hvlsavo opened 1 year ago

hvlsavo commented 1 year ago

Describe the bug A clear and concise description of what the bug is. While using a custom grass shader,the colour of grass changes to black and it does not conform to the terrain. If i use the same grass without shaders it works fine but with no color. Shaders work when applied with multi mesh but i cannot use it for the HTerrain. To Reproduce Steps to reproduce the behavior:

  1. Make a shader with the code:
    
    render_mode cull_disabled;

uniform vec3 color : source_color; uniform vec3 color2 : source_color; uniform sampler2D noise; uniform float noiseScale = 20.0;

varying vec3 worldPos;

void vertex() { worldPos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; }

void fragment() { vec3 noiseLevel = texture(noise, worldPos.xz / noiseScale).rgb; ALBEDO = mix(color, color2, UV.y) * mix(color, color2, noiseLevel.r); if (!FRONT_FACING) { NORMAL = -NORMAL; } }


2. On the mesh,make a new material with the shader and change the colour.
3. Now make a new detail layer and apply the mesh and the shader.
5. See error
Linking a reproduction project may help.
If you see any errors in the console, that may also help.
No errors in the console.
**Expected behavior**
A clear and concise description of what you expected to happen.
I expected the colour of the grass to be the exact one i wanted and the grass to be conformed onto the terrain
**Screenshots**
If applicable, add screenshots to help explain your problem.
![ss](https://github.com/Zylann/godot_heightmap_plugin/assets/87948924/7a8c871b-fda2-4622-9658-2ac4c49ddc30)
![ss1](https://github.com/Zylann/godot_heightmap_plugin/assets/87948924/451fae01-52ec-4553-9fd6-796673bf8ce1)

**Environment**
- OS: [e.g. iOS] Windows 11
- Graphics card (you can find out in Godot's console log) AMD RADEON VEGA 3
- Godot version [e.g. 3.2.1] 4.1.1
- Plugin version (or commit hash if you got it from Github directly) 1.7.2(from assetlib)
- Renderer used: note that GLES2 is not supported. Vulkan
Zylann commented 1 year ago

Currently this isn't a bug, what's happening is that the shader you provide must also take care of terrain displacement, fading and filtering. So you can't apply the same kind of shader you'd apply on a MeshInstance3D, there is some boilerplate required. I don't know what's up with color though.

If you create a new empty shader from the property, you can fork the built-in shader and modify it, rather than starting from scratch. You can also have a look at the default shader directly to copy the base behaviors.

The reason is that this system dates back to Godot 3 (maybe even 2?) and it's using MultiMesh under the hood. Generating individual instances to cover the whole terrain is however not good for performance and memory especially since the plugin is in GDScript. So instead a single multimesh is generated and repeated in flat chunks around the player, and the shader does the rest of the work.

Perhaps one way to allow shaders to be separated from positionning, would be to refactor the system to be based on GPUParticles instead, which means two separate shaders would be used (particle shader, and vertex/fragment), allowing to use regular shaders for the instances and reducing boilerplate, however some features like distance fading, ground coloring and ground-based normals would still have to be implemented by you.


This is what your shader would be after forking the base shader:

shader_type spatial;
render_mode cull_disabled;

#include "res://addons/zylann.hterrain/shaders/include/heightmap.gdshaderinc"

uniform sampler2D u_terrain_heightmap;
uniform sampler2D u_terrain_detailmap;
uniform sampler2D u_terrain_normalmap;
uniform sampler2D u_terrain_globalmap : source_color;
uniform mat4 u_terrain_inverse_transform;
uniform mat3 u_terrain_normal_basis;

uniform sampler2D u_albedo_alpha : source_color;
uniform float u_view_distance = 100.0;
uniform float u_globalmap_tint_bottom : hint_range(0.0, 1.0);
uniform float u_globalmap_tint_top : hint_range(0.0, 1.0);
uniform float u_bottom_ao : hint_range(0.0, 1.0);
uniform vec2 u_ambient_wind; // x: amplitude, y: time
uniform vec3 u_instance_scale = vec3(1.0, 1.0, 1.0);
uniform float u_roughness = 0.9;

/*****CUSTOM UNIFORMS******************************************************************/
uniform vec3 color : source_color;
uniform vec3 color2 : source_color;
uniform sampler2D noise;
uniform float noiseScale = 20.0;
/**************************************************************************************/

varying vec3 v_normal;
varying vec2 v_map_uv;

float get_hash(vec2 c) {
    return fract(sin(dot(c.xy, vec2(12.9898,78.233))) * 43758.5453);
}

vec3 unpack_normal(vec4 rgba) {
    vec3 n = rgba.xzy * 2.0 - vec3(1.0);
    n.z *= -1.0;
    return n;
}

vec3 get_ambient_wind_displacement(vec2 uv, float hash) {
    // TODO This is an initial basic implementation. It may be improved in the future, especially for strong wind.
    float t = u_ambient_wind.y;
    float amp = u_ambient_wind.x * (1.0 - uv.y);
    // Main displacement
    vec3 disp = amp * vec3(cos(t), 0, sin(t * 1.2));
    // Fine displacement
    float fine_disp_frequency = 2.0;
    disp += 0.2 * amp * vec3(cos(t * (fine_disp_frequency + hash)), 0, sin(t * (fine_disp_frequency + hash) * 1.2));
    return disp;
}

float get_height(sampler2D heightmap, vec2 uv) {
    return sample_heightmap(heightmap, uv);
}

void vertex() {
    vec4 obj_pos = MODEL_MATRIX * vec4(0, 1, 0, 1);
    vec3 cell_coords = (u_terrain_inverse_transform * obj_pos).xyz;
    // 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.xz += vec2(0.5);

    vec2 map_uv = cell_coords.xz / vec2(textureSize(u_terrain_heightmap, 0));
    v_map_uv = map_uv;

    //float density = 0.5 + 0.5 * sin(4.0*TIME); // test
    float density = texture(u_terrain_detailmap, map_uv).r;
    float hash = get_hash(obj_pos.xz);

    if (density > hash) {
        vec3 normal = normalize(
            u_terrain_normal_basis * unpack_normal(texture(u_terrain_normalmap, map_uv)));

        // Snap model to the terrain
        float height = get_height(u_terrain_heightmap, map_uv) / cell_coords.y;
        VERTEX *= u_instance_scale;
        VERTEX.y += height;

        VERTEX += get_ambient_wind_displacement(UV, hash);

        // Fade alpha with distance
        vec3 wpos = (MODEL_MATRIX * vec4(VERTEX, 1)).xyz;
        float dr = distance(wpos, CAMERA_POSITION_WORLD) / u_view_distance;
        COLOR.a = clamp(1.0 - dr * dr * dr, 0.0, 1.0);

        // When using billboards,
        // the normal is the same as the terrain regardless of face orientation
        v_normal = normal;

    } else {
        // Discard, output degenerate triangles
        VERTEX = vec3(0, 0, 0);
    }
}

void fragment() {
    NORMAL = (VIEW_MATRIX * (MODEL_MATRIX * vec4(v_normal, 0.0))).xyz;
    ALPHA_SCISSOR_THRESHOLD = 0.5;
    ROUGHNESS = u_roughness;

    vec4 col = texture(u_albedo_alpha, UV);
    ALPHA = col.a * COLOR.a;// - clamp(1.4 - UV.y, 0.0, 1.0);//* 0.5 + 0.5*cos(2.0*TIME);

/*****CUSTOM CHANGES******************************************************************/
    vec3 noiseLevel = texture(noise, v_map_uv / noiseScale).rgb;
    col.rgb = mix(color, color2, UV.y) * mix(color, color2, noiseLevel.r);
/*************************************************************************************/

    ALBEDO = COLOR.rgb * col.rgb;

    // Blend with ground color
    float nh = sqrt(max(1.0 - UV.y, 0.0));
    ALBEDO = mix(ALBEDO, texture(u_terrain_globalmap, v_map_uv).rgb, mix(u_globalmap_tint_bottom, u_globalmap_tint_top, nh));

    // Fake bottom AO
    ALBEDO = ALBEDO * mix(1.0, 1.0 - u_bottom_ao, UV.y * UV.y);
}

image

hvlsavo commented 1 year ago

aight ill try to figure out the colour

hvlsavo commented 1 year ago

weird,the shader i wrote works when used as a multi mesh but the one you gave me shows blank in material preview and makes the grass black

Zylann commented 1 year ago

This is normal, because it is meant to integrate with the terrain system. It takes colors from different sources, it has extra parameters, while a raw MultiMesh is like no more than a MeshInstance3D, so when all those parameters aren't provided they default to zero/black/white, which makes the result black due to some of the operations done on albedo. It would probably render colored if you assign them or remove some undesired operations (but those exist to support features that you may decide to keep or not).

This is what I get with your original shader (which doesn't have the necessary terrain integration code): image but here notice I set the colors in "shader params" in the inspector, perhaps you forgot to assign them? (probably set the noise scale too low)

Not sure what kind of "material preview" you get since there is no material involved here (there is no material to create manually) but if for some reason you created a ShaderMaterial, then it's also going to appear black/empty if parameters aren't assigned. This kind of shader isn't working out of the box in all possible situations, it needs the parameters filled in.

hvlsavo commented 1 year ago

i meant something else. Never mind that. But in shader params i dont see anything like that(using your shader). it looks like this: image i am using a mesh instance so could that cause the problem?

hvlsavo commented 1 year ago

okay,so i tested it and it works when using a texture