2016-08-20 69 views
2

我遇到了一個問題:我讀了一個帶有C++代碼的article on scratchapixel光線跟蹤。 C++沒問題。我試圖將它轉換成Python,它工作(結果減慢17倍,分辨率降低4倍)。我試圖將其轉換爲C#,但我的代碼無法正常工作。只有我能看到的是一張空白的800x600圖像。請參閱以前鏈接的C++代碼文章。光線跟蹤器沒有產生預期的輸出

這是我對它的解釋爲C#代碼:

using System; 
using System.Collections.Generic; 

namespace raytracer 
{ 
class Program 
{ 
    const int MAX_RAY_DEPTH = 8; 
    const float FAR = 100000000; 

    public static void Main(string[] args) 
    { 
     Sphere[] spheres = new Sphere[7]; 
     spheres[0] = new Sphere(new Vec3f(0.0f, -10004, -20), 10000, new Vec3f(0.20f, 0.20f, 0.20f), 0, 0.0f); 
     spheres[1] = new Sphere(new Vec3f(0.0f,  0, -20),  4, new Vec3f(1.00f, 0.32f, 0.36f), 1, 0.5f); 
     spheres[2] = new Sphere(new Vec3f(5.0f,  -1, -15),  2, new Vec3f(0.90f, 0.76f, 0.46f), 1, 0.0f); 
     spheres[3] = new Sphere(new Vec3f(5.0f,  0, -25),  3, new Vec3f(0.65f, 0.77f, 0.97f), 1, 0.0f); 
     spheres[4] = new Sphere(new Vec3f(-5.5f,  0, -15),  3, new Vec3f(0.90f, 0.90f, 0.90f), 1, 0.0f); 
     spheres[5] = new Sphere(new Vec3f( 2f,  2, -30),  4, new Vec3f(0.53f, 0.38f, 0.91f), 1, 0.7f); 
     spheres[6] = new Sphere(new Vec3f( 0,  20, -25),  3, new Vec3f(0.00f, 0.00f, 0.00f), 0, 0.0f, new Vec3f(3)); 
     Render(spheres); 
    } 

    public class Collision 
    { 
     public float t0, t1; 
     public bool collide; 
     public Collision(bool col, float tt0 = 0, float tt1 = 0) 
     { 
      t0 = tt0; 
      t1 = tt1; 
      collide = col; 
     } 
    } 

    public class Vec3f 
    { 
     public float x, y, z; 
     public Vec3f(){ x = y = z = 0; } 
     public Vec3f(float v){ x = y = z = v; } 
     public Vec3f(float xx, float yy, float zz){ x = xx; y = yy; z = zz; } 

