Around The World, Part 28: Scaling up

1 Comment

Even though the in-game world is fairly small compared to Earth, I want it to feel big. Part of that is being able to see things that are far away.

For context: by now, I have settled on a third-person “chase camera” rather than a top-down view. It feels so much more immersive, and makes the gameplay (in particular, matching up maps to your surroundings) more interesting as well.

Remember that my world is now a flat plane that wraps around, rather than an actual sphere. However, it would be great if we could still make it look like a sphere, so that distant lands would gradually appear over the horizon. Mountains would be visible from a greater distance, so that the terrain actually matters beyond just the shape of the coastline.

Until now, I’ve been using a view distance of 5 km. That’s still a lot compared to other open-world games! But a landmass at 5 km does not look very far away, because it’s so big. For gameplay purposes, it would be good if view distance could be increased to about 25 km. Otherwise, the part of the world that the player can see is too small compared to the maps, and they won’t have anything to orient themselves by.

How does it work out if we just look at the geometry? My planar planet is 512 km wide, so let’s pretent that’s the circumference of a spherical planet. Its radius would then be 83 km. Terrain generation is scaled down relative to the real world, so that the tallest mountain is about 2500 m high. Assuming a ship of up to 25 m tall, the mountain’s peak will be visible over the horizon from 22 km away. That’s pretty close to the 25 km I figured out above, so I decided to sail with these numbers for now: let’s fake a planet radius of 83 km.

We now have some problems to solve.

World wrapping

To actually be able to sail around the world, you need to be able to sail off the west edge and reappear at the east edge, and vice versa. That’s easy enough to implement with some modular arithmetic applied to the ship’s coordinates. However, any nearby objects and terrain must also exhibit the same wrapping: if you are near the west edge of the world, you should be able to see across it, so anything near the east edge would have to be moved near you as well. This, too, can be implemented with some modular arithmetic.

However, it would have to be applied to every object in the game individually. That’s tedious at best, and bad for performance at worst, so I came up with the solution of parenting every object to a 2×2 km chunk. Only the chunks are moving around; their children just come along for the ride. The node hierarchy looks like this:

Screenshot of the Godot editor, showing the chunk node hierarchy

Here’s an example of how a nearby NPC ship would become visible, even though it’s all the way on the other side of the map in world coordinates:

Diagram showing a chunk being moved from the east edge of the world to just beyond the west edge

The chunks themselves take care to move their children to adjacent chunks whenever they cross a chunk boundary, so the code of individual objects doesn’t need to care about the chunking at all.

Floating origin

As before with the spherical world, we’re going to run into floating-point precision issues. Godot by default uses 32-bit floats for object and vertex positions, which have only 23 bits of precision; at 512 km from the origin, the distance between two successive floating-point numbers is about 6 cm. The official recommendation in the Godot docs is not to go beyond 8 km for a 3D third-person game, and not beyond 64 km for any 3D game.

I started out hoping that this wouldn’t be an issue, but it turns out to be real: far away from the world origin, the ship started jittering weirdly, and the sails started jittering relative to the ship. A solution was needed.

Building Godot with 64-bits precision is possible, but adds a lot of overhead: now every vector and matrix in the game uses twice the memory, including the large procedurally generated terrain meshes. Would that be a problem? Not necessarily, but it turns out there’s now an easier solution.

Since we’re moving chunks around anyway, we can implement a floating origin system there as well. Just before rendering a frame, the chunk containing the player’s ship is moved to the global origin (0, 0), and all other chunks are positioned relative to it:

Same diagram as above, but with the origin moved to the chunk where the player is

This ensures that every object we care about remains close to the global origin, where floating-point precision is sufficient. As long as none of the code assumes that global coordinates (relative to Godot’s global origin) are the same as world coordinates (relative to the north-west corner of the world map), this works great: the jitter is gone.

Planet curvature

Let’s take a look at how we could implement a fake planet curvature. In particular, I want distant objects to gradually sink below the horizon.

This could be done in a vertex shader: simply compute the distance to the camera (measured on the horizontal plane), calculate how far downwards the vertex should move, and move it:

Projecting a point downwards onto the sphere

The drawback is that this shader would need to be applied to every object in the world, meaning I would have to use custom shaders everywhere and couldn’t benefit from Godot’s built-in materials anymore. Tedious! Another drawback is that the visible meshes would no longer align with their physics shapes, which becomes relevant if we want to do object picking (e.g. clicking on the peak of a distant mountain to set a course towards it).

But I had an idea I wanted to try. Now that we have chunks, maybe we could pull the chunks themselves downwards, and have all their children come along for the ride?

My first attempt was to introduce some skew. In 3D games, the position and orientation of an object is usually represented as a matrix, and mine is no different. Godot uses 3×4 matrices: a 3×3 part to represent rotation and scale, and a 3x1 part for translation. But rotation has 3 degrees of freedom, and scale has another 3, so that means that we have 3 more degrees of freedom we can play with to make the chunk align (somewhat) with the sphere. Because chunks are in the y = 0 plane and are neither rotated nor scaled, the matrix looks like this, where tx, 0, tz is the translation vector (the chunk’s position):

 1 0 0 tx
