Exemplo n.º 1
0
        private Vector3D GetColor(Ray r, HittableList world, int depth)
        {
            HitRecord rec;

            if (world.Hit(r, 0.001, double.MaxValue, out rec))
            {
                Ray      scattered;
                Vector3D attenuation;
                if (depth < 50 && rec.matPtr.Scatter(r, rec, out attenuation, out scattered)) //只递归50次,避免无谓的性能浪费
                {
                    Vector3D color = GetColor(scattered, world, depth + 1);                   //每次光线衰减之后深度加一
                    return(new Vector3D(attenuation.X * color.X, attenuation.Y * color.Y, attenuation.Z * color.Z));
                }
                else
                {
                    return(new Vector3D(0, 0, 0));
                }
            }
            else
            {
                Vector3D unitDirection = r.Direction.UnitVector();
                double   t             = 0.5 * (unitDirection.Y + 1);
                return((1 - t) * new Vector3D(1, 1, 1) + t * new Vector3D(0.5, 0.7, 1));
            }
        }
Exemplo n.º 2
0
        private void Form1_Load(object sender, EventArgs e)
        {
            int      nx         = 200;
            int      ny         = 100;
            Bitmap   bmp        = new Bitmap(nx, ny);
            Vector3D lowerLeft  = new Vector3D(-2, 1, -1);
            Vector3D horizontal = new Vector3D(4, 0, 0);
            Vector3D vertical   = new Vector3D(0, -2, 0);
            Vector3D origin     = new Vector3D(0, 0, 0);

            List <Hittable> list = new List <Hittable>();

            list.Add(new Sphere(new Vector3D(0, 0, -1), 0.5));
            list.Add(new Sphere(new Vector3D(0, -100.5, -1), 100));
            HittableList world = new HittableList(list, 2);

            for (int j = 0; j < ny; j++)
            {
                for (int i = 0; i < nx; i++)
                {
                    double   u     = (double)i / (double)nx;
                    double   v     = (double)j / (double)ny;
                    Ray      ray   = new Ray(origin, lowerLeft + u * horizontal + v * vertical);
                    Vector3D p     = ray.GetPoint(2);
                    Vector3D color = GetColor(ray, world);
                    int      r     = (int)(255 * color.X);
                    int      g     = (int)(255 * color.Y);
                    int      b     = (int)(255 * color.Z);
                    bmp.SetPixel(i, j, Color.FromArgb(r, g, b));   //如果之前没有将判别式除以4的话这里会报错,则需要限定一下边界,
                                                                   //将rgb的值限制在0到255之间,会得到一个和书上不一样的球
                }
            }
            pictureBox1.BackgroundImage = bmp;
        }
