Transparent/noisy 使用简单漫反射计算时的球体
Transparent/noisy spheres when using simple diffuse calculations
我一直在尝试编写一个光线追踪器,但在尝试实现简单的漫反射计算时遇到了一个问题(试图在一个周末光线追踪中复制第一个但没有向导)
相关代码如下:
Intersection/diffuse 计算:
#pragma once
#include "Camera.h"
#include <cmath>
#include "Defs.h"
template<typename O>
class Sphere{
O Radius;
Point3<O> Center;
Color<O> Col;
public:
Sphere(O radius, Point3<O> center, Color<O> color);
O quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max);
bool intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max);
};
template<typename O>
Sphere<O>::Sphere(O radius, Point3<O> center, Color<O> color) : Radius(radius), Center(center), Col(color) {}
template<typename O>
O Sphere<O>::quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max){
Point3<O> origin = ray_in.Origin;
Vec3<O> direction = ray_in.Direction;
Vec3<O> o = origin-Center;
O a = direction.dot(direction);
O b = 2 * direction.dot(o);
O c = o.dot(o) - (Radius * Radius);
O discriminant = b * b - 4 * (a * c);
if (discriminant < 0){
return false;
}
disc = ((-b - sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
disc = ((-b + sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
return false;
}
}
return true;
}
template<typename O>
bool Sphere<O>::intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max){
O disc;
if (quadratic(ray_in, disc, info.Min, t_Max)){
Point3<O> p = ray_in.at(disc);
Vec3<O> normal = (p - Center) / Radius;
info.Point = p;
info.Normal = normal;
info.front_face();
info.Min = disc;
return true;
}
else{
return false;
}
}
示踪剂class:
#pragma once
#include <iostream>
#include "Shapes.h"
#include "Defs.h"
#include "Image.h"
template<typename O>
class Tracer{
std::vector<Sphere<O>> Shapes;
public:
Tracer(std::vector<Sphere<O>> shapes);
void iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min);
};
template<typename O>
Tracer<O>::Tracer(std::vector<Sphere<O>> shapes) : Shapes(shapes) {}
template<typename O>
void Tracer<O>::iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min){
O conc = 1;
Color<O> col(0.4f, 0.8f, 0.9f);
bool hit = false;
rayInfo<O> info;
info.Min = t_Min;
if (depth <= 0)
conc = 0;
while (depth > 0){
for (auto i = Shapes.begin(); i != Shapes.end(); i++){
if (i->intersect(ray, info, t_Max)){
conc *= 0.28;
hit = true;
}
}
if (!hit){
break;
}
Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
Point3<O> target = info.Point + info.Normal + circ;
ray = Ray<O>(info.Point, target - info.Point);
info.Min = t_Min;
hit = false;
depth--;
}
col = col * conc;
Image<O>::ColorPixel(std::cout, col);
}
主要以防万一:
#include <iostream>
#include <cmath>
#include "../Matrix.h"
#include "Camera.h"
#include <vector>
#include "Image.h"
#include "Shapes.h"
#include "Tracer.h"
#include "../Defs.h"
template<typename O>
using Point3 = Vec3<O>;
template<typename O>
using Color = Vec3<O>;
int main(){
const int img_width = 640;
const int img_height = 480;
const float img_ratio = img_width/img_height;
float t_Max = infinity; float t_Min = 0.01; float depth = 50.0f;
float inv_width = 1 / float(img_width);
float inv_height = 1 / float(img_height);
std::vector<Sphere<float>> shapes;
Camera<float> cam1(40.0f, img_ratio, Point3<float>(0.0f, 0.0f, 0.0f), Point3<float>(0.0f, 0.0f, -1.0f), Vec3<float>(0.0f, 1.0f, 0.0f));
Sphere<float> cir1(0.4f, Point3<float>(0.0f, 0.0f, -1.0f), Color<float>(0.7f, 0.3f, 0.2f));
Sphere<float> cir2(3.0f, Point3<float>(0.0f, -3.0f, -1.0f), Color<float>(0.2f, 0.7f, 0.8f));
Sphere<float> cir3(0.5f, Point3<float>(1.0f, 0.0f, -1.0f), Color<float>(0.2f, 0.3f, 0.7f));
shapes.push_back(cir1);
shapes.push_back(cir2);
shapes.push_back(cir3);
Tracer<float> tracer(shapes);
std::cout << "P3\n" << img_width << ' ' << img_height << "\n255" << std::endl;
Ray<float> ray(Point3<float>(0.0f), Vec3<float>(0.0f));
for (int j = 0; j < img_height; j++)
{
std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
for (int i = 0; i < img_width; i++){
depth = 50.0f;
float x = i;
float y = j;
cam1.screenCords(x, y, img_width, img_height);
ray = cam1.get_raydir(x, y);
//ray = Ray<float>(Vec3<float>(x1, y1, 1), Point3<float>(0.0f, 0.0f, 0.0f));
tracer.iterator(ray, depth, t_Max, t_Min);
}
}
std::cerr << "\n done " << std::endl;
}
这是现在的样子:
编辑:当我在形状循环外衰减时(通过将 `conc *= 0.28 移动到它之外),我的图像最终看起来像这样:
我可以看到一些看起来像阴影的东西,但这显然不是预期的行为。
编辑 2:
正如 Yavok 指出的那样,将 info.Min 设置为每个交点命中的顶点是反向逻辑。相反,我应该减少 info.Max,这样光线就不会一直到达比当前最近的物体更远的物体。
我添加了 3(立方根)的抗锯齿和伽马校正,现在图像看起来好多了。还是有点奇怪,但已经进步了:
编辑 3:
终于成功了!结果我的 random_in_unit_sphere() 函数出错了。它应该看起来像这样:
static Vec3<T> random_in_unit_sphere(){
bool flag = true;
Vec3<T> p;
while (flag){
p = randomm(-1, 1);
auto l = p.length();
if (l * l < 1) { flag = false; }
}
return p;
}
感谢 Yakov 和 Spektre!非常感谢。
懒得调试您的代码,但是屏幕截图和快速查看源代码提示存在准确性问题。所以尝试使用 64 位 doubles
而不是 32 位 floats
...
光线和 ellipsoid/sphere 之间的交点往往在浮点数上有噪声...一旦在其上添加折射和反射,噪声就会成倍增加...
有时也有助于使用相对坐标而不是绝对坐标(即使在浮点数上也会产生巨大影响)。有关详细信息,请参阅:
- ray and ellipsoid intersection accuracy improvement
- raytrace through 3D mesh
噪声存在是因为您随机化了漫射光线:
Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
每次你的光线击中某物时,你都会衰减颜色:
conc *= 0.28;
显然,有些光线会比其他光线反射得更多,因此会变暗。
此噪声是任何蒙特卡洛积分器的预期伪影。要减少噪声,您需要增加每个像素的样本数并在非常结束。
“透明度”之所以存在,是因为您在相交循环中应用了该衰减:
for (auto i = Shapes.begin(); i != Shapes.end(); i++){
if (i->intersect(ray, info, t_Max)){
conc *= 0.28; // <-- here
hit = true;
}
}
与多个球体相交的光线将被多次衰减,即使是应该被遮挡的球体也是如此。相反,当你发现你的光线反弹时,你必须在循环外衰减:
if (!hit){
break;
}
conc *= 0.28; // <-- apply attenuation once per bounce
我一直在尝试编写一个光线追踪器,但在尝试实现简单的漫反射计算时遇到了一个问题(试图在一个周末光线追踪中复制第一个但没有向导)
相关代码如下:
Intersection/diffuse 计算:
#pragma once
#include "Camera.h"
#include <cmath>
#include "Defs.h"
template<typename O>
class Sphere{
O Radius;
Point3<O> Center;
Color<O> Col;
public:
Sphere(O radius, Point3<O> center, Color<O> color);
O quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max);
bool intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max);
};
template<typename O>
Sphere<O>::Sphere(O radius, Point3<O> center, Color<O> color) : Radius(radius), Center(center), Col(color) {}
template<typename O>
O Sphere<O>::quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max){
Point3<O> origin = ray_in.Origin;
Vec3<O> direction = ray_in.Direction;
Vec3<O> o = origin-Center;
O a = direction.dot(direction);
O b = 2 * direction.dot(o);
O c = o.dot(o) - (Radius * Radius);
O discriminant = b * b - 4 * (a * c);
if (discriminant < 0){
return false;
}
disc = ((-b - sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
disc = ((-b + sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
return false;
}
}
return true;
}
template<typename O>
bool Sphere<O>::intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max){
O disc;
if (quadratic(ray_in, disc, info.Min, t_Max)){
Point3<O> p = ray_in.at(disc);
Vec3<O> normal = (p - Center) / Radius;
info.Point = p;
info.Normal = normal;
info.front_face();
info.Min = disc;
return true;
}
else{
return false;
}
}
示踪剂class:
#pragma once
#include <iostream>
#include "Shapes.h"
#include "Defs.h"
#include "Image.h"
template<typename O>
class Tracer{
std::vector<Sphere<O>> Shapes;
public:
Tracer(std::vector<Sphere<O>> shapes);
void iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min);
};
template<typename O>
Tracer<O>::Tracer(std::vector<Sphere<O>> shapes) : Shapes(shapes) {}
template<typename O>
void Tracer<O>::iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min){
O conc = 1;
Color<O> col(0.4f, 0.8f, 0.9f);
bool hit = false;
rayInfo<O> info;
info.Min = t_Min;
if (depth <= 0)
conc = 0;
while (depth > 0){
for (auto i = Shapes.begin(); i != Shapes.end(); i++){
if (i->intersect(ray, info, t_Max)){
conc *= 0.28;
hit = true;
}
}
if (!hit){
break;
}
Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
Point3<O> target = info.Point + info.Normal + circ;
ray = Ray<O>(info.Point, target - info.Point);
info.Min = t_Min;
hit = false;
depth--;
}
col = col * conc;
Image<O>::ColorPixel(std::cout, col);
}
主要以防万一:
#include <iostream>
#include <cmath>
#include "../Matrix.h"
#include "Camera.h"
#include <vector>
#include "Image.h"
#include "Shapes.h"
#include "Tracer.h"
#include "../Defs.h"
template<typename O>
using Point3 = Vec3<O>;
template<typename O>
using Color = Vec3<O>;
int main(){
const int img_width = 640;
const int img_height = 480;
const float img_ratio = img_width/img_height;
float t_Max = infinity; float t_Min = 0.01; float depth = 50.0f;
float inv_width = 1 / float(img_width);
float inv_height = 1 / float(img_height);
std::vector<Sphere<float>> shapes;
Camera<float> cam1(40.0f, img_ratio, Point3<float>(0.0f, 0.0f, 0.0f), Point3<float>(0.0f, 0.0f, -1.0f), Vec3<float>(0.0f, 1.0f, 0.0f));
Sphere<float> cir1(0.4f, Point3<float>(0.0f, 0.0f, -1.0f), Color<float>(0.7f, 0.3f, 0.2f));
Sphere<float> cir2(3.0f, Point3<float>(0.0f, -3.0f, -1.0f), Color<float>(0.2f, 0.7f, 0.8f));
Sphere<float> cir3(0.5f, Point3<float>(1.0f, 0.0f, -1.0f), Color<float>(0.2f, 0.3f, 0.7f));
shapes.push_back(cir1);
shapes.push_back(cir2);
shapes.push_back(cir3);
Tracer<float> tracer(shapes);
std::cout << "P3\n" << img_width << ' ' << img_height << "\n255" << std::endl;
Ray<float> ray(Point3<float>(0.0f), Vec3<float>(0.0f));
for (int j = 0; j < img_height; j++)
{
std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
for (int i = 0; i < img_width; i++){
depth = 50.0f;
float x = i;
float y = j;
cam1.screenCords(x, y, img_width, img_height);
ray = cam1.get_raydir(x, y);
//ray = Ray<float>(Vec3<float>(x1, y1, 1), Point3<float>(0.0f, 0.0f, 0.0f));
tracer.iterator(ray, depth, t_Max, t_Min);
}
}
std::cerr << "\n done " << std::endl;
}
这是现在的样子:
编辑:当我在形状循环外衰减时(通过将 `conc *= 0.28 移动到它之外),我的图像最终看起来像这样:
我可以看到一些看起来像阴影的东西,但这显然不是预期的行为。
编辑 2:
正如 Yavok 指出的那样,将 info.Min 设置为每个交点命中的顶点是反向逻辑。相反,我应该减少 info.Max,这样光线就不会一直到达比当前最近的物体更远的物体。
我添加了 3(立方根)的抗锯齿和伽马校正,现在图像看起来好多了。还是有点奇怪,但已经进步了:
编辑 3:
终于成功了!结果我的 random_in_unit_sphere() 函数出错了。它应该看起来像这样:
static Vec3<T> random_in_unit_sphere(){
bool flag = true;
Vec3<T> p;
while (flag){
p = randomm(-1, 1);
auto l = p.length();
if (l * l < 1) { flag = false; }
}
return p;
}
感谢 Yakov 和 Spektre!非常感谢。
懒得调试您的代码,但是屏幕截图和快速查看源代码提示存在准确性问题。所以尝试使用 64 位 doubles
而不是 32 位 floats
...
光线和 ellipsoid/sphere 之间的交点往往在浮点数上有噪声...一旦在其上添加折射和反射,噪声就会成倍增加...
有时也有助于使用相对坐标而不是绝对坐标(即使在浮点数上也会产生巨大影响)。有关详细信息,请参阅:
- ray and ellipsoid intersection accuracy improvement
- raytrace through 3D mesh
噪声存在是因为您随机化了漫射光线:
Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
每次你的光线击中某物时,你都会衰减颜色:
conc *= 0.28;
显然,有些光线会比其他光线反射得更多,因此会变暗。
此噪声是任何蒙特卡洛积分器的预期伪影。要减少噪声,您需要增加每个像素的样本数并在非常结束。
“透明度”之所以存在,是因为您在相交循环中应用了该衰减:
for (auto i = Shapes.begin(); i != Shapes.end(); i++){
if (i->intersect(ray, info, t_Max)){
conc *= 0.28; // <-- here
hit = true;
}
}
与多个球体相交的光线将被多次衰减,即使是应该被遮挡的球体也是如此。相反,当你发现你的光线反弹时,你必须在循环外衰减:
if (!hit){
break;
}
conc *= 0.28; // <-- apply attenuation once per bounce