0%

28:初始图形学(2)

今天简单的学习一下相关类的定义和作用

Vec3类

图形程序中需要一些用于存储几何向量和颜色的类,这里的话我们设置一个最简单的,只需要三个坐标就够了。我们使用相同的类vec3来表示颜色、位置、方向、偏移。所以我们会为这个类声明另外两个别名point3color,但是要注意,不要将一个point3添加到一个color

我们定义一个类文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#ifndef RENDER_C___VEC3_H
#define RENDER_C___VEC3_H

#include <cmath>
#include <iostream>
// 向量类
class vec3 {
public:
double e[3];

vec3(): e{0,0,0} {}
vec3(double e0,double e1,double e2): e{e0,e1,e2} {}

double x() const{ return e[0]; }
double y() const{ return e[1]; }
double z() const{ return e[2]; }

vec3 operator-() const {return {-e[0],-e[1],-e[2]};}
double operator[](int i) const { return e[i];}
double & operator[](int i) {return e[i];}

vec3& operator+=(const vec3& v){
e[0] += v.e[0];
e[1] += v.e[1];
e[2] += v.e[2];
return *this;
}

vec3& operator*=(double t){
e[0] *= t;
e[1] *= t;
e[2] *= t;
return *this;
}

vec3& operator/=(double t){
return *this *= 1/t;
}

double length_squared() const{
return e[0]*e[0]+e[1]*e[1]+e[2]*e[2];
}

double length() const {
return std::sqrt(length_squared());
}
};

// 设置别名
using point3 = vec3;

// 设置内联函数
inline std::ostream& operator<<(std::ostream& out,const vec3& v){
return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2];
}

inline vec3 operator+(const vec3& u,const vec3& v){
return {u.e[0]+v.e[0],u.e[1]+v.e[1],u.e[2]+v.e[2]};
}

inline vec3 operator-(const vec3& u,const vec3& v){
return {u.e[0]-v.e[0],u.e[1]-v.e[1],u.e[2]-v.e[2]};
}

inline vec3 operator*(const vec3& u,const vec3& v){
return {u.e[0]*v.e[0],u.e[1]*v.e[1],u.e[2]*v.e[2]};
}

inline vec3 operator*(double t,const vec3& v){
return {t*v.e[0],t*v.e[1],t*v.e[2]};
}

inline vec3 operator*(const vec3& v,double t){
return t*v;
}

inline vec3 operator/(const vec3& v,double t){
return (1/t)*v;
}

inline double dot(const vec3& u,const vec3& v){
return u.e[0]*v.e[0] + u.e[1]*v.e[1] + u.e[2]*v.e[2];
}

inline vec3 cross(const vec3& u,const vec3& v){
return {u.e[1]*v.e[2]-u.e[2]*v.e[1],u.e[2]*v.e[0]-u.e[0]*v.e[2],u.e[0]*v.e[1]-u.e[1]*v.e[0]};
}

inline vec3 uint_vector(const vec3& v){
return v/v.length();
}

#endif //RENDER_C___VEC3_H

其中大量应用了函数的重载,不过对着《c++ primer plus》还是看的差不多了。

这里的小数我们使用了double数据类型,因为它更加的准确,不过还是以自己使用的机器为主,看你内存空间咯

vec3类在颜色中的应用

基于vec3类,定义一个color.h,并向里面定义一个写入像素的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#ifndef RENDER_C___COLOR_H
#define RENDER_C___COLOR_H

#include "vec3.h"

using color = vec3;

void write_color(std::ostream& out,const color& pixel_color){
auto r = pixel_color.x();
auto g = pixel_color.y();
auto b = pixel_color.z();

int rbyte = int (255.999 * r);
int gbyte = int (255.999 * g);
int bbyte = int (255.999 * b);

out << rbyte << ' ' << gbyte << ' ' << bbyte << '\n';
}

#endif //RENDER_C___COLOR_H

现在我们可以用我们定义的类去实现我们昨天手搓出来的图片了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "color.h"
#include "vec3.h"

#include <iostream>

int main(){
int image_width = 256;
int image_height = 256;

std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";
for (int j=0;j<image_height;j++){
std::clog << "\rScanlines remaining: "<< (image_height-j) << '\r' << std::flush;
for(int i =0;i<image_width;i++){
auto pixel_color = color(double(i)/(image_width-1),double(j)/(image_height-1),0);
write_color(std::cout,pixel_color);
}
}
std::clog << "\rDone\n";
}

虽然没有节省多少内容,但是在后续的过程中,这个简便性会慢慢体现出来。

基于vec3实现射线类

我们需要一个光线类,来实现对光线的计算。我们可以用函数P(t) = A + bt来实现光线路径的模拟。其中A指的是射线的起点,b指的是射线的方向,t是光线的延伸。我们将这个射线的概念封装为一个类,其中用于计算的函数P(t)用函数ray::at(t)表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef RENDER_C___RAY_H
#define RENDER_C___RAY_H

#include "vec3.h"

class ray{
public:
ray(){}

ray(const point3& origin,const vec3& direction): orig(origin),dir(direction){}

const point3& origin() const {return orig;}
const vec3& direction() const {return dir;}

point3 at(double t) const{
return orig + t*dir;
}
private:
point3 orig;
vec3 dir;
};

#endif //RENDER_C___RAY_H

这里将射线的起点和方向进行了封装,只能通过接口访问以确保程序的完整与安全

现在我们接下来要用到几个类就设置完成啦