Exemplo n.º 3
0
        static void Main(string[] args)
        {
            int xRes = 720;
            int yRes = 480;

            int runs = 10;


            IMaterial cyan  = new LambertianDiffuse(new Color3(0, 0.88, 0.88));
            IMaterial gray  = new LambertianDiffuse(new Color3(0.5, 0.5, 0.5));
            IMaterial red   = new LambertianDiffuse(new Color3(0.8, 0.2, 0.2));
            IMaterial metal = new Metal(new Color3(0.9, 0.9, 0.9), 0);

            Renderer raytracer = new()
            {
                // Define objects in the scene
                HittableObjects = new HittableList
                {
                    new Sphere(0, 1, 0, 0.5, gray),
                    new Sphere(1, 1, 0, 0.5, red),
                    new Sphere(0, 1, -100.5, 100, metal),
                    new Sphere(0, -2, 0, 1, cyan)
                }
            };

            // Define the camera
            Camera camera = new(xRes, yRes)
            {
                Origin = new Vector3(-1, 0, 0),
                MultithreadedRendering = true,
                SamplesPerPixel        = 100,
                MaxBounces             = 12
            };

            long[] frameTimes = new long[runs];

            for (int i = 0; i < runs; i++)
            {
                raytracer.RenderScene(camera, out long frameTime);

                frameTimes[i] = frameTime;

                Console.WriteLine("Run {0} | Time: {1} | Time Per Pixel: {2}", i, frameTime, (double)frameTime / (double)(xRes * yRes));
            }

            Console.WriteLine("------------------------------------------------------------------");
            Console.WriteLine("Total Runs: {0}\nResolution: {1}x{2}", runs, xRes, yRes);
            Console.WriteLine("------------------------------------------------------------------");

            long sum = 0;

            Array.ForEach(frameTimes, i => sum += i);
            Console.WriteLine("Total frametime:        {0} ms", sum);
            Console.WriteLine("Average frametime:      {0} ms", sum / runs);
            Console.WriteLine("Average time per pixel: {0} ms", (double)sum / (double)runs / (double)(xRes * yRes));
            Console.WriteLine("------------------------------------------------------------------");
        }
    }
}
Exemplo n.º 4
0
        static async Task Main(string[] args)
        {
            var img_width          = int.Parse(args[0]);
            var img_height         = int.Parse(args[1]);
            var spp                = int.Parse(args[2]);
            var destination_folder = args[3];

            var film = new Film(img_height, img_width, spp);

            var lookFrom = new Vector3(3, 3, 2);
            var lookAt   = new Vector3(0, 0, -1);
            var vup      = new Vector3(0, 1, 0);

            var cam = new Camera(
                lookFrom,
                lookAt,
                vup,
                20,
                ((double)img_width) / img_height,
                1,
                (lookFrom - lookAt).Length
                );

            var rng = new Random(1);

            var world = new HittableList(new[] {
                new Sphere(new Vector3(0, 0, -1), 0.5, new Lambertian(new Vector3(0.1, 0.2, 0.5))),
                new Sphere(new Vector3(0, -100.5, -1), 100, new Lambertian(new Vector3(0.8, 0.8, 0))),
                new Sphere(new Vector3(1, 0, -1), 0.5, new Metal(new Vector3(0.8, 0.6, 0.2), 0)),
                new Sphere(new Vector3(-1, 0, -1), 0.5, new Dielectric(1.5)),
                new Sphere(new Vector3(-1, 0, -1), -0.45, new Dielectric(1.5)),
            });

            for (var j = 0; j < img_height; j++)
            {
                Console.WriteLine($"Scanlines remaining: {(img_height - j).ToString()}");
                for (var i = 0; i < img_width; i++)
                {
                    for (var s = 0; s < spp; s++)
                    {
                        var u = (i + rng.NextDouble()) / img_width;
                        var v = (j + rng.NextDouble()) / img_height;

                        var ray = cam.GetRay(u, v);
                        film.AddSample(i, j, RayColor(ray, world, 50));
                    }
                }
            }
            Console.WriteLine("Writing film");
            await film.WriteToFile($@"{destination_folder}\img_{DateTime.UtcNow.Ticks}.ppm");

            Console.WriteLine("Done");
        }
Exemplo n.º 5
0
        private Vector3D GetColor(Ray r, HittableList world)
        {
            HitRecord rec;

            if (world.Hit(r, 0, double.MaxValue, out rec))
            {
                return(0.5 * new Vector3D(rec.normal.X + 1, rec.normal.Y + 1, rec.normal.Z + 1));
            }
            else
            {
                Vector3D unitDirection = r.Direction.UnitVector();
                double   t             = 0.5 * (unitDirection.Y + 1);
                return((1 - t) * new Vector3D(1, 1, 1) + t * new Vector3D(0.5, 0.7, 1));
            }
        }
        private HittableList RandomScene()
        {
            HittableList world = new HittableList();

            world.Objects.Add(new Sphere(new Vec3(0, -1000, 0), 1000, new Lambertian(new Vec3(0.5, 0.5, 0.5))));

            int  i = 1;
            Vec3 differenceCenter = new Vec3(4, 0.2, 0);

            for (int a = -11; a < 11; a++)
            {
                for (int b = -11; b < 11; b++)
                {
                    double chooseMat = RayTracerUtils.RandomDouble();
                    Vec3   center    = new Vec3(a + 0.9 * RayTracerUtils.RandomDouble(), 0.2, b + 0.9 * RayTracerUtils.RandomDouble());
                    if ((center - differenceCenter).Length > 0.9)
                    {
                        if (chooseMat < 0.8)
                        {
                            //diffuse
                            Vec3 albedo = Vec3.Random() * Vec3.Random();
                            world.Objects.Add(new Sphere(center, 0.2, new Lambertian(albedo)));
                        }
                        else if (chooseMat < 0.95)
                        {
                            //metal
                            Vec3   albedo = Vec3.Random(0.5, 1);
                            double fuzz   = RayTracerUtils.RandomDouble(0, 0.5);
                            world.Objects.Add(new Sphere(center, 0.2, new Metal(albedo, fuzz)));
                        }
                        else
                        {
                            //glass
                            world.Objects.Add(new Sphere(center, 0.2, new Dielectric(1.5)));
                        }
                    }
                }
            }

            world.Objects.Add(new Sphere(new Vec3(0, 1, 0), 1.0, new Dielectric(1.5)));

            world.Objects.Add(new Sphere(new Vec3(-4, 1, 0), 1.0, new Lambertian(new Vec3(0.4, 0.2, 0.1))));

            world.Objects.Add(new Sphere(new Vec3(4, 1, 0), 1.0, new Metal(new Vec3(0.7, 0.6, 0.5), 0.0)));

            return(world);
        }
