Help with GPU Pro 5 Hi-Z Screen Space Reflections

Started by
77 comments, last by WFP 9 years, 2 months ago

If we adjust the algorithm described on page 173 to work with the 2d case, here's what I get for the first cell of MIP-1:

fineZ.x = 100
fineZ.y = 50
minZ = 50
maxZ = 100
coarseVolume = 1/(100 - 50) = 1/50
visibility.x = 1
visibility.y = 1
integration = [100, 50] * 1/50 * [1,1] = [100/50, 50/50] = [2,1]
coarseIntegration = dot(0.5, [2,1]) = 1.5
Which is not a percentage of visibility but a ratio between the fineZ values and the (maxZ-minZ) diff.
Thinking about it, if really the output is meant to be "the percentage of empty voxel volume relative to the total volume of the cells", then (I think) we should calculate the integration value as:
float4 integration = (fineZ.xyzw/maxZ) * visibility.xyzw;
That way, the 2d example would give:
fineZ.x = 100
fineZ.y = 50
maxZ = 100
visibility.x = 1
visibility.y = 1
integration = [100, 50] / 100 * [1,1] = [100/100, 50/100] = [1,0.5]
coarseIntegration = dot(0.5, [1,0.5]) = 0.75
Which seems to be correct? i.e. 25% of the cell is occluded. Then I get the following results for the following cases:
visibility1.jpg
visibility2.jpg
Does this make sense?
Advertisement

Hey Jp,

Thanks for the explanation and seeing it drawn out helped clarify for me a lot what you were getting at. I definitely think you're onto something, and your output looks inline with what I would expect from the visibility buffer. Whenever I get to the cone-tracing step you can bet that I'll try out what you've got above and see where that puts things. Thanks for the update!

-WFP

Hi everyone. I'm now stuck with the cone pass.

From what I understand, the cone tracing consist of sampling each circles along the cone (from the reflection point to the to the reflection incident point, i.e. from the big circles to the small circles) and approximating the integral by weighting each sample appropriately. The article says something like "we intersect each sphere with the hi-z structure and weight them by finding out how much they are in front, in the middle or behind the coarse depth cells".... I've tried and and got rubbish:

ct4.jpg

I've tried both of the following ways to weight in the cone's spheres. Got equally crappy results (min/max represent the value stored in the hi-z structure at a particular mip-level)

ct5.jpg
I'm would think that the sphere intersection test with the hi-z structure needs to be done with the linear min and max depth value. One thing that confuses me, is that in the article the weighting function only takes the 2d position of the current sphere center (as well as the mip_level for that 2d pos). How is the width of the _sphere_ calculated then?!? I would have though that you would need to project the current circle in view space to then do a sphere intersection test with the coarse depth cells?
ct6b.jpg
That part really confuses me.
Here's an example of the case that needs to be solved. (note: you can see the color values that will be fetched by each sphere, the bigger the sphere, the blurrier the fetch). This is the case where a reflection approaches an edge. On the top image the ray hits the sphere, and on the bottom image, the ray hits the background and all of the contribution comes from the edge sphere (so the reflection result is the blurred sky). The question is how should the weight of each of those spheres be set so that there is a smooth transition between the left and the right reflection?

ct2.jpg

ct3.jpg
By hardcoding the weight of the first sphere to 1, we get a hit of what kind of results we could expect (minus the crapy hard edges)
ct1.jpg

Any thoughts? What am I not getting? Grrrr.

Jp

So I've finally figured out what was causing the stair artifacts we were seeing when running in anything but power of two texture sizes. In the article, the author uses offsets of -1 in obtaining his other three points for comparison, but it turns out that, at least for my NVIDIA card (760 GTX), the opposite needed to be true. Using offsets in the positive direction (see below) alleviated the stair artifacts that were showing up. There seems to be an implementation difference in how ATI and NVIDIA cards handle this, because the code worked with -1 offsets on the ATI card that it was tested on. I still need to follow up to make sure changing the sign to positive doesn't break the technique on those cards, but at the very least, at least we have an answer for what was causing it. :) I've posted the modified HiZ buffer construction pixel shader that I use below, as well as a screenshot running at 1536x864 with no stair artifacts showing up. Next steps are filtering this buffer to fill in the tiny artifacts/gaps that show up (as well as temporal stability, etc., eventually), and then applying the cone-tracing step, which Jp is doing some great work on. :)

-WFP

Screenshot: https://www.dropbox.com/s/l70nv650e75z3bw/screenshot_9.png?dl=0

HiZ_PS.hlsl:


struct VertexOut
{
	float4 posH : SV_POSITION;
	float2 tex : TEXCOORD;
};

SamplerState sampPointClamp : register(s0);

Texture2D hiZBuffer : register(t0);

