版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/85247237

A voxel representation of a scene has spatial data as opposed to the conventional rasterization view (as stored in a render target) which just has a slice of depth value. A voxelized scene can be easily traversed spatially and you can access data from coarse world locations. This enables rendering techniques which require spatial data such as indirect illumination, more convincing ambient occlusion and volumetric light shafts. Also it is also possible to utilize voxel information to make gameplay decisions. An example would be to cast a ray in the voxel grid to check a visibility. If you have ideas which could make use of coarse spatially organized information from your scene, voxel representation is worth considering.
The basic concept of GPU Voxelization to use GPU shaders to convert a scene composed of triangle meshes into a regular voxel grid representation.
The process for doing this is pretty straightforward.
First you transform the vertex positions of a primitive into a regular eulerian grid (the “voxel grid”) coordinate system in the Vertex Shader stage.
Then you rasterize the transformed primitive using a viewport of the same dimensions as one of the 2D projections of the voxel grid. Because the orthogonal viewport frustum can cover the voxel grid exactly, and a rasterized pixel position in the render target and its depth value correspond X, Y and Z components of the voxel grid.
Then, finally, map each rasterized fragment onto one or several voxels and write some data into those voxels in the Pixel Shader stage. Note that the data is written to a 3D texture which is bound as a UAV buffer, not a 2D conventional render target. You don’t need a render target at all, but you need to set a proper viewport that is aligned with a face of the voxel grid.
The following pictures describe this process visually.

渲染目标上的像素位置和深度值对应的X, Y, Z的立体像素网格

However you voxelize continuous primitive planes, there are cases that fail to voxelize correctly and have holes or slits in the result. The following two pictures are the results of voxelizations which were done with identical primitives and voxel grid.
The one which was done with horizontal camera direction got correct-looking results and the primitive was sufficiently voxelized. However, the other was done with vertical camera direction and is not sufficiently voxelized, having cracks between voxels. This is because the shader runs based on pixels covered in the viewport. Thus, if the slope of the primitive being drawn is steep with respect to the view direction, you can get cracks.

More specifically, whether or not you’ll get cracks depends on the depth gradient of the primitive being rendered. This can be calculated with ddx(depth), ddy(depth) in the pixel shader. If either of these gradients exceeds 1.0, then the voxelized plane will have “cracks” in a direction perpendicular to the depth direction.

To fix this issue, primitives need to be rendered from an axis that presents the largest face area with respect to the camera. In other words, from a camera direction selected from the X, Y and Z axes according to the face normal. Since the different faces of a single mesh are all differently oriented, this requires rendering the mesh from potentially 3 different directions; but we don’t need 3 render passes. Since the X, Y and Z axes are orthogonal each other, we just need to swizzle the X, Y and Z components to change the projection direction, and this can be done in the geometry shader stage per primitive. Then, the pixel shader restores the coordinate components according to the information that comes from the GS. In that fashion we can avoid the issue of missing voxels caused by high depth gradients.
为了解决这个问题,需要从一个轴上呈现相对于相机最大的面区域。换句话说,从一个相机的方向根据面法线选择X, Y和Z轴。由于单个网格的不同面都有不同的方向,这就需要从3个不同的方向来呈现网格;但是我们不需要3个渲染通道,由于X, Y和Z轴是正交的,我们只需要旋转X, Y和Z分量来改变投影方向,这可以在几何着色器阶段完成。然后,像素着色器根据来自GS的信息恢复坐标分量。通过这种方式,我们可以避免高深度梯度造成的体素缺失问题。

Selecting the proper projection direction has fixed “cracks”. However, by breaking up the projection directions we can introduce a new problem of “holes”. The following picture will describe it in 2D. Please think these three different colored line segments as three adjoining primitives on a single mesh.

In order to project the largest area, we’ve chosen the projection plane in the GS. Blue colored segments are projected in the Z axis direction and the red segment is projected in the X axis direction. This results in the following pixel shader invocations.

