开发者

Why isn't my pathtracing code working?

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:

Why isn't my pathtracing code working?

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 camer开发者_如何学Pythona'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())


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