|
1114 | 1114 |
|
1115 | 1115 | class sphere : public hittable {
|
1116 | 1116 | public:
|
1117 |
| - sphere(const point3& center, double radius) : center(center), radius(radius) {} |
| 1117 | + sphere(const point3& center, double radius) : center(center), radius(fmax(0,radius)) {} |
1118 | 1118 |
|
1119 | 1119 | bool hit(const ray& r, double ray_tmin, double ray_tmax, hit_record& rec) const override {
|
1120 | 1120 | vec3 oc = center - r.origin();
|
|
2795 | 2795 | public:
|
2796 | 2796 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
2797 | 2797 | sphere(const point3& center, double radius, shared_ptr<material> mat)
|
2798 |
| - : center(center), radius(radius), mat(mat) {} |
| 2798 | + : center(center), radius(fmax(0,radius)), mat(mat) {} |
2799 | 2799 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2800 | 2800 |
|
2801 | 2801 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
|
|
3157 | 3157 | splits into a reflected ray and a refracted (transmitted) ray. We’ll handle that by randomly
|
3158 | 3158 | choosing between reflection and refraction, only generating one scattered ray per interaction.
|
3159 | 3159 |
|
| 3160 | +As a quick review of terms, a _reflected_ ray hits a surface and then "bounces" off in a new |
| 3161 | +direction. |
| 3162 | + |
| 3163 | +A _refracted_ ray bends as it transitions from a material's surroundings into the material itself |
| 3164 | +(as with glass or water). This is why a pencil looks bent when partially inserted in water. |
| 3165 | + |
| 3166 | +The amount that a refracted ray bends is determined by the material's _refractive index_. Generally, |
| 3167 | +this is a single value that describes how much light bends when entering a material from a vacuum. |
| 3168 | +Glass has a refractive index of something like 1.5–1.7, and air has a small refractive index |
| 3169 | +of 1.000293. |
| 3170 | + |
| 3171 | +When a transparent material is embedded in a different transparent material, you can describe the |
| 3172 | +refraction with a relative refraction index: the refractive index of the object's material divided |
| 3173 | +by the refractive index of the surrounding material. For example, if you want to render a glass ball |
| 3174 | +under water, then the glass ball would have an effective refractive index of 1.125. This is given by |
| 3175 | +the refractive index of glass (1.5) divided by the refractive index of water (1.333). |
| 3176 | + |
| 3177 | +You can find the refractive index of most common materials with a quick internet search. |
| 3178 | + |
3160 | 3179 |
|
3161 | 3180 | Refraction
|
3162 | 3181 | -----------
|
|
3257 | 3276 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ Highlight
|
3258 | 3277 | class dielectric : public material {
|
3259 | 3278 | public:
|
3260 |
| - dielectric(double ref_index) : ref_index(ref_index) {} |
| 3279 | + dielectric(double refraction_index) : refraction_index(refraction_index) {} |
3261 | 3280 |
|
3262 | 3281 | bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
|
3263 | 3282 | const override {
|
3264 | 3283 | attenuation = color(1.0, 1.0, 1.0);
|
3265 |
| - double refraction_ratio = rec.front_face ? (1.0/ref_index) : ref_index; |
| 3284 | + double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; |
3266 | 3285 |
|
3267 | 3286 | vec3 unit_direction = unit_vector(r_in.direction());
|
3268 |
| - vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); |
| 3287 | + vec3 refracted = refract(unit_direction, rec.normal, ri); |
3269 | 3288 |
|
3270 | 3289 | scattered = ray(rec.p, refracted);
|
3271 | 3290 | return true;
|
3272 | 3291 | }
|
3273 | 3292 |
|
3274 | 3293 | private:
|
3275 |
| - double ref_index; // Refractive index in vacuum or air, or the ratio of the material's |
3276 |
| - // refractive index over the refractive index of the enclosing media |
| 3294 | + // Refractive index in vacuum or air, or the ratio of the material's refractive index over |
| 3295 | + // the refractive index of the enclosing media |
| 3296 | + double refraction_index; |
3277 | 3297 | };
|
3278 | 3298 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
3279 | 3299 | [Listing [dielectric-always-refract]: <kbd>[material.h]</kbd>
|
|
3327 | 3347 | solution does not exist, the glass cannot refract, and therefore must reflect the ray:
|
3328 | 3348 |
|
3329 | 3349 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3330 |
| - if (refraction_ratio * sin_theta > 1.0) { |
| 3350 | + if (ri * sin_theta > 1.0) { |
3331 | 3351 | // Must Reflect
|
3332 | 3352 | ...
|
3333 | 3353 | } else {
|
|
3355 | 3375 | double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
|
3356 | 3376 | double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
|
3357 | 3377 |
|
3358 |
| - if (refraction_ratio * sin_theta > 1.0) { |
| 3378 | + if (ri * sin_theta > 1.0) { |
3359 | 3379 | // Must Reflect
|
3360 | 3380 | ...
|
3361 | 3381 | } else {
|
|
3371 | 3391 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3372 | 3392 | class dielectric : public material {
|
3373 | 3393 | public:
|
3374 |
| - dielectric(double ref_index) : ref_index(ref_index) {} |
| 3394 | + dielectric(double refraction_index) : refraction_index(refraction_index) {} |
3375 | 3395 |
|
3376 | 3396 | bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
|
3377 | 3397 | const override {
|
3378 | 3398 | attenuation = color(1.0, 1.0, 1.0);
|
3379 |
| - double refraction_ratio = rec.front_face ? (1.0/ref_index) : ref_index; |
| 3399 | + double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; |
3380 | 3400 |
|
3381 | 3401 | vec3 unit_direction = unit_vector(r_in.direction());
|
3382 | 3402 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
3383 | 3403 | double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
|
3384 | 3404 | double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
|
3385 | 3405 |
|
3386 |
| - bool cannot_refract = refraction_ratio * sin_theta > 1.0; |
| 3406 | + bool cannot_refract = ri * sin_theta > 1.0; |
3387 | 3407 | vec3 direction;
|
3388 | 3408 |
|
3389 | 3409 | if (cannot_refract)
|
3390 | 3410 | direction = reflect(unit_direction, rec.normal);
|
3391 | 3411 | else
|
3392 |
| - direction = refract(unit_direction, rec.normal, refraction_ratio); |
| 3412 | + direction = refract(unit_direction, rec.normal, ri); |
3393 | 3413 |
|
3394 | 3414 | scattered = ray(rec.p, direction);
|
3395 | 3415 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3396 | 3416 | return true;
|
3397 | 3417 | }
|
3398 | 3418 |
|
3399 | 3419 | private:
|
3400 |
| - double ref_index; // Refractive index in vacuum or air, or the ratio of the material's |
3401 |
| - // refractive index over the refractive index of the enclosing media |
| 3420 | + // Refractive index in vacuum or air, or the ratio of the material's refractive index over |
| 3421 | + // the refractive index of the enclosing media |
| 3422 | + double refraction_index; |
3402 | 3423 | };
|
3403 | 3424 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
3404 | 3425 | [Listing [dielectric-with-refraction]: <kbd>[material.h]</kbd>
|
|
3440 | 3461 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3441 | 3462 | class dielectric : public material {
|
3442 | 3463 | public:
|
3443 |
| - dielectric(double ref_index) : ref_index(ref_index) {} |
| 3464 | + dielectric(double refraction_index) : refraction_index(refraction_index) {} |
3444 | 3465 |
|
3445 | 3466 | bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
|
3446 | 3467 | const override {
|
3447 | 3468 | attenuation = color(1.0, 1.0, 1.0);
|
3448 |
| - double refraction_ratio = rec.front_face ? (1.0/ref_index) : ref_index; |
| 3469 | + double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; |
3449 | 3470 |
|
3450 | 3471 | vec3 unit_direction = unit_vector(r_in.direction());
|
3451 | 3472 | double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
|
3452 | 3473 | double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
|
3453 | 3474 |
|
3454 |
| - bool cannot_refract = refraction_ratio * sin_theta > 1.0; |
| 3475 | + bool cannot_refract = ri * sin_theta > 1.0; |
3455 | 3476 | vec3 direction;
|
3456 | 3477 |
|
3457 | 3478 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
3458 |
| - if (cannot_refract || reflectance(cos_theta, refraction_ratio) > random_double()) |
| 3479 | + if (cannot_refract || reflectance(cos_theta, ri) > random_double()) |
3459 | 3480 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3460 | 3481 | direction = reflect(unit_direction, rec.normal);
|
3461 | 3482 | else
|
3462 |
| - direction = refract(unit_direction, rec.normal, refraction_ratio); |
| 3483 | + direction = refract(unit_direction, rec.normal, ri); |
3463 | 3484 |
|
3464 | 3485 | scattered = ray(rec.p, direction);
|
3465 | 3486 | return true;
|
3466 | 3487 | }
|
3467 | 3488 |
|
3468 | 3489 | private:
|
3469 |
| - double ref_index; // Refractive index in vacuum or air, or the ratio of the material's |
3470 |
| - // refractive index over the refractive index of the enclosing media |
| 3490 | + // Refractive index in vacuum or air, or the ratio of the material's refractive index over |
| 3491 | + // the refractive index of the enclosing media |
| 3492 | + double refraction_index; |
3471 | 3493 |
|
3472 | 3494 |
|
3473 | 3495 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
3474 |
| - static double reflectance(double cosine, double ref_idx) { |
| 3496 | + static double reflectance(double cosine, double refraction_index) { |
3475 | 3497 | // Use Schlick's approximation for reflectance.
|
3476 |
| - auto r0 = (1-ref_idx) / (1+ref_idx); |
| 3498 | + auto r0 = (1-refraction_index) / (1+refraction_index); |
3477 | 3499 | r0 = r0*r0;
|
3478 | 3500 | return r0 + (1-r0)*pow((1 - cosine),5);
|
3479 | 3501 | }
|
|
3485 | 3507 |
|
3486 | 3508 | Modeling a Hollow Glass Sphere
|
3487 | 3509 | -------------------------------
|
3488 |
| -An interesting and easy trick with dielectric spheres is to note that if you use a negative radius, |
3489 |
| -the geometry is unaffected, but the surface normal points inward. |
3490 |
| - |
3491 |
| -However, properly handling negative radii can be tricky. Recall the line from `sphere::hit()` in |
3492 |
| -listing [sphere-material] that calculates the outward normal: |
3493 |
| - |
3494 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
3495 |
| - vec3 outward_normal = (rec.p - center) / radius; |
3496 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
3497 |
| - [Listing [proper-invert-sphere-normal]: Proper normal handling for spheres with negative radii] |
3498 |
| - |
3499 |
| -In your own implementation, you might have been tempted to instead do something like this: |
| 3510 | +Let's model a hollow glass sphere. This is a sphere of some thickness with another sphere of air |
| 3511 | +inside it. If you think about the path of a ray going through such an object, it will hit the outer |
| 3512 | +sphere, refract, hit the inner sphere (assuming we do hit it), refract a second time, and travel |
| 3513 | +through the air inside. Then it will continue on, hit the inside surface of the inner sphere, |
| 3514 | +refract back, then hit the inside surface of the outer sphere, and finally refract and exit back |
| 3515 | +into the scene atmosphere. |
3500 | 3516 |
|
3501 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
3502 |
| - vec3 outward_normal = (rec.p - center).unit_vector(); |
3503 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
3504 |
| - [Listing [improper-invert-sphere-normal]: |
3505 |
| - Problematic normal calculation for spheres with negative radii |
3506 |
| - ] |
| 3517 | +The outer sphere is just modeled with a standard glass sphere, with a refractive index of around |
| 3518 | +1.50 (modeling a refraction from the outside air into glass). The inner sphere is a bit different |
| 3519 | +because _its_ refractive index should be relative to the material of the surrounding outer sphere, |
| 3520 | +thus modeling a transition from glass into the inner air. |
3507 | 3521 |
|
3508 |
| -If you do that, spheres with negative radii won't work properly. Since a sphere with a negative |
3509 |
| -radius is a _bubble_, its interior is the infinite space outside the sphere. Its exterior is the |
3510 |
| -finite bubble inside the sphere, so the outward normal needs to point toward the sphere center. |
3511 |
| -Dividing by the (negative) radius flips the normal as we want. If you implmented your code like the |
3512 |
| -second example above, you'll want to fix that now. |
| 3522 | +This is actually simple to specify, as the `refraction_index` parameter to the dielectric material |
| 3523 | +can be interpreted as the _ratio_ of the refractive index of the object divided by the refractive |
| 3524 | +index of the enclosing medium. In this case, the inner sphere would have an refractive index of air |
| 3525 | +(the inner sphere material) over the index of refraction of glass (the enclosing medium), or |
| 3526 | +$1.00/1.50 = 0.67$. |
3513 | 3527 |
|
3514 |
| -Let's use this hollow sphere hack to model the interior of a sphere with a given thickness. To do |
3515 |
| -this, add a second _inverted_ glass sphere inside the original glass sphere: |
| 3528 | +Here's the code: |
3516 | 3529 |
|
3517 | 3530 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3518 | 3531 | ...
|
| 3532 | + auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0)); |
| 3533 | + auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5)); |
| 3534 | + auto material_left = make_shared<dielectric>(1.50); |
| 3535 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
| 3536 | + auto material_bubble = make_shared<dielectric>(0.67); |
| 3537 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 3538 | + auto material_right = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0); |
| 3539 | + |
3519 | 3540 | world.add(make_shared<sphere>(point3( 0.0, -100.5, -1.0), 100.0, material_ground));
|
3520 | 3541 | world.add(make_shared<sphere>(point3( 0.0, 0.0, -1.0), 0.5, material_center));
|
3521 | 3542 | world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.5, material_left));
|
3522 | 3543 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
3523 |
| - world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), -0.4, material_left)); |
| 3544 | + world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.4, material_bubble)); |
3524 | 3545 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3525 | 3546 | world.add(make_shared<sphere>(point3( 1.0, 0.0, -1.0), 0.5, material_right));
|
3526 | 3547 | ...
|
3527 | 3548 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
3528 | 3549 | [Listing [scene-hollow-glass]: <kbd>[main.cc]</kbd> Scene with hollow glass sphere]
|
3529 | 3550 |
|
3530 | 3551 | <div class='together'>
|
3531 |
| -This gives: |
| 3552 | +And here's the result: |
3532 | 3553 |
|
3533 | 3554 | 
|
|
3780 | 3801 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
3781 | 3802 | auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
|
3782 | 3803 | auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5));
|
3783 |
| - auto material_left = make_shared<dielectric>(1.5); |
| 3804 | + auto material_left = make_shared<dielectric>(1.50); |
| 3805 | + auto material_bubble = make_shared<dielectric>(0.67); |
3784 | 3806 | auto material_right = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0);
|
3785 | 3807 |
|
3786 | 3808 | world.add(make_shared<sphere>(point3( 0.0, -100.5, -1.0), 100.0, material_ground));
|
3787 | 3809 | world.add(make_shared<sphere>(point3( 0.0, 0.0, -1.0), 0.5, material_center));
|
3788 | 3810 | world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.5, material_left));
|
3789 |
| - world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), -0.4, material_left)); |
| 3811 | + world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.4, material_bubble)); |
3790 | 3812 | world.add(make_shared<sphere>(point3( 1.0, 0.0, -1.0), 0.5, material_right));
|
3791 | 3813 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
3792 | 3814 |
|
|
0 commit comments