Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

Advanced search

1174440 Posts in 50195 Topics- by 41203 Members - Latest Member: bearcherian

November 29, 2015, 11:58:06 pm

Need hosting? Check out Digital Ocean
(more details in this thread)
  Show Posts
Pages: [1] 2 3 ... 17
1  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: November 14, 2015, 06:57:38 am

I've hit something of a snag in the narrative/story implementation and decided to take a break from that frustration to do some music. There's nothing audible to share but I thought it worth a post about the tools I'm working with.

Obra Dinn's music uses an orchestral theme and the dev build (released over a year ago, jesus) has a few short pieces in it. For previous projects I almost always used a hardware workstation keyboard for music composition. It's nice to get away from the computer screen and not have to mess around with a mouse when writing music. 

Previous weapon of choice

Keyboard workstations work great for electronic or bread&butter instruments but unfortunately orchestral voicing in hardware synths is way behind the multi-gig sample libraries you can run on a desktop PC. I used Logic Pro's builtin orchestral instruments for the dev build but even those are a little flat. A few months ago I sprang for Kontakt Komplete 10 and Sonokinetic's Da Capo symphonic orchestra library.

Current weapons of resigned convenience

What I'm still missing is any proper knowledge about orchestral instrumentation. Violins apparently can't play from C0 to C5 and a big part of making orchestral music sound right is getting each instrument to play within range and in the right way. I'll try not to get too hung up on this, and Da Capo does a decent job of handling a lot of that complexity automatically.

The basic musical structure of the game is luckily pretty simple: Short, one minute pieces that play only during the flashbacks. I have a bunch of those composed already and I need to eventually pick one as the game's theme song. None of them really stand out as quite right for that yet though.

Random Hangups

Back in the programming, I ran into a small bug with the rendering that emphasized one of the pitfalls of using a big mature popular engine like Unity. Basically, I have a shader effect that uses world position coordinates for each pixel (it's the distance blur effect mentioned earlier). There are a lot of Unity resources for sample code and tips on how to do this sort of thing. When using a popular engine like this it's really easy to, instead of fully understanding everything that's going on, just find and use some shader/script source you find online.

Debug display of the shifting world position calculation

If you look closely, you can see the concentric circles (which should be planted beneath the characters) shifts slightly as the camera moves away. I spent hours trying different screen space -> world space techniques, hoping to stumble on to one that works out of the box. 

Nope, back in the box

In fact, all of these techniques work fine and the problem was somewhere else. It took me awhile to pull my head out though and actually figure out what was wrong. This sort of thing has happened to me before in Unity and it'll probably happen again. There are just so many resources so readily available that it's easy to get stuck on autopilot and unconsciously avoid proper understanding of the systems and my own code.


Anyways, the problem is that I was changing the camera's far plane distance between rendering the scene and applying the post effect. That screwed up the depth -> world position calculations by a hair and resulted in sliding values.

I do kinda like that debug concentric circle effect though.

Hotline Dinn
2  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: November 14, 2015, 06:51:53 am
I'm kinda surprised that people are asking for the exact shaders I'm using.
Eh, was that how you interpreted my post? Me, I just meant including a mode to play the game in black and white instead of 1D, not actually including the shader code for others to copy. Huh?

Actually I wasn't sure what you meant and plauk's quote led me to assume you were (also) talking about the shader code. Now I gotcha. The big problem with adding another rendering mode is that, unless I spend a long time tweaking it, it just won't have the care that the 1bit mode has. There's so many tricks and hacks to get the 1-bit looking good that work directly against straight greyscale.
3  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 29, 2015, 08:03:29 pm
If you pay close attention to the glitched gif [...]

Yup, that's exactly what's happening. Nice deduction!

You should seriously consider making the black and white shader some sort of bonus...
I've been thinking this for a while, but didn't want to flat out say it. Shaders are way too hard, and school is way too expensive, and my job takes up way too many hours. I guess I was also hoping for a "shaders included" sticker on the cover when I suggested Lucas publish a book.

I'm kinda surprised that people are asking for the exact shaders I'm using. There's enough info in the thread to reproduce the code. The hard part is that the overall effect requires coordination with the asset creation and lighting. You can't just drop a post effect on the main camera and get a good result.

Also, I've had to make numerous tweaks over the project to fit with exactly what the game needs. I imagine any other usage will need the same level of tending. You're much less prepared for that level of tweaking if you're just handed the shaders outright.

Inverse Kinematics

What I really want to do.... is dance!

4  Developer / Technical / Re: Beautiful fails. on: October 26, 2015, 06:22:11 am
Bugged 1-bit conversion while testing animated lightmaps

5  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 26, 2015, 06:20:40 am
Didn't Work, Try Again

Unfortunately my super clever layered lightmap system didn't work out that well. Implementation-wise everything's fine and on a small level with a few lights/receivers it works great. But the realities of the Obra Dinn brought it to its knees. First 60 crew and now a boss lightmapping system. That ship is plain bad news.

The ship has ~30 lanterns spread around the decks, ~15 of them swing back and forth with the motion of ocean, and all 30 can be switched on/off by the player. Each of these generates their own personal set of lightmaps which receivers composite together in real time. 

The problem is that because of the density of the ship's geometry it's not unusual for one receiver mesh to get lit by many lanterns. Even when the lanterns are contained in light-proof rooms, the meshes making up the walls can easily be lit from both sides by different lanterns. Combine that with the need to separate receiver groups based on material and things got way out of hand. The end result was that lighting the Obra Dinn with dynamic lightmap layers generated over 600 new materials, with a max of 8 compositing layers.

This breaks Unity's batching system badly, the sheer number of new material assets basically destroys the editor, and it exceeds my sensible limit of 4 layers per receiver. Conclusion, I had to scrap that system and work out an alternative.

Dynamic Lights

I briefly experimented with just using dynamic shadow-casting lights - switching them on and off based on proximity/doors/etc. This works ok and looks good but even the slightest performance hit here is painful, since I know it can be done statically. One possibility is to generate each lantern's shadow depth map once (the slow part) and just reuse it every frame afterwards. This would allow rotating the light cookie and adjusting the intensity in realtime, which is 90% of what I need. There are performance penalties with this due to the added passes so I'd still want some way of occluding lights that were far away or not visible.

Still, this is objectively a good solution and probably the most reasonable way to handle this. So instead I went back to the lightmapper and tried something else.

Global Lightmapping, Dynamic Compositing

Under the old system, each lit receiver was responsible for maintaining unique lightmap uv coordinates for each light layer, and for compositing these layers together in the shader. Memory-wise this is great and with just a few light layers to worry about for each receiver it's also fast. On the downside there's a lot of extra complexity due to how multiple lightmap uvs are stored, and having to muck with the shader complicates the asset pipeline.

Instead of doing it per-receiver like this, an alternative is to use only a single global lightmap that gets dynamically updated. So each lit receiver just samples one lightmap with one set of uvs and the compositing task gets moved to the entire global lightmap.

The complexity now comes from how to A) build a global lightmap that incorporates all the light layers dynamically and B) how to composite the dynamic light layers into the global lightmap in realtime. Fortunately neither of these is too bad.

