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
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.
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.
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.
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
// 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];
FVector right = thisPos - positions[y * size + (x + 1)];
FVector normalA = FVector::CrossProduct(up, right);
// Down and Left
FVector down = thisPos - positions[(y - 1) * size + x];
FVector left = thisPos - positions[y * size + (x - 1)];
FVector normalB = FVector::CrossProduct(down, left);
// Mean of both normals
FVector normalAvg = (normalA + normalB) / 2;
// Set ths normal in the array
// Negated because Unreal uses an inverted y axis
Normals[y * size + x] = -normalAvg;
Here’s some renders using the line renderer to visualise the normals.