     public Vec3f normalize() 
     { 
      float nor2 = length2(); 
      if (nor2 > 0) 
      { 
       float invNor = 1/(float)Math.Sqrt(nor2); 
       x *= invNor; y *= invNor; z *= invNor; 
      } 
      return this; 
     } 
     public static Vec3f operator *(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x * r.x, l.y * r.y, l.z * r.z); 
     } 
     public static Vec3f operator *(Vec3f l, float r) 
     { 
      return new Vec3f(l.x * r, l.y * r, l.z * r); 
     } 
     public float dot(Vec3f v) 
     { 
      return x * v.x + y * v.y + z * v.z; 
     } 
     public static Vec3f operator -(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x - r.x, l.y - r.y, l.z - r.z); 
     } 
     public static Vec3f operator +(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x + r.x, l.y + r.y, l.z + r.z); 
     } 
     public static Vec3f operator -(Vec3f v) 
     { 
      return new Vec3f(-v.x, -v.y, -v.z); 
     } 
     public float length2() 
     { 
      return x * x + y * y + z * z; 
     } 
     public float length() 
     { 
      return (float)Math.Sqrt(length2()); 
     } 
    } 

    public class Sphere 
    { 
     public Vec3f center, surfaceColor, emissionColor; 
     public float radius, radius2; 
     public float transparency, reflection; 
     public Sphere(Vec3f c, float r, Vec3f sc, float refl = 0, float transp = 0, Vec3f ec = null) 
     { 
      center = c; radius = r; radius2 = r * r; 
      surfaceColor = sc; emissionColor = (ec == null) ? new Vec3f(0) : ec; 
      transparency = transp; reflection = refl; 
     } 

     public Collision intersect(Vec3f rayorig, Vec3f raydir) 
     { 
      Vec3f l = center - rayorig; 
      float tca = l.dot(raydir); 
      if (tca < 0){ return new Collision(false); } 
      float d2 = l.dot(l) - tca * tca; 
      if (d2 > radius2){ return new Collision(false); } 
      Collision coll = new Collision(true); 
      float thc = (float)Math.Sqrt(radius2 - d2); 
      coll.t0 = tca - thc; 
      coll.t1 = tca + thc; 
      return coll; 
     } 
    } 

    public static float mix(float a, float b, float mix) 
    { 
     return b * mix + a * (1 - mix); 
    } 

    public static Vec3f trace(Vec3f rayorig, Vec3f raydir, Sphere[] spheres, int depth) 
    { 
     float tnear = FAR; 
     Sphere sphere = null; 
     foreach(Sphere i in spheres) 
     { 
      float t0 = FAR, t1 = FAR; 
      Collision coll = i.intersect(rayorig, raydir); 
      if (coll.collide) 
      { 
       if (coll.t0 < 0) { coll.t0 = coll.t1; } 
       if (coll.t0 < tnear) { tnear = coll.t0; sphere = i; } 
      } 
     } 
     if (sphere == null){ return new Vec3f(2); } 
     Vec3f surfaceColor = new Vec3f(0); 
     Vec3f phit = rayorig + raydir * tnear; 
     Vec3f nhit = phit - sphere.center; 
     nhit.normalize(); 
     float bias = 1e-4f; 
     bool inside = false; 
     if (raydir.dot(nhit) > 0){ nhit = -nhit; inside = true; } 
     if ((sphere.transparency > 0 || sphere.reflection > 0) && depth < MAX_RAY_DEPTH) 
     { 
      float facingratio = -raydir.dot(nhit); 
      float fresneleffect = mix((float)Math.Pow(1 - facingratio, 3), 1, 0.1f); 
      Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit); 
      refldir.normalize(); 
      Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 
      Vec3f refraction = new Vec3f(0); 
      if (sphere.transparency > 0) 
      { 
       float ior = 1.1f; float eta = 0; 
       if (inside){ eta = ior; } else { eta = 1/ior; } 
       float cosi = -nhit.dot(raydir); 
       float k = 1 - eta * eta * (1 - cosi * cosi); 
       Vec3f refrdir = raydir * eta + nhit * (eta * cosi - (float)Math.Sqrt(k)); 
       refrdir.normalize(); 
       refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 
      } 
      surfaceColor = 
      (
       reflection * fresneleffect + refraction * 
       (1 - fresneleffect) * sphere.transparency) * sphere.surfaceColor; 
     } 
     else 
     { 
      foreach(Sphere i in spheres) 
      { 
       if (i.emissionColor.x > 0) 
       { 
        Vec3f transmission = new Vec3f(1); 
        Vec3f lightDirection = i.center - phit; 
        lightDirection.normalize(); 
        foreach(Sphere j in spheres) 
        { 
         if (i != j) 
         { 
          Collision jcoll = j.intersect(phit + nhit * bias, lightDirection); 
          if (jcoll.collide) 
          { 
           transmission = new Vec3f(0); 
           break; 
          } 
         } 
        } 
        surfaceColor += sphere.surfaceColor * transmission * Math.Max(0, nhit.dot(lightDirection)) * i.emissionColor; 

       } 
      } 
     } 
     return surfaceColor; 
    } 

    public static void Render(Sphere[] spheres) 
    { 
     int width = 800, height = 600; 
     List<Vec3f> image = new List<Vec3f>(); 
     float invWidth = 1/width, invHeight = 1/height; 
     float fov = 30, aspectratio = width/height; 
     float angle = (float)Math.Tan(Math.PI * 0.5 * fov/180); 
     for (int y = 0; y < height; y++) 
     { 
      for(int x = 0; x < width; x++) 
      { 
       float xx = (2 * ((x + 0.5f) * invWidth) - 1) * angle * aspectratio; 
       float yy = (1 - 2 * ((y + 0.5f) * invHeight)) * angle; 
       Vec3f raydir = new Vec3f(xx, yy, -1); 
       raydir.normalize(); 
       image.Add(trace(new Vec3f(0), raydir, spheres, 0)); 
      } 
     } 
     Console.Write("P3 800 600 255\r\n"); 
     int line = 150; 
     for(int i = 0; i < width * height; ++i) 
     { 
      if(line <= 0) {line = 150; Console.Write("\r\n");} 
      line--; 
      Vec3f pixel = GetColor(image[i]); 
      Console.Write(pixel.x + " " + pixel.y + " " + pixel.z); 
     } 
    } 

    public static Vec3f GetColor(Vec3f col) 
    { 
     return new Vec3f(Math.Min(1, col.x)* 255, Math.Min(1, col.y)* 255, Math.Min(1, col.z)* 255); 
    } 
} 
} 