Generally, a pixel shader is invoked when a primitive covers the center of a pixel. When the red segment is projected in the X direction, none of the pixel centers were covered, so it wasn’t rasterized at all. This issue wouldn’t happen if all of the primitives were projected in a single direction. Changing the projection direction causes this issue. To fix this issue, we need to change the rasterization rules to consider a pixel covered if any part of it is touched by a primitive. This type of rasterization is called Conservative Rasterization. There is a great article covering how to perform this manually (by expanding primitive edges) in GPU Gems 3: [GPU Gems: Chapter 42 Conservative Rasterization]. As a quick summary, the technique expands primitive edges to ensure all pixels touched are covered. We’ll also talk about a possibly better alternative at the end of this blog post.
通常,当覆盖像素的中心时,将调用像素着色器。当红色线段在X方向上投影时,没有一个像素中心被覆盖,所以根本没有光栅化。如果所有的物体都投射到一个方向上,那么这个问题就不会发生。改变投影方向会导致这个问题,为了解决这个问题,我们需要更改光栅化规则,以便在像素的任何部分被触摸时考虑覆盖的像素。这种类型的光栅化称为Conservative 光栅化。在GPU Gems 3: [https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html]中有一篇很棒的文章介绍了如何手动执行这一操作(通过扩展原始边缘)。该技术扩展了原始边缘,以确保所有被触摸的像素都被覆盖,我们也会在这篇博文的结尾讨论一个更好的选择。
Also, finally, should note that if you are running the new Maxwell base chip set (or higher) you can make use of actual official artifact-free conservative raster by using NVAPI. Here’s the link. [Don’t be conservative with Conservative Rasterization] If you use the hardware conservative raster supported by Maxwell GPUs, then you need not worry about the artifacts from expanding primitive edges, and a lot of the following sections can be ignored (as they are covering how to deal with edge extension artifacts!).
最后,还要注意,如果正在运行新的Maxwell基芯片集(或更高版本),那么可以使用NVAPI来使用实际的无手动创建的Conservative 光栅。https://developer.nvidia.com/content/dont-be-conservative-conservative-rasterization
如果使用Maxwell gpu支持的硬件Conservative 光栅,那么就不必担心扩展原始边缘所带来的问题,并且可以忽略以下许多部分(因为它们涉及了如何处理边缘扩展工件!)

Finally, we’ve got a sufficiently complete voxelization. However, there is still an issue we have left. If you used the technique of expanding the primitive’s edges in GS to get us Conservative Rasterization, some extra pixels are shaded. Yellow pixels in the following picture are the extra pixels not covered by the primitive, but still shaded. Especially for thin triangles, this method will produce a fair number of extra pixel shader calls.
最后,我们得到了一个足够完整的体素化,然而,还有一个问题我们没有解决,如果使用在GS中展开边缘的技术来得到Conservative 的光栅化,一些额外的像素将被着色。下图中的黄色像素是未被覆盖的额外像素,但仍然有阴影,特别是对于细三角形,这种方法将产生相当数量的额外像素着色器调用。

The following picture illustrates a primitive (red plane) intersecting multiple voxels in the depth direction, which means in the same pixel.

The light purple triangle’s ddx(depth) and ddy(depth) are indeed just 1.0, and the red plane is parallel to it. These planes have the maximum depth gradient possible in the process of voxelization. The red plane intersects front, center and rear voxels. Since these are in the same pixel, the Pixel Shader will be invoked once for all of them. If we apply the conservative rule in the depth direction, we must check for intersections between the primitive’s plane and these 3 voxels in the Pixel Shader. To do that, we can calculate the depth gradient in the Geometry Shader and pass it to the Pixel Shader, or compute the depth gradient in the Pixel Shader with the ddx and ddy functions. Then, we check for intersections between the primitive’s plane and these 3 voxels, using the gradient value and the depth value at the center of the pixel (which should be retrieved as a pixel’s depth value in the Pixel Shader).When you intersect a voxel with a primitive, you think of the voxel as being a certain shape, such as a box or sphere. This shape, called the intersection target, will determine topological conditions of the voxelized primitive. If you’re interested in intersection targets, you should refer to A Topological Approach to Voxelization [Samuli Laine, 2013]. In this section, we use the entire box of a voxel as an intersection target. To check intersection between a voxel and a primitive, there are some generic intersection tests, such as the Separating Axis Theorem; we will use a method that can separate its workload between the GS and PS. The process consists of two parts. The first one is an AABB (Axis Aligned Bounding Box) test and the second one is an edge-voxel condition test. If a voxel passes these two tests then the primitive intersects that voxel and the voxel is covered.
浅紫色三角形的ddx(深度)和ddy(深度)实际上只有1.0,红色平面与之平行,这些平面在体素化过程中具有最大的深度梯度。红色平面与前、中和后体素相交。因为它们都在同一个像素中,因此所有像素着色器都会被调用一次。如果我们在深度方向上应用规则,我们必须检查基面的平面和像素着色器中的这3个体素之间的交点,为此,我们可以计算几何着色器中的深度梯度并将其传递给像素着色器,或者使用ddx和ddy函数计算像素着色器中的深度梯度。然后,我们使用渐变值和像素中心的深度值(应该作为像素着色器中的像素深度值检索)来检查平面和这3个体素之间的交集。当你将一个体素与一个基面相交时,你认为体素是一个特定的形状,例如一个盒子或球体。这种形状称为相交目标,它将确定被体化的拓扑条件,如果对交集目标感兴趣,应该参考体素化的拓扑方法[Samuli Laine, 2013]。在本节中,我们使用体素的整个框作为交集目标。为了检查体素和基础物体之间的交集,有一些通用的交集测试,比如分离轴定理;我们将使用一种方法在GS和PS之间分离它的工作负载。第一个是AABB(轴向对齐边界框)测试,第二个是边体素条件测试。如果一个体素通过这两个测试,那么原始物体就会与该体素相交,从而覆盖该体素。

