实现效果
下图中所有的元素都是代码生成的,题目源于《现代图形学作业5-闫令琪》,这个图在很多地方都看到,不知道是哪位大咖最先提出来的模型。
完整代码: github.com/summer-go/g…
欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~
什么是光线追踪
回顾下光栅化
将立体空间中的物体映射到2D屏幕上,本质上是一个采样的流程
上图举了最简单的例子-三角形光栅化,实际上光栅化的方式也可以渲染比较复杂的场景。
但是要实现更真实的效果,得用到光线追踪
光线追踪
要逼近真实感,需从能量守恒的角度,模拟光线传播的路径。从屏幕上每一个像素点射出一条光线,撞到第一个物体,再反弹出去,反向模拟光线传输(镜面反射、漫反射、折射)的效果,上图中,圆球上的一个点的颜色实际由多个光源、其他物体反射的光线叠加而成。
Whitted-style光线追踪实现
光线追踪最基本的原理很简单,实现有许多细节需要注意。下文实现whitted-style风格的光线追踪。
John Turner Whitted既是一位电子工程师,也是一位计算机科学家,1979年在论文《一种改进阴影显示的光照模型》中,将递归式的光线追踪引入到计算机图形学领域
whitted-style方式实现的ray tracing,从"人眼"出发,反向追踪打在物体表面光线的来源,继而基于光线和物体材质的属性计算颜色。
实现逻辑
下面用代码实现whitted-style的光追,场景中有以下元素:
- 格子形状的地板
- 一个漫反射材质的球
- 一个镜面反射 & 折射材质的球
- 两个点光源
场景非常简单,再加上一些辅助的工具类就齐活儿了
关键代码说明
代码中有详细的注释,本文对关键的技术点做说明。
代码中用到不少c++11以上的语法特性,遇到陌生的语法,建议读者查一查
概述
- main.cpp:实现逻辑的调度,创建scene,并在scene中添加了shpere、mesh
- 物体:sphere、Triangle(地板是两个三角形)继承自Object,Object定义了抽象方法 intersect(判断相交)、getSurfaceProperties(获取表面属性)
- Render:最核心的类,实现了反射、折射、光线投射、路径追踪的逻辑
- Light:光照只有两个属性,定义位置、强度
- 其他类:Vector(向量操作)、global(工具方法、状态显示等)
Rrender实现
空间变换
如上图,需要对屏幕的每一个像素点计算光线追踪,像素点的坐标由下面式子定义:
ImageAspectRatio:渲染窗口宽高比
是计算视锥的大小,试想,你睁大眼睛看,能看到的场景更大,但是单一物体在场景中的比重就减小了,即视角增大,物体缩小,原来能采样物体的坐标现在采样到外面空白地方去了。
详细参考: www.scratchapixel.com/lessons/3d-…
castRay
castRay是最核心的逻辑,递归式的探索光线的传播路径,经过了哪些物体的弹射,找到光源,如果最后没有与任何物体相交,则取默认的环境光,其中弹射的方式可以是"反射"、"折射"、"漫反射",弹射的方式取决于物体的材质,材质在global中定义了
enum MaterialType
{
// 漫反射材质
DIFFUSE_AND_GLOSSY,
// 反射 + 折射材质(玻璃)
REFLECTION_AND_REFRACTION,
// 反射(镜子)
REFLECTION
};
像素缓存,生成图片文件
像素的颜色存放到一个framebuffer中,用vector实现
图像最后以ppm格式存储,ppm是一个矢量图,参考: blog.csdn.net/kinghzkingk…
Render中,折射的实现初次接触也有点复杂,涉及到光学的理论,需要耐心的推导,不知道你的中学物理都忘光了没
反射折射参考: www.scratchapixel.com/lessons/3d-…
Sphere、Triangle
判断相交 intersect
球体和三角形判断相交的方法不同
球体相交判断:
方程有解t >= 0 则相交,求方程解并不是我们熟悉的方式,目的是为了减少浮点数运算产生的误差,参考说明:www.zhihu.com/people/cowi…
代码如下:
/**
* 解一元二次方程,实现说明:https://www.zhihu.com/people/cowill/posts
*/
inline bool solvecQuadratic(const float& a, const float& b, const float& c, float& x0, float& x1)
{
float discr = b * b - 4 * a * c;
if (discr < 0)
return false;
else if (discr == 0)
x0 = x1 = -0.5 * b / a;
else
{
float q = (b > 0) ? -0.5 * (b + sqrt(discr)) : -0.5 * (b - sqrt(discr));
x0 = q / a;
x1 = c / q;
}
if (x0 > x1)
std::swap(x0, x1);
return true;
}
三角形相交判断: 本质也是解方程,用Möller Trumbore Algorith做优化
颜色采样
球体的颜色好定义,一个漫反射定义为灰色,一个透明的无色。格子形状的地板使用算法实现的,是一种“procedural-texturing”,程序纹理,自己实现真不太好想,参考文章说明: www.scratchapixel.com/lessons/3d-…
/**
*
* 计算地板网格状的纹理颜色,原理参考:
* https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/procedural-texturing
*/
Vector3f evalDiffuseColor(const Vector2f &st) const override
{
float scale = 5;
float pattern = (fmodf(st.x * scale, 1) > 0.5) ^ (fmodf(st.y * scale, 1) > 0.5);
return lerp(Vector3f(0.815, 0.235, 0.031), Vector3f(0.937, 0.937, 0.231), pattern);
}
掌握了算法原理,也可以实现其他的地板图案
pattern = (cos(st.y * 2 * M_PI * scale) * sin(st.x * 2 * M_PI * scale) + 1) * 0.5; // compute sine wave pattern
欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~
参考资料
[1] wikipedia-J.Turner Whitted: en.wikipedia.org/wiki/J._Tur…
[2] Generating Camera Rays: www.scratchapixel.com/lessons/3d-…
[3] 反射折射: www.scratchapixel.com/lessons/3d-…
[4] procedural-texturing: www.scratchapixel.com/lessons/3d-…
[5] GAMES-101HW代码归档: alexandrite.top/2020/07/13/…
[6] games101作业问题整理: zhuanlan.zhihu.com/p/375391720