To start, all the dynamic layers are generated individually as before. For each dynamic light you get a packed lightmap and a set of atlased uvs for each receiver. Once all of these dynamic layers are built, you then re-atlas every receiver's uv into one huge atlas that represents the global lightmap.

Mapping each light's local lightmaps to the global lightmap atlas

At this point for each receiver you know where it is in any light's local lightmap, and where it is in the global lightmap. That position in the global lightmap becomes the receiver's lightmap uv. Then, the local lightmap data has to be copied into the global lightmap (per frame). 

You could just have a version of each local lightmap that's already been atlased to the global lightmap, but that would waste a fantastic amount of memory. Instead, it's possible to create a 2D mesh with the local lightmap as a texture that remaps all the rectangles to their new positions in the global lightmap. Compositing the global lightmap then just requires rendering each local lightmap mesh into it, adjusting alpha as necessary.

Compositing local lightmap meshes into the global lightmap


Because this system uses both the global lightmap and the local lightmaps at runtime it does require a lot more memory than the old technique. As a small compensation though, the local lightmaps don't need to be power-of-two any more since their mips are never accessed. Also, the final global lightmap is often comprised of many pages. To save time it's possible to composite just one page per frame - light fades/transitions are a little less smooth but it's not that noticeable.

Beyond actually working properly, this system is also way, way simpler on the runtime/shader/asset side. That probably makes me the happiest. Performance is important but I'm strongly biased towards less complexity in the actual implementation.

Bonus Glitch

While testing the fades without a proper 1-bit conversion:

6  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 26, 2015, 06:20:14 am
[Bumping next post to the next page]
7  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 24, 2015, 08:56:09 pm
about all that crazy shadow stuff: how well does this translate into the 2bit visuals?

All the lightmapping business is mostly just a performance optimization - both for faster builds and faster runtime. It looks otherwise the same as dynamic or standard lightmapping when sent through the 1-bit wireframe and dither shaders.

