NVIDIA / gvdb-voxels

Sparse volume compute and rendering on NVIDIA GPUs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Corrections for Depth merge with OpenGL

ramakarl opened this issue · comments

OpenGL render does not correctly handle the Depth merging when the depth buffer is enabled. This is because the raySurfaceTrilinear never had the depth code inserted, and it must handle it differently.

I haven't had any time to work on a branch (because my local code differs too much now in other ways), so I am just posting the fixes here.

Here they are:


// SurfaceTrilinearBrick - Trace brick to render surface with trilinear smoothing
__device__ void raySurfaceTrilinearBrick ( VDBInfo* gvdb, uchar chan, int nodeid, float3 t, float3 pos, float3 dir, float3& pStep, float3& hit, float3& norm, float4& hclr )
{
	float3 vmin;
	VDBNode* node	= getNode ( gvdb, 0, nodeid, &vmin );			// Get the VDB leaf node	
	float3  o = make_float3( node->mValue ) ;				// Atlas sub-volume to trace	
	float3	p = (pos + t.x*dir - vmin) / gvdb->vdel[0];					// sample point in index coords			
	t.x = SCN_PSTEP * ceil ( t.x / SCN_PSTEP );

	for (int iter=0; iter < MAX_ITER && p.x >=0 && p.y >=0 && p.z >=0 && p.x < gvdb->res[0] && p.y < gvdb->res[0] && p.z < gvdb->res[0]; iter++) 
	{	
		// depth buffer test [optional]
		if (SCN_DBUF != 0x0) {
			if (t.x > getLinearDepth(SCN_DBUF) ) {
				hit.x = t.x;
				hit.z = OBJHIT;				
				return;
			}
		}
		if ( tex3D<float>(gvdb->volIn[chan], p.x+o.x, p.y+o.y, p.z+o.z ) >= SCN_THRESH ) {
			hit = p*gvdb->vdel[0] + vmin;
			norm = getGradient ( gvdb, chan, p+o );
			if ( gvdb->clr_chan != CHAN_UNDEF ) hclr = make_float4( getColorC4 ( gvdb, gvdb->clr_chan, p+o ) );
			return;	
		}	
		p += SCN_PSTEP*dir;		
		t.x += SCN_PSTEP;
	}
}

Here is the updated rayCast function:

