Autotiles, instancing and object clusters
The problem
Some of the blocking tiles in the game are things like a tree, a cactus, etc. Occasionally, I want to use these tiles to represent blocked cells in an outdoors map. But, if I just put one such sprite per cell, the result looks poor (see bottom image above, "original approach"). So I thought, "ok, let's try to create an autotile version of the trees".
In the meantime, I've developed some helper tool to assist with creating autotiles (rug/fence/blob) from a selection of input tiles:
... So I hacked a bit of that code away, to automatically place sprites that respect the edge restrictions, so effectively automatically creating the autotile blob from any single sprite. Example output:
While I was super happy initially, I soon realized that it would only work under very specific circumstances (symmetric sprites, placed appropriately at particular spots), and in order to cover all scenarios , I would need to automatically create a lot more sprites. So, after seeing a lot of restrictions, I wanted to go for plan B, and reuse some code that I already have for the overworld. That code uses Poisson disk sampling to create instances of things to populate the overworld.
Sprite shader refactoring
The problem was that that shader was restricted for the overworld vegetation, so I needed to generalise. I took a hard look of the miscellaneous shaders that I'm using for sprites (anything that uses texture atlases) and I noticed ones for the following:
- GUI
- Static objects
- Moving objects
- Moving object shadows
- Moving objects occluded areas
- Vegetation normal
- Vegetation shadows
- Vegetation decal normal
- [Future] static object decals
- [Future] moving object decals
So, lots of combinations. So I delved in Unity's multi_compile pragma and custom, manual shader variants, and I came up with the following scheme, to have 3 different shader variant axes for sprites:
- Orientation: Standing or decal
- Sprite type: Static, moving or "splat"
- Render type: Regular, shadow or occluded
GUI is still its own thing, but all the rest can be expressed with one value per "axis" above. While Unity nicely allows keywords to configure the multi_compile option, such configuration cannot change blend settings, z settings and core things like that. So, variants based on Render type (regular, shadow, occluded) are all different shader files, that define some defines and include the common shader code. The rest of the variants are just expressed with #ifdef. Here's how the "Regular" render type variant shader looks like:
Shader "Sprite/TextureAtlasSpriteRegular" { Properties { g_TextureAtlasSprites("TextureAtlasSprites", 2DArray) = "white" {} g_TextureAtlasConstants("TextureAtlasConstants", Vector) = (32,32,1,0) g_RealTime("Real time", int) = 0 g_RenderingMoveSpeed("Rendering move speed", float) = 1 } SubShader { Tags { "Queue" = "AlphaTest" "RenderType" = "Opaque" } LOD 100 AlphaToMask On Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 4.5 #define VARIANT_REGULAR #pragma multi_compile_local VARIANT_ORIENTATION_DECAL VARIANT_ORIENTATION_STANDING #pragma multi_compile_local VARIANT_SPRITETYPE_STATIC VARIANT_SPRITETYPE_MOVING VARIANT_SPRITETYPE_SPLAT #pragma multi_compile_instancing //#pragma instancing_options procedural:setup #include "UnityCG.cginc" #include "Assets/Shaders/common.cginc" #include "Assets/Shaders/sprite.cginc" #include "Assets/Shaders/noise/random.cginc" // We don't need this, as we don't have gameobjects and materials for each UNITY_INSTANCING_BUFFER_START(Props) UNITY_INSTANCING_BUFFER_END(Props) #include "Assets/Shaders/Sprite/TextureAtlasSprite_common.cginc" ENDCG } } }
So, now all the sprite code for all the variants is in a single source file, which is super convenient for editing. This approach now allows easy proper shadows for any object (static or moving) among other things.
As this was a hell of a tangent, to solve the original problem, I wrote a pseudo-autotile algorithm class called "Splat" where, if I've specified it, instead of autotiling it creates an instance buffer and renders that with the Splat render variant (which includes shadows). This results in the first image shown on the page, where we have nice randomized trees including shadows. And, even though I'm not showing it here, we can use a variety of tree types, which is very, very convenient (with autotiling that would be near impossible).