2bit visuals

2 colors, 1 bit :D

I keep this devlog open in a tab, tucked away in a corner of my browser. Whenever I need inspiration (or distraction), I read up on it. It's always a great read and I never fail to learn a from it. Like learning HaxeFlixel.

Very cool, thanks!
8  Feedback / DevLogs / Re: Even the Ocean on: October 24, 2015, 07:50:39 pm
And this looked good! Here's a bug I came across while doing it

Great post. This horizontal dithering bug/effect looks boss.
9  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 06, 2015, 06:47:31 am
This is a serious suggestion: I think you should publish a book

Biggest problem with actually doing this is that I forget 90% of everything that's happened when a game is done. Even going through the devlog leaves me wondering wtf. The current level of posts is about all I can manage while also working and not completely ignoring my family. I appreciate the encouragement though. Maybe there's some way to put this stuff together in a more professional way that would get closer to a proper book. Hard to reproduce the animated gifs though.


Animating the player actually getting on and off the ship is something I thought would be cool, but didn't expect to have the time or energy to actually implement it. The arm rig in the game is tuned very specifically for realtime IK and using it for straight animation would be difficult. My backup plan was to show a black screen and just play the sounds of climbing. Ditto for climbing the ropes on the ship.

Yesterday I had the idea that it might be reasonable to animate by taking the skin mesh from the complex arm rig, duplicating it for left/right, and re-rigging that to a much simpler HumanIK rig in Maya. For in-game animations, I can hide the IK hand and show the two simpler ones instead. Once that was sorted it was really fast to animate. Mercifully I didn't even need to set the vertex weighting on the new rig - the default crappy weights ended up being good enough for how the hands are seen.

Boarding and disembarking (ship is an empty test version)

The in-game interface for climbing works just like doors. The hand reaches for knobs - pressing space when close enough activates the non-interactive climb animation.

I'm not totally happy with the animation itself but it's good enough. The real comedy is in the Maya scene, where you can see the heroic cheating I'm doing with the arms.

10  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 04, 2015, 08:19:36 pm
MPL might be a good license? Kind of splits the difference between MIT and GPL/LPGL -- you have to share your changes, but only to any of the original source files you modified, it doesn't virally affect the rest of someone's project.

Ah thanks. That does look pretty good. What I really want to prevent is someone from zipping it up, dumping it on the asset store, and charging $X for it. It might not be worth worrying about that though since even the best license won't stop unscrupulous people. Maybe I'll just add a "Any product built on this code must prominently display a link to this free version" note to the eventual github page.

Though seeing it, it's kinda fun to look at the first post now :D
The lower bound for finishing this game is around 3 months but realistically I think it'll take me around half a year.

Oh man :D

This project is way, way bigger than I expected, and it's not even that big by indie game standards. The combination of using a new engine, re-learning Maya, building pipelines from scratch, adding custom features, modeling the ship and characters, etc, has all taken ages. The gameplay is really simple which lured me into thinking the whole thing would be easy. Internally I like to blame Maya but the truth is that I was in over my head from the start. Some day I may go over all the stuff that's slowed me down in detail but it's probably not that interesting.

In Greyscale

As part of testing the lightmapping stuff, I used a different post filter to render in full greyscale instead of 1-bit.

Testing the lightmapping in greyscale

Doesn't look half bad. Way easier to see the face at least. I'm irreversibly committed to 1-bit at this point though so this is mostly just a "Huh.."
11  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: October 03, 2015, 06:06:21 pm
Some more technical fire hosing.

Custom Lightmapping in Unity

I got fed up with Unity's new lighting system and spent the last week or so writing my own custom lightmapping solution. Unity 5's Enlighten can give amazing results if you're going for a next-gen look but it's completely unsuited for what I want. And in the past 8 months of betas and release, I've never once gotten a decent lightmap of the Obra Dinn out of it.

Thankfully, Unity is a flexible engine with especially great editor integration, and rolling my own custom direct-lighting lightmapper wasn't too onerous. The basic idea is to take what the dynamic shadowcasting lights generate, pretty it up a little bit, and bake it into the lightmaps. This kind of direct-only lighting works fine for 1-bit so there's no need for bounces, emission, AO, final gather, etc. All the stuff that makes Unity 5 light baking take hours and hours, gigs of memory, and crash my computer in ways I've never seen.

Lightmap generated with custom GPU-accelerated "lightcaster"

In developing this system my two criteria were that it has to be fast and it has to look decent. "Fast" means GPU-only, which introduces some interesting constraints on the algorithms. "Decent" means soft shadowing.

