OpenGL Programming/Glescraft 6

A voxel world.

Introduction

edit

Now that we can move around in our voxel world, we might want to add, remove or change voxels. In the first tutorial, we have implemented a simple set() function that allows us to change the contents of an arbitrary voxel. However, the trick is to find out exactly which block we are looking at, and more specifically, which face of that block we are looking at.

Unprojecting coordinates

edit

As we have seen in the object selection tutorial, we can retrieve the depth value of an arbitrary pixel in the window, and unproject its coordinates back to object coordinates. Given the current window's width and height, ww and wh, and the view and projection matrices, we do this as follows for the pixel that is in the center:

glReadPixels(ww / 2, wh / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);

glm::vec4 viewport = glm::vec4(0, 0, ww, wh);
glm::vec3 wincoord = glm::vec3(ww / 2, wh / 2, depth);
glm::vec3 objcoord = glm::unProject(wincoord, view, projection, viewport);

int x = floorf(objcoord.x);
int y = floorf(objcoord.y);
int z = floorf(objcoord.z);

The objcoord vector hold the coordinates of the center pixel, but we simply round the floating point values down to the nearest integer to get the voxel coordinates. To make it clear to the user which voxel he is currently looking at, you can draw a bounding box around it. To allow interaction with the world, you can register a callback function that is called whenever one of the mouse buttons is clicked, and use set() on the appropriate chunk to change the currently selected voxel. The set() function will in turn mark the chunk as changed, and when the next frame is rendered, this will automatically cause the VBO to be updated to reflect the changes.

Changing or removing existing voxels is nice, but one would also be able to add new voxels. It is fairly logical that if you want to add voxels, they are added next to the currently selected voxel, depending on which face of it we are looking at. One way of doing this is by realizing that since we are always looking at a point on the surface of a voxel, one of the x, y, z coordinates should always exactly have an integer value. In practice, this will never really be the case, but we can check which of x, y, z is closest to an integer value. If the x coordinate is closest, then we know it is either one of the two faces pointing in the x direction. If the x coordinate is smaller than the x coordinate of the camera, we know that we are looking at the face that is pointing in the positive x direction, and if the x coordinate is smaller, then we are looking at the other face. So, to determine the coordinates for the new voxel, first we define a function that gives us the distance to the nearest integer:

float dti(float val) {
  return fabsf(val - roundf(val));
}

Then we use it as follows:

int nx = x;
int ny = y;
int nz = z;

if(dti(objcoord.x) < dti(objcoord.y))
  if(dti(objcoord.x) < dti(objcoord.z))
    if(lookat.x > 0)
      nx--;
    else
      nx++;
  else
    if(lookat.z > 0)
      nz--;
    else
      nz++;
else
  if(dti(objcoord.y) < dti(objcoord.z))
    if(lookat.y > 0)
      ny--;
    else
      ny++;
  else
    if(lookat.z > 0)
      nz--;
    else
      nz++;

This method has the advantage of working well even for voxels that are very far away. The drawback is that it sometimes selects the wrong voxel due to rounding errors. Another property of this method is that if you are looking through a voxel with a partly transparent texture, and transparent pixels are discarded in the fragment shader, and you are not pointing exactly at one of the opaque parts of its texture, it selects the voxel that is visible behind the partly transparent voxel. Depending on what you want, this is either a feature or a bug.

Ray casting

edit

Another technique is imagining a line that starts at the camera, and goes in the direction you are looking at. You can travel along that line until you encounter a voxel.

The advantage of this method is that it is much more exact. It treats all voxels as opaque, so it will not see through partially transparent voxels. The drawback is that it requires more computation, and the amount of computation increases the farther away the voxel is we are looking at. It is best to limit the maximum distance, so you can only select nearby voxels.

< OpenGL Programming

Browse & download complete code