0 1 0 0
0 0 1 tz

If we want to move points within the chunk only on the vertical axis (y), depending on their position in the xz plane, we have two components rx, rz we can play with, as well as ty:

 1 0 0 tx
rx 1 rz ty
0 0 1 tz

My idea is then as follows: modify these variables rx, rz, ty in such a way that the chunk’s corners end up on the sphere. Because we have 4 corners and only 3 variables, this is impossible; instead, we do it only for the 3 corners that are closest to the camera, and let the 4th end up where it will. It’s hard to draw in 3D, but in 2D you can see the idea better:

Chunks being skewed downwards onto the sphere

Every object inside these chunks will end up skewed as well, but because the effect is slight, this shouldn’t be visible.

Here’s how it looks when I modify the curvature in real time:

At lower curvatures, it’s quite effective!

However, there’s a problem with this approach that might not be obvious at first: physics. The built-in Godot Physics engine is well known to be bad at anything except pure translation and rotation; it doesn’t even support scaling. The newly integrated Jolt engine is better in this regard, but it draws the line at skewing, and starts emitting a lot of errors if a physics shape like a cylinder has any skew introduced to it.

So, let’s try something so stupid that it couldn’t possibly work: do not skew chunks at all, but just move them on the vertical axis, like this:

Chunks being moved downwards onto the sphere

Amazingly, at small curvatures such as I’m using, this works just fine and the visual difference compared to skewing is not even noticeable. You might think it would introduce visible cracks between terrain chunks, but no: because farther chunks are moved farther downwards, the cracks are always hidden behind nearby terrain. It’s a keeper!

Terrain LODs

Now that we have the ability to make stuff gradually appear over the horizon, we need some stuff that actually will gradually appear over the horizon. With a ship of 25 m tall, even though the horizon itself is (surprisingly) only 2 km away, we can see tall mountains at a distance of 25 km. So a draw distance of 5 km is not nearly enough anymore.

It’s easy enough to increase this value in the configuration, but this increased the amount of terrain by a factor of 5² = 25, and tanked the frame rate. Some quick math shows that, at the terrain resolution of 8 m that I’m using, the 50×50 km patch of terrain around the player consists of 39 million triangles, and this is too much for my GPU.

Fortunately, we don’t need that many triangles, because on distant terrain they’re smaller than a pixel. It’s time to introduce a LOD (level-of-detail) scheme to our terrain: render nearby terrain at full detail, but reduce the number of triangles on terrain chunks farther away.

A classic problem with this are seams, or cracks, between adjacent terrain meshes of a different LOD. It’s often just a few pixels, but it’s still quite visible especially in motion. Here, I tweaked some values to make the problem more visible:

Screenshot of a crack in the terrain

There are various solutions. The most elegant is to introduce special bits of mesh to cover the cracks, which are only made visible when the two adjacent terrain chunks are currently displayed at different LODs. Due to all the different edge cases (pun wholeheartedly intended), it’s surprisingly fiddly to get this right.

Fortunately there’s a simpler method, which is to simply add a ‘skirt’ of quads that hangs down from the edge of each chunk:

Screenshot showing a skirt at the edge of a terrain chunk

The normals on the skirt are copied from the adjacent vertices, so they receive the same lighting, and it won’t look as if there’s an actual small cliff there. And with that, the crack is gone:

Screenshot of the same terrain, now without a crack

This should also cover up any cracks caused by moving chunks vertically, though I haven’t spotted any so far.

Impostors

The trees I added in the previous post make a huge difference to the believability and sense of scale of the terrain. It would be sad if they couldn’t be rendered up to a large distance.

However, as it currently stands, being near a rainforest will drop the framerate to about 40 fps; the tree meshes take 15 ms to render. Since I want this game to run well on potato hardware, it should run at around 150-200 fps on my mid-range Radeon RX 7600 card, meaning a frame budget of only 5-7 ms.

The standard trick is to not render full meshes at a large distance, but rather pre-render the mesh into a texture from various points of view, and then just render a single quad with the texture applied: an impostor.

Godot doesn’t have support for impostors built in, but adding tooling inside the editor is really easy, so I built this quick and dirty impostor baking scene:

It iterates through all the tree meshes at 8 different angles, and renders each to a suitably sized viewport through an orthogonal camera (to simulate an infinite distance). Each orientation is rendered twice: once to capture colour and alpha, once to capture normals. The textures have pretty low resolution, but that’s fine, because they’re never viewed up close anyway:

Albedo texture of a tree impostor

Normal texture of a tree impostor

These are then applied directly to the impostor quad, depending on the viewing angle:

This increased the frame rate from 40 fps to about 130 fps. There’s some more low-hanging fruit that will improve the frame rate further, so this is fast enough for the time being.

All together now

Let’s see all of the above in action while the player sails towards a remote island: the distant terrain, planet curvature, and tree impostors. I’ve edited the video for brevity, because it ran over 3 minutes; one of the biggest game design challenges will be to make sure the player has something to do during these long voyages.

