|
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