Individual Learning – Snow Effect

This week I did some more work with vertex displacement and started work on an effect I had wanted to make for a while, even before I started writing shaders: Snow resting on top of an object.

Everything went pretty smoothly, in the vertex function i find the slope angle, if it’s flat enough for snow to be there i set a mask value to white and if not it’s black;  I also displace the vert vertically.

Then in the fragment function I use the mask to lerp between the object’s texture and a snow texture (I left it white as I didn’t have one to hand). The effect is quite appealing though it does stretch the UVs around the displacement and the transition from snow to object can be jagged where there are sharp changes in geometry.

Here’s a video of the shader in action using a rock from the asset store for demonstration.

 

Once I had finished i decided to look into using tessellation which would give me more vertices to work with and in theory a better result. I found this page on the documentation which demonstrates how to implement tessellation in a surface shader.

I had been writing individual vertex and fragment shaders so I would have to re-write them however it did show me that I could perform vertex functions inside a surface shader using the vertex:vertFunction define in my shader code.

I used the Surface shader examples page on the unity docs to help me with my syntax and quickly got this very similar result the only major differance was that i could now store my mask value as a float in the Input struct and not a float4 in the v2f struct.

SnowShader_SurfaceExample

I did manage to get Tessellation and Phong Tessellation working without the snow effect but whenever I tried to include my vertex function I would get an error which i assume is caused because unity uses the vertex modifier after tessellation as it would usually be used to perform some sort of height map displacement. The tessellation was surprisingly easy to get up and running though.

This slideshow requires JavaScript.

The next step I would like to take is finding a way to use the tessellation with my snow effect to hopefully see an improvement compared to the original shader.

Visual Scripting – Spawning Trees

This week I started actually spawning trees on my landscape instead of my debug cubes. I did this like before by using the position array and spawning one at each point, however I used the normals that I generated previously to determine the slope angle  (dot product of the normal and a vector pointing straight up) and only spawning the tree if the angle was less than a certain value

I have also included a water plane which determines the minimum height that the trees can be placed at.

TreesSlopesWater

There are now far fewer objects being spawned on the terrain so the performance is quite bearable while using a lower LOD, so i can now spawn the trees using a greater detail mesh meaning that the Forrest I can generate looks far more realistically dense.

TreesSlopesWater_before

Next I used the highest detail mesh to spawn the trees however i used a seeded Random number generator to only spawn a tree at 10% of the points. I also varied the sizes and orientations that the trees spawn at. All together this leads to a far more realistic and pleasing forest being generated.

TreesSlopesWater_Improvement

I also created a Material for the terrain that applies either a grass or a stone texture depending on the current slope of the terrain. I did this both as an experiment but also so that it is easier to see where the trees should be spawning. It also has the added benefit of making it easier to see the shape of the terrain.

TreesSlopesWater_after

Going forward, I would like to use Noise to determine the tree distribution instead of what i am doing currently because random number generation can look unnatural whereas a noise algorithm such as value or Perlin noise will give a far more natural and configurable look.

As an experiment I implemented Terrain generation in Monogame using fractal value noise. This was useful as it allowed me to have an attempt at implementing a noise generator; Unreal Engine does not have a built in noise generator like other engines do so when i come to use noise for the tree distribution i already know how to implement it.

SpawningTrees_HeightMap

Visual Scripting – Generating Normals

I got rather carried away with actually developing this next feature so this post covers 2 weeks of development . I have since completed more work which i’ll cover in the next post.

Generating Normals

The task for the next feature was to start adding trees to my landscape but only where the slope of the landscape below was suitable, this requires knowing the normals of the mesh so i started here.

My plan was to look at all of the points adjacent to another and use the cross product with 2 adjacent points at a time and then average them. In the first week my attempt was very problem filled, at one point the compiler had decided that the entire function had no purpose and optimised all of it away, making debugging the problem rather difficult. I also had a problem where the normals that were returned to me were just incorrect.

What did not help was that I had decided to use a complicated method to get the normals with the hope that it would run fast. This was a mistake and i should have started with a simpler approach and optimised it later if needed. I do this often and in future i’ll be assuring i don’t do it. KISS: Keep It Simple, Stupid.

My second approach worked immediately, further hammering down the KISS “methodology”. First i initialise my blank array of normals to face along the z axis, this is so that all of the edge cases are handled. Then for each point that is not on an edge i calculate the normal for the positions above and right, then the positions below and left giving me two normals, then i can average these out. I only need to work out the two opposing normals as all four adjacent positions are factored into the calculation.

TArray<FVector> UMyFunctionLibrary::GenerateNormals(const TArray<FVector> positions)
{
    // Output array
    TArray<FVector> Normals;

    // Set every vector to point up
    for (size_t i = 0; i < positions.Num(); i++)
        Normals.Add(FVector(0.f, 0.0f, 1.0f));

    size_t size = sqrt(positions.Num());

    // For each point that is not on the edge
    for (size_t y = 1; y < size - 1; y++)
    {
        for (size_t x = 1; x < size - 1; x++)
        {

            FVector thisPos = positions[y * size + x];

            // Up and Right
            FVector up = thisPos - positions[(y + 1) * size + x];
            up.Normalize();
            FVector right = thisPos - positions[y * size + (x + 1)];
            right.Normalize();

            FVector normalA = FVector::CrossProduct(up, right);

            // Down and Left
            FVector down = thisPos - positions[(y - 1) * size + x];
            down.Normalize();
            FVector left = thisPos - positions[y * size + (x - 1)];
            left.Normalize();

            FVector normalB = FVector::CrossProduct(down, left);

            // Mean of both normals
            FVector normalAvg = (normalA + normalB) / 2;
            normalAvg.Normalize();

            // Set ths normal in the array
            // Negated because Unreal uses an inverted y axis
            Normals[y * size + x] = -normalAvg;
        }
    }

    return Normals;
}

Here’s some renders using the line renderer to visualise the normals.

Normals_AboveNormals_Side