Exemplo n.º 7
0
        private Vector3D GetColor(Ray r, HittableList world)
        {
            HitRecord rec;

            if (world.Hit(r, 0.0000001, double.MaxValue, out rec))
            {
                Vector3D target = rec.p + rec.normal + RandomInUnitShpere( );   //击中点加法线向量得到击中点单位球的球心,球心加上
                                                                                //随机向量得到反射的方向。
                return(0.5 * GetColor(new Ray(rec.p, target - rec.p), world));  //每次碰撞都使光线强度减半
            }
            else
            {
                Vector3D unitDirection = r.Direction.UnitVector();
                double   t             = 0.5 * (unitDirection.Y + 1);
                return((1 - t) * new Vector3D(1, 1, 1) + t * new Vector3D(0.5, 0.7, 1));
            }
        }
Exemplo n.º 8
0
        private void Form1_Load(object sender, EventArgs e)
        {
            int nx = 200;
            int ny = 100;
            int ns = 100;

            Bitmap bmp = new Bitmap(nx, ny);

            Vector3D lookFrom    = new Vector3D(3, 3, 2);
            Vector3D lookAt      = new Vector3D(0, 0, -1);
            double   diskToFocus = (lookFrom - lookAt).Length();
            double   aperture    = 2;
            Camera   cam         = new Camera(lookFrom, lookAt, new Vector3D(0, 1, 0), 20,
                                              (double)nx / (double)ny, aperture, diskToFocus);
            List <Hittable> list = new List <Hittable>();

            list.Add(new Sphere(new Vector3D(0, 0, -1), 0.5, new Lambertian(new Vector3D(0.1, 0.2, 0.5))));
            list.Add(new Sphere(new Vector3D(0, -100.5, -1), 100, new Lambertian(new Vector3D(0.8, 0.8, 0))));
            list.Add(new Sphere(new Vector3D(1, 0, -1), 0.5, new Metal(new Vector3D(0.8, 0.6, 0.2), 0.0)));
            list.Add(new Sphere(new Vector3D(-1, 0, -1), 0.5, new Dielectric(1.5)));
            list.Add(new Sphere(new Vector3D(-1, 0, -1), -0.45, new Dielectric(1.5)));
            HittableList world = new HittableList(list, list.Count);

            for (int j = 0; j < ny; j++)
            {
                for (int i = 0; i < nx; i++)
                {
                    Vector3D color = new Vector3D(0, 0, 0);
                    for (int s = 0; s < ns; s++)
                    {
                        double u   = (double)(i + RandomDouble()) / (double)nx;
                        double v   = 1 - (double)(j + RandomDouble()) / (double)ny;
                        Ray    ray = cam.GetRay(u, v);
                        color += GetColor(ray, world, 0);                                              //将所有采样点的颜色相加
                    }
                    color /= ns;                                                                       //除以采样点的数量得到平均值
                    color  = new Vector3D(Math.Sqrt(color.X), Math.Sqrt(color.Y), Math.Sqrt(color.Z)); //进行伽马校正
                    int r = (int)(255 * color.X);
                    int g = (int)(255 * color.Y);
                    int b = (int)(255 * color.Z);
                    bmp.SetPixel(i, j, Color.FromArgb(r, g, b));
                }
            }
            pictureBox1.BackgroundImage = bmp;
        }
