2011-06-29 44 views
13

我在python中有一個簡單的raytracer。渲染圖像200x200需要4分鐘,這對我來說絕對是太多了。我想改善這種情況。一些要點:我每個像素拍攝多條光線(提供抗鋸齒),每個像素總共有16條光線。 200x200x16是總共640000射線。必須測試每條射線對場景中多個球體對象的影響。雷也是一個相當瑣碎對象提高光線跟蹤命中功能的性能

class Ray(object): 
    def __init__(self, origin, direction): 
     self.origin = numpy.array(origin) 
     self.direction = numpy.array(direction) 

球稍微複雜一些,而且攜帶命中/ nohit邏輯:

class Sphere(object): 
    def __init__(self, center, radius, color): 
     self.center = numpy.array(center) 
     self.radius = numpy.array(radius) 
     self.color = color 

    @profile 
    def hit(self, ray): 
     temp = ray.origin - self.center 
     a = numpy.dot(ray.direction, ray.direction) 
     b = 2.0 * numpy.dot(temp, ray.direction) 
     c = numpy.dot(temp, temp) - self.radius * self.radius 
     disc = b * b - 4.0 * a * c 

     if (disc < 0.0): 
      return None 
     else: 
      e = math.sqrt(disc) 
      denom = 2.0 * a 
      t = (-b - e)/denom 
      if (t > 1.0e-7): 
       normal = (temp + t * ray.direction)/self.radius 
       hit_point = ray.origin + t * ray.direction 
       return ShadeRecord.ShadeRecord(normal=normal, 
               hit_point=hit_point, 
               parameter=t, 
               color=self.color)   

      t = (-b + e)/denom 

      if (t > 1.0e-7): 
       normal = (temp + t * ray.direction)/self.radius    hit_point = ray.origin + t * ray.direction 
       return ShadeRecord.ShadeRecord(normal=normal, 
               hit_point=hit_point, 
               parameter=t, 
               color=self.color)  

     return None  

現在,我進行了一些分析,並且看起來最長處理時間在命中()功能

ncalls tottime percall cumtime percall filename:lineno(function) 
    2560000 118.831 0.000 152.701 0.000 raytrace/objects/Sphere.py:12(hit) 
    1960020 42.989 0.000 42.989 0.000 {numpy.core.multiarray.array} 
     1 34.566 34.566 285.829 285.829 raytrace/World.py:25(render) 
    7680000 33.796 0.000 33.796 0.000 {numpy.core._dotblas.dot} 
    2560000 11.124 0.000 163.825 0.000 raytrace/World.py:63(f) 
    640000 10.132 0.000 189.411 0.000 raytrace/World.py:62(hit_bare_bones_object) 
    640023 6.556 0.000 170.388 0.000 {map} 

這並不讓我感到意外,我想盡可能減少這個值。我通過排隊分析,其結果是

Line #  Hits   Time Per Hit % Time Line Contents 
============================================================== 
    12            @profile 
    13            def hit(self, ray): 
    14 2560000  27956358  10.9  19.2   temp = ray.origin - self.center 
    15 2560000  17944912  7.0  12.3   a = numpy.dot(ray.direction, ray.direction) 
    16 2560000  24132737  9.4  16.5   b = 2.0 * numpy.dot(temp, ray.direction) 
    17 2560000  37113811  14.5  25.4   c = numpy.dot(temp, temp) - self.radius * self.radius 
    18 2560000  20808930  8.1  14.3   disc = b * b - 4.0 * a * c 
    19             
    20 2560000  10963318  4.3  7.5   if (disc < 0.0): 
    21 2539908  5403624  2.1  3.7    return None 
    22             else: 
    23  20092  75076  3.7  0.1    e = math.sqrt(disc) 
    24  20092  104950  5.2  0.1    denom = 2.0 * a 
    25  20092  115956  5.8  0.1    t = (-b - e)/denom 
    26  20092  83382  4.2  0.1    if (t > 1.0e-7): 
    27  20092  525272  26.1  0.4     normal = (temp + t * ray.direction)/self.radius 
    28  20092  333879  16.6  0.2     hit_point = ray.origin + t * ray.direction 
    29  20092  299494  14.9  0.2     return ShadeRecord.ShadeRecord(normal=normal, hit_point=hit_point, parameter=t, color=self.color) 

所以,似乎大多數時間都在此塊的代碼是花:

 temp = ray.origin - self.center 
     a = numpy.dot(ray.direction, ray.direction) 
     b = 2.0 * numpy.dot(temp, ray.direction) 
     c = numpy.dot(temp, temp) - self.radius * self.radius 
     disc = b * b - 4.0 * a * c 

在哪裏我實在不明白了很多進行優化。你有沒有想法如何使這個代碼更高性能,而不去C?

+3

+1非常好。我不知道Python,所以作爲一個問題:numpy.dot調用C實現?如果不是,也許你可以通過執行手動點積計算來提高速度。 – Phrogz

