GAMES101-作业3
序
这一系列博客主要是为了记录自己对GAMES101课程的作业完成情况.
作业3: 主要是写各个shader
,
这里面也包括对代码框架的解读
作业描述
在本次作业中, 会进一步模型现代图形技术.
本次在代码框架中添加了Object Loader
,
Vertex Shader
, Fragment Shader
,
并且支持了纹理映射.
本次作业的主要任务其实是完成几个fragment shader
:
phong_fragment_shader
,
texture_fragment_shader
, bump_fragment_shader
,
displacement_fragment_shader
由于本次代码的框架已经很切合现代的图形技术,
对于rasterizer
这个过程的理解很有帮助,
故这里从main
函数开始解读, 顺便看一看代码的框架.
main()
在main()
函数中:
- 初始化了一些参数,
eye_pos
,file_name
,obj_path
等等- 进行了
Object Loader
(注: Object Loader 这个过程我这里不详细阅读代码(感觉其实一个很系统的工程, obj也是一个约定好的文件格式))- 进行了
Triangle Mesh
三角形网格的初始化
接下来, 则进行了光栅化器(Rasterizer
)的初始化
1 | rst::rasterizer r(700, 700); |
上述代码的最后一句调用draw()
函数来正式进入渲染管线,
接下来将进行介绍
draw(): pipeline
我们可以看到, draw()函数整体是一个循环,
因为我们使用的是Triangle Mesh
,
因此这里将对所有Triangle
分别进行处理.
对于一个for
循环, 其实主要包含三部分(按顺序):
- 对每个点进行model-view-proj变换(Vertex Processing)
- 对变换后的每个点生成新的Triangle, 其实就是求变换后的三角形(Triangle Processing)(不包含内插的过程, 也即求重心坐标的过程)
- Rasterization(光栅化)
注:
- 展示代码中忽略
for
循环 - 关于四个变换的问题, 请参考作业1的笔记
- 关于法线如何进行
model-view
的变换这个问题, 请参考这篇文章
1 | Eigen::Matrix4f mvp = projection * view * model; // 这里设置 model-view-projection矩阵 |
关于我个人对中间部分的viewspace_pos
以及
newNormal
的处理的理解(仅针对这个过程,
希望有专业的解读可以留言):
如上图, 我认为, 之所以对 normal
和
viewspace_pos
只使用 model-view的变换的原因是:
保证入射方向和出射方向的不变性,
以及法线方向的不变性
因为Camera
的位置就是定义在 view坐标系下的
,
而且经过model-view
后的空间可以称为Camera Space
,
在这个空间保留一个交点的坐标, 以及法线方向,
对于后续的Rander
很有帮助
至于为什么不全部移到Pixel Space
中去, 我认为原因可能是:
经过投影变换后的空间, 不能保证法线还是原来的法线,
甚至入射角和出射角都会发生变换(尤其是透视投影这种,
不仅仅是简单的平移和旋转).
线代中的线性变换对线性空间的影响会导致原来的坐标系不再垂直, 也即会改变整个入射角和出射角
Rasterizer
这个函数与作业2基本相同, 这里贴上相关代码,
具体求重心坐标的过程不在赘述 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
37auto v = t.toVector4();
// bounding box
int widthMax = static_cast<int>(std::ceil(std::max(t.v[0][0], std::max(t.v[1][0], t.v[2][0]))));
int widthMin = static_cast<int>(std::floor(std::min(t.v[0][0], std::min(t.v[1][0], t.v[2][0]))));
int heightMax = static_cast<int>(std::ceil(std::max(t.v[0][1], std::max(t.v[1][1], t.v[2][1]))));
int heightMin = static_cast<int>(std::floor(std::min(t.v[0][1], std::min(t.v[1][1], t.v[2][1]))));
// 计算是否再三角形中
for(int x = widthMin; x <= widthMax; ++x){
for(int y = heightMin; y <= heightMax; ++y)
if(insideTriangle(x+0.5, y+0.5, t.v)){
// 插值
auto[alpha, beta, gamma] = computeBarycentric2D(x+0.5, y+0.5, t.v);
float Z = 1.0 /(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w()); // w 是向量的第四个维度
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
// 也包括了 z-buffer 的一些细节
zp *= Z;
if(zp < depth_buf[get_index(x, y)]){
Eigen::Vector3f interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1.f);
Eigen::Vector3f interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1.f);
Eigen::Vector2f interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1.f);
Eigen::Vector3f interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1.f);
// 初始化 fragment_shader_payload, 以方便传给 fragment_shader
fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
depth_buf[get_index(x, y)] = zp;
// 传递给 fragment_shader
auto pixel_color = fragment_shader(payload);
set_pixel(Vector2i(x, y), pixel_color);
}
}
}fragment_shader
进行下一步工作,
fragment_shader()
这个fragment_shader
也就是本作业的要求,
接下来各个进行分析.
phong_fragment_shader
这个函数感觉就是照着公式写, 所以这里直接粘贴代码了: 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
47Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}};
std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10};
float p = 150;
Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
// components are. Then, accumulate that result on the *result_color* object.
// 首先, 求 三个方向向量
Eigen::Vector3f lightVec = light.position - point;
Eigen::Vector3f normalVec = normal;
Eigen::Vector3f viewVec = eye_pos - point;
float r = lightVec.dot(lightVec);
// diffues
Eigen::Vector3f diffuseReflect = kd.cwiseProduct(light.intensity / r) * std::max(0.0f, normalVec.dot(lightVec.normalized()));
// specular
Eigen::Vector3f harfVec = lightVec + viewVec;
harfVec.normalize();
Eigen::Vector3f specularReflect = ks.cwiseProduct(light.intensity / r) * std::pow(std::max(0.0f, normalVec.dot(harfVec)), p);
// ambient
Eigen::Vector3f ambientReflect = amb_light_intensity.cwiseProduct(ka);
result_color += (diffuseReflect + specularReflect + ambientReflect);
}
return result_color * 255.f;
}
texture_fragment_shader
emm, 就是将kd
的内容变换为我们从材质文件中提取到的内容,
因此这里只粘贴前半部分 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
// TODO: Get the texture value at the texture coordinates of the current fragment
return_color = payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y());
}
Eigen::Vector3f texture_color;
texture_color << return_color.x(), return_color.y(), return_color.z();
Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = texture_color / 255.f;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
// 后面的和上面的差不多, 照着写下来就行
}
displacement && bump
怎么说呢, 感觉注释给出了代码, 也就是照着注释实现一下, 这里就不在粘贴了.