The Process

Here's the basic steps for the process that I ended up with. I call it lightcasting to emphasize that it's direct light only.

Step 1: UV2 Unwrap
Unwrap each object into the second UV channel. Unity's built-in model import unwrapping isn't bad but I found Maya's auto-UV generation a little better so that's what I'm using.

Torus and its unwrapped UV2 channel

Step 2: UV2 Scale
For every object, determine the texel scale for its lightmap. This scale is based on the area of the triangles in the unwrapped UV2 channel, the area of the triangles in the final transformed model, and the desired texel density of the lightmap. Create a blank unlit local lightmap at the right size for each object.


Step 3: Build UV2->Pos/Norm Maps
Key step: For each object, generate a mapping that will transform UV2 coordinates to a local position and normal at that point on the model. The overall lightcasting technique is based on realtime shadowmapping, which requires the world position and normal for each point on screen to compare against the shadowmap depth. Because lightmaps are rendered into a UV2-mapped texture and not the screen, we need a way to convert those UV2 coordinates to position/normal. The UV2->position and UV2->normal maps can be rendered into a target with a simple shader.

Mappings from UV2 to local position & normal.
These are used to translate UV2 lightmap coordinates to worldspace position/normal.

Step 4: Render Light View Depths
For each light: Build a frustum and render the depth buffer at some suitable resolution - same as realtime shadowmapping. Spot and area lights are easy but point lights require 6 frustums (to form a ~cubemap) and special handling to average all 6 results together and avoid artifacts on the edges. Also, large area lights can be broken up into multiple smaller ones to keep the depth buffer at a manageable size.

A typical spotlight depth view

Step 5: Render Light/Shadow Into Local Lightmaps
For each light: For each object within the light's frustum: Use the UV2->pos/norm maps to perform standard lighting and shadowmapping into the local lightmap. Most forms of soft-shadowing are handled at this stage. Light cookies are easy to support here - something that Unity's lightmap baking has never supported for some reason.

Lighting and shadows rendered into a local lightmap

Step 6: Prune Unaffected Receivers
Determine which objects were actually lit by something. That means checking each object's local lightmap and searching for any pixel > 0. This step is a little tricky because we want everything to run on the GPU where searching isn't efficient. The solution is to repeatedly shrink each local lightmap down into a temporary buffer. At each shrinking step, sample the nearby area to find the maximum pixel value. So the brightest pixel in a 4x4 area will become the new value in a 1/4 size reduced buffer. Do that enough times until your buffer is 1x1, then write that value into another buffer that's collecting the results of all objects. This all happens on the GPU. Once all objects have been collected, you can copy the collection buffer to the CPU and read out the max pixel values for each object to determine which ones were hit by a light.

Reducing local lightmap to a single pixel containing the max brightness

Step 7: Atlas Local Lightmaps Into Global Lightmap
Atlas all the (lit) local lightmaps into multiple pages of a global lightmap. Unity has a basic built-in texture atlassing feature but if you want optimal support for multiple pages, you need to use something else. I ended up porting a really good C++ solution to C#.

Local object lightmaps atlased into a single global lightmap

Step 8: Dilate Global Lightmap
Dilate the lightmap to hide any seams.

Dilating the final lightmap atlas

Soft Shadowing

Lightcasting is based off realtime shadowmapping but offline baking lets me use some beefy soft-shadowing techniques that'd be harder to justify for realtime performance. I implemented a few different techniques, each with their own plusses and minuses.


For reference, the scene with no shadows and with Unity's dynamic realtime lighting.

Percentage Closer Filtering (PCF)

A standard technique for softening shadowmap sampling. This is basically a uniform blur on the entire shadow.

GOOD Simple, looks ok, not many artifacts
BAD Softens everything equally and doesn't simulate penumbras

Variance Shadow Maps (VSM)

Another standard technique that allows blurring the shadow map in a separate, very fast pass. If you need something simple and fast that looks good for realtime shadowing, VSM is one of the better solutions.

GOOD Simple, looks good, simulates a poor man's penumbra with perspective frustums
BAD Weak penumbras and bad artifacts in complex scenes at higher blur settings

Percentage Closer Soft Shadowing (PCSS)

An advanced technique that attempts to estimate more accurate penumbras. It's a little heavier processing-wise and the blocker estimation breaks down for complex scenes but for simple scenes it looks ok most of the time.

GOOD Decent penumbra simulation
BAD Artifacts where shadows overlap, hard penumbra edges when reducing artifacts


