为什么我的路径跟踪代码不起作用?

发布于 2024-10-30 01:00:05 字数 4064 浏览 0 评论 0原文

我一直在用纯 Python 编写一个路径跟踪器,只是为了好玩,因为我之前的着色东西不太漂亮(兰伯特余弦定律),我正在尝试实现递归路径跟踪。

我的引擎给出了中止的输出:

在此处输入图像描述

我的路径跟踪函数是递归定义的,如下所示:

def TracePath2(ray, scene, bounce_count):
  result = 100000.0
  hit = False

  answer = Color(0.0, 0.0, 0.0)

  for object in scene.objects:
    test = object.intersection(ray)

    if test and test < result:
      result = test
      hit = object

  if not hit:
    return answer

  if hit.emittance:
    return hit.diffuse * hit.emittance

  if hit.diffuse:
    direction = RandomDirectionInHemisphere(hit.normal(ray.position(result)))

    n = Ray(ray.position(result), direction)
    dp = direction.dot(hit.normal(ray.position(result)))
    answer += TracePath2(n, scene, bounce_count + 1) * hit.diffuse * dp

  return answer

我的场景 (I制作了自定义 XML 描述格式)是这样的:

<?xml version="1.0" ?>

<scene>
  <camera name="camera">
    <position x="0" y="-5" z="0" />
    <direction x="0" y="1" z="0" />

    <focalplane width="0.5" height="0.5" offset="1.0" pixeldensity="1600" />
  </camera>

  <objects>
    <sphere name="sphere1" radius="1.0">
      <material emittance="0.9" reflectance="0">
        <diffuse r="0.5" g="0.5" b="0.5" />
      </material>

      <position x="1" y="0" z="0" />
    </sphere>

    <sphere name="sphere2" radius="1.0">
      <material emittance="0.0" reflectance="0">
        <diffuse r="0.8" g="0.5" b="0.5" />
      </material>

      <position x="-1" y="0" z="0" />
    </sphere>
  </objects>
</scene>

我很确定我的引擎中存在一些基本缺陷,但我就是找不到它...


这是我的新跟踪功能:

def Trace(ray, scene, n):
  if n > 10: # Max raydepth of 10. In my scene, the max should be around 4, since there are only a few objects to bounce off, but I agree, there should be a cap.
    return Color(0.0, 0.0, 0.0)

  result = 1000000.0 # It's close to infinity...
  hit = False

  for object in scene.objects:
    test = object.intersection(ray)

    if test and test < result:
      result = test
      hit = object

  if not hit:
    return Color(0.0, 0.0, 0.0)

  point = ray.position(result)

  normal = hit.normal(point)
  direction = RandomNormalInHemisphere(normal) # I won't post that code, but rest assured, it *does* work.

  if direction.dot(ray.direction) > 0.0:
    point = ray.origin + ray.direction * (result + 0.0000001) # We're going inside an object (for use when tracing glass), so move a tad bit inside to prevent floating-point errors.
  else:
    point = ray.origin + ray.direction * (result - 0.0000001) # We're bouncing off. Move away from surface a little bit for same reason.

  newray = Ray(point, direction)

  return Trace(newray, scene, n + 1) * hit.diffuse + Color(hit.emittance, hit.emittance, hit.emittance) # Haven't implemented colored lights, so it's a shade of gray for now.

我很确定路径跟踪代码有效,因为我手动投射了一些光线并获得了相当合法的结果。我(现在)遇到的问题是相机无法发射光线穿过图像平面中的所有像素。我编写了这段代码来查找与像素相交的光线,但它无法正常工作:

origin = scene.camera.pos                 # + 0.5 because it      # 
                                          # puts the ray in the   # This calculates the width of one "unit"
                                          # *middle* of the pixel # 
