One way of handling seams at texture edges is to adjust the texture coords inwards, so instead of 0 use 1.0f / texture size, and instead of 1 use (texture size - 1) / texture size. Personally I'd use a cubemap texture instead.

Are your vertex coordinates still generated in the same way as the first code example you posted (but with correct values), or have you changed it since?

If you have changed it to calculate the vertex coordinates in some other way (eg. using floating point arithmetic operations, or different matrix transforms between each face), then you'll likely run into floating-point issues, where coordinates being generated aren't exactly the same, which results in overlaps/gaps between triangles.

For example, 0.6 + 0.3 is not likely to be equal to 1.2 - 0.3 when using floating point. Similarly it can be hard to get sets of vertices that are transformed by different matrices to precisely match even if they should match theoretically.

One way of handling seams at texture edges is to adjust the texture coords inwards, so instead of 0 use 1.0f / texture size, and instead of 1 use (texture size - 1) / texture size. Personally I'd use a cubemap texture instead.

The main problem with that is that the texture size varies with the mipmap level.

For a skybox, the eye-space Z of any point on the skybox will always be between 1.0 and sqrt(3) ~= 1.732 "units" (the distance of the centre of each face to the centre of the cube), so you may be able to avoid mipmapping altogether, or at least determine reasonably close bounds on which levels will be used.

But in general, if you want to avoid sampling from the border you need to use GL_CLAMP_TO_EDGE.

One way of handling seams at texture edges is to adjust the texture coords inwards, so instead of 0 use 1.0f / texture size, and instead of 1 use (texture size - 1) / texture size. Personally I'd use a cubemap texture instead.

Thanks! I replaced 1.0f with 1.0f - (1.0f / 1024.0f), and 0.0f with 1.0f / 1024.0f and now the seams are gone!

The main problem with that is that the texture size varies with the mipmap level.

For a skybox, the eye-space Z of any point on the skybox will always be between 1.0 and sqrt(3) ~= 1.732 "units" (the distance of the centre of each face to the centre of the cube), so you may be able to avoid mipmapping altogether, or at least determine reasonably close bounds on which levels will be used.

But in general, if you want to avoid sampling from the border you need to use GL_CLAMP_TO_EDGE.

You shouldn't really be mipmapping a skybox anyway as it's always displayed as if it were a fixed distance from the viewpoint.

That aside, the cubemap approach really is the cleanest, as it can use any arbitrary geometry (doesn't have to be on a cube), you can use seamless cubemapping, and draw the full thing in a single draw call (rather than 6).