games101 作业4及作业5 详解光线追踪框架

作业4

代码分析

作业四的代码整体比较简单 主要流程就是 通过鼠标事件 获取四个控制点的坐标 然后绘制贝塞尔曲线的内容就由我们来完成

理论分析

贝塞尔曲线的理论就是给定一组控制点 然后不断的在控制点之间进行插值 再在得到的新的插值点之间进行插值 具体过程可以用树形结构来表示:

我们可以使用递归来推导插值得到贝塞尔曲线上的点的位置 也可以使用二项分布多项式来表示:

贝塞尔曲线的优良性质:



1.曲线的起点与终点一定是控制点的第一个点与最后一个点

2.曲线在起点和终点处分别会与第一个和最后一个控制点之间的线段保持切线一致

3.可以通过变换控制点来变换整条曲线,无需重新计算曲线的所有点

4.曲线完全包含在由控制点构成的凸包内部。凸包是指能够完全包围所有控制点的最小凸多边形。因此,贝塞尔曲线不会离开这个多边形的范围。这个特性帮助我们确保曲线的形状在控制点之间得到良好的控制

贝塞尔曲线的抗锯齿 锯齿的来源就是曲线和背景像素过渡的不自然 我们对像素周边的其它四个像素 根据距离 给背景像素一个平均的颜色即可

贝塞尔曲面 就是对一组贝塞尔曲线进行进一步的插值:

实际解决

这里我尽量不改变原来的代码结构 在recursive_bezier中进行递归 相当于每次递归都算出树形结构中插值得到的一层点 直至递归到插值得到的只有一个点返回

cv::Vec3b blendColors(const cv::Vec3b& color1, const cv::Vec3b& color2, float alpha)
{
return color1 * (1.0 - alpha) + color2 * alpha;
} void draw_anti_aliased_pixel(cv::Mat& window, cv::Point2f point, cv::Vec3b color)
{
int x = static_cast<int>(std::floor(point.x));
int y = static_cast<int>(std::floor(point.y)); float alpha_x = point.x - x;
float alpha_y = point.y - y; // Blend colors based on distance to pixel center
window.at<cv::Vec3b>(y, x) = blendColors(window.at<cv::Vec3b>(y, x), color, (1 - alpha_x) * (1 - alpha_y));
window.at<cv::Vec3b>(y, x + 1) = blendColors(window.at<cv::Vec3b>(y, x + 1), color, alpha_x * (1 - alpha_y));
window.at<cv::Vec3b>(y + 1, x) = blendColors(window.at<cv::Vec3b>(y + 1, x), color, (1 - alpha_x) * alpha_y);
window.at<cv::Vec3b>(y + 1, x + 1) = blendColors(window.at<cv::Vec3b>(y + 1, x + 1), color, alpha_x * alpha_y);
} cv::Point2f recursive_bezier(const std::vector<cv::Point2f>& control_points, float t)
{
cv::Point2f point;
int size = control_points.size();
std::vector<cv::Point2f> new_points(size);
if (size == 1) {
return control_points[0];
}
// TODO: Implement de Casteljau's algorithm
for (int i = 0; i < size - 1; i++)
{
new_points[i] = (1 - t) * control_points[i] + t * control_points[i + 1];
}
new_points.resize(size - 1);
point = recursive_bezier(new_points, t);
return point; }
void bezier(const std::vector<cv::Point2f>& control_points, cv::Mat &window)
{
cv::Vec3b color(0, 255, 0); // 绿色
for (double t = 0.0; t <= 1.0; t += 0.001)
{
cv::Point2f point = recursive_bezier(control_points, t);
//window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
draw_anti_aliased_pixel(window, point, color);
} }

结果展示:

作业5

代码分析

整体的代码框架大致如下:

初始化场景 场景中包含物体 灯光等等

场景中会包含整个rast space的大小 fov 相机的视角宽度 背景颜色

maxDepth用于控制光线发生反射折射的次数 我们不能让光线无限的传播下去

epsilon 用于防止浮点数精度问题 导致计算和物体交点时计算到了表面的下方 这样再次反射会又一次和相同的表面相交