Exemplo n.º 9
0
        private void Form1_Load(object sender, EventArgs e)
        {
            int nx = 200;
            int ny = 100;
            int ns = 100;

            Bitmap   bmp        = new Bitmap(nx, ny);
            Vector3D lowerLeft  = new Vector3D(-2, 1, -1);
            Vector3D horizontal = new Vector3D(4, 0, 0);
            Vector3D vertical   = new Vector3D(0, -2, 0);
            Vector3D origin     = new Vector3D(0, 0, 0);

            List <Hittable> list = new List <Hittable>();

            list.Add(new Sphere(new Vector3D(0, 0, -1), 0.5, new Lambertian(new Vector3D(0.8, 0.3, 0.3))));
            list.Add(new Sphere(new Vector3D(0, -100.5, -1), 100, new Lambertian(new Vector3D(0.8, 0.8, 0))));
            list.Add(new Sphere(new Vector3D(1, 0, -1), 0.5, new Metal(new Vector3D(0.8, 0.6, 0.2), 0.3)));
            list.Add(new Sphere(new Vector3D(-1, 0, -1), 0.5, new Metal(new Vector3D(0.8, 0.8, 0.8), 1)));
            HittableList world = new HittableList(list, list.Count);
            Camera       cam   = new Camera();

            for (int j = 0; j < ny; j++)
            {
                for (int i = 0; i < nx; i++)
                {
                    Vector3D color = new Vector3D(0, 0, 0);
                    for (int s = 0; s < ns; s++)
                    {
                        double u   = (double)(i + RandomDouble()) / (double)nx;
                        double v   = (double)(j + RandomDouble()) / (double)ny;
                        Ray    ray = cam.GetRay(u, v);
                        color += GetColor(ray, world, 0);                                              //将所有采样点的颜色相加
                    }
                    color /= ns;                                                                       //除以采样点的数量得到平均值
                    color  = new Vector3D(Math.Sqrt(color.X), Math.Sqrt(color.Y), Math.Sqrt(color.Z)); //进行伽马校正
                    int r = (int)(255 * color.X);
                    int g = (int)(255 * color.Y);
                    int b = (int)(255 * color.Z);
                    bmp.SetPixel(i, j, Color.FromArgb(r, g, b));
                }
            }
            pictureBox1.BackgroundImage = bmp;
        }
Exemplo n.º 10
0
        static void Main()
        {
            bool writeDebugInfo = false;

            IMaterial groundMaterial = new LambertianDiffuse(Color3.FromRgb(255, 212, 216));
            IMaterial rightMaterial  = new Metal(Color3.FromRgb(220, 220, 220));
            IMaterial leftMaterial   = new Dielectric(1.5);
            IMaterial centerMaterial = new LambertianDiffuse(Color3.FromRgb(222, 133, 255));

            Renderer renderer = new()
            {
                // Define objects in the scene
                HittableObjects = new HittableList
                {
                    new Sphere(new Vector3(0, 1, -100.5), 100, groundMaterial),
                    new Sphere(new Vector3(0, 1, 0), 0.5, centerMaterial),
                    new Sphere(new Vector3(-1, 1, 0), 0.5, leftMaterial),
                    new Sphere(new Vector3(1, 1, 0), 0.5, rightMaterial),
                }
            };

            // Define the camera
            Camera camera = new(1280, 720)
            {
                Origin = new Vector3(0, 0, 0),
                MultithreadedRendering = true,
                SamplesPerPixel        = 100,
                MaxBounces             = 12
            };

            // Render
            RenderResult result = new();

            result.frameNumber = 0;
            result.camera      = camera;
            result.pixels      = renderer.RenderScene(camera, out result.frameTime);

            // Write to disk
            System.IO.Directory.CreateDirectory("./images/");
            Renderer.WriteFrame("./images/image_" + result.frameNumber + ".png", result.pixels, result.camera.ResolutionHeight, result.camera.ResolutionWidth, ImageFormat.Png, writeDebugInfo, result.frameTime, result.camera);
        }
    }
Exemplo n.º 11
0
        static async Task Main(string[] args)
        {
            var rayTracer = new RayTracer(1024, 768);

            rayTracer.Progress += remaining => Console.WriteLine("Remaining scan lines: {0}", remaining);

            var world = new HittableList();

            world.Add(new Sphere(new Vector3(0, 0, -1), 0.5d));
            world.Add(new Sphere(new Vector3(0, -50.5d, -1), 50));

            var filePath = Path.Combine(Directory.GetCurrentDirectory(), "result.ppm");

            using (var fileStream = new FileStream(filePath, FileMode.Create))
            {
                using (var result = await rayTracer.Go <PpmOutput>(world))
                {
                    await result.CopyToAsync(fileStream);
                }
            }
        }
