Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

really beautiful! It took me quite a few trips up and down the path to find the secret, but I didn't mind too much because this janky atmosphere was delightful to spend some time in. honestly, the narration takes away from this, would probably be a lot spookier without it. I spent a long time just admiring the grass and flowers - would you mind sharing how you achieved that look? is the source available?

Thanks, it's just a vertex shader on a 1x8 Quad Mesh, then I ran this vertex shader:

shader_type spatial;

render_mode cull_disabled;

varying vec3 instance_pos;

uniform sampler2D windNoise: source_color;

uniform sampler2D jitterNoise: source_color;

uniform sampler2D densityNoise: source_color;

uniform vec4 shortColor : source_color;

uniform vec4 tallColor : source_color;

uniform vec4 botColor : source_color;

uniform float subdivision = 10.0;

varying float grass_height;

const float grass_scale = 0.2;

mat2 rotate(float rad){

    return mat2(

        vec2(cos(rad), -sin(rad)),

        vec2(sin(rad), cos(rad)));

    

}

mat3 scale(float scale){

    return mat3(vec3(scale, 0.0, 0.0),

    vec3(0.0, scale, 0.0),

    vec3(0.0, 0.0, scale));

}

void vertex() {

    

    float windSpeed = 0.07;

    float windScale = 200.0;

    float windIntensity = 0.7;

    float jitterSpeed = 0.1;

    float windContrast = 1.0;

    

    vec2 windPos =  vec2(TIME * windSpeed) + MODEL_MATRIX[3].xz / windScale;

    float windF = clamp(texture(windNoise, windPos).r * windContrast, 0.0, 1.0);

    float jitter = texture(jitterNoise,  vec2(TIME * jitterSpeed) + MODEL_MATRIX[3].xz).r;

    jitter = jitter * 2.0 - 1.0;

    jitter *= 0.5;

    windF = windF * 2.0 * PI - PI;

    

    vec2 windDir = vec2(cos(windF), sin(windF)) * windIntensity;

    windF *= 1.0;

    vec2 tipAngle = windDir - jitter * 0.6;

    VERTEX.xy = floor(VERTEX.xy * rotate(tipAngle.x * (VERTEX.y / subdivision)));

    VERTEX.yz = floor(VERTEX.yz * rotate(tipAngle.y * (VERTEX.y / subdivision)));

    

    

    //BIllboarding

    MODELVIEW_MATRIX = VIEW_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), INV_VIEW_MATRIX[2].xyz)), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0), MODEL_MATRIX[3]);

    NORMAL = vec3(0.0,1.0,0.0); //hack

    ///Normal Hack, return when you have 3D rot matrix function

    if(false){

        vec3 norm1 = vec3(0.0,1.0* 0.8,1.0);

        vec3 norm2 = vec3(1.0,0.0* 0.8,1.0);

        vec3 normal = mix(norm1,norm2,UV.x);

        NORMAL = normalize(normal);

    }

    grass_height = texture(densityNoise, MODEL_MATRIX[3].xz / 75.0 ).r;

    grass_height = max(0.25, grass_height);

    VERTEX.xyz = VERTEX.xyz * scale(grass_height * grass_scale);

}

void fragment(){

    if(!FRONT_FACING){

        NORMAL = NORMAL * -1.0;

    }

    //ALBEDO = some_color;

    //ALBEDO = vec3((1. - UV.y)/ 2.0);

    vec4 topColor = mix(shortColor,tallColor, grass_height);

    //vec4 topColor = mix(shortColor,tallColor, 1.0);

    float contrast_uv = clamp((UV.y-0.5) * 2.0 + 0.5, 0.0, 1.0);

    ALBEDO = mix(botColor.rgb,topColor.rgb, floor((1. - contrast_uv) * subdivision) / subdivision);

    

    //ALBEDO = vec3(topColor.rgb * (contrast_uv));

}

it's not very clean as this was all playground/testing stuff. But if you copy and paste this onto a shader you should achieve the same thing.

thx so much! I'm having trouble getting this working, pretty rusty with vertex shaders. if you wouldn't elaborating a little more on the setup from a few questions? 

1. did u have the shader in the surface material override of a MeshInstance3D? 

2. did u mean 1x8 subdivisions on a quad that's horizontal? 

3. I'm on Godot 4.2, do u think the version might matter? there's no errors

There is not much difference in shaders in any godot 4 version, Sintra was made in 4.2.2. This video might help explain. Also apologies for the lip-smacking, I had just eaten a sandwich.

hey hey I really appreciate you going to the effort of recording this, it's helped a lot with my brushing up on shaders :]
the only thing I can't figure out is how it's meant to display multiple pieces of grass from one geometry, if you wouldn't mind touching on that a bit?

So a mesh Instance has the advantage of being easily replicated on the screen with minimal performance drawbacks, essentially because they are all the same, for the CPU, all that grass in Sintra is only 16 triangles. You have 3 ish options:

1 - Use MultiMeshInstance3D node: this is godot node, its really fast but u have a limit on the ammount of instances as it's not really meant for grass and stuff like that, maybe for 100 trees in flat surface or other other real-time usages

2 - You can write your own instancer: pseudocode for this would be:

- Get every grass point in space

- Add each point to an array

- Feed the array with the mesh to a Draw call

I have never done this on godot but this is the better way to go if you have time and you wanna get the best performance

3 - Use the Proton Scatter Plugin: https://github.com/HungryProton/scatter

This is what I used, it's very easy to use, it's not amazigly optimized but works as a middle ground where you can get a large amount of grass and not have to hack the code as much. Then you create chunks, you read where the camera is, depending on each chunks distance it will cull or have a lower fidelity mesh. And that's it

thx so much :]
maybe you'll see this janky grass in a game WIP I plan to share soon! 

keep making stuff, ya legend.