class Scene
{
public:
// setting up options
int width = 1280;
int height = 960;
double fov = 90;
Vector3f backgroundColor = Vector3f(0.235294, 0.67451, 0.843137);
int maxDepth = 5;
float epsilon = 0.00001; Scene(int w, int h) : width(w), height(h)
{} void Add(std::unique_ptr<Object> object) { objects.push_back(std::move(object)); }
void Add(std::unique_ptr<Light> light) { lights.push_back(std::move(light)); } [[nodiscard]] const std::vector<std::unique_ptr<Object> >& get_objects() const { return objects; }
[[nodiscard]] const std::vector<std::unique_ptr<Light> >& get_lights() const { return lights; } private:
// creating the scene (adding objects and lights)
std::vector<std::unique_ptr<Object> > objects;
std::vector<std::unique_ptr<Light> > lights;
};

框架中用到了两种物体 分别是两个球体 和 三角形网格 这里的三角形网格是两个三角形 拼接成的正方形 两个球体的材质一个是glossy_specular 一个是反射透射材质 三角形网格是glossy_specular材质:

auto sph1 = std::make_unique<Sphere>(Vector3f(-1, 0, -12), 2);
sph1->materialType = DIFFUSE_AND_GLOSSY;
sph1->diffuseColor = Vector3f(0.6, 0.7, 0.8); auto sph2 = std::make_unique<Sphere>(Vector3f(0.5, -0.5, -8), 1.5);
sph2->ior = 1.5;
sph2->materialType = REFLECTION_AND_REFRACTION; scene.Add(std::move(sph1));
scene.Add(std::move(sph2)); Vector3f verts[4] = {{-5,-3,-6}, {5,-3,-6}, {5,-3,-16}, {-5,-3,-16}};
uint32_t vertIndex[6] = {0, 1, 3, 1, 2, 3};
Vector2f st[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
auto mesh = std::make_unique<MeshTriangle>(verts, vertIndex, 2, st);
mesh->materialType = DIFFUSE_AND_GLOSSY; scene.Add(std::move(mesh));
scene.Add(std::make_unique<Light>(Vector3f(-20, 70, 20), 0.5));
scene.Add(std::make_unique<Light>(Vector3f(30, 50, -12), 0.5));

之后就是投射camera ray 计算每个像素的颜色

trace用于计算和物体的交点 球体的话就用解析解 三角形网格需要遍历每个三角形图元求交点 使用Moller-Trumbore 算法来计算交点 注意这里求得交点之后还要判断是不是最近的:

std::optional<hit_payload> trace(
const Vector3f &orig, const Vector3f &dir,
const std::vector<std::unique_ptr<Object> > &objects)
{
float tNear = kInfinity;
std::optional<hit_payload> payload;
for (const auto & object : objects)
{
float tNearK = kInfinity;
uint32_t indexK;
Vector2f uvK;
if (object->intersect(orig, dir, tNearK, indexK, uvK) && tNearK < tNear)
{
payload.emplace();
payload->hit_obj = object.get();
payload->tNear = tNearK;
payload->index = indexK;
payload->uv = uvK;
tNear = tNearK;
}
} return payload;
}

castray中 反射折射材质如果光线弹射的次数的超过我们设定的五次 就会终止递归 或者打到了diffuse_glossy材质也会终止

diffuse_glossy材质的着色计算就采用bling-phong模型

这里代码很多 就不贴了 讲几个细节

1.反射计算:

Vector3f reflect(const Vector3f &I, const Vector3f &N)
{
return I - 2 * dotProduct(I, N) * N;
}

简单的向量运算

2.折射计算

这里的推导见:https://www.cnblogs.com/night-ride-depart/p/7429618.html

虽然他整体推导有些复杂 但是思路还是对的 我也推了一下没啥问题 就偷个懒



这里作业加入了关于法线 与 入射光线是否在同侧的讨论

如果同侧 点积小于0 说明是从物体的外部打来的光线

如果异侧 点积大于0 说明是从物体的内部打来的光线 这是需要调整我们表面法线的方向,并且光密到光疏也要相应的调整:

Vector3f refract(const Vector3f &I, const Vector3f &N, const float &ior)
{
float cosi = clamp(-1, 1, dotProduct(I, N));
float etai = 1, etat = ior;
Vector3f n = N;
//根据法线和入射光的位置 进行相应的调整
if (cosi < 0) { cosi = -cosi; ???} else { std::swap(etai, etat); n= -N; }
float eta = etai / etat;
//全反射临界计算 计算cos’
float k = 1 - eta * eta * (1 - cosi * cosi);
return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n;
}

这里我觉得是不是框架的代码有点问题 其实点积算cos都是负的 为什么在小于0的时候再取负 这样下面eta * cosi - sqrtf(k)又要反过来 所以其实cosi = -cosi;这句是不需要的?

3.epsilon的使用

我们之前提到epsilon 用于控制hitpoint位置变化 防止再次打到相同表面 这里也要根据同侧还是异侧进行一个调整

如果是 反射 同侧就是加 异侧应该是减 如果是折射 同侧就是减 异侧应该是加 所以这里框架写的是不是有点问题:

Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
+ ? hitPoint - N * scene.epsilon :
-? hitPoint + N * scene.epsilon;
Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ?
hitPoint - N * scene.epsilon :
hitPoint + N * scene.epsilon;

4.shadow的生成

从hitpoint处向光源打一根光线 检测是否打到物体 并且检测物体是不是在光源与hitpoint之间(用距离判断)同时满足 则该点为阴影:

Vector3f shadowPointOrig = (dotProduct(dir, N) < 0) ?
hitPoint + N * scene.epsilon :
hitPoint - N * scene.epsilon;
auto shadow_res = trace(shadowPointOrig, lightDir, scene.get_objects());
bool inShadow = shadow_res && (shadow_res->tNear * shadow_res->tNear < lightDistance2);

结果展示:

这里下面那个棋盘的正方形 就是一开始初始化的三角形网格 可以看到图中有阴影 也能看出一些折射 反射的现象 后面那个球就是diffuse_glossy材质 上面也能看出一些高光

棋盘格纹理的生成:

 Vector3f evalDiffuseColor(const Vector2f& st) const override
{
float scale = 5;
float pattern = (fmodf(st.x * scale, 1) > 0.5) ^ (fmodf(st.y * scale, 1) > 0.5);
return lerp(Vector3f(0.815, 0.235, 0.031), Vector3f(0.937, 0.937, 0.231), pattern);
}

理论分析

本次作业要完成的内容比较简单

首先是生成camera ray 需要我们将rast space上的二维点转换成三维点 需要经历一系列变换 可参考我之前的一篇博文:https://www.cnblogs.com/dyccyber/p/17806284.html 最后乘以 * imageAspectRatio * scale 其实就是转换到世界空间 对应着我们作业1透视矩阵对xy坐标的缩放

然后是计算三角形与光线的相交 就是套用课上的公式 需要注意的是要加入边界范围的限制 一个是确定不是光线的反向相交 即tnear>0 一个是重心坐标在0-1之间 防止交点在三角形外部

实际解决

for (int j = 0; j < scene.height; ++j)
{
for (int i = 0; i < scene.width; ++i)
{
// generate primary ray direction
float x = (2 * ((i + 0.5) / (float)scene.width) - 1) * imageAspectRatio * scale;
float y = (1 - 2 * ((j + 0.5) / (float)scene.height)) * scale;
// TODO: Find the x and y positions of the current pixel to get the direction
// vector that passes through it.
// Also, don't forget to multiply both of them with the variable *scale*, and
// x (horizontal) variable with the *imageAspectRatio* Vector3f dir = normalize(Vector3f(x, y, -1)); // Don't forget to normalize this direction!
framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
}
UpdateProgress(j / (float)scene.height);
}
bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
const Vector3f& dir, float& tnear, float& u, float& v)
{
// TODO: Implement this function that tests whether the triangle
// that's specified bt v0, v1 and v2 intersects with the ray (whose
// origin is *orig* and direction is *dir*)
// Also don't forget to update tnear, u and v. Vector3f E1 = v1 - v0;
Vector3f E2 = v2 - v0;
Vector3f S = orig - v0;
Vector3f S1 = crossProduct(dir, E2);
Vector3f S2 = crossProduct(S, E1);
float div = dotProduct(S1,E1);
tnear = dotProduct(S2, E2) / div;
u = dotProduct(S1, S) / div;
v = dotProduct(S2, dir) / div;
//两个边界 一个是光线的方向 一个是交点要在三角形内部
if (tnear >= 0 && u >=0 && u<=1 && v>=0 && v<=1) {
return true;
}
return false;
}

