#Voxel game trouble

1 messages · Page 1 of 1 (latest)

rapid grail
#

I am making a voxel-based game. I want to optimize memory usage.
I generate the world in chunks, like in Minecraft, and I have voxels, which have vertices and UVs and stuff. I decided to start optimization from vertices, positions of which are stored in Vector3s, like this:

public static readonly Vector3[] voxelVerts = new Vector3[8]
{
    new Vector3(0, 0, 0),
    new Vector3(1, 0, 0),
    new Vector3(1, 1, 0),
    new Vector3(0, 1, 0),
    new Vector3(0, 0, 1),
    new Vector3(1, 0, 1),
    new Vector3(1, 1, 1),
    new Vector3(0, 1, 1)
};

The one thing that is obvious to me is that Vector3's store 3 floats, which is a lot of bits. The chunks are not bigger than 16 blocks wide, even though I planned to increase that to 32 someday if possible. Anyway, 32 can be stored nicely in 6 bits (18 bits for a coordinate), which is small and just what I need. After asking around and reading forums, I came to a conclusion that an int can store 4 bytes which is 32 bits. This might sound like a silly question, but can I store those 18 bits in an int and then read them? From what I read, I can't store bit info directly without ints or other memory-consuming methods. So, is it possible? If not, is there another way? Someone did it, so there is definiely a way.

random knollBOT
rapid grail
#

If I'll know how to optimize that, I think i'll know how to optimize the rest

#

I understand the concept, but I have no coding literacy at all

whole lintel
#

If you know the accurate sizing of chunks, can you not use ints?

For storing data for chunk data, you can store the coordinates but there are other aspects you could take for storing chunk info.

What about storing per chunk level? And then from there using ints to know what type of data is within each position in the chunk. This significantly cuts down the data you are using.

Bits can still be stored up to 8 and you can read/write to that, but its also limited.

Compression takes time and also depends on the source you are making. If you want really good compression your data has to be laid out for it too. But as the above shows there are alternatives to floats

rapid grail
#

Yeah, even using ints would do, but still, how would I write that? Is it an array of ints or something? There is certainly no Vector3 that uses only ints.

rapid grail
#

Oh darn

#

I'll have to try that

restive crypt
#

That's not any more space efficient

rapid grail
#

Oh

restive crypt
#

Both int and float are 4 bytes (32 bits)

whole lintel
#

im pretty sure ints are faster though

rapid grail
whole lintel
#

and even then, using coordinates for all blocks (i presume) in a chunk feels wasteful

vast gazelle
#

maybe the first question is why are you storing the vertices of a cube you know will be the same size each tine

whole lintel
#

better to use a single int as an index

restive crypt
whole lintel
#

and then another for the type of block

rapid grail
#

Like I know anything

whole lintel
#

this vs three floats/ints for coords cuts down a lot

restive crypt
#

A float is 4 bytes. Which means a Vector3 is 12 bytes, because it's 3 floats
An int is also 4 bytes. Which means Vector3Int is also 12 bytes, because it's 3 ints

They both take up the exact same amount of memory

whole lintel
#

before even thinking of memory storage in the first place i would recommend just making a baseline system for your chunks

#

before any of this

#

given you only posted some vector3s i have a feeling you havent made big lengths in this

#

And to consider this before you have an example to work on is taking steps on the wrong stepping stones

rapid grail
vast gazelle
#

can't you derive the mesh verts from a single 3d coordinate?

whole lintel
#

so at that point you know:

  • The size
  • the index of the position in the chunk/level
  • the type of block/mesh
rapid grail
whole lintel
#

you can use all this to generate at the coordinate with the mesh

#

if you think of (0,0) as index 0, then (1,0) as index 1 and… (10,10) as index 100

#

But ye i would generate your models and data first before considering compressing down voxel data

rapid grail
#

i halfway followed a "Minecraft in Unity" tutorial from b3agz with a bit here and there from Sebastian Lague so i have a basic voxel terrain generator with 3 interlinked scripts
i just thought that the array was the most important info so i posted it

whole lintel
#

Could also look into an octree but im not sure what that would do for compression per say

rapid grail
#

sorry about being such a nuisance

whole lintel
#

Not at all, you made a post to ask 🙏

restive crypt
#

There is absolutely no need to apologise for not knowing things. No one knew any of this at first, we had to learn too. Now it is your turn

rapid grail
rapid grail
#

there are also a Chunk and a World Generator script, which pull stuff from eachother

restive crypt
#

I can give you some tips on performance improvements here

#
public static readonly int chunkWidth = 16, chunkHeight = 128, worldSizeInChunks = 128;
public static readonly int viewDistanceInChunks = 6;
public static readonly int TextureAtlasSizeInBlocks = 4;

These are static readonly, which means their values are evaluated at runtime. They can be made const, to evaluate at compile time

