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

Login with username, password and session length

 
Advanced search

1340999 Posts in 61268 Topics- by 52792 Members - Latest Member: raisen

June 22, 2018, 04:41:35 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsReturn of the Obra Dinn [GDC 2016 Demo Build]
Pages: 1 ... 38 39 [40] 41
Print
Author Topic: Return of the Obra Dinn [GDC 2016 Demo Build]  (Read 542168 times)
Octopus Tophat
Level 1
*



View Profile
« Reply #780 on: October 22, 2017, 08:17:41 PM »

I would like to see what it looks like when rendered at higher resolutions and downscaled to 640x360. I feel like that would be a better start to tackling the flickering problem.
Logged

Roguelike platformer: RogueWorld
Ninety
Level 1
*


turnip boy


View Profile
« Reply #781 on: October 22, 2017, 10:22:18 PM »

It makes much more sense to me to render the lines at your output resolution and then add a pass to the line shader to fatten them up - that'll preserve the straight lines and the angles of the geometry, and it won't produce these strange artefacts when the camera moves.

Agreed. I can definitely see the problem with the original, but I'm not a fan of the new style. Like someone else said, it looks more like a cheap filter than a deliberately retro aesthetic. To be perfectly honest, I didn't see a problem with simply rendering to a higher resolution. But I may be in the minority on that one, heh.
Logged

JobLeonard
Level 10
*****



View Profile
« Reply #782 on: October 23, 2017, 12:46:15 AM »

I would like to see what it looks like when rendered at higher resolutions and downscaled to 640x360. I feel like that would be a better start to tackling the flickering problem.
That would just give you grayscale, defeating the point of a dithering aesthetic.
Logged
Octopus Tophat
Level 1
*



View Profile
« Reply #783 on: October 23, 2017, 02:04:43 PM »

I would like to see what it looks like when rendered at higher resolutions and downscaled to 640x360. I feel like that would be a better start to tackling the flickering problem.
That would just give you grayscale, defeating the point of a dithering aesthetic.

But I think to get rid of the flickering, you have to start with a higher input resolution. When you're upscaling, you are always dealing with the same data, and just changing it around. So even though there's more pixels, when you move the camera slightly, you have chunks of pixels jumping around instead of individual pixels.

I would try rendering at 1280x720, which gives more detail and finer wireframe, but downscale that back down to 630x360 with some kind of filter to smooth things out. It would be all up to the filter at that point, but I just think that's the only way to fix this. That or some kind of frame-persistence.
Logged

Roguelike platformer: RogueWorld
JobLeonard
Level 10
*****



View Profile
« Reply #784 on: October 24, 2017, 08:06:14 AM »

Ah, I follow your reasoning better now.

> When you're upscaling, you are always dealing with the same data, and just changing it around.

This especially seems an interesting point.

Of course, we have currently only been looking at the dithered output, but a few pages back the whole "graphical pipeline" was laid out. Perhaps flicker prevention has to happen earlier?

For example, an alternate idea would be to render the pre-dithered textures at low-res, upscale that, then pass some dithering algorithm to it.

EDIT: Or alternatively, "staged" dithering, where the high-res input is first posterized (or something similar) to reduce detail, before being put through the 1-bit dithering process.
Logged
Zaphos
Level 5
*****



View Profile WWW
« Reply #785 on: October 24, 2017, 09:27:16 AM »

The new effect looks bad to me, too ...

The pixel upscaling thing comes through badly and smells like old emulators, and the way the diagonal dither lines kind of swim reminds me of the 'shower door effect' -- the effect where a non-photorealistic rendering filter ends up just looking like you're looking at the scene through some dirty glass ... it just makes me wish I could turn it off.

Also the old pixel aesthetic felt like it was reaching back to old computer graphics in a charming way, and this new look loses that and doesn't really get close enough to woodcut or anything else to actually make sense.  So it loses the good nostalgia thing and replaces it with ... ambiguous confusion.
Logged

dukope
Level 3
***


View Profile WWW
« Reply #786 on: November 23, 2017, 05:53:02 AM »

Fullscreen, Round 3

Thanks everybody for all the suggestions. I tried everything, literally, and concluded that the best way to maintain the game's style and fix the fullscreen discomfort was to stabilize the swimming dither and subdue the flickering dots. I got there in the end, with a few compromises. This is the 3rd full devlog post I've written on this. For each previous version I'd get an idea or find something else to try while checking over it. At this point, don't even care.


