Progress Update #11
Planetary Ring Systems in UE4
Since completing gas giants last week I wanted to add one more feature to make these pretty simple planets a little more interesting: rings (or as Wikipedia defines them "ring systems").
The first problem to solve was how I was going to go about adding rings to planets. As in would they be a component, part of the root mesh, a separate actor, or something else? In the end, I decided to make ring systems an actor component for a few main reasons. 1) I would be able to add rings to terrestrial planets too 2) Actor components would be more flexible than hard-coding them into a single planetary class and 3) I haven't used actor components yet in this project and it would expand my knowledge base. The biggest downside to this approach, as I would soon find out, is that actor components have no rendering capabilities meaning that I would have to spawn and use a separate static mesh component to take care of all rendering.
The Material
Now, I also saw rendering coming with it's own set of challenges. Mainly what kind of mesh would I use. The obvious choice would be a ring mesh, duh. But this would mean I would have less control over the ring. I want to be able to control the radius and the width of the rings and having a static mesh wouldn't allow for that in any way that I know of.
The alternative that I thought of was to use a simple plane mesh and make it look ring-like by editing the UVs in the material. I also thought that this would get around having the sharp edges of a custom static mesh, but as we'll see there ended up being a problem that caused jagged edges anyway. This can be fixed in the future but for now it looks good enough from most angles so... ¯\_(ツ)_/¯
With my new knowledge of how UVs work from the last post I drew two UV maps of what the rings should be.
This one gave me exactly what I was looking for, but the size of the black dot in the middle was not variable so I couldn't change the width of the rings. With my next attempt I got rid of the dot and with some math was able to make a dot in the middle. I also plugged this value into opacity so the black part would be invisible.
It was at this point though that I realized I wasn't actually using the Red (aka U) channel so I was able to simplify the UVs to a fuzzy dot and get the same result.
Here's where I noticed the aliasing? Compression? I don't actually know, but whatever the issue is it makes the ring look really messy when you get too close:
Different color gradient btw |
Regardless, at this point I considered the material complete enough to start working on the actor component.
The Actor Component
The main problem I ran into with the component as I mentioned earlier was the fact that I had to keep track of a separate mesh component. This meant that since the mesh component variable is a pointer, and pointers don't persist when the engine is relaunched, every time you relaunched the game and tried to change the properties of the ring, the game would crash with a nullptr error. To solve this I had every time before a property was changed, if the pointer was null I would run the following function to find the mesh:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | void URingSystemComponent::FindRingMesh() { TArray<UActorComponent*> Components = GetOwner()->GetComponentsByTag(UStaticMeshComponent::StaticClass(), FName("Ring")); if (Components.IsValidIndex(0)) { RingMesh = Cast<UStaticMeshComponent>(Components[0]); RingMesh->RegisterComponent(); RingMesh->SetRelativeScale3D(GetOwner()->GetActorScale() * Radius * 6); CreateMaterial(); DynamicMaterial->SetScalarParameterValue("_ringWidth", RingWidth); if (Gradient) { GradientTexture = Cast<AGasGiant>(GetOwner())->CreateTexture("RingTexture", Gradient); DynamicMaterial->SetTextureParameterValue(FName("_Gradient"), GradientTexture); } } else { RingMesh = NewObject<UStaticMeshComponent>(GetOwner(), UStaticMeshComponent::StaticClass(), "RingMesh", RF_NoFlags, nullptr, false, nullptr, GetOwner()->GetPackage()); RingMesh->ComponentTags.Add(FName("Ring")); GetOwner()->AddOwnedComponent(RingMesh); RingMesh->AttachToComponent(GetOwner()->GetRootComponent(), FAttachmentTransformRules::SnapToTargetNotIncludingScale); RingMesh->RegisterComponent(); RingMesh->SetRelativeScale3D(GetOwner()->GetActorScale() * Radius * 6); RingMesh->SetStaticMesh(LoadObject<UStaticMesh>(NULL, TEXT("StaticMesh'/Engine/BasicShapes/Plane.Plane'"), NULL, LOAD_None, NULL)); CreateMaterial(); DynamicMaterial->SetScalarParameterValue("_ringWidth", RingWidth); if (Gradient) { GradientTexture = Cast<AGasGiant>(GetOwner())->CreateTexture("RingTexture", Gradient); DynamicMaterial->SetTextureParameterValue(FName("_Gradient"), GradientTexture); } } } |
The thing is, about half-way through writing this I realized, "couldn't I just inherit from UStaticMeshComponent and have the component itself be a mesh component"? I have just tested it and as it turns out, yes, yes you can do that and it works exactly the same if not better because I now don't need like half of the old script that I spent almost two days on. Sometimes my genius is almost frightening.
Gas Giant Storms
Last time I also talked about how I wanted the storms on gas giants to be procedural so this week I also worked on a method for making a procedural texture that will make the storms more interesting.
In the other blog that I was following for the creation of the gas giant material, the author said how they used a voronoi diagram to plot out where storms would occur. To make a similar effect I added a few more lines to the texture generation method.
First I generate some random 2D vectors in an array Points:
Then we find the closest other point to the point closest to the ClosePoint then divide that distance by a falloff to determine the radius of that storm point:
Finally we determine if the current pixel we are testing is within that falloff. If it is within that falloff then it will be lerped between white if it is at the point and black if it is at the very edge. Then we assign that color in BGRA to the Pixels array:
I love messing with texture generation because the errors are often quite entertaining. Here's one of the first tests that went really wrong because I wasn't clamping percent:
The algorithm used to find the color of pixels could be optimized quite a lot as right now if NumStorms is large it will take a few seconds to generate the texture. The only real big optimization I can think of right now though is to precompute the distance between each point to find the closest so it doesn't have to redo the calculation for each pixel.
Comments
Post a Comment