在一个立方体内进行球体光线追踪

6
我正在尝试在一个立方体内进行球体光线追踪。该立方体由12个三角形构成,带有法向量。
该立方体具有单位坐标和单位法向量。因此,在其本地空间(介于-1和1之间),应该有半径为0.5的球体。
因此,我认为我应该在顶点着色器中计算光线:光线起点是插值的顶点位置,光线方向是顶点法线(或其相反方向,但我认为这不重要)。插值应该做剩下的事情。
然后,在片段着色器中,我应该计算光线-球体交点,如果有的话,改变片段的颜色。
在立方体的前面和后面,结果似乎是正确的,但在左侧、右侧、顶部和底部,结果似乎来自错误的角度。我应该一直看到中间的球体,但在这些侧面上并非如此。
有人能告诉我我做错了什么吗?
以下是着色器代码:
顶点着色器:
#version 400

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;

uniform mat4 uProj;
uniform mat4 uView;
uniform mat4 uModel;

out vec3 vRayPos;
out vec3 vRayDir;

void main(void)
{
  gl_Position = uProj * uView * uModel * vec4(aPos, 1);
  vRayPos = aPos;
  vRayDir = inverse(mat3(uModel)) * aNor;
}

片元着色器:

#version 400

in vec3 vRayPos;
in vec3 vRayDir;

out vec4 oFrag;

void main(void)
{
  const vec3 sphereCenter = vec3(0, 0, 0);
  const float sphereRadius = 0.5;

  vec3 rayPos = vRayPos;
  vec3 rayDir = normalize(vRayDir);
  float a = dot(rayDir, rayDir); // TODO: rayDir is a unit vector, so: a = 1.0?
  float b = 2 * dot(rayDir, (rayPos - sphereCenter));
  float c = dot(rayPos - sphereCenter, rayPos - sphereCenter) - sphereRadius * sphereRadius;
  float d = b * b - 4 * a * c;
  float t = min(-b + sqrt(max(0, d)) / 2, -b - sqrt(max(0, d)) / 2);

  vec3 color = (1.0 - step(0, d)) * vec3(0.554, 0.638, 0.447) + step(0, d) * abs(t) * vec3(0.800, 0.113, 0.053);

  oFrag = vec4(color, 1);
}

注意:因子t实际上并非必要,但它可以给出光线离球体边缘的距离,从而使其看起来阴影更深。使用步进函数step(0, d)来查看是否存在任何交点,使用max(0, d)来防止着色器在sqrt(<0)错误时停止运行,两者都是为了避免代码分支。
参考资料:我从https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection获取了计算结果。
编辑:这里有一个问题的视频:Video

这个Reflection and refraction impossible without recursive ray tracing?可能会引起你的兴趣。无论如何,单位球的半径为1.0而不是0.5。你可以通过这个ray and ellipsoid intersection accuracy improvement来交叉检查你的相交点。你有问题的截图吗?此外,你的射线方向/位置描述听起来有些奇怪,请参考第一个链接中的"How I do it (Quad covering whole screen,position is the Vertex position but direction is position-focus instead of normal !)"。 - Spektre
1
我在我的原帖中添加了一个视频! - scippie
1
哦,那和我教的完全不同。无论如何,对于你正在做的vRayDir = inverse(mat3(uModel)) * aNor;看起来很可疑。我会使用你的立方体中心,并将其沿法线方向平移半个大小加上焦距,以获得焦点,然后vRayDir = vRayPos - focal_point... - Spektre
另外,在你的其他评论中,你提到不使用全屏四边形是因为旋转时它无法工作。如果你旋转射线起点(视点)而不是旋转四边形,仅用于射线计算的目的,你可能会得到你想要的效果。(旋转射线跟踪/射线投射内容而不是四边形。)我用这个方法实现了一些有趣的效果,比如透镜效应:https://www.youtube.com/watch?v=kTliozdVeh0 - 3Dave
1
我从未说过那句话,但我理解那个想法可能是从哪里来的。我说的是我不能为3D粒子使用简单的广告牌。但是,如果我正确地阅读了您的评论,我认为仍然有可能:保持广告牌不旋转并更改其视口角度。谢谢。顺便说一句,视频很酷! - scippie
显示剩余2条评论
1个回答

2
你应该通过计算给定片段和相机位置之间的方向来计算光线。(在视图空间中,这将是原点。)顶点法线与此毫无关系。
你可以在顶点着色器中计算光线,并将其作为插值传递到片段着色器中。但是,这可能会导致不正确的结果,因为输出是线性的,这是不正确的。
更好的方法是在顶点着色器中输出您的顶点的视图空间位置。在片段着色器中,从原点到片段的视图空间位置计算出一条射线。然后,使用该射线执行射线相交测试。光栅化器将正确地插值视图空间位置。您也可以在片段着色器中自己计算,但硬件在这方面非常出色,所以让它为您完成这项工作是有意义的。
话虽如此,您当前实现的主要问题是使用顶点法线来计算光线。那是错误的。你只需要相机位置和片段位置。如果你仔细观察你的视频,你会发现无论相对于相机的位置如何,都会画出同样的东西。
对于简单的球体,你只需要相机-片段光线。计算该线包含的距离到球心的距离。如果小于球体的半径,则是一个命中。

@Spektre 我理解你的想法,但OP也说:“我应该一直看到中间的球,但在那些边上并不是这样。” 这听起来像是一个经典的光线追踪体积。如果“立方体的每个面都是相机镜头本身,以该面法线相反的方向观察”,那么OP应该选择该面法线上的一个点(而不是顶点法线),并从该位置计算射线到片段,并按照通常的方式进行处理。问题的陈述有些不清楚。 :| - 3Dave
1
没错,我同意这一点,这就是为什么我一开始没有回答,只是发表了评论/提示。 - Spektre
1
也许是,也许不是……中心的球可能只是由于原帖作者对于影响行为的错误假设。 - Spektre
1
你们两个都是对的。你们的讨论让我意识到我不知道自己想要什么,我正在解决问题B,而我需要的是A。我开始这个项目的原因是因为我想要一些基于数学计算的3D粒子(我从一个球体开始,因为它很容易)。我滥用了立方体来获得深度,因为当绕Y轴旋转时,广告牌将会变得不可见。我现在相信我只需要普通的光线追踪,但我的屏幕四边形限制在立方体的光栅化表面上,对象始终相对于立方体。我会回报的。 - scippie
1
@3Dave 在澄清之后,看起来你更正确 :) +1 - Spektre
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接