Dithering Process

First, a quick explanation. Obra Dinn renders everything internally in 8-bit grayscale then converts the final output to 1-bit in a post-processing pass. The conversion from 8-bit to 1-bit is handled by comparing each source image's pixel to the corresponding dot in a tiling dither pattern. If the image pixel value is greater than the dither pattern dot value, the output bit is set to 1. Otherwise it's 0. The output gets reduced to 1-bit and the viewer's eye will merge the pixels back together to approximate more bits.


Thresholding a source image by a dither pattern


The two components of this process are the source image and the dither pattern. Obra Dinn uses two distinct patterns for different cases: an 8x8 bayer matrix for a smoother range of shades, and a 128x128 blue noise field for a less ordered output.


bayer / blue noise


In-engine result without wireframe lines. Bayer on the sphere, blue noise everywhere else.


Hold Still Please

The basic dithering process works great for static images and much less great for moving or animated images. When the source image changes from frame-to-frame the static dither pattern and low resolution output become a major problem. What should be solid shapes and shades now read as a wiggling mess of pixels.


Moving the sphere


These days, dithering is mostly used when the source image is either static or the output has a high resolution. The first thought when seeing this low-res swimming dither effect is not "yeah that's how dither works" but "what is this warping shaking effect and how can I turn it off."


Exhibit A. Reduced contrast for your comfort.


Try to focus on something here when it moves and behold the crinkled heart of Obra Dinn's fullscreen problems. There are ways to fix this that mostly boiling down to "this style doesn't work, change it." I went pretty far down that path, experimenting with different styles, before swinging back and wondering if maybe I shouldn't let these bullshit little pixels push me around.


Stabilizing The Dither

To give your eyes the best chance at recombining everything, dithering works best when the dither pattern dots have a 1:1 correlation with the output pixels. But, correlating only with the output means that as a scene post effect there's no connection between the geometry being rendered and the pattern that thresholds it. Each frame, moving scene elements threshold against different values. What I want instead is for the dither pattern to be "pinned" to the geometry and to appear stable as it moves with the rest of the scene.

The core of this is a mapping problem. As told by the length of this post, there's a conflict between the ideal dither pattern mapping (1:1 with the screen) and the ideal scene mapping (x:1 with the geometry) so get ready for some compromises. Most of my work was focused on mapping the input dither pattern into different spaces that better correlate the pattern with the scene geometry. Everything here is done at the pre-thresholding stage.


Texel Space

My first try was to map the dither pattern in texel space. This is equivalent to dithering the object textures during scene rendering instead of in a post-processing pass on the 8-bit output. I didn't expect this to work well but wanted to see what a perfectly scene-matched mapping looked like anyways.


Dither pattern in texel space


Ok well, expectations solidly met. The objects are all mapped differently so their pattern scales don't match. Those could be unified. The real problem is the aliasing. Any resampling from one space to another like this will result in aliasing, and dither patterns can't be easily mipped or filtered like traditional textures. Still, to carry it through:


Applied to the moving scene


This isn't a total loss - the pattern is nicely pinned to geometry. The aliasing produces its own swimming effect and unifying or scaling the mappings won't help with that. Texels change size with distance from the camera so there will always be dither pattern pixels that alias badly when resampled to the screen.


Motion Warping

If I want the dither pattern to track the moving geometry beneath it, why not just warp the pattern using the change in position of each rendered pixel in the scene? Indeed why not. This is a bit like a motion blur, where each pixel tracks its movement from the previous frame. In this case, I update the dither texture to keep its pattern moving with the scene. If a scene pixel was not represented in the previous frame, the dither pattern is reloaded there. This technique is made much simpler by the game's static-ness - I only need to worry about the movement of the camera, not individual objects.


Warping the dither pattern to maintain frame-to-frame coherence with the scene


This was a pretty quick & dirty try but a few things are clear. First, it kinda works. Second, a dither pattern needs a neighborhood - it can't be individual pixels. If you consider each pixel individually, as this method does, then you'll get breaks and discontinuities in the pattern which are obvious. I shifted the camera in this test scene to highlight those on the chest here. Viewing the warped dither pattern itself makes this a little easier to see.


Thresholding solid gray with the warping dither pattern


