Raytracer не выдает ожидаемый результат

c# graphics raytracing

198 просмотра

1 ответ

У меня проблема: я прочитал статью о скретчапикселе с кодом C ++ для трассировки лучей. С ++ в порядке. Я попытался преобразовать его в Python, он работал (в 17 раз медленнее и в 4 раза меньше разрешения). Я пытался преобразовать его в C #, но мой код не работает. Единственное, что я вижу, это чистое белое изображение 800х600. Пожалуйста, смотрите ранее связанную статью для кода 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"

Автор: Yahya Gedik Источник Размещён: 08.11.2019 11:28

Ответы (1)


2 плюса

Решение

Основная проблема, с которой сталкивается ваш код на C #, заключается в использовании intзначений, для которых вы хотите получить результат с плавающей запятой. Как и в коде C ++, перед использованием их в делении исходные intзначения преобразуются float, это необходимо сделать и в коде C #. В частности, ваши invHeight, invWidthи 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 + " ");

У вас также была небольшая ошибка в преобразовании, которую вы не смогли добавить sphere.emissionColorв конце trace()метода:

        return surfaceColor + sphere.emissionColor;

Эти три изменения исправят ваш код и приведут к желаемым результатам.


Теперь, что сказал, ИМХО стоит рассмотреть некоторые другие изменения. Наиболее заметным было бы использование structтипов для Vec3fи Collisionвместо class. В отличие от C ++, где единственной реальной разницей между structи classявляется доступность по умолчанию для членов, в C # эти два типа типов сильно отличаются по своему базовому поведению. В такой программе использование structвместо classчасто используемых значений может значительно повысить производительность, сводя к минимуму объем данных, выделенных в куче, особенно данных, которые существуют только временно и должны быть собраны сборщиком мусора, пока ваша программа пытается сделать другую работу.

Вы также можете рассмотреть возможность изменения типа данных с floatна double. Я тестировал код в обоих направлениях; это не имеет значения в визуальном выводе, но я видел, что рендеринг занимал в среднем 2,1 секунды с doubleи 2,8 секунды в среднем с float. 25% -ное улучшение в скорости - это то, что вы хотели бы получить. :)

Что касается вопроса « structпротив» class, то в моих тестах, используя более быстрый doubleтип для арифметики, я увидел улучшение скорости на 36% structвместо использования class(использование classдля этих типов выполняется за 3,3 секунды, а использование structвыполняется за 2,1 секунды).

В то же время structтипы, в которых значения могут быть изменены, могут привести к трудным для обнаружения ошибкам. A structдействительно должен быть неизменным, поэтому, как часть изменений, я настроил типы так, чтобы они были. Это было относительно просто для Collisionтипа, но в случае Vec3f, в вашем коде есть несколько мест, где эти значения были изменены (путем вызова normalize()). Чтобы изменение неизменных structзначений работало, все они должны были измениться так, чтобы normalize()вместо исходного значения использовалось возвращаемое значение метода.

Другие изменения, которые я сделал, включают в себя:

  • Удаление Vec3f()конструктора. В structлюбом случае это не разрешено для типов, и в этом нет необходимости, потому что конструктор по умолчанию будет делать правильные вещи.
  • Перемещение проверки столкновения t0 < 0в Collisionтип, чтобы поддержать неизменность этого типа.
  • Изменение вашей Sphereитерации возвращается к использованию целочисленных индексов, как в оригинальном C ++. foreachЗаявление включает в себя выделение перечислителя для каждого цикла; напрямую индексируя массив, вы можете избежать этих ненужных распределений, и это означает, что имена переменных также имеют больше смысла ( iи jтрадиционно зарезервированы для индексов, так что это странный код чтения, где они представляют что-то другое).
  • Я также вернул код, чтобы он был больше похож на код C ++ в других местах, таких как инициализация etaи выравнивание кода, более похожего на код C ++.
  • Я изменил код с использования List<Vec3f>вместо использования массива. Это более эффективно и позволяет избежать перераспределения резервного хранилища для списка периодически.

Наконец, я внес значительные изменения в вывод программы. Я не был заинтересован в том, чтобы ждать, пока окно консоли напечатает все выходные данные, и не был заинтересован в попытке отследить и установить программу, которая считывала бы и отображала текстовый вывод изображения.

Вместо этого я изменил вывод текста так, чтобы он записывал только строку в памяти, и добавил код, чтобы программа генерировала фактический файл PNG, который я мог открыть напрямую, без какой-либо сторонней программы.

Все сказано и сделано, вот что я получил:

трассируемые лучами шары

Вот мой окончательный вариант кода:

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. Вам также нужно будет проверить параметр «Разрешить небезопасный код» в настройках проекта.

Автор: Peter Duniho Размещён: 20.08.2016 06:36
Вопросы из категории :
32x32