public static int worldSizeInVoxels
{
    get { return worldSizeInChunks * chunkWidth; }
}

public static float NormalizedBlockTextureSize
{
    get { return 1f / (float)TextureAtlasSizeInBlocks; }
}

Which means these two don't need to be properties - they can also be const. The compiler will inline the arithmetic and remove the need to compute them at runtime as well

#

(The cast to float is also redundant, since you have the literal 1f, the right-hand operand will be implicitly converted)

rapid grail
#

Thank you

rapid grail
#

i can store ints instead of vector3s to use as indexes

#

but then i have to know what offset should i add using the index

#

which makes me store another table of vector3s just somewhere else

#

not sure if that would optimize anything

whole lintel
#

you can test this out spitting out every value from 0 to max size (using for loops)

rapid grail
#

how would i do that with x y and z?

whole lintel
#

replace y with z

#

y is a height value

#

I would handle that elsewhere

#

there is a form for 3d indexes too

#

but i forgot the ordering

#

nevermind google told me

#

return x + y * Nx + z * Nx * Ny;

rapid grail
#

thanks a bunch, i'll try this

whole lintel
#

personally I'd do this over storing coordinates for each and every block, as an index significantly cuts down the number of values you need to store. Just converting is required.
Take note though, if you have items, drops, entities etc, these will of course not work as they'll be floating point and will need something a bit more specific (prolly coordinate storing)

#

But I'd also note that writing to a file vs storing for runtime use is different too

rapid grail
#

i was planning on doing this optimization for voxels only, i thought of handling everything else differently

#

uh
which of the xSizes do i replace with zSize?

whole lintel
#

Say you have 1000 coordinates and you're writing those to a file, you'll probably only need to load in once (can vary a lotbut for simplicity lets say so)
But at runtime, if you're just walking around its better to have the coordinates on hand. Unless you're doing millions of chunks in one game session your memory use won't be that bad.

At one point you'll probably load/unload chunks out of memory really saving time & memory. Minecraft most likely does some real crazy stuff to handle everything, but ram use will be required for anything loaded in.

whole lintel
#

y here could be replaced with z

#

and the same with ySize for the z equiv

#

Ignore that, you'd have to reshuffle stuff to y and z are switched: return x + z * xSize + y * xSize * zSize;

#

realised what you were asking lol

rapid grail
#

i'm lowkey confused about Size ints

#

what should they even be
if they are 1, i get repeating results

whole lintel
#

they're the size of your chunks

#

like minecraft is 16 x 16 x 384 per chunk

#

the link explains it pretty well how they work too

rapid grail
#

the formulas work

whole lintel
#

Awesome!

rapid grail
#

so, i can just store the indexes in the array and reconvert them into coordinates when needed, right?

whole lintel
#

Because you have both of them too, if you do not need one of the coordinates - say you just need to calculate for x and z - you can use the 2d version

whole lintel
#

For chunks that are spawned, I'd say keep their coordinates stored though

#

but this can become part of a debate i suppose based on who is developing it, how things are loaded/unloaded etc

#

Yasa if he's around may potentially have better ideas of this

rapid grail
#

i can't use int3s when adding Vector3s
i just make the conversion from "index to int3" to "index to Vector3"?
would this whole thing still save memory?

#

even after adding a converter, everything works as it should, but i'm yet to tell the performance difference

restive crypt
#

But you are trading space for speed in that case. because instead of having the 3 ints cached, you are computing them dynamically

#

This will always be the case. you can trade speed efficiency for space efficiency. you can't really have both

whole lintel
#

I would determine when you need coordinates and when you dont.
Generally however, conversions will come more common

#

I would have examples myself but i can't find my own examples (i dont think my personal hard drive is in)

restive crypt
#

(Though int arithmetic is blazingly fast on modern CPUs and the difference in performance is going to be negligible, virtually zero)

rapid grail
#

so, did i really save memory? will that, in theory, give me a chunk loading performance boost?

whole lintel
#

I'd say:

  • Loaded in chunks, store vectors or coordinates, and you can convert to indexes when needed.
  • Loading in chunks, using indexes could be nice if you know how to set them up, but vectors will work. Take note that for each coordinate you store in memory vs a single int its 3 vs 1 per piece of data that needs vectors.
  • Getting tile/block/chunk neighbours, using indexes would be very nice for this as you already have the index especially if you're using 2D arrays. (advanced voxel/terrain gens will use more sophisticated systems than just arrays)

What yasa says is true though, cpus are optimised for a lot of arithmetic and a lot of it will be negligable until you're dealing with a lot.

I will say too, that dont worry about memory until you need to. I do feel creating the chunks first, seeing where issues come in and then worrying where to start breaking down issues is better than to fix something you may change in the future

#

I'd also think about the fact that loading in a lot of individual meshes will be part of the memory you're worried about

#

Rather than just coordinates

#

