Website powered by

Good Night Old Town

Here I wanted to share my journey of porting and remaking an old game of mine for the Meta Quest 2 hardware.
---
The text space under the images is limited, so I needed to be really creative to close my thoughts and stay within limits.
---
This is a port of an old game I originally created for my Master's thesis in 2018. I remember one evening, walking through the Old Town district in Kraków, I suddenly realized I was completely alone. For at least half an hour, I didn’t see a single living soul. The only signs of life were the muffled sounds of random TVs drifting from apartment windows. It gave me the creeps, because I had this thought: if I were to encounter someone truly dangerous in that moment, no one would hear it. No one would ever know anything had happened.
---
Link to Meta Horizon Store:
https://www.meta.com/en-gb/experiences/good-night-old-town/24878568928426688
Link to Sidequest store:
https://sidequestvr.com/app/44547

Originally I thought my work will be only 3D modeling, texturing, level setup, optimization etc. 
Most of the games on the Quest 2 have really janky, pixelated and color-banded compression look. 
I thought this is how it is on Android VR, but I was wrong

Originally I thought my work will be only 3D modeling, texturing, level setup, optimization etc.
Most of the games on the Quest 2 have really janky, pixelated and color-banded compression look.
I thought this is how it is on Android VR, but I was wrong

Initially I planned all environment to run on dynamic resolution minimum 2-2.4 pixel density with Asynchronous SpaceWarp
turned ON. I really wanted to fix all janky edges and pixel popping. 
But later in development I needed to downgrade

Initially I planned all environment to run on dynamic resolution minimum 2-2.4 pixel density with Asynchronous SpaceWarp
turned ON. I really wanted to fix all janky edges and pixel popping.
But later in development I needed to downgrade

Currently the game runs on Quest 2 dynamically from min 2052x2257 to max 2376x2613 pixels per eye.
Quest 3 scales from min 2394x2508 to max 2772x2904 per eye.
So it’s just a humble 1.425–1.65 pixel density.

Currently the game runs on Quest 2 dynamically from min 2052x2257 to max 2376x2613 pixels per eye.
Quest 3 scales from min 2394x2508 to max 2772x2904 per eye.
So it’s just a humble 1.425–1.65 pixel density.

But the battle with janky render wasn’t over.
The trees on Meta Quest are the real offenders.
So I learned from the old internet how to properly deal with alpha masks and MSAA.
You just need to enable Alpha to Coverage and Scale Mips for AC in UE5

But the battle with janky render wasn’t over.
The trees on Meta Quest are the real offenders.
So I learned from the old internet how to properly deal with alpha masks and MSAA.
You just need to enable Alpha to Coverage and Scale Mips for AC in UE5

The other thing is Mips generation itself.
The default option in Unreal Engine creates enormous jank on far textures, so I applied the Blur 2/3 option in Mips settings.
I also set MipBias to -0.7 so textures don’t become blurry right before my eyes

The other thing is Mips generation itself.
The default option in Unreal Engine creates enormous jank on far textures, so I applied the Blur 2/3 option in Mips settings.
I also set MipBias to -0.7 so textures don’t become blurry right before my eyes

Even more jank was happening on sharp Mips transitioning. 
So Tri-linear filter was applied to both textures and lightmaps.
There is one thing you can’t really fix without post-processing—pixel popping on bright contrasting places

Even more jank was happening on sharp Mips transitioning.
So Tri-linear filter was applied to both textures and lightmaps.
There is one thing you can’t really fix without post-processing—pixel popping on bright contrasting places

You can only mitigate it by avoiding strong contrasting elements in the scene.
When you look at those places in the distance, they become thin and start the jank dance.
In VR the perceived distance is shifted because the image is super close to your eyes

You can only mitigate it by avoiding strong contrasting elements in the scene.
When you look at those places in the distance, they become thin and start the jank dance.
In VR the perceived distance is shifted because the image is super close to your eyes

The Meta fork of UE5 provides Vulkan Subpass Tonemapper, which gives back small color grading and minor adjustments.
The whole project still runs without any post-processing and without HDR, so all effects in the game were created by hand

The Meta fork of UE5 provides Vulkan Subpass Tonemapper, which gives back small color grading and minor adjustments.
The whole project still runs without any post-processing and without HDR, so all effects in the game were created by hand

Most textures are tiled and scaled indefinitely, providing high Texel Density to please the eye.
The overall darkness helps hide texture repetition, but I also added scaled Perlin noise to serve as additional blended Color, Roughness, and Normal map

Most textures are tiled and scaled indefinitely, providing high Texel Density to please the eye.
The overall darkness helps hide texture repetition, but I also added scaled Perlin noise to serve as additional blended Color, Roughness, and Normal map

The ground geometry also has slight height variations, which gave nice results with Baked GI.
I used the Beta GPU Lightmass plugin from Epic here.
But it works only on Nvidia RTX cards, so it's not a good fit for everyone

The ground geometry also has slight height variations, which gave nice results with Baked GI.
I used the Beta GPU Lightmass plugin from Epic here.
But it works only on Nvidia RTX cards, so it's not a good fit for everyone

And here is the main performance tanker and my reason to downgrade from high supersampling.
Without Mobile-HDR, you can only have fog as Vertex Exp Height fog or making a plane with Depth Fade before Player's camera.
But both of them do not have MSAA

And here is the main performance tanker and my reason to downgrade from high supersampling.
Without Mobile-HDR, you can only have fog as Vertex Exp Height fog or making a plane with Depth Fade before Player's camera.
But both of them do not have MSAA