This is by far the best-looking technique, and the closest to modeling shadow penumbras accurately. It's also pretty simple: render into the lightmap multiple times, moving/rotating the light slightly for each pass. The big downside is that it's slow. Each jitter pass is nearly equivalent to adding another light to the bake process.

GOOD Looks great and simulates penumbras accurately
BAD Can be very slow. Long shadows require many, many jitter passes to avoid banding

So, that's a lot of choices. For now I'm keeping them all as optional settings. Different techniques look best in different circumstances. With a distant sky light for example, PCF works best with its general blur and reduced artifacts.

Custom = More

Since this is now a custom system, it's possible to go beyond simple lightmapping. The main extra features I wanted were the ability to blend between multiple lightmap snapshots for animating moving lights and to have multiple baked lightmap layers where each layer can adjust the color/intensity of its contribution at runtime.

Animated Snapshots

If I can be happy with monochromatic lighting per lightmap (I can) then a simple form of multiple lightmap snapshots are possible by storing each frame (up to 4) in the different color channels.

Blending between snapshots baked with hard shadows

Blending between snapshots baked with soft shadows

All snapshots/channels

This works well enough with subtle movement and very soft shadows. I could also use it for coarser state changes, say to lightmap a room with the door closed and the door open and to blend between them when the door state changes. With standard global lightmapping this would give me 4 frames of animation for the entire level. For more localized animation and more frames I created a layers system.


A layer is a collection of lights and the lightmap they generate. Each layer controls parameters about format, texel density, shadowing technique, etc. 

Lightcaster with multiple layers

It's possible for any object to be lit by multiple layers and thus need to perform multiple lightmap lookups during in-game rendering. Compared to actual realtime dynamic lighting though, a few extra texture lookups per object is cheap.

The main complexity from adding multiple layers is in dealing with the newly-required material variations/assets/shaders during the baking process. Critically, you want to minimize the number of materials in your scene. This is important for draw-call performance and especially Unity's built-in static batching system. 

Also, now that an object may reference more than one lightmap the built-in Renderer lightmapping properties are not enough. By default Unity provides Renderer.lightmapScaleOffset to specify how to transform the local UV2 coordinates into the larger lightmap atlas. This is a great feature and lets you lightmap instances differently while sharing the same UV2 channels. Unfortunately, there's only a single lightmapScaleOffset per Renderer; if an object is lit by more than one layer you're SOL. To further complicate things, static batching will bake lightmapScaleOffset into the UV2 channel, so that shader value becomes useless after batching. These are all esoteric details but the point is that it complicates support for multiple lightmaps per object.

During baking the steps above are performed for each layer in turn, then there are additional steps to combine them all:

Step 9: Create Receiver Groups
Group every lit object based on which materials it currently has applied and which layers affect it. These are called receiver groups. Generate a new material for each receiver group that combines mixing the X layer lightmaps and the old material that was applied before.

Step 10: Encode Multiple Lightmap Coordinates per Receiver
Hard part: Encode the multiple lightmap coordinates for each object. This is done by building a unique uv table texture for each receiver group that encodes the lightmap scale+offset for all objects in the group. That uv table texture gets added to each receiver group material and each object's index into that table is encoded into lightmapScaleOffset in a way that it can be recovered both before and after static batching.

Fading the green pointlight layer while leaving the spotlight unaffected

Stay Global

The layers are useful for the "present day" part of the game where stuff actually moves. On the other hand, everything is frozen still for the flashbacks so the layers/receiver groups/animation/etc is all overkill. In those cases, the system supports collapsing all the layers to a single set of lightmaps that can be slotted into Unity's built-in lightmapping table. No additional materials or tricks are required for that to work.

The layer data is still kept around even in global mode though so one nice thing is that individual layers can be re-baked, then re-combined into the global lightmaps without having to re-bake the entire level.


Except for jittering, all the shadowing techniques are blazingly fast when run offline. Going through all the steps can take a while though, especially with lots of lights and receivers in the scene. On my relatively fast iMac, the small test scene bakes in about 3 seconds. A huge scene like the Obra Dinn takes around 30 seconds with a single large area light.

Baking the lightmaps in the small test scene

Open Source

I'd like to eventually release this code as open source. It still needs some more production testing, and I want to figure out a license that prevents people from trying to just resell it on the asset store, but it's modular enough that someone else may get some use out of it.
12  Feedback / DevLogs / Re: Manifold Garden (previously known as Relativity) on: September 28, 2015, 09:38:04 pm
Just dropping in to say that A) looking good, as always, and B) "Manifold Garden" is a great, great name. A thorough homerun.
13  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: September 23, 2015, 08:36:01 am
interesting stuff, but I don't think a post effect would do the trick. the geometry would need to be manipulated to get a really consistent look, otherwise it might just look like your drunk? I'm sure you will know best how it looks, this is just my imagination.

