|
2292 | 2292 | surprisingly complicated to understand, and quite a bit complicated to implement. Instead, we'll use |
2293 | 2293 | what is typically the easiest algorithm: A rejection method. A rejection method works by repeatedly |
2294 | 2294 | generating random samples until we produce a sample that meets the desired criteria. In other words, |
2295 | | -keep rejecting samples until you find a good one. |
| 2295 | +keep rejecting bad samples until you find a good one. |
2296 | 2296 |
|
2297 | 2297 | There are many equally valid ways of generating a random vector on a hemisphere using the rejection |
2298 | 2298 | method, but for our purposes we will go with the simplest, which is: |
2299 | 2299 |
|
2300 | | -1. Generate a random vector inside of the unit sphere |
2301 | | -2. Normalize this vector |
| 2300 | +1. Generate a random vector inside the unit sphere |
| 2301 | +2. Normalize this vector to extend it to the sphere surface |
2302 | 2302 | 3. Invert the normalized vector if it falls onto the wrong hemisphere |
2303 | 2303 |
|
2304 | 2304 | <div class='together'> |
2305 | 2305 | First, we will use a rejection method to generate the random vector inside the unit sphere (that is, |
2306 | 2306 | a sphere of radius 1). Pick a random point inside the cube enclosing the unit sphere (that is, where |
2307 | | -$x$, $y$, and $z$ are all in the range $[-1,+1]$). If this point lies outside (or on) the unit |
2308 | | -sphere, then generate a new one until we find one that lies inside the unit sphere. |
| 2307 | +$x$, $y$, and $z$ are all in the range $[-1,+1]$). If this point lies outside the unit |
| 2308 | +sphere, then generate a new one until we find one that lies inside or on the unit sphere. |
2309 | 2309 |
|
2310 | | - ![Figure [sphere-vec]: Two vectors were rejected before finding a good one |
| 2310 | + ![Figure [sphere-vec]: Two vectors were rejected before finding a good one (pre-normalization) |
2311 | 2311 | ](../images/fig-1.11-sphere-vec.jpg) |
2312 | 2312 |
|
| 2313 | + ![Figure [sphere-vec]: The accepted random vector is normalized to produce a unit vector |
| 2314 | + ](../images/fig-1.12-sphere-unit-vec.jpg) |
| 2315 | + |
| 2316 | +Here's our first draft of the function: |
| 2317 | + |
2313 | 2318 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2314 | 2319 | ... |
2315 | 2320 |
|
|
2319 | 2324 |
|
2320 | 2325 |
|
2321 | 2326 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2322 | | - inline vec3 random_in_unit_sphere() { |
| 2327 | + inline vec3 random_unit_vector() { |
2323 | 2328 | while (true) { |
2324 | 2329 | auto p = vec3::random(-1,1); |
2325 | | - if (p.length_squared() < 1) |
2326 | | - return p; |
| 2330 | + auto lensq = p.length_squared(); |
| 2331 | + if (lensq <= 1) |
| 2332 | + return p / sqrt(lensq); |
2327 | 2333 | } |
2328 | 2334 | } |
2329 | 2335 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
2330 | | - [Listing [random-in-unit-sphere]: <kbd>[vec3.h]</kbd> The random_in_unit_sphere() function] |
| 2336 | + [Listing [random-in-unit-sphere]: <kbd>[vec3.h]</kbd> The random_unit_vector() function, version one] |
2331 | 2337 |
|
2332 | 2338 | </div> |
2333 | 2339 |
|
2334 | | -<div class='together'> |
2335 | | -Once we have a random vector in the unit sphere we need to normalize it to get a vector _on_ the |
2336 | | -unit sphere. |
| 2340 | +Sadly, we have a small floating-point abstraction leak to deal with. Since floating-point numbers |
| 2341 | +have finite precision, a very small value can underflow to zero when squared. So if all three |
| 2342 | +coordinates are small enough (that is, very near the center of the sphere), the norm of the vector |
| 2343 | +will be zero, and thus normalizing will yield the bogus vector $[\pm\infty, \pm\infty, \pm\infty]$. |
| 2344 | +To fix this, we'll also reject points that lie inside this "black hole" around the center. With |
| 2345 | +double precision (64-bit floats), we can safely support values greater than $10^{-160}$. |
2337 | 2346 |
|
2338 | | - ![Figure [sphere-vec]: The accepted random vector is normalized to produce a unit vector |
2339 | | - ](../images/fig-1.12-sphere-unit-vec.jpg) |
| 2347 | +Here's our more robust function: |
2340 | 2348 |
|
2341 | 2349 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2342 | | - ... |
2343 | | - |
2344 | | - inline vec3 random_in_unit_sphere() { |
| 2350 | + inline vec3 random_unit_vector() { |
2345 | 2351 | while (true) { |
2346 | 2352 | auto p = vec3::random(-1,1); |
2347 | | - if (p.length_squared() < 1) |
2348 | | - return p; |
2349 | | - } |
2350 | | - } |
2351 | | - |
2352 | | - |
| 2353 | + auto lensq = p.length_squared(); |
2353 | 2354 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2354 | | - inline vec3 random_unit_vector() { |
2355 | | - return unit_vector(random_in_unit_sphere()); |
| 2355 | + if (1e-160 < lensq && lensq <= 1) |
| 2356 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 2357 | + return p / sqrt(lensq); |
| 2358 | + } |
2356 | 2359 | } |
2357 | 2360 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
2358 | | - [Listing [random-unit-vec]: <kbd>[vec3.h]</kbd> Random vector on the unit sphere] |
2359 | | - |
2360 | | -</div> |
| 2361 | + [Listing [random-in-unit-sphere]: <kbd>[vec3.h]</kbd> The random_unit_vector() function, version one] |
2361 | 2362 |
|
2362 | 2363 | <div class='together'> |
2363 | | -And now that we have a random vector on the surface of the unit sphere, we can determine if it is on |
2364 | | -the correct hemisphere by comparing against the surface normal: |
| 2364 | +Now that we have a random vector on the surface of the unit sphere, we can determine if it is on the |
| 2365 | +correct hemisphere by comparing against the surface normal: |
2365 | 2366 |
|
2366 | 2367 | ![Figure [normal-hor]: The normal vector tells us which hemisphere we need |
2367 | 2368 | ](../images/fig-1.13-surface-normal.jpg) |
|
2377 | 2378 | ... |
2378 | 2379 |
|
2379 | 2380 | inline vec3 random_unit_vector() { |
2380 | | - return unit_vector(random_in_unit_sphere()); |
| 2381 | + while (true) { |
| 2382 | + auto p = vec3::random(-1,1); |
| 2383 | + auto lensq = p.length_squared(); |
| 2384 | + if (1e-160 < lensq && lensq <= 1) |
| 2385 | + return p / sqrt(lensq); |
| 2386 | + } |
2381 | 2387 | } |
2382 | 2388 |
|
2383 | 2389 |
|
|
0 commit comments