745 字
4 分钟
光线追踪:灯光

光照是光线追踪的关键组成部分。早期简单的光线追踪器使用抽象光源,比如空间中的点或方向。现代方法则采用更符合物理规律的光源,它们具有位置和尺寸。要创建这样的光源,我们需要能够将任何常规物体转化为场景中的发光体

自发光材质#

//自发光材质
class diffuse_light : public material {
public:
diffuse_light(std::shared_ptr<texture> tex) : tex(tex) {} //纹理作为发光颜色
diffuse_light(const color& emit) : tex(std::make_shared<solid_color>(emit)) {} //传入颜色包装为纯色纹理
// 物体本身发出的光
color emitted(double u, double v, const point3& p) const override {
return tex->value(u, v, p);
}
private:
std::shared_ptr<texture> tex;
};

为了避免让所有非发光材质都实现 emitted() ,我在基类中让它返回黑色

virtual color emitted(double u, double v, const point3& p) const {
return color(0,0,0);
}

为光线颜色函数添加背景色#

接下来,我们希望背景是纯黑的,这样场景中唯一的光源就来自发光体。为此,我们将在 ray_color 函数中添加一个背景色参数,并关注新的 color_from_emission 值。

color ray_color(const ray& r, int depth, const hittable& world) const {
hit_record rec;
// 递归深度耗尽时返回黑色,表示这条光线不再贡献能量。
if(depth <= 0)
return color(0,0,0);
// 如果光线没有击中物体,返回背景颜色
if (!world.hit(r, interval(0.001, infinity), rec))
return background;
// scattered 保存材质散射出的下一条光线,attenuation 表示本次散射后的颜色衰减。
ray scattered;
color attenuation;
// 先取材质自身发出的光;普通材质通常返回黑色,光源材质会返回发光颜色。
color color_from_emission = rec.mat->emitted(rec.u,rec.v,rec.p);
// 如果材质不散射光线,说明递归在这里结束,只返回自发光部分。
if(!rec.mat->scatter(r, rec, attenuation, scattered))
return color_from_emission;
// 递归追踪散射光线,并乘以材质衰减,得到反射/折射带来的间接光。
color color_from_scatter = attenuation * ray_color(scattered, depth - 1, world);
// 最终颜色 = 材质自身发光 + 散射光线继续追踪得到的颜色。
return color_from_emission + color_from_scatter;
}

将物体转化为光源#

我们把一个矩形设置为光源:

注意,光源比 (1,1,1) 更亮。这样才能有足够的亮度来照亮物体

void simple_light() {
hittable_list world;
auto pertext = make_shared<noise_texture>(4);
world.add(make_shared<sphere>(point3(0,-1000,0), 1000, make_shared<lambertian>(pertext)));
world.add(make_shared<sphere>(point3(0,2,0), 2, make_shared<lambertian>(pertext)));
auto difflight = make_shared<diffuse_light>(color(4,4,4));
world.add(make_shared<quad>(point3(3,1,-2), vec3(2,0,0), vec3(0,2,0), difflight));
camera cam;
cam.aspect_ratio = 16.0 / 9.0;
cam.image_width = 400;
cam.samples_per_pixel = 100;
cam.max_depth = 50;
cam.background = color(0,0,0);
cam.vfov = 20;
cam.lookfrom = point3(26,3,6);
cam.lookat = point3(0,2,0);
cam.vup = vec3(0,1,0);
cam.defocus_angle = 0;
cam.render(world);
}

空的康奈尔盒#

void cornell_box() {
hittable_list world;
auto red = make_shared<lambertian>(color(.65, .05, .05));
auto white = make_shared<lambertian>(color(.73, .73, .73));
auto green = make_shared<lambertian>(color(.12, .45, .15));
auto light = make_shared<diffuse_light>(color(15, 15, 15));
world.add(make_shared<quad>(point3(555,0,0), vec3(0,555,0), vec3(0,0,555), green));
world.add(make_shared<quad>(point3(0,0,0), vec3(0,555,0), vec3(0,0,555), red));
world.add(make_shared<quad>(point3(343, 554, 332), vec3(-130,0,0), vec3(0,0,-105), light));
world.add(make_shared<quad>(point3(0,0,0), vec3(555,0,0), vec3(0,0,555), white));
world.add(make_shared<quad>(point3(555,555,555), vec3(-555,0,0), vec3(0,0,-555), white));
world.add(make_shared<quad>(point3(0,0,555), vec3(555,0,0), vec3(0,555,0), white));
camera cam;
cam.aspect_ratio = 1.0;
cam.image_width = 600;
cam.samples_per_pixel = 200;
cam.max_depth = 50;
cam.background = color(0,0,0);
cam.vfov = 40;
cam.lookfrom = point3(278, 278, -800);
cam.lookat = point3(278, 278, 0);
cam.vup = vec3(0,1,0);
cam.defocus_angle = 0;
cam.render(world);
}

由于光源很小,大部分随机光线无法击中光源,因此图像噪点非常多

光线追踪:灯光
https://dingfengbo.vercel.app/posts/rtiow/灯光/
作者
Eureka
发布于
2026-05-29
许可协议
CC BY-NC-SA 4.0