//----------------------------- MASTER RAYCAST FUNCTION
// 1. Performs empty skipping of GVDB hiearchy
// 2. Checks input depth buffer [if set]
// 3. Calls the specified 'brickFunc' when a brick is hit, for custom behavior
// 4. Returns a color and/or surface hit and normal
//
__device__ void rayCast ( VDBInfo* gvdb, uchar chan, float3 pos, float3 dir, float3& hit, float3& norm, float4& clr, gvdbBrickFunc_t brickFunc )
{
	int		nodeid[MAXLEV];					// level variables
	float	tMax[MAXLEV];
	int		b;	

	// GVDB - Iterative Hierarchical 3DDA on GPU
	float3 vmin;	
	int lev = gvdb->top_lev;
	nodeid[lev]		= 0;		// rootid ndx
	float3 t		= rayBoxIntersect ( pos, dir, gvdb->bmin, gvdb->bmax );	// intersect ray with bounding box	
	VDBNode* node	= getNode ( gvdb, lev, nodeid[lev], &vmin );			// get root VDB node	
	if ( t.z == NOHIT ) return;	

	// 3DDA variables		
	t.x += gvdb->epsilon;
	tMax[lev] = t.y -gvdb->epsilon;
	float3 pStep	= make_float3(isign3(dir));
	float3 p, tDel, tSide, mask;
	int iter;

	PREPARE_DDA	

	for (iter=0; iter < MAX_ITER && lev > 0 && lev <= gvdb->top_lev && p.x >=0 && p.y >=0 && p.z >=0 && p.x <= gvdb->res[lev] && p.y <= gvdb->res[lev] && p.z <= gvdb->res[lev]; iter++ ) {

		NEXT_DDA

		// depth buffer test [optional]
		if (SCN_DBUF != 0x0) {
			if (t.x > getLinearDepth(SCN_DBUF) ) {
				hit.x = t.x;
				hit.z = OBJHIT;
				return;
			}
		}

		// node active test
		b = (((int(p.z) << gvdb->dim[lev]) + int(p.y)) << gvdb->dim[lev]) + int(p.x);	// bitmaskpos
		if ( isBitOn ( gvdb, node, b ) ) {							// check vdb bitmask for voxel occupancy						
			if ( lev == 1 ) {									// enter brick function..
				nodeid[0] = getChild ( gvdb, node, b ); 
				t.x += gvdb->epsilon;
				(*brickFunc) (gvdb, chan, nodeid[0], t, pos, dir, pStep, hit, norm, clr);
				if ( clr.w <= 0) {								// deep termination
					clr.w = 0; 
					return; 
				}			
				if (hit.z != NOHIT) return;						// surface termination: OBJHIT or volume hit
				
				STEP_DDA										// leaf node empty, step DDA
				//t.x = hit.y;				
				//PREPARE_DDA

			} else {				
				lev--;											// step down tree
				nodeid[lev]	= getChild ( gvdb, node, b );				// get child 
				node		= getNode ( gvdb, lev, nodeid[lev], &vmin );	// child node
				t.x += gvdb->epsilon;										// make sure we start inside child
				tMax[lev] = t.y -gvdb->epsilon;							// t.x = entry point, t.y = exit point							
				PREPARE_DDA										// start dda at next level down
			}
		} else {			
			STEP_DDA											// empty voxel, step DDA
		}
		while ( t.x > tMax[lev] && lev <= gvdb->top_lev ) {
			lev++;												// step up tree
			if ( lev <= gvdb->top_lev ) {		
				node	= getNode ( gvdb, lev, nodeid[lev], &vmin );
				PREPARE_DDA										// restore dda at next level up
			}
		}	
	}	
}

At the top of cuda_gvdb_scene.cuh:

constant float NOHIT = 1.0e10f;
constant float OBJHIT = 2.0e10f;

In the above code I've added a "OBJHIT" semantic which is part of the Vec3 hit return value, in addition to the existing NOHIT.

The reason the depth merge failed was that NOHIT implies hitting the background (should shade with 1.0 alpha), whereas OBJHIT implies hitting an opengl surface (newly added).

Note the line ~615 in cuda_gvdb_raycast.cuh, which says:
if (hit.z != NOHIT) return;
Used by both surface & deep pathways, this is the key as it means that rays should terminate if they hit an actual volume surface (x,y,z), or if the opengl object is hit (hit.z=OBJHIT), but rays will not terminate so long as the (hit.z==NOHIT) meaning that a deep volume render is in progress, or a surface trilinear has not yet hit a surface.

The above code should fix the OpenGL depth merge case for surfaceTrilinear rendering.

For clarification here is a summary:

raySurfaceTrilinear for isosurfaces:
hit = <any, any, NOHIT> => nothing hit yet
hit = <any, any, NOHIT> => when trace terminates, means backgrd was hit.
hit = valid <x,y,z> => hit is surface point of trilinear volume isosurface. normal will also be returned.
hit = <any, any, OBJHIT> => hit the opengl depth buffer. hit.x is the t-value of the hit. clr is still valid as ray may be partially rendered.

rayDeep for varying density clouds:
hit = <any,any, NOHIT> => trace in progress, semi-transparent samples will continue to accumulate in clr, as a cloud has no surface.
hit = <any,any, OBJHIT> => opengl surface was hit during deep render.
hit = <tminx, tmax, 1> => when raytrace finishes, x and y hold the t-min and t-max values for the range of the ray containing the cloud. clr holds the accumulated result.

Quick note - I think this might not have been fixed in the latest series of commits (contrary to the above references, whoops), since only rayDeepBrick includes this test against the depth buffer on the voxel level, and OBJHIT hasn't been implemented! Will need to look into this again in the future.