+0

是的,n是在C中實現的。這就是爲什麼我有這樣的感覺,重新實現C中的點擊函數並沒有太大的收穫。 –

+0

是你的方向向量單位向量?你可以使它們成爲'__init__'中的單位向量嗎?如果是這樣,你的點積算術變得更簡單。 – underrun

回答

1

有了這樣的代碼,你可以從常見的子表達式中獲益,例如self.radius * self.radius作爲self.radius21/self.radius作爲self.one_over_radius。 python解釋器的開銷可能會支配這種微不足道的改進。

+0

我試過了。好主意,但它並沒有太大改變。我意識到我在使用self.radius作爲numpy數組時發生錯誤,而不是簡單的float。再次,不是一個可衡量的變化。我正在考慮改變設計並嘗試減少通話次數,但是我懷疑在性能上會有很多好處,並且會在代碼清晰度方面損失很多。我想我很快就要平行了。 –

+0

的確如此。 C實現將比Python VM可以獲取操作碼更快地計算這些事情。儘管它們是有效的優化,並且將顯示出小但可衡量的改進是一個性能良好的C程序。 – phkahler

7

1)光線追蹤是樂趣,但如果你關心的所有關於性能,轉儲蟒蛇並切換到C.不是C++,除非你是某種超級高手,只是C.

2)大的勝利具有多個(20個或更多)對象的場景是使用空間索引來減少相交測試的數量。受歡迎的選項是kD樹,OctTrees,AABB。 3)如果您認真的話,請查看ompf.org - 這是它的資源。

4)不要去與ompf蟒蛇詢問優化 - 大多數人可以每秒通過一個室內場景拍攝100萬到200萬光線,每個場景有10萬個三角形......每個核心。

我喜歡Python和光線追蹤,但絕不會考慮將它們放在一起。在這種情況下,正確的優化是切換語言。

+0

+1我完全同意這一點。 Python對於原型設計來說可能很好,但是一旦你證明了你的算法並且關心性能,那麼現在是C/C++的時候了(或者編譯爲正確的二進制文件的東西;不要欺騙一些VM/GC語言來做)。無論如何,然後將光線追蹤器作爲python組件包裝起來,並在更高級別的系統中使用它。 – timday

+0

@timday - 1995年左右我在C++中創建了一個光線追蹤OLE組件(無論它們是什麼時候被調用的),並從Visual Basic應用程序中進行相機移動。 120x90像素的圖像很多FPS我曾考慮在我的圖書館週圍包裝PyGame :-)沒有時間... – phkahler

+0

我認爲C++現在可以編寫高性能光線跟蹤代碼。事實上,我以此爲生。您只需避免使用C++的蹩腳部分,例如異常和STL。 – Crashworks

1

一個較小的優化是:ab * b總是正數,所以disc < 0.0爲真如果(c > 0 && b < min(a, c))。在這種情況下,您可以避免計算b * b - 4.0 * a * c。鑑於你所做的配置文件,我估計最多可以減少7%的運行時間。

1

最好的辦法是儘可能使用查找表和預先計算的值。

由於您對我的評論的回覆表明您的光線方向矢量是單位矢量,因此在您列出的關鍵部分中,您可以立即進行至少一次優化。任何矢量點本身都是長度的平方,因此單位矢量點本身始終爲1.

此外,預先計算半徑平方(在您的球體的__init__函數中)。

那麼你已經有了:

temp = ray.origin - self.center 
a = 1 # or skip this and optimize out later 
b = 2.0 * numpy.dot(temp, ray.direction) 
c = numpy.dot(temp, temp) - self.radius_squared 
disc = b * b - 4.0 * c 

溫度點溫度是要給你的sum(map(lambda component: component*component, temp))相當於...我不知道這是更快,雖然。

+0

做了一些測試 - numpy.dot()比sum(map())快得多。 – underrun

6

看看你的代碼,它看起來像你的主要問題是你有被稱爲2560000次的代碼行。無論你在代碼中做什麼樣的工作,這往往需要很長時間。然而,使用numpy,你可以把這些工作聚合成少數的numpy調用。

首先要做的就是將你的光線組合成大陣列。而不是使用具有1x3矢量作爲原點和方向的Ray對象,而是使用Nx3陣列,這些Nx3陣列具有用於命中檢測所需的所有光線。你的命中功能的頂部將最終看起來像這樣:

temp = rays.origin - self.center 
b = 2.0 * numpy.sum(temp * rays.direction,1) 
c = numpy.sum(numpy.square(temp), 1) - self.radius * self.radius 
disc = b * b - 4.0 * c 

在接下來的部分,你可以使用

possible_hits = numpy.where(disc >= 0.0) 
a = a[possible_hits] 
disc = disc[possible_hits] 
... 

繼續只是通過判別測試值。通過這種方式您可以輕鬆獲得數量級的性能改進。