OpenGL 编程/Glescraft 6
现在我们可以在体素世界中四处移动,我们可能想添加、删除或更改体素。在第一个教程中,我们实现了一个简单的set()函数,它允许我们更改任意体素的内容。但是,诀窍是找出我们正在查看的确切块,更具体地说,是该块的哪个面。
正如我们在物体选择教程中看到的,我们可以检索窗口中任意像素的深度值,并将它的坐标反投影回物体坐标。给定当前窗口的宽度和高度,ww和wh,以及视图和投影矩阵,我们对位于中心的像素执行以下操作
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);
该objcoord向量保存中心像素的坐标,但我们只需将浮点数向下舍入到最接近的整数即可获得体素坐标。为了让用户清楚地了解他目前正在查看哪个体素,您可以在它周围绘制一个包围盒。为了允许与世界进行交互,您可以在单击鼠标按钮时注册一个回调函数,并使用set()在相应的块上更改当前选定的体素。该set()函数将依次将块标记为已更改,当渲染下一帧时,这将自动导致 VBO 更新以反映更改。
更改或删除现有的体素很好,但人们也应该能够添加新的体素。如果要添加体素,它们会根据我们正在查看的体素的哪个面,添加在当前选定的体素旁边,这是合乎逻辑的。这样做的一种方法是意识到,由于我们始终看着体素表面的一个点,因此 x、y、z 坐标之一应该始终恰好具有整数值。在实践中,这实际上永远不会发生,但我们可以检查 x、y、z 中哪一个最接近整数值。如果 x 坐标最接近,那么我们知道它要么是两个指向 x 方向的面之一。如果 x 坐标小于相机的 x 坐标,我们知道我们正在查看指向正 x 方向的面,如果 x 坐标较小,那么我们正在查看另一个面。因此,为了确定新体素的坐标,首先我们定义一个函数,该函数提供到最近整数的距离
float dti(float val) {
return fabsf(val - roundf(val));
}
然后我们按以下方式使用它
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++;
这种方法的优点是即使对于非常远的体素也能够很好地工作。缺点是它有时会由于舍入错误而选择错误的体素。这种方法的另一个特性是,如果您正在通过具有部分透明纹理的体素查看,并且透明像素在片段着色器中被丢弃,并且您没有精确地指向其纹理的不透明部分,它会选择在部分透明体素后面可见的体素。这取决于您想要什么,这是一个特性或一个错误。
另一种技术是想象一条从相机开始并朝你正在查看的方向前进的线。您可以沿着该线移动,直到遇到体素。
这种方法的优点是它更加精确。它将所有体素视为不透明,因此它不会透过部分透明的体素。缺点是它需要更多计算,并且计算量会随着我们正在查看的体素的距离增加而增加。最好限制最大距离,这样您只能选择附近的体素。