This was supposed to be a quick, low-detail item. But it looked too much like a tv shelf. Two days later…
The everything-is-hand-modelled marathon continues with an assortment of new chairs, cleats, rings, hooks, rain gear, ropes and more. Look for Lillie is the Keeper 1.5 later this week, if all goes well.
1.5 will be titled “Secrets.” You’ll find out why.
Stealing a bit of time to work on the next release of Lillie is the Keeper. I always wanted some sort of hero wall-mounted desk in the watch room (top floor, beneath the lantern). It’s rather a hub, as far as the game is concerned.
The design is inspired by a Victorian-era Cortland desk.
Each level of “Lillie is the Keeper” features a unique high-definition skybox texture. These Unity cubemaps are rendered in Bforartists, the UI-focused fork of Blender. Here’s how you can create your own.
Create a new scene in Bforartists, and switch the Render Engine to Cycles (camera icon: Render Engine). Add a single Directional Light, to act as the sun. Model a gently curving lenticular piece of geometry, about 5 km in radius, to act as the ground/ocean, and texture it appropriately.
If you’re interested in accuracy, you can enable an add-on called Sun Position to set your sun angle for the date, time, latitude and longitude of your game scene (Preferences menu: Add-ons: Lighting: Sun Position).
Switch to the Shading tab, and select the World (background) shader. Create a Sky Texture node. Set it to Preetham, and link its output to the World Output node’s Surface input.
Clouds
Next, model your clouds (as ordinary geometry) in rough form. Hide them from rendering. Create a Volume object (Add menu: Volume: Empty). Go to the Volume’s Modifiers tab (wrench icon), and add a Mesh to Volume modifier. Set the Modifier’s Object to your hidden cloud geometry object. What you’ll get is a Minecraft-like blocky cloud. Increase the Voxel Amount to reduce this blockiness somewhat–try 1024.
To fully remove the blockiness and get cloudier edges, go back to the Modifiers tab, and add a Volume Displace modifier. Go to the Texture tab (checkerboard icon). Create a new texture, and set its type to Clouds. Go back to your Modifiers, and assign the cloud texture to your Volume Displace modifier. Adjust the Texture and Modifier settings until you like the results.
Go to the Material tab (crash test dummy head icon), create a new material, and assign it the Principled Volume shader. Adjust the material properties to your desired cloud appearance. (This can take a while. Start with a Density of .001-.005. You’re welcome.)
Rendering
Finally, set up your camera to render the six faces of the cubemap. In the Data tab (camera icon) set the Field of View to 90 degrees. In the Output tab (printer icon) set the Resolution to a power-of-two square, like 1024 or 2048. Set the Frame Range to 0-5. Select a folder and filename to render to, setting the File Format to OpenEXR (.exr), in order to save HDR color values. Go to the Animation tab, and animate your camera to be rotated as follows on frames 0-5 (x,y,z):
0: 90, 0, 270
1: 90, 0, 90
2: 180, 0, 0
3: 0, 0, 0
4: 90, 0, 0
5: 90, 0, 180
Render all six frames (Render menu: Render Animation). This would be a good time to take a walk around the block. Notice the blackbirds.
Building the Skybox
When you get the renders looking how you want, open your frames in an image editor and arrange them in one big vertical strip. Frame 0 should be at the top, and 5 at the bottom. Save this, again, as an OpenEXR file.
In Unity, import the strip image. Click on the texture to see its Import Settings in the Inspector. Set the Texture Type to Default, the Texture Shape to Cube, and check Fixup Edge Seams.
Finally, create a material. Set the Shader to Skybox/Cubemap, and under Cubemap (HDR), select your texture. In your Scene, open the Lighting panel (Window menu: Rendering: Lighting). Click the Environment tab, and assign your material to Skybox Material.
Bonus
Download my .blend file for LitK level 5: Light right here.
There’s a bug in the FBX exporter for Blender 3.x (and now 4.x) that doesn’t export materials on instanced meshes. While the original mesh imports into Unity just fine (and remaps materials correctly under the importer’s Materials tab) duplicate instances of the same mesh import with only the default Lit material. If you’re (perhaps oldschool) like me, and use a lot of instancing to save disk space, this is a problem.
It supports Undo, regular and skinned meshes, and will walk down the hierarchy as far as it needs to. Can take a few seconds to grind through big hierarchies. Works on my ultra-complex Lighthouse prefab, and all the others I’ve thus far thrown at it. If you find a bug, let me know!
Here’s how to use it:
Download & unzip the files.
Add FixFBXImportMatsUI.cs to your project’s Editor folder. (If your project doesn’t currently have one, add a folder named “Editor” to your Assets folder.)
Add FixFBXImportMats.cs to your project, wherever makes the most sense.
After importing an FBX with missing Materials, open the Prefab or drag it into a Scene, and add a FixFBXImportMats Component to the root of the import’s hierarchy.
Assign the Default Material you need replaced to the Component’s slot.
The default Material will vary depending on your render pipeline and import settings.
If using URP, it’s usually the Lit material, located in Packages: Universal RP: Runtime: Materials.
A quick way to find it is the select an instanced mesh in the hierarchy, and click on the Material shown in the Editor.
Leave Clean Up After checked to have the Component remove itself after running.
VFX Graph is Unity’s high performance pure-GPU particle effects system. I recently helped Chop Chop Games get a sizzle reel out for an upcoming card battler, which involved a fair amount of prerendered particle work in Apple Motion and Blackmagic Fusion. Looking at their competitors in the space, like Slay the Spire and personal favorite Black Book, got me tinkering again with VFX Graph.
It’s an impressive set of tools, but shader experience is definitely a prerequisite. You need to “think in GPU.” One great decision was making Animation Curves a first-class citizen in VFX Graph, allowing for a lot of nuanced per-channel animation. Getting particle motion properly punchy is always a challenge.
You can download these all in a .unitypackage here. The download also includes a couple of potentially useful subgraphs. Point Force can be positive, to push particles away from a given point, or negative, to suck them in. Orbit can also be positive or negative, to control the direction particles orbit around a center point.
Unlike many tasks, Blender’s powerful but bafflingly-designed texture map baking tools are not improved by its UI-focused fork, Bforartists. Here’s how to use them, with a downloadable example (the Hoosier Cabinet from Lillie is the Keeper‘s upcoming 1.2 release). Click here to download the cabinet model with textures.
Honestly, if I haven’t done it for a while, I can’t remember how the process works, so this will be as much a cheat sheet for me as you.
Since it’s not our focus, we’ll skip quickly through the high-to-low-poly sculpt method. You create a low-polygon model, unwrap its UVs, and make a duplicate (using Duplicate Objects, not Duplicate Linked) in the same world position. Hiding the original, you sculpt a high-detail version, to be baked onto the original as (at minimum) a Normal Map. You can also use Bforartist’s Shading tab to create texture, displacement and color, even applying multiple materials to your high-resolution mesh. Since the UVs don’t have to match between the high and low-poly versions, effects like differently-oriented wood grain are just a matter of mucking with the high-poly sculpt’s UVs.
I’m also largely assuming you’re familiar with importing textures into Unity.
Baking a Normal Map
In Object Mode, select the high-poly model.
Multiple-select the low-poly model as well (on Mac, Command-click, in the scene hierarchy window).
Under Render Properties (the camera icon in the sidebar) set Render Engine to Cycles. (Baking is not supported in Eevee or Workbench.)
Farther down in the Render Properties sidebar, click to expand the Bake options.
Set Bake Type to Normal.
Check Selected to Active (if it’s not already checked).
For Extrusion, enter the minimum distance the baking system will need to “puff out” your low-poly model’s surfaces to completely enclose your high-poly sculpt. (This may take a few goes, before you hit on the right distance to catch everything without artifacts.)
Max Ray Distance I usually set to twice the Extrusion.
Under Target, select Image Textures.
(Wait, shouldn’t there be a control here to select which texture to bake to? Yes, but, Blender.)
So instead, switch from the Main tab to the Shading tab.
Make sure your low-poly model has a Material of its own.
In the View window, create a new texture (Image menu: New) or open an existing texture to overwrite (Image menu: Open…).
Down in the texture nodes window, click Add: Texture: Image Texture, and drop the node into your workspace. Don’t bother connecting it to anything.
In the Image Map node’s Image Browser menu (picture icon) select the Normal Map texture you’ve just created/opened.
Select the Image Texture node itself. For some reason, this is how you choose which texture to bake to. If an Image Texture node is hilighted in the node workspace, baking will write to it. BE CAREFUL: It’s easy to accidentally bake to the wrong texture, if you’re using texture maps in your Material.
Back in the sidebar, click that big Bake button.
If the results are good, save the texture map from the View window. (Image menu: Save Image.)
If not, play with your Extrusion parameter, and/or your sculpt geometry. Make sure your normals are facing the right way. (The Normal Map baker doesn’t seem to ignore backfaces.)
Baking an Occlusion Map
Ambient Occlusion darkens cracks and crannies in your surface. Most importantly, it keeps the inner edges of your Normal Maps from reflecting a bunch of weird phantom light.
It’s possible to bake Ambient Occlusion (as well as Diffuse and others) from a single model to itself–no high-poly needed. To do this, simply select the model, go to the Bake options and uncheck Selected to Active.
Follow the same procedure down to Step 5, but now set the Bake Type menu to Ambient Occlusion. If baking high-to-low, keep the same Extrusion and Max Ray Distance settings that worked for your Normal Map.
Follow the rest of the procedure above. You can reuse the Image Texture node, just make sure to switch it to the Occlusion Map texture you create/open. Remember to save the texture after the bake.
Baking a Diffuse Map
If you’re comfortable with the node-based shader system, and applying multiple materials to a mesh, you can do a lot of interesting texturing in Bforartists.
Typically, you’ll be baking Diffuse maps with a single model, rather than high-poly-to-low. Again, simply select the model, go to the Bake options and uncheck Selected to Active.
Follow the same procedure down to Step 5, but now set the Bake Type menu to Diffuse.
Chances are you don’t want to bake your shadows and global illumination into the Diffuse Map. Under Contributions, uncheck Direct and Indirect.
Follow the rest of the procedure above, with the same reminders.
The Prestige: Baking a MOxS Map
This one’s on Unity. In the Universal Render Pipeline (URP), there’s an awkwardly not-quite documented way to pack three monochromatic textures into one, saving two texture taps per draw (and textures in memory). The four channel (RGBA) texture packs a Metallic Map, Occlusion Map, an unused channel, and a Smoothness Map into one, I guess, “MOxS Map.”
First we’ll need to bake a Metallic Map. Unfortunately, the Bake Type menu has no such option. We’ll get around this in the Shading node editor by temporarily piping our Metallic value into the Emission input (since it’s the least likely to already be in use) and baking an Emit Map:
For each Material on the model…
If there’s a node piped into the Principled BSDF node’s Metallic input, pipe it into the Emission input instead.
If it’s a constant value, just copy it into the Emission color’s V value (in HSV mode).
Set the Bake Type to Emit.
As above, create/open a texture to overwrite with the Metallic Map, select it in the Image Texture node, hilight the node, and click the Bake button. Save the texture.
Next we need an Occlusion Map. Disconnect/revert any Emission changes you made in your Materials to create the Metallic Map, and bake an Occlusion Map as above.
Finally, we need a Smoothness Map. We can’t natively bake this either, but we can bake its (literal) inverse:
Set Bake Type to Roughness.
Create/open a texture, bake the Roughness Map to it, and save.
Combine all three textures in Photoshop (as follows) or another image editor:
Create a new document the same dimensions as your textures.
Open the Metallic Map texture, Select All, and Copy.
In the new document, click on the Channels tab, select the Red channel, and Paste.
Open the Occlusion Map texture, Select All, and Copy.
In the new document, select the Green channel, and Paste.
Open the Roughness Map texture, Select All.
Invert the image (Image menu: Adjustments: Invert) and Copy.
In your new document, create an Alpha channel (plus-in-a-box icon, at the bottom of the Channels tab).
Select the Alpha channel, and Paste.
Save as a Photoshop file. (PNG may try to knock out parts of the image based on the alpha channel, and Unity imports Photoshop files well.)
In Unity, select the “MOxS” texture. Make sure sRGB (Color Texture) and Alpha is Transparency are unchecked. (This is a data texture, so we don’t want to adjust the colors to the current gamut, especially in a Linear Color Space project.)
In your Unity Material (using the Lit, or Complex Lit shader) assign your MOxS texture to the Metallic Map input. Leave the Smoothness slider alone, as it won’t affect anything, and make sure Source is set to Metallic Alpha.
Finally, plug the MOxS map into the Occlusion Map input as well. (The workflow feels off, but the shader seems to recognize the packed green channel as the one to use.)
In this ZIP file you’ll find three C# scripts for Unity: PlaySounds.cs, PlaySoundsMultitrack.cs and PlaySoundsBySpeed.cs. The latter two are subclasses of PlaySounds.cs, and require the former in your project. These small, lightweight scripts are used throughout Lillie is the Keeper (along with a couple other subclasses that are dependent on features of the game).
Basically, they do everything that I wish Unity’s own AudioSource Component did by itself. Play through a list of AudioClips? No problem. Play a random clip from a list? Done. Play OnTriggerEnter()? One click. You can play a single clip or all clips, disable the GameObject after playing, trigger audio from an external script, and monitor playing status with UnityEvents or a simple bool.
Check the scripts’ headers for a full rundown of features and how to use them. You’ll also see helpful tooltips in the Unity Editor.
The two subclassed scripts, PlaySoundsMultitrack and PlaySoundsBySpeed let you do two additional things. With the former, you can swap between up to four wholly different sets of AudioClips. Think of a windmill randomly playing different sounds from a playlist at different speeds: a slow, creaky set of sound clips at lower speeds, and a higher, whooshier set at high speed. PlaySoundsBySpeed lets you specify a minimum velocity at which to trigger sounds, and scales the volume up from 0 to 100% at a maximum speed. (Setting both speeds equal always plays the sound at normal volume.)
There are a couple things you may want to customize. These are written for rapid prototyping, trying things out, and generally seeing what works. Just about everything that can be public is, rather than using [SerializeField] private. If there are no AudioClips in the list, PlaySounds will simply disable itself; you may prefer to throw an error. Additionally, you’ll notice that an AudioSource component is necessary, but not required in code via [RequireComponent(typeof(AudioSource))]. (Instead, PlaySounds logs the issue for you.) This is deliberate, to keep Component coupling loose while trying things out, but may not be what you want in production.
I encourage you to use these, without limitations, in your own work. (But if you do something cool, please do let me know!)
This is it! “Lillie is the Keeper,” the innovative new AR adventure game for iPhone & iPad, is live…
And best of all, it’s FREE, through January 6!
Explore your own virtual lighthouse, and live Lillie’s story. I’m proud to bring this playable short story to you, and hope you enjoy your time at Switch Rock Light Station.