任何人看到什麼是錯的?

編輯 程序正在將追蹤的顏色寫入控制檯屏幕。然後,我可以使用Windows批處理文件寫入ppm文件。 我使用CSC.EXE 「CSC.EXE raytracer.cs」 和運行程序與 「raytracer.exe> out.ppm」

+0

如果它在C++中工作,並且由於C++通常更快,也許你應該把它放在你從C#中調用/調用的DLL中?我不知道你轉換它的原因,但這就是我要做的。 –

+0

那麼...你檢查了生成的ppm文件嗎?它實際上包含什麼? – Cubic

+1

@VisualVincent這不是關於性能,而是將它轉換爲練習 – Cubic

回答

2

的基本問題你的C#代碼有創建可執行文件使用int值,其中你想要一個浮點結果。就像在C++代碼中一樣,原來的int值在轉換爲分區之前轉換爲float,您也需要在C#代碼中執行此操作。特別是,你invHeightinvWidth,並aspectratio計算都需要使用的,而不是整數數學浮點運算進行:

float invWidth = 1f/width, invHeight = 1f/height; 
    float fov = 30, aspectratio = (float)width/height; 

像素之間的同時,你的文本輸出實際上是缺少空格。在你的代碼版本,你可以通過每個像素值前插入空格,除了在一行中的第一個解決這個問題:

for(int i = 0; i < width * height; ++i) 
    { 
     if(line <= 0) {line = 150; Console.Write("\r\n");} 
     else if (line < 150) Console.Write(" "); 
     line--; 
     Vec3f pixel = GetColor(image[i]); 
     Console.Write(pixel.x + " " + pixel.y + " " + pixel.z); 
    } 

或者你可以,當然,只是一直寫空間:

 Console.Write(pixel.x + " " + pixel.y + " " + pixel.z + " "); 

你也不得不在轉換一個小錯誤,因爲你沒有在trace()方法的末尾添加sphere.emissionColor

 return surfaceColor + sphere.emissionColor; 

這三個變化將解決喲你的代碼併產生你想要的結果。


現在,這就是說,恕我直言,這是值得考慮一些其他的變化。最值得注意的是將struct類型用於Vec3fCollision而不是class。與C++不同,其中structclass之間的唯一真正區別是成員的默認可訪問性,在C#中,這兩種類型的基本行爲有很大不同。在像這樣的程序中,使用struct而不是class作爲這些頻繁使用的值可以顯着提高性能,這可以通過最小化堆分配數據量,特別是僅臨時存在且需要由垃圾回收器收集的數據你的程序正在嘗試做其他工作。

您可能還想考慮將數據類型從float更改爲double。我用兩種方式測試了代碼;它在視覺輸出上沒有任何區別,但我看到渲染平均需要2.1秒的時間,double和平均2.8秒,float。速度提高25%可能是你想要的。 :)