worldX = scene.camera.focalplane.width - (x + 0.5)                * (2 * scene.camera.focalplane.width / scene.camera.focalplane.canvasWidth)
worldY = scene.camera.pos.y - scene.camera.focalplane.offset # Offset of the imaging plane is know, and it's normal to the camera's direction (directly along the Y-axis in this case).
worldZ = scene.camera.focalplane.height - (y + 0.5)               * (2 * scene.camera.focalplane.height / scene.camera.focalplane.canvasHeight)

ray = Ray(origin, (scene.camera.pos + Point(worldX, worldY, worldZ)).norm())

I've been hacking together a pathtracer in pure Python, just for fun, and since my previous shading-thing wasn't too pretty (Lambert's cosine law), I'm trying to implement recursive pathtracing.

My engine gives an abortive output:

enter image description here

My pathtracing function is defined recursively, like this:

def TracePath2(ray, scene, bounce_count):
  result = 100000.0
  hit = False

  answer = Color(0.0, 0.0, 0.0)

  for object in scene.objects:
    test = object.intersection(ray)

    if test and test < result:
      result = test
      hit = object

  if not hit:
    return answer

  if hit.emittance:
    return hit.diffuse * hit.emittance

  if hit.diffuse:
    direction = RandomDirectionInHemisphere(hit.normal(ray.position(result)))

    n = Ray(ray.position(result), direction)
    dp = direction.dot(hit.normal(ray.position(result)))
    answer += TracePath2(n, scene, bounce_count + 1) * hit.diffuse * dp

  return answer

And my scene (I made a custom XML description format) is this:

<?xml version="1.0" ?>

<scene>
  <camera name="camera">
    <position x="0" y="-5" z="0" />
    <direction x="0" y="1" z="0" />

    <focalplane width="0.5" height="0.5" offset="1.0" pixeldensity="1600" />
  </camera>

  <objects>
    <sphere name="sphere1" radius="1.0">
      <material emittance="0.9" reflectance="0">
        <diffuse r="0.5" g="0.5" b="0.5" />
      </material>

      <position x="1" y="0" z="0" />
    </sphere>

    <sphere name="sphere2" radius="1.0">
      <material emittance="0.0" reflectance="0">
        <diffuse r="0.8" g="0.5" b="0.5" />
      </material>

      <position x="-1" y="0" z="0" />
    </sphere>
  </objects>
</scene>

I'm pretty sure that there's some fundamental flaw in my engine, but I just can't find it...


Here's my new-ish tracing function:

def Trace(ray, scene, n):
  if n > 10: # Max raydepth of 10. In my scene, the max should be around 4, since there are only a few objects to bounce off, but I agree, there should be a cap.
    return Color(0.0, 0.0, 0.0)

  result = 1000000.0 # It's close to infinity...
  hit = False

  for object in scene.objects:
    test = object.intersection(ray)

    if test and test < result:
      result = test
      hit = object

  if not hit:
    return Color(0.0, 0.0, 0.0)

  point = ray.position(result)

  normal = hit.normal(point)
  direction = RandomNormalInHemisphere(normal) # I won't post that code, but rest assured, it *does* work.

  if direction.dot(ray.direction) > 0.0:
    point = ray.origin + ray.direction * (result + 0.0000001) # We're going inside an object (for use when tracing glass), so move a tad bit inside to prevent floating-point errors.
  else:
    point = ray.origin + ray.direction * (result - 0.0000001) # We're bouncing off. Move away from surface a little bit for same reason.

  newray = Ray(point, direction)

  return Trace(newray, scene, n + 1) * hit.diffuse + Color(hit.emittance, hit.emittance, hit.emittance) # Haven't implemented colored lights, so it's a shade of gray for now.

I'm pretty sure that the pathtracing code works, as I manually casted some rays and got pretty legitimate results. The problem I'm having (now) is that the camera doesn't shoot rays through all the pixels in the image plane. I made this code to find the ray intersecting a pixel, but it's not working properly:

origin = scene.camera.pos                 # + 0.5 because it      # 
                                          # puts the ray in the   # This calculates the width of one "unit"
                                          # *middle* of the pixel # 
worldX = scene.camera.focalplane.width - (x + 0.5)                * (2 * scene.camera.focalplane.width / scene.camera.focalplane.canvasWidth)
worldY = scene.camera.pos.y - scene.camera.focalplane.offset # Offset of the imaging plane is know, and it's normal to the camera's direction (directly along the Y-axis in this case).
worldZ = scene.camera.focalplane.height - (y + 0.5)               * (2 * scene.camera.focalplane.height / scene.camera.focalplane.canvasHeight)

ray = Ray(origin, (scene.camera.pos + Point(worldX, worldY, worldZ)).norm())

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

往事随风而去 2024-11-06 01:00:06

我的第一个问题是应该 if test >结果:if test 结果:?您正在寻找最近的命中,而不是最远的命中。

其次,为什么这里的命中点要加上direction*0.00001n = Ray(ray.position(result) + Direction * 0.00001, Direction)?这会将新射线的起点置于球体内部。我相信,当您递归调用 TracePath2 时,您乘以的点积将为负,这将有助于解释问题。

编辑:更新的问题

这一行让我感到困惑:answer += TracePath2(n, scene,ounce_count + 1) * hit.diffuse * dp。首先,answer 将只是 Color(0.0, 0.0, 0.0),因此您可以简单地 return racePath2(n, scene,ounce_count + 1) * hit。漫反射 * dp。但这仍然困扰着我,因为我不明白为什么要将递归调用和 hit.diffuse 相乘。这样的事情对我来说更有意义return racePath2(n, scene,ounce_count + 1) * dp + hit.diffuse。还有一件事,你永远不会检查bounce_count。无论如何,你永远不会在这个场景中永远递归,但如果你想渲染更大的场景,你会在开始时需要这样的东西ifounce_count>; 15:返回黑色

编辑2:

我看到我仍然想知道的一件事是最后的if-else。首先,我不完全确定这部分代码在做什么。我认为您正在测试光线是否在物体内部。在这种情况下,您的测试将类似于 inside = normal.dot(ray.direction) > 0.0。我只是针对正常而不是方向进行测试,因为在半球中使用随机方向可能会给出错误的答案。现在,如果你在物体内部,你想出去,但如果你已经在外面了,你又想留在外面吗?就像我说的,我不太确定那部分应该做什么。

My first question is should if test > result: be if test < result:? You're looking for the closest hit, not the furthest.

Second, why do you add direction*0.00001 to the hit point here n = Ray(ray.position(result) + direction * 0.00001, direction)? That would put the start of your new ray inside the spheres. I believe then when you recursively call TracePath2, the dot product that you multiply by would be negative, which would help explain the problem.

Edit: updated problem

This line confuses me: answer += TracePath2(n, scene, bounce_count + 1) * hit.diffuse * dp. First of all answer will just be Color(0.0, 0.0, 0.0) so you could simply return racePath2(n, scene, bounce_count + 1) * hit.diffuse * dp. But that still bothers me because I don't understand why you're multiplying the recursive call and hit.diffuse. Something like this makes more sense to me return racePath2(n, scene, bounce_count + 1) * dp + hit.diffuse. One more thing, you never check bounce_count. You're never going to recurse forever in this scene anyway but if you want to render larger scenes you'll want something like this at the beginning if bounce_count > 15: return black.

Edit 2:

The one thing I see that I still wonder about is the if-else towards the end. First of all I'm not entirely sure what that part of the code is doing. I think you're testing whether or not the ray is on the inside of the object. In that case your test would be like this inside = normal.dot(ray.direction) > 0.0. I'm just testing against normal instead of direction because using the random direction in the hemisphere could give the wrong answer. Now, if you're inside the object you want to get out, but you want to stay out if you're already out? Like I said, I'm not quite sure what that part is supposed to do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文