为什么我的路径跟踪代码不起作用?
我一直在用纯 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:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我的第一个问题是应该
if test >结果:
为if test
结果:
?您正在寻找最近的命中,而不是最远的命中。其次,为什么这里的命中点要加上
direction*0.00001
n = 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:
beif test < result:
? You're looking for the closest hit, not the furthest.Second, why do you add
direction*0.00001
to the hit point heren = 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 callTracePath2
, 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 allanswer
will just beColor(0.0, 0.0, 0.0)
so you could simplyreturn 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 andhit.diffuse
. Something like this makes more sense to mereturn racePath2(n, scene, bounce_count + 1) * dp + hit.diffuse
. One more thing, you never checkbounce_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 beginningif 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 againstnormal
instead ofdirection
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.