These discontinuities are down to the differing pixel depths and thresholds that I chose. I reasoned an elaborate fix based on tracking regions, averaging their depth and shifting all dither pattern dots in that region by the same amount. A discontinuity along a region boundary could be hidden by sharp lighting changes or a wireframe line. This would've been enabled by the game's existing setup of colored regions for the wireframe generation. When I sat down to implement all that, the depth term dropped out of the first equation I came up with and gave me a much simpler alternative:


Screen-mapping With Offset


When putting together the equations for the warping dither, a very simple transform fell out:
Code:
DitherOffset = ScreenSize * CameraRotation / CameraFov

Shifting the screen-mapped dither pattern based on camera rotation


Basically, this expresses that I want the screen-mapped dither pattern to shift by exactly one screen when the camera rotates through one field of view. That maintains a 1:1 mapping with the screen while also considering a simplified transform of the scene geometry in view. This really only matches the movement at the center of the screen but bless this fucked up world because it's nearly good enough.


Offsetting the dither pattern to track one screen per camera fov rotation


Note how the dithered pixels on the chair appear to mostly move with the geometry. Likewise for the sphere. Planes more perpendicular to the view don't match very well; the floor is still a mess.

So while not being perfect, simply shifting the screen-mapped dither keeps the overall pattern and scene movement close enough that the eyes can better track them together. I was pretty happy with this. While cleaning up the code and committing everything, maybe writing a devlog post or two, the idea of a perfectly-pinned dither kept nagging at me:


World Space - Cube Mapping

The experiments so far suggest that any correlation between the dither pattern and scene geometry would have to ignore depth information from the scene. What this means practically is that the dither can be pinned to the geometry during camera rotation but not during camera translation. This isn't such a bad thing for Obra Dinn considering the slow pace of the game and the observational role of the player. You're normally walking around, stopping, and looking at things. When walking, so many things are changing onscreen that the swimming dither isn't as obvious.

With that in mind, my next attempt was mapping the dither pattern to the geometry indirectly by pre-rendering it onto the sides of a cube centered around the camera. The cube translates with the camera but stays oriented to the world. In the mix: little bit of screen, little bit of scene.


Dither pattern mapped to a cube centered around the camera


Camera's view looking up into a corner. Mapping scaled up for clarity.


The cube's mapping works well when looking directly into the sides, and not so well when aimed into a corner. Still, the dither pattern stays perfectly fixed in 3D space as the camera rotates. Even rough, the result is promising.


Thresholding scene with the cube-mapped dither pattern


Now we're talking. Being a post-processing pass makes this more general than texel-space mapping, which is good. The problem is now down to the particular cube mapping. An ideal mapping would have one texel on the cube always resolve to exactly one pixel on the screen, regardless of the camera rotation. That's not possible with a cube...


World Space - Sphere Mapping

...but I got pretty close with a sphere.


Mapping the dither pattern onto the inside of a sphere


Finding this particular spherical mapping took some time. There's no way to perfectly tile a square texture onto a sphere. It would've been possible to redefine the dither matrices in terms of a hexagon grid or something else that does tile on a sphere. Possible maybe, I didn't try. Instead, I just hacked on the square tiling until this carefully tweaked "rings" mapping of the original dither pattern gave good results.


Applied to the scene


Better than the cube. Still lots of aliasing. The spherically-mapped dot size is very similar to the screen pixel size - off just enough to cause moire patterns. I could feel the closeness, and a very simple fix for this kind of aliasing is to supersample: apply the dither thresholding at a higher resolution and downsample.


Spherically-mapped dither pattern at 2x and downsampled to 1x


Thresholding at 2x, then downsampling to 1x


This is the best I got. There are a few compromises:

    1 The dither pattern dots get larger and less effective at the edges of the screen
    2 The pattern isn't aligned up-down-left-right for most camera rotations
    3 The output is no longer 1-bit due to the final box-downsample

But the upside is pretty lofty:

    1 The dithering is perfectly pinned for all camera rotations. This feels slightly uncanny in-game.
    2 Discomfort from swimming dither is totally gone, even at fullscreen
    3 The pixellated style of the game is preserved

It's possible eliminate compromise #3 by reducing the output back to 1-bit with a simple 50% threshold. The result is still better than without supersampling (the triple comparison directly below is thresholded).


Side by side, by side


In the game's default palette

Wrapup

It feels a little weird to put 100 hours into something that won't be noticed by its absence. Exactly no one will think, "man this dithering is stable as shit. total magic going on here." I don't want to give people problems they didn't know they should have though so it was worth fixing.