float2 main(VertexOut pIn) : SV_TARGET
{
	float2 texcoords = pIn.tex;
	float4 minDepth = 0.0f;
	float4 maxDepth = 0.0f;

	// sample level zero since only one mip level is available with the bound SRV
	float2 tx = hiZBuffer.SampleLevel(sampPointClamp, texcoords, 0.0f, int2(0, 0)).rg;
	minDepth.r = tx.r;
	maxDepth.r = tx.g;

	float2 ty = hiZBuffer.SampleLevel(sampPointClamp, texcoords, 0.0f, int2(0, 1)).rg;
	minDepth.g = ty.r;
	maxDepth.g = ty.g;

	float2 tz = hiZBuffer.SampleLevel(sampPointClamp, texcoords, 0.0f, int2(1, 0)).rg;
	minDepth.b = tz.r;
	maxDepth.b = tz.g;

	float2 tw = hiZBuffer.SampleLevel(sampPointClamp, texcoords, 0.0f, int2(1, 1)).rg;
	minDepth.a = tw.r;
	maxDepth.a = tw.g;

	return float2(
		min(min(minDepth.r, minDepth.g), min(minDepth.b, minDepth.a)),
		max(max(maxDepth.r, maxDepth.g), max(maxDepth.b, maxDepth.a)));
}

Thinking about it, if really the output is meant to be "the percentage of empty voxel volume relative to the total volume of the cells", then (I think) we should calculate the integration value as:

Reading page 172/173, I think visibility is supposed to be "the percentage of empty space within the minimum and maximum of a depth cell" modulated with the visibility of the previous mip.

So I also think that there is an error on the pre-integration pass, but the correct code would be:

float4 integration = (fineZ.xyzw - minZ) * abs (coarseVolume) * visibility.xyzw;

This makes MIP 1 on page 159 diagram correct but I still have no idea how the 37.5% visibility on MIP 2 was calculated.

Can one of you try the line of code above in your implementation and see how it looks? I haven't had time to implement the article myself.

Btw, has anyone tried to contact the article author about the source code? I wasn't able to find it anywhere.

Hi TiagoCosta,

There definitely seems to be a few things off in the implementation provided in the book. I tried yours out and didn't see much difference, but that could be because we're still wrestling with the actual cone-tracing part itself.

One thing I noticed this afternoon that I had messed up on in the original code I posted to this thread was that I had mixed up some parameters in the cone tracing step. For the method isoscelesTriangleInRadius - I was sending the parameters in backwards. The code should read:


float isoscelesTriangleInRadius(float a, float h)
{
	float a2 = a * a;
	float fh2 = 4.0f * h * h;
	return (a * (sqrt(a2 + fh2) - a)) / (4.0f * h);
}

and should be called from the loop with:


// calculate in-radius of the isosceles triangle
float incircleSize = isoscelesTriangleInRadius(oppositeLength, adjacentLength);

The difference is that I had originally posted adjacentLength as the first parameter, followed by oppositeLength - obviously incorrect, and easily verified here: http://mathworld.wolfram.com/IsoscelesTriangle.html

I'm also wondering if isoscelesTriangleOpposite should be using half the cone angle, since we're basically splitting the cone into two halves to make it a right triangle for finding its sides.


float isoscelesTriangleOpposite(float adjacentLength, float coneTheta)
{
	// should this be like below with the additional *0.5f term?
	return 2.0f * tan(coneTheta * 0.5f) * adjacentLength;
}

I'm not positive on that yet, though, so don't want to steer anyone in the wrong direction if it's incorrect.

EDIT: A little verifying with scratch paper says yes, the cone angle should be halved as shown above. If anyone sees an error in that, please let me know.

Regarding contacting the author, I think several people have already. He's responded to a few posts on his twitter account making it sound like he was unable to release the code for whatever reason: https://twitter.com/YasinUludag/with_replies. The disappointing thing is that the article is obviously incomplete and directs the user to the source for a better understanding at multiple points. Not to mention that Section 4.12 'Acknowledgments' has enough people mentioned, including leadership roles, that surely someone should have had the whereabouts to speak up and stop the article from going out if it were going to be released in an incomplete state. Oh well, we're making some good progress on it, and I'm hoping we can all find a solution together smile.png.

Thanks!

WFP

Hi Tiago, not sure if you saw my post #18 but I proposed the same solution. I think it's probably what was intended.

Hi Tiago, not sure if you saw my post #18 but I proposed the same solution. I think it's probably what was intended.

Yes, I missed that post! Do you have any idea how the 37.5% was calculated? By applying the modified pre integration pass, Mip 2 should also have 50% visibility, however I'm not sure if it is correct

Anyway the formula on post #21 is probably incorrect because it should only calculate the percentage of empty volume between the coarser cell's min and max z, right?


Do you have any idea how the 37.5% was calculated

No idea how that 37.5% value is gotten. Also it doesn't make sense that summing a quarter of each newly calculated values (dot(0.25,a,b,c,d)) can keep the property VisibilityN <= VisibilityN-1. Doesn't make sense.

Here's an example of the case that needs to be solved. (note: you can see the color values that will be fetched by each sphere, the bigger the sphere, the blurrier the fetch). This is the case where a reflection approaches an edge. On the top image the ray hits the sphere, and on the bottom image, the ray hits the background and all of the contribution comes from the edge sphere (so the reflection result is the blurred sky). The question is how should the weight of each of those spheres be set so that there is a smooth transition between the left and the right reflection?

Are you weighting the spheres using the visibility buffer? The article mentions 3 weighting methods: basic averaging, distance-based weighting, and hierarchical pre-integrated visibility buffer weighting.


ct2.jpg
ct3.jpg

Btw, shouldn't each of those circles have a solid color (single texture fetch)?

This topic is closed to new replies.

Advertisement