Exemplo n.º 12
0
        private void Form1_Load(object sender, EventArgs e)
        {
            int nx = 200;
            int ny = 100;
            int ns = 100;                         //设置采样率抗锯齿,但渲染所需要的时间也变长了,获得随机数使用了大量时间,
                                                  //有时间的话可以优化一下函数RandomDouble
            Bitmap   bmp        = new Bitmap(nx, ny);
            Vector3D lowerLeft  = new Vector3D(-2, 1, -1);
            Vector3D horizontal = new Vector3D(4, 0, 0);
            Vector3D vertical   = new Vector3D(0, -2, 0);
            Vector3D origin     = new Vector3D(0, 0, 0);

            List <Hittable> list = new List <Hittable>();

            list.Add(new Sphere(new Vector3D(0, 0, -1), 0.5));
            list.Add(new Sphere(new Vector3D(0, -100.5, -1), 100));
            HittableList world = new HittableList(list, 2);
            Camera       cam   = new Camera();

            for (int j = 0; j < ny; j++)
            {
                for (int i = 0; i < nx; i++)
                {
                    Vector3D color = new Vector3D(0, 0, 0);
                    for (int s = 0; s < ns; s++)
                    {
                        double u   = (double)(i + RandomDouble()) / (double)nx;
                        double v   = (double)(j + RandomDouble()) / (double)ny;
                        Ray    ray = cam.GetRay(u, v);
                        color += GetColor(ray, world);      //将所有采样点的颜色相加
                    }
                    color /= ns;                            //除以采样点的数量得到平均值
                    int r = (int)(255 * color.X);
                    int g = (int)(255 * color.Y);
                    int b = (int)(255 * color.Z);
                    bmp.SetPixel(i, j, Color.FromArgb(r, g, b));
                }
            }
            pictureBox1.BackgroundImage = bmp;
        }
Exemplo n.º 13
0
        private void Form1_Load(object sender, EventArgs e)
        {
            int nx = 800;                       //渲染这一章的图需要的时间比较长,可能需要四五个小时,建议在relese模式下进行,也可以改小分辨率和采样率
            int ny = 400;
            int ns = 100;

            Bitmap bmp = new Bitmap(nx, ny);

            Vector3D lookFrom    = new Vector3D(13, 2, 3);
            Vector3D lookAt      = new Vector3D(0, 0, 0);
            double   diskToFocus = (lookFrom - lookAt).Length();
            double   aperture    = 0;
            Camera   cam         = new Camera(lookFrom, lookAt, new Vector3D(0, 1, 0), 20,
                                              (double)nx / (double)ny, aperture, 0.7 * diskToFocus);


            HittableList world = RandomScene();

            for (int j = 0; j < ny; j++)
            {
                for (int i = 0; i < nx; i++)
                {
                    Vector3D color = new Vector3D(0, 0, 0);
                    for (int s = 0; s < ns; s++)
                    {
                        double u   = (double)(i + RandomDouble()) / (double)nx;
                        double v   = 1 - (double)(j + RandomDouble()) / (double)ny;
                        Ray    ray = cam.GetRay(u, v);
                        color += GetColor(ray, world, 0);                                              //将所有采样点的颜色相加
                    }
                    color /= ns;                                                                       //除以采样点的数量得到平均值
                    color  = new Vector3D(Math.Sqrt(color.X), Math.Sqrt(color.Y), Math.Sqrt(color.Z)); //进行伽马校正
                    int r = (int)(255 * color.X);
                    int g = (int)(255 * color.Y);
                    int b = (int)(255 * color.Z);
                    bmp.SetPixel(i, j, Color.FromArgb(r, g, b));
                }
            }
            pictureBox1.BackgroundImage = bmp;
        }
