一个周末搞懂光线追踪 理论+实践
本文翻译自 Ray Tracing One Weekend
- 后续将继续做出演示demo,敬请关注
1 显示结果的方法
- 使用PPM格式图片展示渲染结果
- PPM格式很方便,改了就能看到改变
2 vec3类
- 虽然有4D vector,但是本文使用3D vector就够了
- 位置、缩放、旋转
- 颜色
- 提供基本的向量运算操作
- 数学4则运算
- 模长
3 射线、简单的相机、背景
- ray tracer
- ray class
- 计算一条射线能看到什么颜色
- P(t) = A + t * B
- t 浮点参数
- A 射线起点
- B 射线方向
- 射线追踪的核心
- 发射穿过像素的射线
- 计算通过这些射线能看到的颜色
- 使用的图片
- 200x100
- 中心点(0,0)
- y轴向上、x轴向右
- 朝向屏幕的是z轴负方向,朝向屏幕外的是z轴正方向
- 从屏幕左下角扫描屏幕,使用2个偏移向量来移动射线终点来穿过屏幕
lower_left_corner + u*horizontal + v*vertical
- horizontal: (4.0,0.0,0.0)
- vertical: (0.0,2.0,0.0)
- 没有让射线是单位向量,为了代码保持简单轻快
- 流程
遍历每一个像素,把像素坐标转换成到0,1),计算指向每一个像素的射线,计算这个射线的颜色
4 加入球体
- 球体计算直接了当
x*x + y*y + z*z = R*R
(x - cx)^2 + (y - cy)^2 + (z - cz)^2 = R*R
- 点p,
dot((p-c),(p-c))=R*R
- 根据公式
p(t)=A+t*B
dot(p(t)-c,p(t)-c)=R*R
- 用p(t)展开的形式:
dot(A+t*B-C,A+t*B-C)=R*R
- 展开等式并且左移后得:
t*t*dot(B,B)+2*t*dot(A-C,A-C)+dot(C,C)-R*R=0
5 表面法线和多个物体
- 表面法线垂直于表面指向外面
- 球体法线:撞击点 - 球心
- 显示法线的颜色
- 如果是单位向量,范围是(-1,1),需要把这些值映射到(0,1)
- x = (x + 1) * 0.5
- 着色时如果射线抵达的是球体表面显示法线颜色
- 如果是单位向量,范围是(-1,1),需要把这些值映射到(0,1)
- 增加了一个hitable类,用来计算给定射线和tmin, tmax,射线射中了哪里
- 新的hit函数
hit(ray r, float tmin, float tmax, hit_recrod rec)
- 计算射中球体表面时增加了选择的策略
- 优先使用距离摄像机较近的那个点,即计算结果中t更小的那个点
- 这个t必须满足 tmin < t < tmax
- 新的hit函数
- 创建多个球体用来显示,更新了球体的类和main函数
- 在main函数中遍历一个hitable列表来渲染多个球体
6 抗锯齿
- 编写一个Camera类来创建射线
- 在一个像素点周围做一组采样然后求平均值
- 在计算一个像素点的颜色的时候循环若干次,随机的在其周围获取像素并且求平均值
7 漫反射材质
- 分离几何体与材质
- 漫反射
- 漫反射物体不发光只根据自身颜色反射环境光
- 光线在漫反射表面随机反射
- 如果向2个漫反射物体的夹角里发射射线,射线会随机反射
- 吸收大于反射
- 创建反射射线
- 找到撞击点hit point,撞击点沿着法线上移0.5,获得一个单位球的球心
- 在球内随机选取一点的方法
- 在恰好包裹立方体的圆里随机选一点
- 如果这个点不在球内,重复上一步,直到获得可用的随机点s
- 获得射线 s - hit point,求这条射线的颜色 color
- 最终颜色 = 吸收率 * color
- 伽马矫正:(0,1)的值在被存储为byte前需要被转换
- 有很多原因,我们知道这事就可以
- “gamma 2”
- 提亮颜色:颜色的1/gamma次方
- 我们这里使用:color的(1/2)次方,即开方
8 金属材质
- 为了显示不同材质的物体
- 需要不同材质类型,用不同的参数调整
- 编写材质类
- 创建散射光线
- 指明吸收率
- 金属材质使用反射公式直接计算反射射线
- 物体和材质需要互相引用,出现循环引用
- 反射策略
- 用反射率R来削弱射线
- 使用1-R来削弱射线
- 混用以上2种方法
- Lambertian材质,拥有反照率参数
- 越大的球体其反射越显得模糊
- 增加一个模糊参数fuzz
- 计算最终的反射射线 reflected + fuzz * random_in_unit_sphere
- 根据不同大小的球体传入不同的fuzz控制反射射线随机偏移程度,从而控制模糊程度
9 半透明物体
- 水、玻璃、钻石,半透明物体同时具有反射和折射
- 射线碰撞到物体时,在反射和折射中随机选一种作为追踪的方式
- 折射光线的debug是困难的
- 折射公式:n*sign(theta) = n’*sign(theta’)
- 折射率过大时折射不会再发生
- 实践中反射和折射都是发生在固体之间
- 球体法线向内可以做出气泡效果
- 本章新增dielectric材质用于渲染半透明物体
- 如果用一个陡峭的角度,比如基本垂直着看过去,会像镜面,使用Christophe Schlick的简化公式
- 计算一个阈值,当随机一个点是用折射时发现随机出的值小于阈值,那这里是作为镜面,则使用反射
10 可以调整位置的相机
- 对相机进行debug是痛苦的
- adjustable field of view
- vertical fov
- 和以前相机的不同
- 相机制作从左下角到右上角进行扫描的射线
- 现在使用vfov来准备制作扫描射线的参数,定义宽高比
- camera(float vfov, float aspect)
- 关心的位置
- lookfrom
- lookat
- 如果需要,这个可以变成方向
- 处理旋转
- view up(vup)
- u 水平方向
- v 垂直方向
- w 从物质指向相机的向量
- 以上向量都不是单位向量
- 新的左下角:origin - half_widthu - half_heightv - w
- 和仅有fov的情况以前相比,现在可以控制相机的位置和朝向
11 视野深度
- 现实中为了获得更多的光线使用大的孔
- 如果从孔中伸出一个透镜,会有一个距离能让所有的东西都被聚焦
- 这个距离被相机的透镜和感知器的距离控制
- 光圈越大获得的光线越多
- 虚拟相机有完美的感知器并且不需要更多光线,所有我们只需要光圈
- 模拟感知、透镜、光圈,指出从哪里发射射线,翻转图片
- 胶卷中的图片上下颠倒
- 具体到代码
- 发射射线的时候从一个圆的表面随机的选取一个点作为射线的起点
12 后续
- 放一堆材质球到场景里面
- 列表
- 光
- 散射射线
- 模型
- 纹理
- 坚实材质
- 体积和介质,比如冰块
- 并行,用不同的种子+核心运行不同追踪器,gpu ray tracing