简单的光线追踪教程(三)
6. 表面法线和多个物体
6.1. 表面法线阴影
首先,我们先设计一个表面法线,这样我们就可以将球面遮住。我们将法线设计为单位长度,则这样方便计算与移植。
球面法线:
double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = dot(r.direction(), r.direction());
auto b = 2.0 * dot(oc, r.direction());
auto c = dot(oc, oc) - radius*radius;
auto discriminant = b*b - 4*a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (-b - sqrt(discriminant) ) / (2.0*a);
}
}
color ray_color(const ray& r) {
auto t = hit_sphere(point3(0,0,-1), 0.5, r);
if (t > 0.0) {
vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
return 0.5*color(N.x()+1, N.y()+1, N.z()+1);
}
vec3 unit_direction = unit_vector(r.direction());
t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}
java实现:
double hitSphere(Vec3 center, double radius, Ray r){
Vec3 oc = Vec3.minus(r.getOrigin(), center);
double a = r.getDirection().lengthSquared();
double halfB = Vec3.dot(oc, r.getDirection());
double c = oc.lengthSquared() - radius * radius;
double discriminant = halfB * halfB - a * c;
if(discriminant < 0){
return -1.0;
}
else{
return (-halfB - Math.sqrt(discriminant)) / a;
}
}
Vec3 rayColor(Ray r, Hittable world, int depth){
HitRecord rec = new HitRecord();
if(depth <= 0){
return new Vec3(0, 0, 0);
}
if(world.hit(r, 0, Data.infinity, rec)){
Vec3 target = Vec3.add(Vec3.add(rec.getP(), rec.getNormal()), Vec3.randomInUnitSphere());
Vec3 v1 = rayColor(new Ray(rec.getP(), Vec3.minus(target, rec.getP())), world, depth - 1);
return Vec3.mulConstant(v1, 0.5);
}
Vec3 unitDirection = Vec3.unitVector(r.getDirection());
double t = 0.5 * (unitDirection.getY() + 1.0);
Vec3 v1 = new Vec3(1.0, 1.0, 1.0);
Vec3 v2 = new Vec3(0.5, 0.7, 1.0);
return Vec3.add(Vec3.mulConstant(v1, (1.0 - t)), Vec3.mulConstant(v2, t));
}
我们就可以得到一个按法向量着色的球体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsPgFsc6-1625488104125)(https://raytracing.github.io/images/img-1.04-normals-sphere.png)]
6.2. 简化射线交球的代码
我们回顾一下我们的射线交球方程:
double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = dot(r.direction(), r.direction());
auto b = 2.0 * dot(oc, r.direction());
auto c = dot(oc, oc) - radius*radius;
auto discriminant = b*b - 4*a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (-b - sqrt(discriminant) ) / (2.0*a);
}
}
我们可得优化之后的代码为:
double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = r.direction().length_squared();
auto half_b = dot(oc, r.direction());
auto c = oc.length_squared() - radius*radius;
auto discriminant = half_b*half_b - a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (-half_b - sqrt(discriminant) ) / a;
}
}
java实现:
double hitSphere(Vec3 center, double radius, Ray r){
Vec3 oc = Vec3.minus(r.getOrigin(), center);
double a = r.getDirection().lengthSquared();
double halfB = Vec3.dot(oc, r.getDirection());
double c = oc.lengthSquared() - radius * radius;
double discriminant = halfB * halfB - a * c;
if(discriminant < 0){
return -1.0;
}
else{
return (-halfB - Math.sqrt(discriminant)) / a;
}
}
6.3. 抽象类:可命中对象Hittable
我们用一个抽象类Hittable来表示一个接收射线命中的函数类。
C++实现:
#ifndef HITTABLE_H
#define HITTABLE_H
#include "ray.h"
struct hit_record {
point3 p;
vec3 normal;
double t;
};
class hittable {
public:
virtual bool hit(const ray& r, double t_min, double t_max, hit_record& rec) const = 0;
};
java实现:
public interface Hittable {
boolean hit(Ray r, double tMin, double tMax, HitRecord rec);
}
对于球体需要实现可命中类的接口,来完成接收射线的击中,画出图形的功能
#ifndef SPHERE_H
#define SPHERE_H
#include "hittable.h"
#include "vec3.h"
class sphere : public hittable {
public:
sphere() {}
sphere(point3 cen, double r) : center(cen), radius(r) {};
virtual bool hit(
const ray& r, double t_min, double t_max, hit_record& rec) const override;
public:
point3 center;
double radius;
};
bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const {
vec3 oc = r.origin() - center;
auto a = r.dire