• Nem Talált Eredményt

Approximate visibility testing methods are extremely useful in the virtual light sources algo-rithm, and other successful solutions [RGK+08] based on this recognition have been published after the proposed spherical occluder method. We have demonstrated that our algorithm is able to speed up rendering of indirect illumination by an order of magnitude, albeit at the cost of some minor artifacts. There is also no reason why spherical occluders would not work perfectly with the light cuts method [WFA+05], which would allow for effective rendering of massively complex scenes.

The algorithm has also been successfully used in Monte Carlo global illumination variance reduction techniques to get a good a-priori approximation of integrands containing a visibility term [I8, F8].

Chapter 6

The hierarchical ray engine

Ray shooting acceleration hierarchies discussed in Chapter 4 have favorable complexity char-acteristics in the number of scene primitives and they do not assume any coherence between the processed rays. Therefore, they are well suited for random walk algorithms like the photon shooting phase of the virtual light sources method. In the gathering phase, however, it might be necessary to trace coherent secondary rays if reflective or refractive objects are visible. These objects might also be dynamic, excluding the possibility of building acceleration structures as pre-processing.

In this chapter we describe an improved algorithm [I2] based on the ray engine approach, which aims at hardware supported ray tracing in these situations. The algorithm builds a hier-archy of rays instead of objects, completely on the graphics card. Our algorithm circumvents the problems that prohibited the adaptation classical spatial subdivision schemes that had proven to be successful on the CPU, because it uses a different approach, more fitting the the GPU architecture. We evaluate the method in comparison to contemporary solutions, as the rapid evolution of GPU hardware allowed for even more effective algorithms to be born. The applica-bility of the method on current hardware and its impact on later algorithms is briefly discussed in Section 6.7 at the end of this chapter.

If we are looking for a solution which does not rely on a pre-built acceleration structure, the most important milestone we find is the ray engine [CHH02]. Based on the recognition that ray tracing is a crossbar on rays and primitives, while scan conversion is a crossbar on pixels and primitives, they have devised a method for computing all possible ray–primitive intersections on the GPU. On contemporary hardware they could achieve processing power similar to the CPU’s.

rays texture raytracing

primitives

full screen quad

RayCastPS ray-primitive intersection draw

fetch

z-test refracted rays

Figure 6.1: Rendering pass implementing the ray engine.

As the ray engine serves as the basis of our approach, let us reiterate its working mechanism 54

CHAPTER 6. THE HIERARCHICAL RAY ENGINE 55 in current GPU terminology. Figure 6.1 depicts the rendering pass realizing the algorithm.

Every pixel of the render target is associated with a ray. The origin and direction of rays to be traced are stored in textures that have the same dimensions as the render target. One after the other, a single ray tracing primitive is taken, and it is rendered as a full-viewport quad, with the primitive data attached to the quad vertices. Thus, pixel shaders for every pixel will receive the primitive data, and can also access the ray data via texture reads. The ray-primitive intersection calculation can be performed in the shader. Then, using the distance of the intersection as a depth value, a depth test is performed to verify that no closer intersection has been found yet.

If the result passes the test, it is written to the render target and the depth buffer is updated.

This way every pixel will hold the information about the nearest intersection between the scene primitives and the ray associated with the pixel.

From this point on, we will refer to the primitives for the ray tracing as triangles, this being the general case. However, please note that the method is applicable to any other type of object for which an intersection test against a ray can be implemented in a shader. Thus, the ray engine implements the naive ray tracing algorithm of testing every ray against every primitive.

6.1 Acceleration hierarchy built on rays

CPU-based acceleration schemes are spatial object hierarchies. The basic approach is that, for a ray, we try to exclude as many objects as possible from intersection testing. This cannot be done in the ray engine architecture, as it follows a per primitive processing scheme instead of the per ray philosophy. Therefore, we also have to apply an acceleration hierarchy the other way round, not on the objects, but on the rays.

DirectX point primitives covering the screen

RayCastPS ray-primitive intersection draw

fetch

z-test rays texture refracted rays raytracing

primitives

Figure 6.2: Point primitives are rendered instead of full screen quads, to decompose the array of rays into tiles.

In typical applications, real-time ray tracing augments scan conversion image synthesis where recursive ray tracing from the eye point or from a light sample point is necessary. In both scenarios, the primary ray impact points are determined by rendering the scene from either the eye or the light. As nearby rays hit similar surfaces, it can be assumed that reflected or refracted rays may also travel in similar directions, albeit with more and more deviation on multiple iterations. If we are able to compute enclosing objects for groups of nearby rays, it may be possible to exclude all rays within a group based on a single test against the primitive being processed. This approach fits well with the ray engine. Whenever the data of a primitive is processed, we should find a way not to render it on the entire screen as a quad, but invoke the pixel shaders only where an intersection is possible.

CHAPTER 6. THE HIERARCHICAL RAY ENGINE 56 The solution (as illustrated in Figure 6.2) is to split the render target into tiles, render a set of tile quads instead of a full-viewport one, but make a decision for every tile beforehand whether it should be rendered at all. At a first glimpse, this may appear counterproductive, as, apparently, far more quads will be rendered. However, there is a set of issues that disprove concerns:

The ray engine is pixel shader intensive, and vertex processing time is negligible in com-parison. The number of pixel shader runs, which remains crucial, is by no means increased.

Instead of small quads, one can use point primitives, described by a single vertex. This eliminates the fourfold overhead of processing the same vertex data for all quad vertices, and needlessly interpolating values.

The high level test of whether a tile may include valid intersections can be performed in the vertex shader. If the intersection test fails, the vertex is transformed out of view, and discarded by clipping. Moving the vertices out of view does not require any computation, they are simply assigned an outlying extreme position.

We can render all the triangles (the primitives of ray tracing) for a single tile at once. With a vertex buffer encoding the triangles, this will be a single draw call of point primitives.

Tile data will be constant for all triangles, and can be passed in uniform registers.

rays texture cones texture

ConePS compute enclosing

cone for tile fetch

render to texture

copy

cone array in system memory

Figure 6.3: Data flow in the hardware pass computing enclosing cones for tiles of rays.

To be able to perform the preliminary test, for rays grouped in the same tile an enclosing object should be computed. This object will be an infinite cone. If we test it against the enclosing sphere of the triangle, we can exclude tiles not containing any intersections. As rays are described in textures, and are not static, the computation of ray-enclosing cones should be performed on the GPU, in a rendering pass, computing data to a texture. This step is shown in Figure 6.3.

Figure 6.4 shows how the hierarchical ray engine pass proceeds. For all the tiles, the vertex buffer is rendered that contains triangle data including the description of the triangle’s enclosing sphere. The tile position and the ray-enclosing cone data for the current tile are uniform pa-rameters to the vertex shader. Based on the intersection test between the current triangle’s and the tile’s enclosing objects, the vertex shader either transforms the vertex out of view, or moves it to the desired tile position. The pixel shader performs the classic ray engine ray–triangle intersection test.