You're right, it definitely has a wobbly drunk feel to it. In the flashback context it feels just about right to me.

Yeah, for the flashbacks, a way to display the remaining time could be to slowly blur the scene, and when it's too blurry it'll transition away. One thing I was thinking is directional blur, might make a (imo) nicer transition than an uniform blur. Maybe even motion blur? P:

The blur isn't _quite_ smooth enough for that to work. It's more like big discrete steps. As marcfgx mentioned though I could pull the depth-of-field distance in for a smoother effect.

I really appreciate the novelty of this "1-bit-blur" idea, and its crafty execution, so tons of kudos, but, result-wise, I feel that this feature seriously hurts that amazingly perfect superposition of "typical visual style of old '80s Macintosh games" and "3D", which I loved so much.  Or, said differently, the original crisp tidiness of this unique and expressive rendering style.

Yeah I definitely understand this. I'm still mostly on the fence about it, but I did find two functionally useful places to put the blur: For depth-blurring in flashbacks so they feel markedly foreign, and for blurring objects that have appeared/moved in the current time. The best example being the doors that open after visiting a flashback - blurring them is a really nice indicator that they've inherited some property from the past (being open). That works best if it's understood that "blurry" = "past".

In other news, I've been working on another big technical task and I'll try to get a post up about it soon.
14  Feedback / DevLogs / Re: Creepy Castle - Sideview Retro Puzzle RPG (Greenlit & Kickstarted) on: September 04, 2015, 06:33:51 am
Since the last time I made a palette swap shader gif there have been some new palettes added:

15  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: August 29, 2015, 08:53:26 am
Screen Warping - 1-bit Edition

In modeling the ship and testing things out in-game I got the distinct and unmistakable feeling that "this could use some screen-warping post effects to roughen up the low-poly straight lines everywhere." I'm sure you know the feeling.

I have been down a dark path. Let me take you there.

Starting with this innocent screen:

Testing screen, untouched

The first experiment is with Photoshop's "Wave" distortion filter:

Wobble things around a bit with Photoshop's "Wave" distortion filter

B+. Warps the lines around to make them look less polygonal. The problem is that Photoshop's algorithm doesn't work in 1-bit, so you need to apply it, then re-threshold or re-dither the result. Not a huge deal, but it does double some pixels and erase others. That hurts the legibility so what if there was a way to just shift pixels around and not affect their color at all?

Next try, just offset each pixel by a random amount, using some custom Haxe testing code:

Each pixel shifted by a random amount

Ok, too much. Let's offset large blocks in a random direction instead, to reduce the frequency:

Blocks of pixels shifted by a random amount

Not bad. In motion, there are large stretches of lost or doubled pixels which destroy the nice single-pixel-wide lines and looks more clearly like a post effect than a soft warping which I want. But, altogether a decent solution and solved very quickly... Too quickly. Let's identify an arbitrary problem with this randomized approach that we can spend days and days trying to solve.

The Problem

With the low-res visual style here, and just 1-bit to work with, each pixel is important. The "wireframe" lines make lost or doubled pixels especially ugly. That leads to a rule: If we move one pixel, it shouldn't ever overlap another pixel or leave behind a hole.

... which sounds pretty dumb because of course a pixel can only move in 4 directions (ignore diagonals), and each direction already has a pixel there. The key is that the first pixel move has to go offscreen, leaving a hole - then another pixel can move into that spot, creating another hole - etc. Wind your way around the entire screen this way and you can move each pixel one spot and never overlap or double another pixel. How?

Using Mazes

Build a special 2D maze (with no branches and no dead-ends) where the solution visits each square once, then while walking the solution pick up the pixel in front of you and put it down where you're standing. This shifts each pixel one space along a winding path without overlapping or doubling. If if the maze is sufficiently winding, then the direction you shift each pixel is nearly random. In pictures:

To start, build a maze using the trivial depth-first algorithm, with a bias towards changing directions at every possible step:

Simple depth-first maze. Note the branches and dead ends

Next, this needs to be converted to a unicursal maze - a single path through without any branches or dead-ends. Thanks to Thomas Hooper for the technique for this - it's pretty cool. To convert a normal maze into a unicursal one, first get a solution that visits every square in the maze. Any solving technique will work but the easiest is to just follow the left-hand wall as you walk:

Solving the maze by sticking to the left-hand wall