The voxel’s AABB is defined by the regular voxel grid. The primitive’s AABB can be found from the maximum and minimum X, Y and Z of its vertex coordinates. To achieve better workload balancing, a primitive’s AABB should be calculated in the GS, then the voxel-primitive AABB test should be done in the PS. The test is passed if the voxel bounding box intersects the primitive’s AABB.
The following pseudocode illustrates this process.

Checking the edge-voxel condition is done in in each of the three axis-aligned planes. For each edge, we calculate the signed distance from the edge to the voxel’s vertices. The maximum signed distance value across all the voxel’s vertices is used for the test. In the following picture, the edge with the red normal vector is checked with the vertex colored red, which is the voxel corner with the maximum signed distance along the red normal. The other two edges (green and blue) are checked similarly. If all of the calculated signed distances are positive, this test is passed.

The following code snippet shows the edge-voxel condition tests in the pixel shader.

As we described above, to manually implement conservative rasterization on the GPU, we extended the edges of primitives in the Geometry Shader, which consumes many GPU cycles. In this section, we describe another GPU voxelization approach using MSAA, which does not require expansion of the primitive to get conservative rasterization.
如前所述,为了在GPU上手动实现conservative 光栅化,我们扩展了几何着色器中原始物体的边缘,这将消耗许多GPU。在本节中,我们描述了另一种使用MSAA的GPU体素化方法,它不需要扩展原始物体就可以得到conservative 的光栅化。
Without MSAA, the pixel shader is only invoked for pixels whose centers are covered by a primitive, as we previously discussed.
However, when enabling 8xMSAA, the pixel shader is invoked if any of the subsamples are covered by a primitive. This subsample region covers most of the pixel in 8xMSAA. Additionally, the pixel shader is invoked only once, regardless how many subsamples are covered in a pixel. This is really close to actual conservative rasterization, as long as you have sufficient count and spacing of subsample points. So by using MSAA we don’t need to expand the primitive’s edges, there are no extra pixel shader calls, and the geometry shader is much simpler! This also enables us to skip those primitive-voxel intersection tests which we needed to do to remove the extra voxels!
However, this method is not genuine conservative rasterization. Primitives which lie in-between subsamples still have a possibility not to be voxelized properly. Additionally, you need to create an MSAA render target to bind, which slightly increases the memory cost in vidmem (probably not that big of a deal). If you can utilize Forced Sample Count in DX11.1, you can enable MSAA rasterization without binding an MSAA render target.
So this is a relatively aggressive method, as it has a possibility to have “holes”, which are avoidable by making sure the voxel grid is fine/high-res enough for the sizes of voxelized primitives.
The following three pictures are examples of pixel shader invocations in different rasterization methods.
在没有MSAA的情况下,像素着色器只对中心被原始物体覆盖的像素调用,就像我们前面讨论的那样。但是,在启用8xMSAA时,如果任何子样本被基元覆盖,就会调用像素着色器。这个子样例区域覆盖了8xMSAA中的大部分像素。此外,像素着色器只被调用一次,不管像素中包含多少个子样本。这非常接近实际的conservative 光栅化,只要你有足够的子样本点的计数和间距。因此,通过使用MSAA,我们不需要扩展原始物体的边缘,没有额外的像素着色器调用,而且几何着色器更简单!这也使我们能够跳过那些原始体素交叉测试,我们需要做这些测试来删除额外的体素!然而,这种方法并不是真正的conservative 光栅化。位于子样本之间的原始物体仍然有可能不能正确地进行体素化。另外,您需要创建一个MSAA呈现目标来绑定,这将略微增加vidmem中的内存开销(可能不是很大)。如果可以在DX11.1中使用强制样本计数,则可以启用MSAA栅格化,而无需绑定MSAA呈现目标。因此,这是一种相对激进的方法,因为它可能有“洞”,这是可以避免的,确保体素网格是精细的/高分辨率,足以满足体素化原始物体的大小。下面三幅图是不同栅格化方法中像素着色器调用的例子。
The following three pictures are also examples of voxelizations without voxel-primitive intersection tests.
In this article, we have described the basics of GPU voxelization. You can choose methods as you like to fit your purpose. Currently, MSAA voxelization is reasonable in most cases, but you might need to implement an accurate method as a reference. Also, if you are running on Maxwell or higher GPU architecture then you should definitely use the official conservative rasterization functions and you can skip edge extension issues.
在本文中,我们描述了GPU 体素化的基础。您可以选择适合您的方法。目前,MSAA 体素化在大多数情况下是合理的,但是您可能需要实现一个准确的方法作为参考。此外,如果您运行在Maxwell或更高的GPU架构上,那么肯定应该使用官方的光栅化函数,可以跳过边缘扩展问题。

An Accurate Method for Voxelizing Polygon Meshes[Huang et al. 98]
Fast Parallel Surface and Solid Voxelization on GPUs [Michael et al. 10]
Octree-Based Sparse Voxelization Using the GPU Hardware Rasterizer [Cyril et al. 11]
A Topological Approach to Voxelization [Samuli Laine, 2013]
GPU Gems 2 Chapter 42. Conservative Rasterization


  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他