# **光线追踪**
本次作业使用平台:Windows IDE:DEVC++
## 1
直接编译运行代码,生成如下的图片:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/b8dc2c9f1592f2725dc370b56da41037.writebug)
### 1.1 光线、简单的照相机和背景
首先是光线的表示。光线可以由两个点A和B形成的射线表示,其中A为射线发出的原点,B为光线指示传播方向。射线上任何一点可以表示为$p(t)=A+tB$,其中t为常数。当$t\geq 0$时,光线方向为AB方向,否则为反向。
创建`ray.h`,如下所示:
```cpp
# ifndef RAYH
# define RAYH
# include "vec3.h"
class ray{
public:
ray(){}
ray(const vec3& a, const vec3& b){A=a;B=b;};
vec3 origin() const {return A;} // 返回原点,即点A
vec3 direction() const {return B;} // 返回方向,即点B
vec3 point_at_parameter(float t) const {return A + t*B;} // 给定t值求位置
vec3 A;
vec3 B;
};
# endif
```
由此可以表示任何一条光线。
默认观察视点在原点,方向朝向z轴负方向,上方朝y轴正方向。故设置A值为(0,0),定义好屏幕范围后,将屏幕等比例与图片像素一一对应,就能得到每个像素在屏幕上对应的点。将得到的这些点作为B值,就能得到每个像素发出的光线了。
在`PathTracer::render`里定义屏幕为z=-1的平面,考虑到输出图片的大小800 * 600,设置屏幕的x范围为[-4, 4],y范围为[-3, 3],并定义原点,代码如下:
```cpp
vec3 lower_left_corner(-4.0, -3.0, -1.0);
vec3 horizontal(8.0, 0.0, 0.0);
vec3 vertical(0.0, 6.0, 0.0);
vec3 origin(0.0, 0.0, 0.0);
```
其中`lower_left_corner`为屏幕左下角的点,`horizontal`和`vertical`分别代表了该点能够向水平方向和竖直方向延伸的距离。通过像素点位置比长和宽,就能知道当前像素点对应的屏幕位置。在渲染每个像素时,使用以下代码确定像素在屏幕上的位置:
```cpp
float u = static_cast<float>(col) / static_cast<float>(m_width);
float v = static_cast<float>(row) / static_cast<float>(m_height);
```
如此一来,像素点的位置就在`lower_left_corner + u*horizontal + v*vertical`处。以该点为B点,原点为A点,就能创建光线对象:
```cpp
ray r(origin, lower_left_corner + u*horizontal + v*vertical);
```
得到光线后,我创建了一个函数`calcColor`通过光线来计算光线对应的像素显示的颜色。函数原型为:
```cpp
vec3 calcColor(ray &r);
```
这里生成简单的背景颜色:将光线B点的值单位化,其y值+1.0再除以2得到`t`。因为单位化后向量y值取值范围为[-3, 3],故`t`的取值范围为[0, 1]。将`t`和`1-t`作为权重,让背景颜色在`(1.0, 1.0, 1.0)`(全白)和`(1.0,0.8,0.4)`(橘色)之间变换:
```cpp
vec3 calcColor(ray &r){
// 生成背景颜色
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5 * (unit_direction.y() + 1.0) ;
return (1.0-t)*vec3(1.0,1.0,1.0) + t*vec3(1.0,0.8,0.4);
}
```
编译运行得到以下结果:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/2d9deff93b4d63a94086f43dd02714e9.writebug)
### 1.2 球的加入
在三位空间中,球可以用球心坐标$(x_c, y_c, z_c)$和半径`R`表示。球上任意一点$(x, y, z)$满足$(x-x_c)^2+(y-y_c)^2+(z-z_c)^2=R^2$。使用向量形式表达,记圆心为$\bold C$,圆上一点为$\bold P$,则可以表达为:$(\bold C-\bold P)\cdot (\bold C-\bold P)=R^2$。要判断光线是否与圆相交,只需要将光线的方程$\bold A+t\bold B$代入$\bold P$值,看方程是否有解。代入后化简得到:
$$
(\bold B\cdot\bold B)t^2+2(\bold B\cdot(\bold A-\bold C))t+(\bold A-\bold C)\cdot(\bold A-\bold C)-R^2=0
$$
这是一个一元二次方程,只要使得其判别式大于零则有解,等于零是特殊情况:光线与球相切,这里认为相切的情况不算光线与球有交点。由此,可以写出一个判断光线是否与球产生交点的函数:
```cpp
bool hit_sphere(const vec3& center, float radius, const ray& r) {
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = 2.0 * dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - 4*a*c;
return (discriminant > 0);
}
```
其中,`a`、`b`、`c`分别为上述一元二次方程的系数,判别式为$b^2-4ac$。若判断式大于0则返回`true`,否则返回`false`。
有了该函数,就能判断之前讨论的每个像素引出的光线是否与某个球相交了。修改`clacColor`函数,若光线与圆心为$(0,0,-2)$、半径为1.5的球相交,则输出对应的颜色$(0.7,0.8,0.9)$:
```cpp
vec3 calcColor(ray &r){
// 若与球相交,则输出球的颜色
if (hit_sphere(vec3(0,0,-2), 1.5, r))
return vec3(0.7,0.8,0.9);
// 否则输出背景颜色
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5 * (unit_direction.y() + 1.0) ;
return (1.0-t)*vec3(1.0,1.0,1.0) + t*vec3(1.0,0.8,0.4);
}
```
这样就能成功显示球了:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/b32f222478718bf59c9dbc08c6bebc2f.writebug)
需要注意的是,如果按照教程中所给的屏幕参数:
```cpp
vec3 lower_left_corner(-2.0, -1.0, -1.0);
vec3 horizontal(4.0, 0.0, 0.0);
vec3 vertical(0.0, 2.0, 0.0);
```
会得到以下错误的结果:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/6f361c481939e5002b3c40941b36c78f.writebug)
因为输出图片的像素数量为800*600,按照教程中2:1的屏幕大小设置屏幕会导致非等比例的映射,出现图片变形的情况。因此我重新设置的x范围为[-4, 4],y范围为[-3, 3],从而保证长宽比和输出图片相同。为了让球显示的更加自然,我也更改了球的参数。
```cpp
vec3 lower_left_corner(-4.0, -3.0, -1.0);
vec3 horizontal(8.0, 0.0, 0.0);
vec3 vertical(0.0, 6.0, 0.0);
```
### 1.3 表面法向量和多个物体
球表面的法向量很好求:球上任意一点P减去球心C得到的就是P点的法向量方向,单位化后就得到了要求的法向量。在上述的模型中,光线和求相交得到的一元二次方程可以求解出`t`的值,将`t`值代入光线方程就能得到光线和球的交点,从而可以计算出该点的法向量。所以可以修改上述的`hit_sphere`函数,返回求解得到的`t`值。暂时不考虑球的部分出现在$z>0$的情况,考虑到$(\bold B\cdot\bold B)$的值必然大于0,视线首先看到的点,或者说光线首先照射到的点,必然是`t`值更小的那个点,因此只需要返回减去判别式平方根的那一个解即可。因为这种情况下`t`必然大于0,在没有交点时返回-1即可。修改后的函数如下:
```cpp
float hit_sphere(const vec3& center, float radius, const ray& r) {
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = 2.0 * dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - 4*a*c;
// 若无解,返回-1
if (discriminant < 0) {
return -1.0;
}
// 若有解,返回较小的那一个(减去sqrt(discriminant)的)
else {
return (-b - sqrt(discriminant) ) / (2.0*a);
}
}
```
在得到了`t`值,就可以调用上述的`point_at_parameter(t)`函数求解交点。减去圆心并单位化后就得到了该点的法向量。将法向量的坐标值+1后除以0.5作为该光线对应像素的颜色并返回:
```cpp
vec3 (const ray& r) {
float t = hit_sphere(vec3(0,0,-2), 1.5, r);
// 有交点则计算法向量,依据法向量计算颜色
if (t > 0.0) {
vec3 N = unit_vector(r.point_at_parameter(t) - vec3(0,0,-2));
return 0.5*vec3(N.x()+1, N.y()+1, N.z()+1);
}
// 输出背景颜色
vec3 unit_direction = unit_vector(r.direction());
t = 0.5*(unit_direction.y() + 1.0);
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
首先是光线的表示。光线可以由两个点A和B形成的射线表示,其中A为射线发出的原点,B为光线指示传播方向。射线上任何一点可以表示为$p(t)=A+tB$,其中t为常数。当$t\geq 0$时,光线方向为AB方向,否则为反向。
资源推荐
资源详情
资源评论
收起资源包目录
100012091-基于C++实现光线追踪.zip (32个子文件)
trace
pic
10.png 551KB
9.png 498KB
3.png 80KB
12.png 527KB
15.png 667KB
1.png 20KB
11.png 508KB
13.png 795KB
6.png 93KB
a.png 12KB
5.png 86KB
4.png 106KB
8.png 511KB
16.png 616KB
b.png 18KB
7.png 565KB
2.png 90KB
14.png 606KB
wrong1.png 49KB
LICENSE 1KB
README.md 36KB
code
hitable_list.h 799B
ray.h 284B
PathTracer.h 736B
camera.h 1KB
vec3.h 3KB
stb_image_write.h 65KB
material.h 3KB
main.cpp 690B
PathTracer.cpp 4KB
sphere.h 1KB
hitable.h 289B
共 32 条
- 1
资源评论
- 玛卡-巴卡的衣柜2023-10-28总算找到了想要的资源,搞定遇到的大问题,赞赞赞!
神仙别闹
- 粉丝: 2667
- 资源: 7640
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功