Next, take that solution and use it to insert a new set of walls between the existing ones:

Solution collapsed to a single line, then added as a new set of walls in-between the existing walls.

The new maze (double the size of the original one) can be solved by walking straight through without any decisions:

Unicursal maze - single solution that visits each square once

So, making a unicursal maze the size of the screen and shifting each pixel one spot along the solution path gives us this:

Shifting each pixel using offsets from a full-screen unicursal maze solution

Ok, that looks cool. Compare it to the random offset image above and it maintains a bit more order. Lines are a consistent width and there are no breaks or extra-thickened areas. But again it's too high-frequency to be considered a gentle warping. We need to reduce the frequency. I'll come back to this image below though.

Instead of generating a full 640x360 maze and applying it directly to the screen, generate a much smaller maze, scale it up, then apply it. This gets its own section...

Maze Scaling

The trick to scaling a (unicursal) maze in this case is that we need to maintain the property that the solution traverses the entire maze. For the mazes above, there's just one solution track so it's easy. When we scale the maze up, there are multiple independent solution tracks - each one must trace uninterrupted through to the end. I now realize that this is hard to describe in words. More pictures are needed.

If we take the unicursal maze above and represent its solution as a "flow":

A flow with one track that traverses the entire maze (starting in the top left)

Scaling that flow up arbitrarily yields this, which breaks our "must trace uninterrupted" rule at the turns:

Scaled flow with broken turns

Turns have to be handled specially during the scale:

Scaled flow with fixed turns - each track can be traced from start to finish

Scaled x5 and animated:

Each track runs through the maze independently. In the end every pixel is visited exactly once

Now we can generate a small maze, scale it up, and use that to shift the screen pixels at a lower frequency:

Small maze scaled up and applied to the screen

Hmm, ok. Every pixel is accounted for. That's good. But there are disjoints where the tracks pass by each other in the opposite direction. This ends up shifting adjoining pixels by 2 spots, which makes some lines appear to break. And with such a low resolution maze the structure is faintly evident. Luckily, we have multiple tracks and can apply a sine wave to the track offsets. Applied to the x5 animated gif above, that would mean shifting the white track by 0 spots, the green track by 1, the cyan by 0, the purple by -1 and the blue track by 0, roughly.

Tracks offset in a sine wave to reduce disjoints

Same effect, exaggerated and applied to a test grid

Obligatory Lena

Multiple mazes at differing resolutions can be stacked. Still no pixels are lost or doubled.

Back in the game, just one low-frequency maze

In motion (rounding errors in my shader are eating some pixels)

Ok! That looks good... Well, it's what I wanted and it holds together pretty well in-game. There's a nice warping which definitely makes it feel more ropey than straight low-poly shapes. Unfortunately it's a bit too distracting. Maybe it'll only be applied in the flashbacks. Maybe not. No surprise then that this rabbit hole has only dirt at the end.


There was something cool in the middle there.

1-Bit Screen Blur

That intermediate, high-frequency maze-offset thing looks a lot like a blur - using just offsets, no blending:

If you don't think this looks cool then go back in time and unread this whole post

This 1-bit blur is something I never wanted or needed but hey let's see what we can do with it anyways.

Just Blur

Here the high-frequency maze is layered with lower-frequency mazes to add irregularity. 
This maintains an important legibility over using randomized offsets

Another spot. This can be globally faded in and out, so may be good for a transition effect or something

Depth of Field

Scaling the effect with distance

Scaling between low and high-frequency mazes based on distance

Focal Point

Scaling the effect based on world distance from a hand-placed focal point

Same thing, different angle

These hold up surprisingly well in motion. I can't prove that though b/c the animated gifs are too big.

That last one might work especially well to highlight the killer/killed in each flashback. Or not. After all this work I'm still not sure where or if it'll end up in the game. I really just wanted an excuse to post those maze-solving animated gifs.
16  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: July 11, 2015, 08:29:16 pm
Weird question, I've been noticing that all the rendered lines look really nice and easy to see. Is there anything you specifically did to make them look like that, or did they just turn out nice on their own?

Probably the only particular trick is ensuring that they're always 1 pixel wide, which requires some special handling in the post-processing shader. There's a post here (and some afterwards) about how the lines are rendered.
17  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: July 11, 2015, 08:05:10 am
Back to Ship Modeling

I've finally got the entire ship roughed in - all rooms, areas, etc for the top, gun, orlop, and cargo decks. Fitting everything in was a bitch. They didn't design these ships for first person adventure games unfortunately.

