我正在尝试从Eric Haines 的标准程序数据库 (SPD)中渲染“安装”场景,但折射部分只是不想合作。我已经尝试了我能想到的一切来修复它。
这是我的渲染(使用瓦特公式):
(来源:philosoraptor.co.za)
这是我使用“正常”公式的渲染:
(来源:philosoraptor.co.za)
这是正确的渲染:
(来源:philosoraptor.co.za)
如您所见,只有几个错误,主要是在球体的两极附近。这让我认为折射或一些精度误差是罪魁祸首。
请注意,场景中实际上有 4 个球体,它们的 NFF 定义 ( s x_coord y_coord z_coord radius
) 是:
s -0.8 0.8 1.20821 0.17
s -0.661196 0.661196 0.930598 0.17
s -0.749194 0.98961 0.930598 0.17
s -0.98961 0.749194 0.930598 0.17
也就是说,在前景中更明显的三个后面有第四个球体。从这三个球体之间留下的空隙中可以看出。
这是仅第四个球体的图片:
(来源:philosoraptor.co.za)
这是仅第一个球体的图片:
(来源:philosoraptor.co.za)
您会注意到我的版本和正确版本中存在的许多奇怪之处都丢失了。我们可以得出结论,这些影响是球体之间相互作用的结果,问题是哪些相互作用?
我究竟做错了什么?以下是我已经考虑过的一些潜在错误:
- 折射矢量公式。
据我所知,这是正确的。这是几个网站使用的相同公式,我亲自验证了推导。这是我的计算方法:
double sinI2 = eta * eta * (1.0f - cosI * cosI);
Vector transmit = (v * eta) + (n * (eta * cosI - sqrt(1.0f - sinI2)));
transmit = transmit.normalise();
我在 Alan Watt 的第三版 3D Computer Graphics 中找到了一个替代公式。它更接近正确的图像:
double etaSq = eta * eta;
double sinI2 = etaSq * (1.0f - cosI * cosI);
Vector transmit = (v * eta) + (n * (eta * cosI - (sqrt(1.0f - sinI2) / etaSq)));
transmit = transmit.normalise();
唯一的区别是我最后除以 eta^2。
- 全内反射。
我对此进行了测试,在我的其余交集代码之前使用以下条件:
if (sinI2 <= 1)
- eta的计算。
我使用类似堆栈的方法来解决这个问题:
/* Entering object. */
if (r.normal.dot(r.dir) < 0)
{
double eta1 = r.iorStack.back();
double eta2 = m.ior;
eta = eta1 / eta2;
r.iorStack.push_back(eta2);
}
/* Exiting object. */
else
{
double eta1 = r.iorStack.back();
r.iorStack.pop_back();
double eta2 = r.iorStack.back();
eta = eta1 / eta2;
}
如您所见,这将包含此射线的先前对象存储在堆栈中。退出代码时,将当前 IOR 从堆栈中弹出并使用它以及它下面的 IOR 来计算 eta。据我所知,这是最正确的方法。
这适用于嵌套传输对象。但是,它会因相交传输对象而崩溃。这里的问题是您需要为交叉点独立定义 IOR,而 NFF 文件格式不这样做。那时还不清楚什么是“正确”的行动方案。
- 移动新射线的原点。
新光线的原点必须沿着传输路径稍微移动,这样它就不会与前一条光线在同一点相交。
p = r.intersection + transmit * 0.0001f;
p += transmit * 0.01f;
我尝试使这个值更小(0.001f)和(0.0001f),但这会使球体看起来很实心。我猜这些值不会使光线远离前一个交点。
编辑:这里的问题是反射代码在做同样的事情。因此,当一个物体既具有反射性又具有折射性时,光线的起源就会完全出现在错误的位置。
- 光线反弹量。
我人为地将光线反弹的数量限制为 4。我测试了将此限制提高到 10,但这并没有解决问题。
- 法线。
我很确定我正在正确计算球体的法线。我取交点,减去球体的中心并除以半径。