Note: This section generally covers runtime storage, not serialization.

The simplest way to store voxels is to define a three-dimensional array of elements (be it structs or integers), where each element represents a single voxel:

int width = ...;
int height = ...;
int depth = ...;
var voxels = new VOXEL[width][height][depth];

// Set a voxel:
voxels[x][y][z] = voxel;

// Get a voxel:
var voxel = voxels[x][y][z];


However, because storing arrays within arrays often means having pointers pointing at pointers (which ain't good for various reasons we won't get into here), it is generally recommended to use a one-dimensional array with a clearly defined spatial indexing scheme.

Here's an example:

--snip--
// Spatial Indexing Scheme is: Y-height, Z-depth, X-width
// The arrays size is just all axes multiplied together:
var voxels = new VOXEL[height * depth * width];

// Our Indexing Formula is the indexing scheme in reverse,
// with the components being multiplied subsequently:
//     x + z*width + y*width*depth

// So let's define a function (here a lambda) for it:
var idx = (int x, int y, int z) => (x + z*width + y*width*depth);
// ^ NOTE: You may want to throw an error right here
//         if the coordinate components are out of bounds.

// Set a voxel:
voxels[idx(x,y,z)] = voxel;

// Get a voxel:
var voxel = voxels[idx(x,y,z)];


Now, storing voxels in a plain array like this is perfectly fine for small scenes...

However, for larger scenes, we'll have to use a data-structure that allows both loading and purging parts of our volume (called Chunks) from memory, nearly in realtime, without slowing down accessing our voxel data.

Note: These techniques can often be combined.