pouët.net

How to calculate the correct viewing cone radius at any distance in raymarching?

category: code [glöplog]
 
Raymarching distance fields aka sphere-tracing. To implement cone-marching atop of it (and also to minimize the number of raymarching steps regardless of whether cone-marching is added or not), I need to estimate the radius of the ray cone at any given distance.

Recall with raymarching distance fields, a "hit" is recorded when the distance to an object is smaller than a threshold value, often in code named nearLimit or epsilon. This threshold can be seen as equivalent to the ray-cone radius if we increase it exponentially with distance traveled -- this way, we don't shoot straight thin ray lines into space but cones expanding in accordance with perspective projection. This more accurately covers catching the "right" distant objects (at this point let's ignore the issue of blending materials and filtering normals of all intersected objects in the viewing cone at distance t for now...).

At step 0, for a view-plane of unit-width, that radius can be approximated by something such as:

float fInitialRadius = 1 / min(screenwidth, screenheight);

This can then be increased at each step exponentially by applying the starting radius to the distance; aka naive approach:

fNearLimit = fTotalDist * fInitialRadius; // after each raymarching step

This works OK but still has artifacts. If I use fInitialRadius*fInitialRadius (resulting in a smaller number since initial radius for a 640px framebuffer and a unit-width view-plane is 1/640) I get less artifacts and a more accurate result. But both approaches are sub-optimal, the first is too eager (increases radius too much too early -- undersampling), the latter too lazy (increases radius too little too late -- oversampling).

The most accurate factor to increase fNearLimit (the cone radius) at a given distance MUST most likely take into account my current field of view and will vary depending on whether field-of-view is 45° or 60° or 90° or...

TL;DR: I want to know what's the proper calculation or most acceptable approximation of the cone radius at a given distance given the initial pixel radius at step 0 and the field of view angle?
added on the 2012-05-08 06:24:51 by voxelizr voxelizr
I don't know the answer but here is my thinking: when converting 3d points to 2d you multiply the coords by a constant and divide by z.

To calculate a size that would appear the same as it goes off into the distance, you would just use the inverse of that calculation?

(like i said, i'm not sure, but that is my first thought).
added on the 2012-05-08 08:41:57 by xeron xeron
Yeah you're correct, I've by now reached the same conclusion: this is effectively just the fov-based perspective projection inverted and applied to a pixel-sized disc. =)
added on the 2012-05-08 09:09:14 by voxelizr voxelizr
I guess if you want to do this "right" then you need to consider that your projection plane is flat, not spherical.

In camera space, if your projection plane is z = z0, and the diameter at z = z0 is 1 pixel, then the diameter at any given z is (1 pixel) * z / z0. So that's the simple approximation and accurate at the precise centre of the screen. Look at this, though:

BB Image

At the bottom left is the result of a circular cone intersecting a plane at an angle. The intersection is elliptical. So if you want the cone to encompass all the space behind a circular disc centered on a given pixel, then the cone will need to be elliptical to compensate for that distortion. Otherwise you might see an aliasing effect along radial lines. It may be subtle and irrelevant, though.
added on the 2012-05-08 11:27:14 by doomdoom doomdoom
Good points, doomdoom! Let's simplify things a bit.

Indeed of course I'm still getting such aliasing artifacts -- but regardless of those, to implement cone-marching, I would need to at least approximate a "suitable radius" that errs on the right side -- what happens here in cone-marching is comparing "the size of the sphere" (curPos + distance returned by the distance field) to the "current cone width" at the current position.

Ignoring the whole radial-cone / pixel-spherical-disc approximation for a moment and considering the rectangular "view frustum". For a view-plane w world units wide and projecting to 320px * 320px screen units, the total width of the frustum at distance t should be t * w * tan(fovRadians * 0.5), correct? (... or times 1/tan ... or div by tan... or div by 1/tan? :)) For fov=90° then the whole tan (or 1/tan etc.) equals 1, so t * w. (width=height for now. Later then take the min of both.)

So for a given pixel p, the rectangular sub-portion of the entire up-scaled-as-above view frustum at that distance t is t * w / 320. To err on the safe side, my cone circle then needs to fit inside that rectangle but not overlap it, so radius = min(pixelRectInFrustumWidth, pixelRectInFrustumHeight) / 2.

See any issues with this reasoning? It's tricky to "test this visually" since I'm getting aliasing artifacts one way or the other, mostly at the edges though which makes me think I shouldn't use the "last cone radius recorded" as the normal epsilon...
added on the 2012-05-08 12:22:58 by voxelizr voxelizr
Look at Kevin Beasons SmallPt
http://www.kevinbeason.com/smallpt/

In particular check out the presentation slides page 62 from smallPt
http://www.kevinbeason.com/smallpt/smallpt_cline_slides.ppt

The pdf explains the version of smallPT with explicit lighting. This version samples lights by casting out rays in a cone towards the sphere / light source. The radius of the cone is calculated as follows...

Code: double cos_a_max = sqrt( 1 - s.rad*s.rad / ( x-s.p ).dot( x-s.p );


Where:
x is ray intersection point
s.p is sphere / light source position
s.rad is the radius of the sphere

login