gpt4 book ai didi

c# - 光线追踪器未产生预期的输出

转载 作者:行者123 更新时间:2023-11-30 23:21:53 26 4
gpt4 key购买 nike

我有一个问题:我已经阅读了使用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”

最佳答案

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;


这三个更改将修复您的代码并产生所需的结果。


话虽如此,恕我直言,值得考虑其他一些更改。最值得注意的是将 struct类型用于 Vec3fCollision而不是 class。与C ++中 structclass之间唯一真正的区别是成员的默认可访问性不同,在C#中,这两种类型的基本行为是非常不同的。在这样的程序中,对于此类经常使用的值,使用 struct代替 class可以通过最大程度地减少堆分配的数据量(尤其是仅临时存在且需要收集的数据)来显着提高性能。程序尝试执行其他工作时的垃圾回收器。

您可能还需要考虑将数据类型从 float更改为 double。我用两种方法测试了代码。它对视觉输出没有影响,但是我看到渲染使用 double平均需要2.1秒,使用 float平均需要2.8秒。速度提高25%可能是您想要的。 :)

struct vs class问题而言,在我的测试中,使用更快的 double类型进行算术运算,我发现使用 struct而不是 class(使用 会在3.3秒内运行,而使用 class会在2.1秒内运行)。

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

我进行的其他更改包括:


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


最后,我对程序的输出进行了重大更改。我对等待控制台窗口中的所有输出都没有兴趣,也不想跟踪并安装一个程序来读取和显示基于文本的图像输出。

因此,相反,我更改了文本输出,以便仅将其写入内存字符串中,并添加了代码,以便该程序生成可以直接打开的实际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程序集。您还需要在项目设置中选中“允许不安全的代码”选项。

关于c# - 光线追踪器未产生预期的输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39055584/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com