games101 作业4及作业5 详解光线追踪框架的更多相关文章

  1. 详解Executor框架

    在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...

  2. 详解工作流框架Activiti的服务架构和组件

    摘要:通过这篇文章,可以对工作流有一个基本的认识,为后续工作流框架Activiti的学习打下坚实的基础. 本文分享自华为云社区<BPMN工作流的基本概念!详解工作流框架Activiti的服务架构 ...

  3. [转载] 多图详解Spring框架的设计理念与设计模式

    转载自http://developer.51cto.com/art/201006/205212_all.htm Spring作为现在最优秀的框架之一,已被广泛的使用,51CTO也曾经针对Spring框 ...

  4. JAVA Eclipse使用Maven构建web项目详解(SSM框架)

    tips: 启动项目后,welcome-file的链接即为测试用例 部署maven web项目 Eclipse使用Maven构建web项目详解 pom.xml添加webapp依赖: <depen ...

  5. JavaWeb配置详解(结合框架SpringMVC)

    详解 先说一说常识性的东西,我们的JavaWeb程序运行一开始走的是web.xml文件,这是我们的核心文件,可以说没有web.xml文件我们就无法运行项目,这个文件长什么样子,读者自己新建一个web项 ...

  6. hadoop之yarn详解(框架进阶篇)

    前面在hadoop之yarn详解(基础架构篇)这篇文章提到了yarn的重要组件有ResourceManager,NodeManager,ApplicationMaster等,以及yarn调度作业的运行 ...

  7. 详解Spring框架AOP(面向切面编程)

    最近在学习AOP,之前一直很不明白,什么是AOP?为什么要使用AOP,它有什么作用?学完之后有一点小小的感触和自己的理解,所以在这里呢就跟大家一起分享一下 AOP(Aspect-Oriented Pr ...

  8. 详解ABP框架的多租户

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:ABP框架对多租户场景提供了很好的支持,内建了多租户的处理机制,今天我们来深入解析一下 ...

  9. 详解SSH框架的原理和优点

    Struts的原理和优点.        Struts工作原理  MVC即Model-View-Controller的缩写,是一种常用的设计模式.MVC 减弱了业务逻辑接口和数据接口之间的耦合,以及让 ...

  10. pycaffe︱caffe中fine-tuning模型三重天(函数详解、框架简述)

    本文主要参考caffe官方文档[<Fine-tuning a Pretrained Network for Style Recognition>](http://nbviewer.jupy ...

随机推荐

  1. python + pytest多进程、多线程执行用例生成报告总结

    背景: 使用多进程.多线程执行测试用例,生成测试报告:不使用多进程.多线程,以下两种方式都可生成报告 两种生成报告的形式 1. pytestreport(pytest_session_finish时生 ...

  2. pytest_fixture通过参数request获取测试数据,并在fixture方法里面使用

    pytest fixture传参request的使用 获取request对pytest插件的版本有要求,如果找不到request报错的话, 建议先升级pytest的版本 要实现的效果 执行测试用例,调 ...

  3. 【动手学深度学习】第三章笔记:线性回归、SoftMax 回归、交叉熵损失

    这章感觉没什么需要特别记住的东西,感觉忘了回来翻一翻代码就好. 3.1 线性回归 3.1.1 线性回归的基本元素 1. 线性模型 \(\boldsymbol{x}^{(i)}\) 是一个列向量,表示第 ...

  4. 搭建redis-sentinel(哨兵)

    1.先创建redis一主两从的配置文件 2.编辑配置文件 cat >> /data/8012/redis.conf <<EOF port 8012 daemonize yes ...

  5. Java在创建同名目录/同名文件时名称拼接(数字)

    /** * 创建同名文件名称拼接(数字) * * @param path 需要创建的目录 * @return */ public static String recursionMkdirsFile(S ...

  6. SpringBoot 处理xss攻击

    添加依赖 <!-- xss跨站脚本攻击 --> <dependency> <groupId>net.dreamlu</groupId> <arti ...

  7. SMOTE与SMOGN算法R语言代码

      本文介绍基于R语言中的UBL包,读取.csv格式的Excel表格文件,实现SMOTE算法与SMOGN算法,对机器学习.深度学习回归中,训练数据集不平衡的情况加以解决的具体方法.   在之前的文章S ...

  8. oeasy教您玩转vim - 79 - # 编码格式encoding

    ​ ​ - `help encoding-name` ​ ![图片描述](https://s4.51cto.com/images/blog/202201/03083053_61d243bd719358 ...

  9. 手写数字识别-使用TensorFlow构建和训练一个简单的神经网络

    下面是一个具体的Python代码示例,展示如何使用TensorFlow实现一个简单的神经网络来解决手写数字识别问题(使用MNIST数据集).以下是一个完整的Python代码示例,展示如何使用Tenso ...

  10. odoo 为form表单视图添加chatter功能

    实践环境 Odoo 14.0-20221212 (Community Edition) 需求描述 如图,给表单新增一个类似聊天的窗口,当记录一些表单活动(本例为自动记录当前记录状态变化) 需求实现 模 ...