sci*_*pie 6 trace raytracing glsl
我正在尝试对立方体内的球体进行光线追踪。立方体由 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;
}
Run Code Online (Sandbox Code Playgroud)
片段着色器:
#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);
}
Run Code Online (Sandbox Code Playgroud)
注意:因子 t 实际上不是必需的,但它给出了光线与球体接触的侧面有多远的概念,这使得球体看起来很阴暗。step(0, d) 函数用于查看是否存在任何交点,而 max(0, d) 用于防止着色器因 sqrt(<0) 错误而停止,两者都是为了防止代码分支。
参考:我从https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection得到计算结果
编辑:这是问题的视频:视频
应通过给定片段和相机位置之间的方向来计算光线。(在视图空间中,这将是原点。)顶点法线与它完全无关。
从技术上讲,您可以在顶点着色器中计算光线并将其作为插值传递给片段着色器。然而,这有可能给出不正确的结果,因为输出是线性的,这是不正确的。
更好的方法是在顶点着色器中输出顶点的视图空间位置。在片段着色器中,计算从原点到片段的视图空间位置的射线。然后,使用该光线执行光线相交测试。光栅化器将正确插入视图空间位置。您也可以在片段着色器中自行计算,但硬件非常擅长于此,因此让它为您执行此操作是有意义的。
话虽如此,当前实现的主要问题是使用顶点法线来计算光线。那是错误的。您所需要的只是相机位置和片段位置。如果仔细查看视频,您会发现所有侧面都绘制了相同的东西,无论相对于相机的位置如何。
对于一个简单的球体,您所需要的只是相机片段射线。计算从包含该线的线到球体中心的距离。如果它小于球体的半径,则命中。