Exemplo n.º 14
0
        private string RenderScene()
        {
            pboxPreview.Image = null;


            int width     = int.Parse(txtbxWidth.Text);
            int height    = int.Parse(txtbxHeight.Text);
            int samples   = int.Parse(txtbxSamples.Text);
            int chunkSize = int.Parse(txtbxChunkSize.Text);

            string fname = "Test";

            if (txtbxFileName.Text != "")
            {
                fname = txtbxFileName.Text;
            }

            List <Hittable> activeHittables = new List <Hittable>();

            foreach (string hittableName in listbxHittables.CheckedItems)
            {
                activeHittables.Add(GetHittable(hittableName));
            }

            XmlSerializer serializer = new XmlSerializer(typeof(PTObject[]));

            using (XmlWriter writer = XmlWriter.Create("autosave.xml"))
            {
                List <PTObject> temp = new List <PTObject>();
                foreach (PTObject material in materials.Values)
                {
                    temp.Add(material);
                }
                foreach (PTObject hittable in hittables.Values)
                {
                    temp.Add(hittable);
                }
                serializer.Serialize(writer, temp.ToArray());
            }

            PTObject scene = new HittableList(activeHittables.ToArray());

            Vec3 lookFrom = new Vec3(2f, 1.2f, -2f);
            Vec3 lookAt   = new Vec3(-2f, 0f, 2f);

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            if (!PTObject.RenderSceneChunked(
                    width, height, samples, chunkSize, fname,
                    lookFrom, lookAt, new Vec3(0f, 1f, 0f), (float)Math.PI / 3f, (float)width / height, 0f, (lookFrom - lookAt).Length,
                    scene))
            {
                // all objects are invalidated if rendering fails.
                foreach (string textureName in textures.Keys)
                {
                    GetTexture(textureName).Destroy();
                }
                textures.Clear();
                foreach (string materialName in materials.Keys)
                {
                    GetMaterial(materialName).Destroy();
                }
                materials.Clear();
                foreach (string hittableName in hittables.Keys)
                {
                    GetHittable(hittableName).Destroy();
                }
                hittables.Clear();
                scene.Destroy();

                using (XmlReader reader = XmlReader.Create("autosave.xml"))
                {
                    PTObject[] autosaved = serializer.Deserialize(reader) as PTObject[];
                    for (int i = 0; i < autosaved.Length; i++)
                    {
                        switch (autosaved[i].Kind)
                        {
                        case PTObject.PTObjectKind.Hittable:
                            hittables.Add(i.ToString(), autosaved[i] as Hittable);
                            break;

                        case PTObject.PTObjectKind.Material:
                            materials.Add(i.ToString(), autosaved[i] as Material);
                            break;

                        case PTObject.PTObjectKind.Texture:
                            textures.Add(i.ToString(), autosaved[i] as Texture);
                            break;
                        }
                    }
                }

                return("Rendering failed.");
            }
            sw.Stop();

            scene.Destroy();

            Bitmap image = ReadBitmapFromPPM(Directory.GetCurrentDirectory() + "\\" + fname + ".ppm");

            pboxPreview.Image = image;
            return($"Last Render Took: {sw.ElapsedMilliseconds} ms");
        }
        public void Output(string path)
        {
            Stopwatch sw = new Stopwatch();

            sw.Start();
            using (StreamWriter writer = new StreamWriter(path))
            {
                writer.WriteLine("P3");
                writer.WriteLine(IMAGE_WIDTH + " " + IMAGE_HEIGHT);
                writer.WriteLine("255");

                double aspectRatio     = IMAGE_WIDTH / IMAGE_HEIGHT;
                Vec3   lookFrom        = new Vec3(13, 2, 3);
                Vec3   lookAt          = new Vec3(0, 0, 0);
                Vec3   vUp             = new Vec3(0, 1, 0);
                double distanceToFocus = 10.0;
                double aperture        = 0.1;

                Camera cam = new Camera(lookFrom, lookAt, vUp, 20, aspectRatio, aperture, distanceToFocus);

                HittableList world = RandomScene();
                Vec3[,] colors = new Vec3[IMAGE_HEIGHT, IMAGE_WIDTH];

                int linesRemaining = IMAGE_HEIGHT;

                Parallel.For(0, IMAGE_HEIGHT, j =>
                {
                    // We handle each pixel of the line on a different thread to speed up the process
                    Parallel.For(0, IMAGE_WIDTH, i =>
                    {
                        Vec3 color = new Vec3();
                        for (int s = 0; s < SAMPLES_PER_PIXEL; ++s)
                        {
                            double u = (i + RayTracerUtils.RandomDouble()) / IMAGE_WIDTH;
                            double v = (j + RayTracerUtils.RandomDouble()) / IMAGE_HEIGHT;
                            Ray r    = cam.GetRay(u, v);
                            color   += RayColor(r, world, MAX_DEPTH);
                        }

                        lock (colors)
                        {
                            colors[j, i] = color;
                        }
                    });

                    double percentage = 100.0 - (double)--linesRemaining / IMAGE_HEIGHT * 100.0;
                    Progress(this, new ProgressArgs(percentage));
                });

                // Output the colors in the file
                for (int j = IMAGE_HEIGHT - 1; j >= 0; --j)
                {
                    for (int i = 0; i < IMAGE_WIDTH; ++i)
                    {
                        colors[j, i].WriteColor(writer, SAMPLES_PER_PIXEL);
                    }
                }
            }

            sw.Stop();

            Console.WriteLine();
            Console.WriteLine($"Time: {sw.Elapsed.Minutes}:{sw.Elapsed.Seconds:00}");
        }
 public RenderEngine(Camera camera, HittableList hittables)
 {
     this.Camera    = camera;
     this.Hittables = hittables;
 }