The screenspace mapping with offset works best at 1x and the sphere mapping works best at 2x. All scene rendering is at 800x450 now (up from 640x360), which helps legibility without sacrificing the low-res style. The final game will have two display modes: 

DIGITAL
Border-boxed, screenspace offset dither, 1-bit output

ANALOG
Fullscreen, sphere-mapped dither, softened output
Logged

Schrompf
Level 5
*****

Currently off any meaningful development


View Profile WWW
« Reply #787 on: November 23, 2017, 06:23:37 AM »

Well, if you wouldn't have invested these 100h, everybody would notice. Good work, and thanks for documenting your path!
Logged

Let's Splatter it and then see if it still moves.
Cranktrain
Level 4
****


making gams


View Profile WWW
« Reply #788 on: November 23, 2017, 06:32:51 AM »

The discomfort from the swimming pixels is pretty much entirely gone. 100 hours it might have taken, but I think it's seriously been worth it. Well done!
Logged

bombjack
Level 3
***


That's me :)


View Profile
« Reply #789 on: November 23, 2017, 06:37:08 AM »

It's really amazing and very interesting as usual.
Logged

Bennett
Jinky Jonky and the Spell of the Advergamez 3
Level 10
*



View Profile
« Reply #790 on: November 23, 2017, 06:39:26 AM »

So much better. Well done!
Logged
gprosser
Level 0
**



View Profile
« Reply #791 on: November 23, 2017, 07:11:29 AM »

This is impressive work. It's almost a shame there aren't more games using this visual style that could benefit from your technique!

You should post about this somewhere else, so that it's easier to find.
Logged

Citsua
Level 0
**


View Profile
« Reply #792 on: November 23, 2017, 08:12:56 AM »

This is incredible. I can't imagine how much work you must have put into this. Regardless, it was worth it.
Logged
Kyace
Level 0
**


View Profile WWW
« Reply #793 on: November 23, 2017, 08:46:37 AM »

Well done on the solution and nice write up on the post.
Logged
Thaumaturge
Level 7
**



View Profile WWW
« Reply #794 on: November 23, 2017, 10:11:51 AM »

That's a really impressive effect--well done! I'm sorry to read that it took so much time and effort to achieve, but hopefully it proves worth it!

The journey to it described in this blog-post was interesting to read--thank you for sharing it. ^_^
Logged


Traversal, exploration, puzzles, and combat in a heroic-fantasy setting
Website ~ Twitter
marcgfx
Level 6
*


if you don't comment, who will?


View Profile WWW
« Reply #795 on: November 23, 2017, 10:36:35 AM »

It looks a lot better than before. You created a new problem and solved it, one of the things I love about gamedev. You create a problem that has never existed before and must find a solution because of your own stubbornness.
I do notice about the new solution, that the outlines are now noticeably wobbly compared to the rest. What I personally would like is clean aliased lines and it might work well with the new pixel style.
Logged

@falkenbrew
tobru
Level 0
*


View Profile
« Reply #796 on: November 23, 2017, 11:45:45 AM »

Fantastic, such a big improvement.

I have to say motion warping stands out to me as by far the best one of the examples. I wonder if you could get rid of the stretching artefacts by lightly dithering the depth map itself before applying the motion warp, so you get a little bit of randomness into the seams that the stretching appears to originate from.

The almost-but-not-quite 1 bit one seems unfortunate, I'd suggest doing that final thresholding even if it brings back a little bit of the aliasing.
Logged
agersant
Level 4
****



View Profile
« Reply #797 on: November 23, 2017, 12:30:52 PM »

Great writeup, thanks for sharing!
Logged
JobLeonard
Level 10
*****



View Profile
« Reply #798 on: November 23, 2017, 12:38:35 PM »

It's fantastic.

I guess the non-square pixels in the final output are also an inevitable compromise? It kind of breaks the suspension of disbelief.
Logged
Tizzdogg
TIGBaby
*


View Profile
« Reply #799 on: November 23, 2017, 01:05:02 PM »

I wonder if it would work to map the dither pattern in texture space, like you tried, but instead use a mipmapped dither texture with different resolutions of dithering depending on the mip level. Kind of like this cross hatching paper:
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.28.8017&rep=rep1&type=pdf
Logged
Pages: 1 ... 38 39 [40] 41
Print
Jump to:  

Theme orange-lt created by panic