至於struct VS class問題去,在我的測試,使用更快的double類型的算術,我看到速度使用struct代替class(使用class這些類型運行在36%的改善3.3秒,同時使用struct在2.1秒內運行)。

與此同時,值可以修改的struct類型可能會導致難以找到的錯誤。一個struct真的應該是不可變的,所以作爲變化的一部分,我調整了它們的類型。對於Collision類型,這是相對簡單的,但在Vec3f的情況下,您的代碼有一些地方修改了這些值(通過調用normalize())。要使更改爲不可更改的struct值有效,必須更改這些全部值,以便使用normalize()方法的返回值代替原始值。

我的其他修改包括:

  • 卸下Vec3f()構造。無論如何,這是不允許的struct類型,並且不需要它,因爲默認的構造函數會做正確的事情。
  • 將衝突檢查t0 < 0轉換爲Collision類型,以支持該類型的不變性。
  • 更改您的Sphere迭代循環回到使用整數索引,如在原始的C++中。語句foreach涉及爲每個循環分配一個枚舉器;通過直接索引數組,您可以避免這些不必要的分配,這意味着變量名也更有意義(ij通常爲索引保留,所以它是奇怪的閱讀代碼,它們代表了其他內容)。
  • 我還將代碼返回爲更類似於其他地方的C++代碼,例如eta的初始化和排列更類似於C++代碼的代碼。
  • 我將代碼從使用List<Vec3f>更改爲使用數組。這樣更有效,並且避免了必須定期重新分配列表的後備存儲。

最後,我對程序的輸出進行了重大改變。我對等待控制檯窗口打印所有輸出不感興趣,也沒有興趣試圖追蹤並安裝可讀取並顯示基於文本的圖像輸出的程序。

因此,我改變了文本輸出,以便它只寫入內存字符串,並添加了代碼,以便程序生成一個實際的PNG文件,我可以直接打開,派對節目。

一切都成定局,這是我的了:

ray-traced balls

這裏是我的代碼的最終版本:

class Program 
{ 
    const int MAX_RAY_DEPTH = 8; 
    const float FAR = 100000000; 

    public static void Main(string[] args) 
    { 
     Sphere[] spheres = new Sphere[7]; 
     spheres[0] = new Sphere(new Vec3f(0.0f, -10004, -20), 10000, new Vec3f(0.20f, 0.20f, 0.20f), 0, 0.0f); 
     spheres[1] = new Sphere(new Vec3f(0.0f,  0, -20),  4, new Vec3f(1.00f, 0.32f, 0.36f), 1, 0.5f); 
     spheres[2] = new Sphere(new Vec3f(5.0f,  -1, -15),  2, new Vec3f(0.90f, 0.76f, 0.46f), 1, 0.0f); 
     spheres[3] = new Sphere(new Vec3f(5.0f,  0, -25),  3, new Vec3f(0.65f, 0.77f, 0.97f), 1, 0.0f); 
     spheres[4] = new Sphere(new Vec3f(-5.5f,  0, -15),  3, new Vec3f(0.90f, 0.90f, 0.90f), 1, 0.0f); 
     spheres[5] = new Sphere(new Vec3f( 2f,  2, -30),  4, new Vec3f(0.53f, 0.38f, 0.91f), 1, 0.7f); 
     spheres[6] = new Sphere(new Vec3f( 0,  20, -30),  3, new Vec3f(0.00f, 0.00f, 0.00f), 0, 0.0f, new Vec3f(3)); 
     Render(spheres); 
    } 

    public struct Collision 
    { 
     public readonly float t0, t1; 
     public readonly bool collide; 

     public Collision(bool col, float tt0, float tt1) 
     { 
      t0 = tt0 < 0 ? tt1 : tt0; 
      t1 = tt1; 
      collide = col; 
     } 
    } 

    public struct Vec3f 
    { 
     public readonly float x, y, z; 
     public Vec3f(float v) { x = y = z = v; } 
     public Vec3f(float xx, float yy, float zz) { x = xx; y = yy; z = zz; } 

