DustAfter getting the basic gameplay down for the dev build, I wanted to add something to enhance the sense of volume and 3D-ness in the flashbacks. The gimmick is that you're able to walk around in something like a 3D picture. That's not so special when you're already walking around in the same environment before and afterwards. It needs something "otherworldly."
My first thought was to invert the colors. Bad thought. Next, I tried just putting a bunch of particles floating around the player. That got the basic idea across but actual particles aren't well suited for low resolution 1-bit rendering: they pop in/out and they scale with distance. Those two problems led me directly to the idea of using a "true" point cloud:
Dust cloud in a test level
The points need to be exactly one pixel in size, no matter how far from the camera. Unity's built-in particles can't be restricted like this OOTB and they also have a lot of extra logic that I didn't need (almost everything), along with some stuff I couldn't turn off (lifetime). So I threw together a custom solution that surprisingly worked well right away.
To render the dust cloud, I generate actual little quads on the CPU. Each of the 4 quad points has the same 3D position with different UV coordinates to specify which corner of the quad it is. That gets picked up in the shader and transformed to be exactly one pixel in screenspace.
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// convert from homogenous
float3 screenPos = o.pos.xyz / o.pos.w;
// push corner out based on UV
float2 screenSize = float2(640,360);
screenPos += float3(1 / (screenSize * v.texcoord), 0);
// convert back to homogenous
o.pos.xyz = screenPos.xyz * o.pos.w;
return o;
Even with huge meshes and tons of quads the performance is really good. Probably because GPUs are optimized for churning through large static data sets like this. And that's another advantage of a custom solution over Unity particles: it's completely static, generated once at asset import time.
To make the points more legible in various situations, I invert their color against whatever's behind them. That took some finagling (and one of the renderbuffer color channels), but the whole greyscale/1-bit thing helped again here since there was a channel to spare.
TransparencyOne of the things I'd been holding off with this 1-bit rendered was support for partial transparency. Unfortunately, the public dev build has a gun-firing scene right at the start that calls for some kind of smoke/muzzleflash. So this needed to be tackled sooner rather than later. My first attempt was to just model and texture the smoke shape, apply an alpha shader, and render as usual:
Modeled smoke with transparency shader (lined)
Nope. Second try, remove the lines:
Modeled smoke with transparency shader (unlined)
That's much better from the side when you have a simple bright background, but completely illegible from the front. The poor mix of transparency and 1-bit-ness makes the shape completely disappear on an unsuitable background. Also, moving around the clearly-defined shape of the smoke makes it look cheap and fake. More-so than usual anyways. I tried a few shader tweaks to fade out the edges or otherwise mask the shape, but nothing helped much.
Dust Come BackAfter messing around with the modeled smoke a bit, an idea came to me: why not use the dust clouds for this instead of trying to fake it with surface geometry. At 640x360, you can put enough single-pixel particles onscreen to suggest exactly the shapes I'm after.
Modeled dust cloud in-game
The tricky part to make this work was figuring out how to take the procedurally-generated dust clouds and enable modeling their shape manually. There are dedicated point-cloud editors but that's not something I want to deal with.
The solution ended being pretty simple. In Maya, I model the shape and subdivide it a bunch until there's an overkill of verts. Export that to Unity where an import script picks it up, shifts each vert randomly, and converts each vert into a full quad for the dust cloud system. The original shape's triangles are discarded. It's a lot of data but again, GPUs eat this shit up.
Cloud modeled in Maya
In another stroke of pure luck, this technique is much easier production-wise than modeling and texturing traditional geometry. My favorite kind of solution.
Action LinesReplacing transparent shapes with dust clouds turned out ok visually, but not objectively great. The real win comes when using this same system to indicate lines of action. I found these a great way to make the scenes feel dynamic, even while perfectly static.
Action line in-game and modeled in Maya
This meandering path from otherworldy clouds, to transparency replacement, to action lines is one of the fortunate surprises that I really enjoy in game dev.