I'm trying to implement reflections using Cascaded Voxel Cone Tracing using volume textures (i.e. not an octree).
When a cone starts inside a cascade and hits an object within the same cascade, the reflections are mostly correct:
But when the cone starts on the inner cascade and hits something in the next (coarser) cascade (I'm using only two nested cascades for now),
it goes through the walls of the next cascade and shows the inside of the hit object (here the cones hitting something in the coarser cascade pick the red color for debugging):
1) Does it happen because of thin (surface) voxelization?
But cones that start and end in the same cascade (texture) do not go through the walls.
2) I trace cones in volume texture UVW space. When I detect that a cone exits the current cascade, I transform the cone's origin into the UVW space of the coarser cascade (and scale the travelled distance as needed). Should anything else be done to avoid missing intersections with geometry?
Just in case, here's my (ugly) cone tracing shader:
float4 coneTraceSpecularReflection_Cascaded(
float3 surfacePositionUVW
, float3 surfaceNormal
, float3 eyeToSurfaceDirection
, VCT_ConeConfig cone
, VCT_Params vctParams
, float roughness // The (linear) roughness of the surface material.
, Texture3D< float4 > volumeTextures[GPU_MAX_VXGI_CASCADES]
, int cascadeCount // [1..GPU_MAX_VXGI_CASCADES]
, SamplerState volumeTextureSampler
)
{
//TODO: Find the tightest cascade enclosing the surface point.
// Trace the cone through the cascades.
int iCurrentCascade = 0;
float3 color = 0.0f;
float alpha = 0.0f; // 'opacity'/transmittance/density accumulator
// Compute the initial distance to avoid sampling the voxel containing the cone's apex (self-occlusion).
// Unfortunately, it will result in disconnection between nearby surfaces :(
const float surfaceBias = vctParams.getStartOffsetDistance();
float distanceTravelled = surfaceBias;
// in UVW space
const float borderWidth = vctParams.m_grid_inv_resolution * 2; // the size of voxel at coarser LoD
//const float borderWidth = 0;// 0.25f;
while( alpha < 1.0f )
{
const float3 currPos = getPositionAt( distanceTravelled );
const float diameter = vctParams.getDiameter( cone, distanceTravelled );
const float mipLevel = vctParams.getMipLevel( diameter );
// break if the ray exits the voxel grid, or we sample from the last mip:
[branch]
if(
mipLevel >= vctParams.m_texture_max_mipLevel
||
(currPos.x < borderWidth) || (currPos.y < borderWidth) || (currPos.z < borderWidth)
||
(currPos.x > (1.0f-borderWidth)) || (currPos.y > (1.0f-borderWidth)) || (currPos.z > (1.0f-borderWidth))
)
{
// prepare for tracing through the next cascade
++iCurrentCascade;
// If this is the last cascade...
if( iCurrentCascade == cascadeCount )
{
// ...then we hit the sky.
const float skyVisibility = 1.0f - alpha;
color += float3(0,0,1) * (skyVisibility * skyVisibility);
break;
}
else
{
GPU_VXGI_CascadeParams cascadeToTraceNext = g_vxgi_cascades[ iCurrentCascade ];
cone.apex = cascadeToTraceNext.transformPositionFromFinerCascade( currPos );
distanceTravelled = cascadeToTraceNext.transformLengthFromFinerCascade( distanceTravelled );
continue;
}
}
// NOTE: in D3D11, an array of textures can be indexed only with a literal constant!
//const float4 sample = volumeTextures[ iCurrentCascade ].SampleLevel( volumeTextureSampler, currPos, mipLevel );
float4 sample;
[branch]
if( iCurrentCascade == 0 )
{
sample = volumeTextures[ 0 ].SampleLevel( volumeTextureSampler, currPos, mipLevel );
}
else if( iCurrentCascade == 1 )
{
sample = volumeTextures[ 1 ].SampleLevel( volumeTextureSampler, currPos, mipLevel );
sample.rgb *= float3(1,0,0);//ZZZ
}
else
{
sample = (float4) 0;
}
const float weight = (1.0f - alpha) * sample.w;
color += weight * sample.xyz;
alpha += weight;
distanceTravelled += diameter;
}
return float4( color, alpha );
}