     public Vec3f normalize() 
     { 
      float nor2 = length2(); 
      if (nor2 > 0) 
      { 
       float invNor = 1/(float)Math.Sqrt(nor2); 

       return new Vec3f(x * invNor, y * invNor, z * invNor); 
      } 

      return this; 
     } 
     public static Vec3f operator *(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x * r.x, l.y * r.y, l.z * r.z); 
     } 
     public static Vec3f operator *(Vec3f l, float r) 
     { 
      return new Vec3f(l.x * r, l.y * r, l.z * r); 
     } 
     public float dot(Vec3f v) 
     { 
      return x * v.x + y * v.y + z * v.z; 
     } 
     public static Vec3f operator -(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x - r.x, l.y - r.y, l.z - r.z); 
     } 
     public static Vec3f operator +(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x + r.x, l.y + r.y, l.z + r.z); 
     } 
     public static Vec3f operator -(Vec3f v) 
     { 
      return new Vec3f(-v.x, -v.y, -v.z); 
     } 
     public float length2() 
     { 
      return x * x + y * y + z * z; 
     } 
     public float length() 
     { 
      return (float)Math.Sqrt(length2()); 
     } 
    } 

    public class Sphere 
    { 
     public readonly Vec3f center, surfaceColor, emissionColor; 
     public readonly float radius, radius2; 
     public readonly float transparency, reflection; 
     public Sphere(Vec3f c, float r, Vec3f sc, float refl = 0, float transp = 0, Vec3f? ec = null) 
     { 
      center = c; radius = r; radius2 = r * r; 
      surfaceColor = sc; emissionColor = (ec == null) ? new Vec3f() : ec.Value; 
      transparency = transp; reflection = refl; 
     } 

     public Collision intersect(Vec3f rayorig, Vec3f raydir) 
     { 
      Vec3f l = center - rayorig; 
      float tca = l.dot(raydir); 
      if (tca < 0) { return new Collision(); } 
      float d2 = l.dot(l) - tca * tca; 
      if (d2 > radius2) { return new Collision(); } 
      float thc = (float)Math.Sqrt(radius2 - d2); 
      return new Collision(true, tca - thc, tca + thc); 
     } 
    } 

    public static float mix(float a, float b, float mix) 
    { 
     return b * mix + a * (1 - mix); 
    } 

    public static Vec3f trace(Vec3f rayorig, Vec3f raydir, Sphere[] spheres, int depth) 
    { 
     float tnear = FAR; 
     Sphere sphere = null; 
     for (int i = 0; i < spheres.Length; i++) 
     { 
      Collision coll = spheres[i].intersect(rayorig, raydir); 
      if (coll.collide && coll.t0 < tnear) 
      { 
       tnear = coll.t0; 
       sphere = spheres[i]; 
      } 
     } 
     if (sphere == null) { return new Vec3f(2); } 
     Vec3f surfaceColor = new Vec3f(); 
     Vec3f phit = rayorig + raydir * tnear; 
     Vec3f nhit = (phit - sphere.center).normalize(); 
     float bias = 1e-4f; 
     bool inside = false; 
     if (raydir.dot(nhit) > 0) { nhit = -nhit; inside = true; } 
     if ((sphere.transparency > 0 || sphere.reflection > 0) && depth < MAX_RAY_DEPTH) 
     { 
      float facingratio = -raydir.dot(nhit); 
      float fresneleffect = mix((float)Math.Pow(1 - facingratio, 3), 1, 0.1f); 
      Vec3f refldir = (raydir - nhit * 2 * raydir.dot(nhit)).normalize(); 
      Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 
      Vec3f refraction = new Vec3f(); 
      if (sphere.transparency > 0) 
      { 
       float ior = 1.1f; float eta = inside ? ior : 1/ior; 
       float cosi = -nhit.dot(raydir); 
       float k = 1 - eta * eta * (1 - cosi * cosi); 
       Vec3f refrdir = (raydir * eta + nhit * (eta * cosi - (float)Math.Sqrt(k))).normalize(); 
       refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 
      } 
      surfaceColor = (
       reflection * fresneleffect + 
       refraction * (1 - fresneleffect) * sphere.transparency) * sphere.surfaceColor; 
     } 
     else 
     { 
      for (int i = 0; i < spheres.Length; i++) 
      { 
       if (spheres[i].emissionColor.x > 0) 
       { 
        Vec3f transmission = new Vec3f(1); 
        Vec3f lightDirection = (spheres[i].center - phit).normalize(); 
        for (int j = 0; j < spheres.Length; j++) 
        { 
         if (i != j) 
         { 
          Collision jcoll = spheres[j].intersect(phit + nhit * bias, lightDirection); 
          if (jcoll.collide) 
          { 
           transmission = new Vec3f(); 
           break; 
          } 
         } 
        } 
        surfaceColor += sphere.surfaceColor * transmission * 
         Math.Max(0, nhit.dot(lightDirection)) * spheres[i].emissionColor; 

       } 
      } 
     } 

     return surfaceColor + sphere.emissionColor; 
    } 

    public static void Render(Sphere[] spheres) 
    { 
     int width = 800, height = 600; 
     Vec3f[] image = new Vec3f[width * height]; 
     int pixelIndex = 0; 
     float invWidth = 1f/width, invHeight = 1f/height; 
     float fov = 30, aspectratio = (float)width/height; 
     float angle = (float)Math.Tan(Math.PI * 0.5 * fov/180); 
     for (int y = 0; y < height; y++) 
     { 
      for (int x = 0; x < width; x++, pixelIndex++) 
      { 
       float xx = (2 * ((x + 0.5f) * invWidth) - 1) * angle * aspectratio; 
       float yy = (1 - 2 * ((y + 0.5f) * invHeight)) * angle; 
       Vec3f raydir = new Vec3f(xx, yy, -1).normalize(); 

       image[pixelIndex] = trace(new Vec3f(), raydir, spheres, 0); 
      } 
     } 

     StringWriter writer = new StringWriter(); 
     WriteableBitmap bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Rgb24, null); 

     bitmap.Lock(); 

     unsafe 
     { 
      byte* buffer = (byte*)bitmap.BackBuffer; 

      { 
       writer.Write("P3 800 600 255\r\n"); 
       for (int y = 0; y < height; y++) 
       { 
        for (int x = 0; x < width; ++x) 
        { 
         if (x > 0) { writer.Write(" "); } 
         Vec3f pixel = GetColor(image[y * width + x]); 
         writer.Write(pixel.x + " " + pixel.y + " " + pixel.z); 

         int bufferOffset = y * bitmap.BackBufferStride + x * 3; 
         buffer[bufferOffset] = (byte)pixel.x; 
         buffer[bufferOffset + 1] = (byte)pixel.y; 
         buffer[bufferOffset + 2] = (byte)pixel.z; 
        } 

        writer.WriteLine(); 
       } 
      } 
     } 

     bitmap.Unlock(); 


     var encoder = new PngBitmapEncoder(); 

     using (Stream stream = File.OpenWrite("temp.png")) 
     { 
      encoder.Frames.Add(BitmapFrame.Create(bitmap)); 
      encoder.Save(stream); 
     } 

     string result = writer.ToString(); 
    } 

    public static Vec3f GetColor(Vec3f col) 
    { 
     return new Vec3f(Math.Min(1, col.x) * 255, Math.Min(1, col.y) * 255, Math.Min(1, col.z) * 255); 
    } 
} 

注意,對於上面的代碼編譯,你」您需要在您的項目中添加對PresentationCore,WindowsBase和System的引用。Xaml組件。您還需要檢查項目設置中的「允許不安全的代碼」選項。

+0

非常感謝!我現在看到我的錯誤。我在編程時認爲浮動就像真正的數學(如1/2)。現在我明白爲什麼C++代碼在任何地方都使用0.0表達式。再次感謝你。我會記住這些。 –