So I ended up skipping last month’s update post due to the holidays simultaneously throwing my sense of time off and me also throwing myself whole-hog into working on this thing. Because of this, this latest edition will be skipping a fair few things in the interest of article length, but I’ll be covering most of the major stuff.
Lights and Lighting
This section is a double; the first bit is that we now have Bakery installed, which allows us to have much smarter light baking than vanilla Unity’s Progressive GPU. For one, it doesn’t immediately crash if you turn the settings up above minimum. Secondly, the lighting looks a lot better. And finally, the baked lightmaps are packed much more cleanly, and their Import settings can be adjusted like most other textures. This dropped the map size significantly from 180 MB to 156 MB.
For the other half of this, I discovered that MaterialPropertyBlocks actually work in UdonSharp. What this actually means is that we can have multiple instances of the same material on different objects without breaking batching or requiring multiples of the same material. Initially, this was used to turn the emissive map for the “light crystals” inside the lanterns and flashlights on and off to make it more convincing.
The above was also applied to the emissive maps of the portholes all around the ship. I created a quick UdonSharp script to control all the portholes around the ship. More on this in the next section.
Day/Night Cycle and Skybox
The most striking change in all this has got to be the Day/Night cycle that is now present in the map. This is based off of an asset called Azure[Sky], but heavily modified to make it work for VRChat. To expound upon that, the base Azure[Sky] asset has a TON of scripting behind it, including some pretty extensive math to put the moon in its physically accurate position in the sky based on a given time and date. I ended up stripping out a lot of this from the shader and completely recreating the EnvironmentController script in UdonSharp.
I also public’d the TimeOfDay variable in my version of EnvironmentController, and was able to reference it in other Udon behaviors. As previously touched upon in the last section, I did this in the PortholeController script and made the porthole lights turn on and off with the time of day.
This linkage should also allow us to hook pretty much anything into it without syncing more variables over the network. For a future idea: clocks. Most RPs have very little concept of “time”, from what I’ve seen, and having some clocks around the place for time-keeping would do nicely.
FIRE!
I’ll mostly let this one speak for itself, but I’ll talk about a couple things for this. Kaderen was responsible for the recoil animations on the turret’s barrels after firing, and they’re based on actual footage from the USS Iowa firing her guns, as well as on input from EnDjinn for more accuracy. The firing effect is mine with one sprite sheet from HQ Realistic Explosions to fill out the smoke plume.
I did have to write a custom Lit Particle shader, though, since Unity’s in-built one bases its lighting off of billboard position, which means as you turn your head, the particle colors change. Instead, my version of the lighting just takes Light Attenuation into account.
Performance Optimizations
There will not be any fancy header image for this section, mainly because there’s not much to depict other than a frame counter. However, I spent a fair whack of my time between the last post and now cleaning up the map and just generally making it run better. This effort was because just existing in the map, alone, could bring your FPS down below 30 without other players present.
First off, I took the Porthole model into Blender, copied it twice, and began to manually decimate the mesh to generate LOD levels. Then, I copied those results twice as well, and lopped off the interior polygons from one copy and the exterior ones from the other, leaving me with 3 prefabs for Interior Porthole models, Exterior, and Both modes. This brought the editor FPS up from 150 FPS to 200-ish.
Next, I took a look at our railings. They’re very easy to place with the editor extension EasyRoads3D, but they are also the biggest sink of FPS on the map, and that’s INCLUDING the Global Reflection Probe that runs in realtime. After doing some analysis, this was because of two major reasons:
The first reason the railings were overpowering the frame time was that each and every rail section had its own Mesh Collider; a concave one at that. This was because ER3D does not come with a setting to put in a custom collider mesh. So, I swapped all those to Box Colliders and was dismayed to find that they both spawned in the wrong spot without an Offset input, and were also one-per-rail-section. I said “Screw It” and “finalized” the road network to get rid of all of the ER3D scripts that were interfering with everything, then manually adjusted and culled out every…single…Box Collider. Editor FPS is now up to 240 FPS from 200.
Still not enough, so I looked back into it further. Even though ER3D was supposed to stitch the meshes of the “road network” together, I noticed that the Batch Count was still stupid high. This seems to be because ER3D doesn’t stitch in the other components of the rails: the posts and the end caps. Because of this, the rails around the ship accounted for 90% of the game objects on the map, each with their own draw calls. So I popped each rail section into the plugin SimpleLOD to generate singular models for each bit. Editor FPS to 310 from 240.
Finally, I took a look at the next biggest hog on the Profiler. Nope, still not the Global Realtime Reflection Probe; it’s actually Udon. Udon is actually terrible. Its performance in comparison to MonoBehaviours is out and out trash. For an example, Kaderen made a quick AudioSource cleanup script on his own test world; a simple loop over a list of GameObjects. However, this brought the FPS of his testbed from 1000 to 40. Recompiling as a MonoBehaviour instead of UdonSharp, the FPS sat at 500. We narrowed the performance sink down to Array Accessing; seriously, just accessing arrays is glacial on Udon. What’s more is that each and every single UdonBehaviour on the map gets FixedUpdate() called against it, even if that function is not defined in the script; the mere existence of UdonBehaviours on the map kills performance.
So I went through everything we had thus far and tried to clean up everything possible. This is why the above Day/Night cycle script looks choppy when running fast: I broke every update call out into its own “step”, with Time of Day updating every 0.1 seconds, and Ambient/Direct light colors changing every 1 second. This looks perfectly fine on cycle times greater than 30 minutes, but looks jank when you let the sun fly around at like 3 minute cycles. After that, I took the PortholeController script and had it actually DISABLE the UdonBehaviour of each Porthole Light Toggler when not transitioning.
All this, and the Editor FPS is now up to around 360 FPS from the base of 150 or less. VRChat performance is a solid 90 on Desktop, though it still reprojects down to 45 on VR and I’m not sure why, but it still refuses to dip below that on my rig until 30 people with unoptimized meme avatars show up.
The Ocean, Rewritten
So here is where the grand majority of my dev time in December ran off to. I spent a good amount of time completely redoing the ocean shader from scratch. As mentioned in the last article, the old ocean was made in Amplify Shader Editor and completely lacked proper Vertex Normals. What this means is that the vertex normals of the ocean all pointed straight up, making Unity render the ocean as a flat plane, regardless of any wave deformations.
So, instead of any graph editors or shader recompilers or whatever, I popped up Visual Studio and got cracking on reimplementing all the things. It’s really hard to describe the difference between working vertex normals and not, so I’ll just post a Before and After:
To note on the above, both version screenshots are taken from the same position at the same Time Of Day, and the same exact wave settings (though I did place the region capture slightly sloppily between shots, but that’s whatever). Note how the second picture’s waves actually stand out and are noticeable as more than just slight perturbations in the surface.
From there, I worked on reconstructing the Depth Fog and GrabPass stuff to make it look more like water and not a wrinkly blue sheet.
After that was all back in place, now it was time for new stuff. As this article is getting really long, I’ll just sum it all up rather than going across each and every piece of shader code and attempt to get the displacements right and just say that I added localized turbulence displacements and whitecap foam. The result of which looks really nice, but is ever so slightly inaccurate as I didn’t want to delve into the Signal Processing Mathematics hellhole that is proper whitecap generation.
Top it all off with some subtle translucency code from Amplify Shader Editor’s generated shader code (cleaned up slightly), and we end up with this:
The Hiatus
And now here is the bad news about this. I have spent almost all of my free time making this map, learning new skills to make the map, making stuff for the map, researching stuff to add to the map, theorizing how to interface the ship with other maps so it’s not just a boat, and just plain thinking about the map in general. I have to say that I’ve put something around 300-400 hours into the map in just the latter half of the year. You probably guessed it: I’m suffering from a severe case of burn-out. This, compounded with some heavy depression setting in and a seeming lack of interest from the project lead, has pretty much been killing me for the past couple of months.
Because of this, I will be taking the duration of January off of this project (at least), and will be returning to it when I have the energy to do so. I will probably start working on some side projects in the interim, I have some ideas for a couple set pieces that I will be making as VRC maps first, and then perhaps use them as testing grounds for various things that can port back to this. For instance, I have a hankerin’ to build a map based on the Oregon coastline and use that to further flesh out my ocean shader with fancy things like depth-based wave heights and such.
Until next time.