The transition from impostors to real geometry is still fairly noticeable, probably because the impostors cast a smaller shadow. But it’s plenty good enough for now.

Read the whole story
jlvanderzwan
5 hours ago
reply
> one of the biggest game design challenges will be to make sure the player has something to do during these long voyages.

[flashbacks to grinding for rupees in the original Gamecube version of Wind Waker intensify]

... yes, please.

By the way, have you ever played Heaven's Vault? They have conversations during the boat rides in the Nebula, making it a good moment to have story exposition. Don't know what your ambitions are there but conversations and lore is a way to keep the player occupied without interfering with the actual act of traveling
Share this story
Delete

hero vs villain voice acting challenge

1 Comment
From: ProZD
Duration: 1:01
Views: 275,422

Read the whole story
jlvanderzwan
1 day ago
reply
Bro really released this exactly one year after a certain healthcare CEO was murdered huh?
Share this story
Delete

detective pikachu

1 Share
From: ProZD
Duration: 0:34
Views: 172,743

Read the whole story
jlvanderzwan
1 day ago
reply
Share this story
Delete

100000001 is divisible by 17 - Numberphile

1 Share
From: Numberphile
Duration: 22:53
Views: 26,118

Featuring Matt Parker. Learn for free on Brilliant (and get 20% off a premium subscription) at https://brilliant.org/numberphile (episode sponsor). Extended footnote and other links in full description below ↓↓↓

Extra clips and outtakes from this video on our Patreon - https://www.patreon.com/posts/145064106

Matt Parker books - https://mathsgear.co.uk/collections/books
And Matt's books on Amazon - https://amzn.to/4rK4j3C
Penguin UK: https://www.penguin.co.uk/authors/77633/matt-parker
Penguin Random House US: https://www.penguinrandomhouse.com/authors/2198716/matt-parker/

Matt Parker - https://standupmaths.com/
Matt's YouTube channel (standupmaths) - https://www.youtube.com/user/standupmaths

More Matt on Numberphile - https://www.youtube.com/playlist?list=PLt5AfwLFPxWKuRpivZd_ivR2EvEzKrDUu

FOOTNOTE TO THIS VIDEO
* I contacted Wilfrid Keller after filming about his retired website, and he replied with the following...
"I am delighted by your interest in the topic of 'Prime factors of generalized Fermat numbers Fm(10) and complete factoring status'. The (constantly updated) webpage with exactly this title is still available at http://www.prothsearch.com/GFN10.html (only the Internet domain has changed to http://www.prothsearch.com/ about nine years ago). Some remarkable results have been achieved in the meantime. The largest prime factor currently known of a number Fm(10) has 4666663 digits (you might also have a look at https://t5k.org/top20/page.php?id=10 ), and large prime factors for m = 14 or 15 are likewise exceptional. May I point out that similar "factoring status" pages are also maintained for bases a = 3, 5, 6, 7, 11, 12 at http://www.prothsearch.com/ "

Patreon: http://www.patreon.com/numberphile

Numberphile is supported by Jane Street. Learn more about them (and exciting career opportunities) at: https://bit.ly/numberphile-janestreet

We are also grateful for support from the Ben Delo Foundation - https://delo.org/

NUMBERPHILE
Website: http://www.numberphile.com/

Video by Brady Haran and Pete McPartlan

Numberphile T-Shirts and Merch: https://teespring.com/stores/numberphile

Brady's videos subreddit: http://www.reddit.com/r/BradyHaran/

Brady's latest videos across all channels: http://www.bradyharanblog.com/

Sign up for (occasional) emails: http://eepurl.com/YdjL9

Read the whole story
jlvanderzwan
3 days ago
reply
Share this story
Delete

Answering Hank Green's Food Science Questions: Exploding Churro Edition

1 Comment
From: Farm to Taber
Duration: 3:06
Views: 6,871

If you saw Hank Green's video about the exploding churro, his diagnosis is correct. Also, the thing that happened to that churro has a name! Case hardening!

Case hardening is a whole thing in food science. It can make churros explode, and it's also why cheese & cured meats have to cure slowly for a long time. But for something that's such a big deal in food handling, it's weirdly hard to find info on. So we're gonna talk about it.

Also hopefully nobody in that video got hurt. I mean a steam explosion to the mouth, yikes. : /

Find me at:
Patreon: https://www.patreon.com/farmtotaber
TikTok: https://www.tiktok.com/@sarahtaber_nc
IG: https://www.instagram.com/sarahtabernc/
Bluesky: https://bsky.app/profile/sarahtaber.bsky.social
Twitter: https://x.com/SarahTaber_bww

@hankschannel @HankGreen @vlogbrothers

#churros #churro #cooking #explosion #frying #science #foodscience #knitting #knitting #fiber #fiberart #fiberartist

Read the whole story
jlvanderzwan
3 days ago
reply
Meanwhile, in the halls of dwarves on Discworld: "Ugh, this bread hasn't been case hardened enough!"
Share this story
Delete

Philosophy in a Foxhole

2 Shares
PERSON:
Read the whole story
jlvanderzwan
3 days ago
reply
Share this story
Delete
Next Page of Stories