Raytracer没有产生预期的输出

时间:2016-08-20 15:06:59

标签: c# graphics raytracing

我遇到了一个问题:我已经使用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创建可执行文件 &#34; csc.exe raytracer.cs&#34; 并运行程序 &#34; raytracer.exe&gt; out.ppm&#34;

1 个答案:

答案 0 :(得分:2)

C#代码的基本问题是在需要浮点结果的地方使用int值。就像在C ++代码中一样,原始int值在分区中使用之前会转换为float,您也需要在C#代码中执行此操作。特别是,您的invHeightinvWidthaspectratio计算都需要使用浮点数学而不是整数数学来执行:

    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 + " ");

您在转化中也遇到了一个小错误,因为您未能在sphere.emissionColor方法的末尾添加trace()

        return surfaceColor + sphere.emissionColor;

这三项更改将修复您的代码并生成您想要的结果。


现在,说,恕我直言,值得考虑其他一些变化。最值得注意的是structVec3f使用Collision类型而不是class。与C ++不同,structclass之间唯一真正的区别是成员的默认可访问性,在C#中,这两种类型的基本行为非常不同。在这样的程序中,对这些常用值使用struct而不是class可以通过最小化堆分配数据的数量来显着提高性能,尤其是仅暂时存在且需要的数据当你的程序试图做其他工作时由垃圾收集器收集。

您可能还需要考虑将数据类型从float更改为double。我用两种方式测试了代码;它在视觉输出上没有任何区别,但我看到渲染平均需要2.1秒double和2.8秒平均float。速度提高25%可能是你想要的。 :)

struct vs class问题而言,在我的测试中,使用更快的double类型算术,我看到使用{{1}速度提高了36%而不是struct(使用class这些类型在3.3秒内运行,而使用class在2.1秒内运行。

同时,可以修改值的struct类型可能导致难以发现的错误。 struct确实应该是不可变的,所以作为变更的一部分,我调整了它们的类型。这对于struct类型来说相对简单,但对于Collision,您的代码有许多地方可以修改这些值(通过调用Vec3f)。要使对不可变normalize()值的更改起作用,必须更改这些值,以便使用struct方法的返回值代替原始值。

我所做的其他更改包括:

  • 删除normalize()构造函数。对Vec3f()类型不允许这样做,并且不需要它,因为默认构造函数会做正确的事。
  • struct的碰撞检查移至t0 < 0类型,以支持该类型的不变性。
  • Collision迭代循环更改回使用整数索引,就像在原始C ++中一样。 Sphere语句涉及为每个循环分配枚举器;通过直接索引数组,可以避免这些不必要的分配,这意味着变量名称也更有意义(foreachi通常是为索引保留的,所以它们表示奇怪的读取代码别的东西)。
  • 我还将代码返回到更类似于其他地方的C ++代码,例如j的初始化,并排列更类似于C ++代码的代码。
  • 我将代码从使用eta更改为使用数组。这样更有效,并且无需定期为列表重新分配后备存储。

最后,我对程序的输出做了重大改变。我没有兴趣等待控制台窗口打印所有输出,也没有兴趣尝试跟踪并安装一个程序来读取和显示基于文本的图像输出。

所以相反,我更改了文本输出,以便它只写入内存中的字符串,我添加了代码,以便程序生成一个我可以直接打开的实际PNG文件,而不通过某些第三方程序

所有说完了,这就是我得到的:

ray-traced balls

这是我的最终版本代码:

List<Vec3f>

请注意,要进行上述编译,您需要将项目中的引用添加到PresentationCore,WindowsBase和System.Xaml程序集中。您还需要在项目设置中选中“允许不安全的代码”选项。