I've had to add buffer space here and there to give the player enough room to move around comfortably. And because the ship has to properly articulate for the different flashbacks I couldn't cheat and leave out a critical capstan or threading hole for the anchor ropes for instance.

At the moment I'm going through and decorating each deck. One of the key ways to identify people will be to recognize which rooms they spend most of their time in. So, for example, the carpenter's room has to look the part. This bit is pretty fun to work on.

An in-progress Maya shot of the orlop deck. It'll get a lot busier before it's done:

Orlop deck

Also, this is what OCD looks like:

Old carpenter's walk - off-center grating and passageway width varies along the ship

New carpenter's walk - more centered grating and consistent passageway width

That took ~4 hours to "fix"...
18  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: July 10, 2015, 06:39:35 am
[...] You could make marking something in the Muster Roll permanent, too, since you can't exactly erase ink. After you decide on the fate of someone and mark it in the ledger, you get some sort of feedback from the family themselves - a line of dialogue or something small like that. Your role as investigator and arbiter gains some narrative weight, and it's harder to play the game "wrong".

There's a cool idea here. This game is probably too fantasy-oriented to work exactly like this - I'm not planning on injecting any pathos for the families, or including them at all really. But the idea that the fate entries have to be "finalized" before getting feedback is interesting.

Maybe I could have a "finalize these fates" button on each page, or a global "finalize all current fates" that can be used X times. Then you can "spend" those finalizations to find out if you're correct. Players could game this a little bit into something like Mastermind, but not much. Or they could wait until the end and just hit it once and get some special reward if they're all correct.

I think this is at least better than instantly getting feedback on page 1.

okay this is fantastic, despite the fact that when i looked at the sky with white pepper noises i feel like my screen is going to explode at certain extent, everything is so nice and unique!

I promise to fix the sky :D
19  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: June 25, 2015, 06:41:04 am
80 > 60

Finished sculpting and painting faces for 60 passengers and crew. 

From a distance

There's supposed to be 80 people in the game - I'm cutting the remaining 20. If I find an easy way to add them back later in I may go for it. 60 is way too few for sailing a boat of this size (120 would be a realistic minimum), but I just don't think I can build out that much content in a reasonable time. The character models themselves aren't actually that hard, it's integrating the characters into the story, adding them in flashbacks, and making sure the player has enough information about each one to deduce their identity. Also there are some UI issues with enabling the player to efficiently sort through that many characters; even 60 will be a bitch.

Feature Randomizer

At my fastest, I was able to sculpt+paint 6 faces in one day. That rate was mostly thanks to a simple feature randomizer tool I built from blend targets. I could either select the features manually or hit a button to generate randomized facial geometry. Most combinations were useless but after a few clicks something inspirational would come up that I could tweak and paint fairly quickly.

Making heads from randomized blend target sets. Textures are hand-painted after finding something good.

Reference and Consistency

Most of the faces are just sketched from my imagination but I did use references for some. Referenced faces took a lot longer, and the detail gives them a slightly different look. Overall I wasn't able to stay very consistent with my technique. Some faces are realistically detailed and some are more painterly. Luckily, in-game this comes across as making the characters look unique (a useful thing for the mechanics) as opposed to out of place. Another benefit of outputting to 1-bit 640x360.

Russian sailor reference and hand-painted result

Two different characters. Left is off-the-cuff, right is from reference.

Accessories Etc

The next step for the characters is to model and attach all their clothes and accessories and add unique scars/tattoos/etc. That process is pretty straightforward so I may take a detour and assemble some flashbacks first. Would be nice to know if 60 people is actually enough, and how hard it is to arrange that many characters.
20  Feedback / DevLogs / Re: Return of the Obra Dinn [Playable Build] on: June 25, 2015, 02:25:00 am
Did you find a solution to the magically open doors?
You could do something such as when the flashback ends, the player is in the position where he was in the flashback. That way he can be behind doors that were closed, and can undo a latch or just open them from the inside.

I don't have a good solution for this yet. Restoring the player's last flashback position is potentially way more complicated than it looks in the dev build. The configuration of the ship (cargo, crew, broken stuff) will change a lot more between flashbacks so some places you could stand in a flashback won't translate to the current time.

More than that though, I kinda like the idea that viewing a flashback is completely passive and you're always reset afterwards. I'd really like a better mechanic than "magically opening doors" but part of the problem is that I only plan on having 3 locked door gates in the whole game. So either I integrate a proper mechanic and use it a lot more or I punt on the whole thing and just swing the doors open when needed.
Pages: [1] 2 3 ... 17
Theme orange-lt created by panic