The Depth prepass doesn't have MSAA, and overlaying it at the end of the render pass was really unncacceptable for my efforts to eliminate jank from the render. 
So after literally month of probes and tryings I found a solution on the Epic forums

The Depth prepass doesn't have MSAA, and overlaying it at the end of the render pass was really unncacceptable for my efforts to eliminate jank from the render.
So after literally month of probes and tryings I found a solution on the Epic forums

As you can see, it may not be as realistic as the real one from UE5. 
I tried to match its look as close as possible, and it was a hard task. 
In VR you percieve things differently, so even if my settings look good in the Editor, they appear weird in VR

As you can see, it may not be as realistic as the real one from UE5.
I tried to match its look as close as possible, and it was a hard task.
In VR you percieve things differently, so even if my settings look good in the Editor, they appear weird in VR

The trick is, that there's no fog.
Each object in the game has a code in Material that tracks the Player’s distance and applies a gradient to those objects.
So it looks like a Depth Scene without actually using one. Everything happens in the Base pass.

The trick is, that there's no fog.
Each object in the game has a code in Material that tracks the Player’s distance and applies a gradient to those objects.
So it looks like a Depth Scene without actually using one. Everything happens in the Base pass.

The only thing left for this fake fog was to fix color banding in dark areas.
This time, the solution came quickly. I added Dither to the Fog mask, and the 'low-compression' look is now gone.
On low resolutions without ASW, the Dither is quite noticeable

The only thing left for this fake fog was to fix color banding in dark areas.
This time, the solution came quickly. I added Dither to the Fog mask, and the 'low-compression' look is now gone.
On low resolutions without ASW, the Dither is quite noticeable

The game was ported with ASW in mind,so the Dither Fog mask isn’t noticeable with the intended settings.
What IS noticeable is the rain—another heavy performance hit for the poor Quest 2.
The rain uses a Mesh stack up close and a cylinder for the distance

The game was ported with ASW in mind,so the Dither Fog mask isn’t noticeable with the intended settings.
What IS noticeable is the rain—another heavy performance hit for the poor Quest 2.
The rain uses a Mesh stack up close and a cylinder for the distance

The cylinder uses World Pos Offset and Depth Fade in an additive material.
The WPO adds dynamic and inconsistency.
The distant rain is subtle but visible, so the Depth Fade overlay doesn’t ruin MSAA and helps blend it with walls and buildings

The cylinder uses World Pos Offset and Depth Fade in an additive material.
The WPO adds dynamic and inconsistency.
The distant rain is subtle but visible, so the Depth Fade overlay doesn’t ruin MSAA and helps blend it with walls and buildings

The Mesh stack rain up close reacts to special 'Rain blockers', carefully placed where you don’t want to see any rain.
There’ll be heavy compression on all images, so maybe some of my text about clear, crisp rendering won’t be convincing.
Sad.

The Mesh stack rain up close reacts to special 'Rain blockers', carefully placed where you don’t want to see any rain.
There’ll be heavy compression on all images, so maybe some of my text about clear, crisp rendering won’t be convincing.
Sad.

But what about performance? Well.
The Quest's 2 GPU isn't weak and can handle it.
But CPU was begging for help, so I reduced drawcalls to the absolute minimum and allowed myself a bit of Overdraw

But what about performance? Well.
The Quest's 2 GPU isn't weak and can handle it.
But CPU was begging for help, so I reduced drawcalls to the absolute minimum and allowed myself a bit of Overdraw

The streets are rendered quite far and not culled to not break immersion.
But many small objects and wall lightings caused pixel popping mess in the distance.
So I've put additional to Fake fog effect to gently darken the AO channel of the objects

The streets are rendered quite far and not culled to not break immersion.
But many small objects and wall lightings caused pixel popping mess in the distance.
So I've put additional to Fake fog effect to gently darken the AO channel of the objects

I guess I’m almost done sharing my journey with the Quest 2.
I could write all this as a blog, but I am really broke in this moment of life, so maybe someday... ;)

I guess I’m almost done sharing my journey with the Quest 2.
I could write all this as a blog, but I am really broke in this moment of life, so maybe someday... ;)

It was really weird to suddenly lose 99% of Unreal Engine’s render features, postprocessing, and HDR.
You get gray bland slate of a render, without much of safeguards left.
Every mistake you can possibly make, Quest will expose as a visual nightmare

It was really weird to suddenly lose 99% of Unreal Engine’s render features, postprocessing, and HDR.
You get gray bland slate of a render, without much of safeguards left.
Every mistake you can possibly make, Quest will expose as a visual nightmare

I think Meta’s engineers did a nice fork.
They brought back tonemapping and ported back some optimization techniques from UE4 that were removed in UE5.
Many features are still missing—they need to be rewritten to work with Mobile Multiview

I think Meta’s engineers did a nice fork.
They brought back tonemapping and ported back some optimization techniques from UE4 that were removed in UE5.
Many features are still missing—they need to be rewritten to work with Mobile Multiview

The most important missing feature is dynamic shadows with Multiview.
But I believe there’s a good reason it doesn’t work right now. I started looking into Vulkan to better understand why some things I used to have are now not possible.
It is a wild tech!

The most important missing feature is dynamic shadows with Multiview.
But I believe there’s a good reason it doesn’t work right now. I started looking into Vulkan to better understand why some things I used to have are now not possible.
It is a wild tech!

For everybody who stayed with me to here so far, I'll say thank you,
and Good night...in old town...

For everybody who stayed with me to here so far, I'll say thank you,
and Good night...in old town...