A lot of the memory you're concerned about will come from rendering, and knowing how to turn off meshes, or parts of meshes you can't see will provide huuuge gains

rapid grail
restive crypt
#

I would argue that is almost certainly due to geometry, rather than the code

rapid grail
#

most probably

#

i'll have to force myself to understand greedy meshing 🥲

restive crypt
#

Mesh optimisation is a whole thing. If you have a bunch of adjascent coplanar faces, you should merge them into a single face. Also cull occluded faces. Also backface culling

rapid grail
#

i already do backface culling and not rendering faces you can't see (inside voxels)

#

i will have to implement so called "frustum culling"

restive crypt
#

that is another one yep

#

But combining coplanar faces is also a big one

#

For example, when generating voxels you'll likely have topology like this. 3 separate faces

#

You can merge them into one

rapid grail
#

ain't that greedy meshing

restive crypt
#

That cuts down on the number of draw calls, which REALLY matters in a voxel game

rapid grail
#

yeah that's what i'll have the most pain with

whole lintel
#

After you have that in, defo look into read/writing from files (and combining some of this with loading/unloading chunks)

#

it's not like you can just load everything if your world is a billion units long

#

Maybe your game is limited

restive crypt
whole lintel
#

some voxel stuff

rapid grail
restive crypt
whole lintel
#

you cant load/unload if you dont have stuff to load and unload aha

rapid grail
#

i might have to go full Vercidium on this project

#

i am not qualified for that but life's purpose is to learn

restive crypt
whole lintel
#

you cant do something without knowing how to first

restive crypt
#

And someone on reddit shared a basic implementation, though it doesn't seem too efficient
(This is where I found the other link)

rapid grail
#

oh god
the letters in math are scary

whole lintel
#

he should meet yilly

#

that man works in letters and math

#

he probably is the most knowledgeable of maths and equations in the server tbh

restive crypt
#

Math is beautiful smh

whole lintel
#

and hes a nerd for it

restive crypt
#

the math on that page is not hard to understand though

#

x y w h are self evident, x/y coord and width/height

#

Q is the set of quads you're going through

midnight scrollBOT
restive crypt
#

that's just math speak for Vector2

rapid grail
restive crypt
#

Pretty much!

#

That's what the { } denotes in math. It represents a set

rapid grail
#

which i fill by checking adjacent meshes

midnight scrollBOT
restive crypt
#

that's just int[] a = [1, 2, 3, 4];

rapid grail
#

i think i might just be capable enough to continue

#

thanks a lot, again

whole lintel
#

can always make another thread if you need help with more stuff tbf

#

(just yk google first of course)

restive crypt
#

And we always have #922561515926732850 if you want to dive into the math behind stuff

rapid grail
#

i might have just struck gold

restive crypt
#

Nice!

rapid grail
#

so, i just started reading this code

#

to break it down

#

i don't understand what is AppendQuad()

#

it seems to be a custom method, because i can't find anything about it

#

yeah, it is

#

traced it

#

it's beyond my comprehension

#

i have absolutely no idea what is this and how it works

rapid grail
#

Voxel game trouble

vast gazelle
#

What's the current question

rapid grail
#

from what i've seen, to do greedy meshing i need to have neighboring chunks
so i decided to build a world generator

#

but here's the catch
i decided to make cubic chunks

#

of course, that hasn't gone well

#

for a start, i decided to use this as a base:

#

and from there i modified the code

#

here i uploaded the main scripts

#

and this is the error i get

#

no idea why would this part of code give me errors

foreach (ChunkCoord coord in previouslyActiveChunks)
    chunks[coord.x, coord.y, coord.z].isActive = false;
#

this is all a big spaghetti mess

#

i know i'm asking for a lot

#

but i'm not asking to be spoonfed

#

i need a hint at what i missed

#

and possibly at what i could improve

vast gazelle
#

debug log stuff

#

or use the debugger

#

you understand the error?

rapid grail
#

i can only guess
but i it's trying to access a chunk on any coordinate and it never gets any

#

should i look into how the list is being filled or something

vast gazelle
#

okay how could you check if this is right

#

sure try that out

rapid grail
#

okay, debug.log told me the list is empty

#

for some reason, activeChunks.Count fills a lot, like over 6400

#

i get roughly 16 fps

#

and no chunks are seen

whole lintel
#

Are the meshes generating in the right way

rapid grail
#

i have a doctor's appointment so i'll check that soon

rapid grail
#

okay, something is definitely wrong

#

i reduced the world size, and instead of 2chunks by 2chunks by 2chunks it spawns only one chunk, at coordinates (0, 0, -1)

#

it should never do that

#

okay it turns out i messed up

#

i'm back at the previous issue

#

huh, that's all it creates in a 6x6x6 world

#

also yeah, there is no mesh

#

could it be because of Y checks

#

it's trying to get coordinates of chunks that are no longer in view distance