附其他所有作业超链接如下:
Games101 作业0:作业0
Games101 作业1:作业1
Games101 作业2:作业2
Games101 作业3:作业3
Games101 作业4:作业4
Games101 作业5:作业5
Games101 作业6:作业6
Games101 作业7:作业7
完整代码获取途径:
https://github.com/liupeining/Games_101_homework
照旧把这段代码粘贴过来:
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
float n = zNear;
float f = zFar;
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
float t = -tan((eye_fov/360)*MY_PI)*(abs(n)); //top
float r = t/aspect_ratio;
Eigen::Matrix4f Mp;//透视矩阵
Mp <<
n, 0, 0, 0,
0, n, 0, 0,
0, 0, n+f, -n*f,
0, 0, 1, 0;
Eigen::Matrix4f Mo_tran;//平移矩阵
Mo_tran <<
1, 0, 0, 0,
0, 1, 0, 0, //b=-t;
0, 0, 1, -(n+f)/2 ,
0, 0, 0, 1;
Eigen::Matrix4f Mo_scale;//缩放矩阵
Mo_scale <<
1/r, 0, 0, 0,
0, 1/t, 0, 0,
0, 0, 2/(n-f), 0,
0, 0, 0, 1;
projection = (Mo_scale*Mo_tran)* Mp;//投影矩阵
//这里一定要注意顺序,先透视再正交;正交里面先平移再缩放;否则做出来会是一条直线!
return projection;
}
修改函数 rasterize_triangle(const Triangle& t) in rasterizer.cpp: 在此 处实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值。
下面三角形的rasterizer和上次的思路完全一致,这里仍然沿用了MSAA;
大部分都和上次的代码相同,更改以及需要注意的地方均在代码中做了注释。
其实区别就在于添加了用重心坐标各种插值最终返回一个color
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4(); //v[0],v[1],v[2]分别为三角形的三个顶点,是四维向量
//比较三个顶点的横纵坐标,确定包围盒的边界并取整
double min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
double max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
double min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
double max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
min_x = static_cast<int>(std::floor(min_x));
min_y = static_cast<int>(std::floor(min_y));
max_x = static_cast<int>(std::ceil(max_x));
max_y = static_cast<int>(std::ceil(max_y));
//此处实现的是MSAA
std::vector<Eigen::Vector2f> pos
{ //对一个像素分割四份 当然你还可以分成4x4 8x8等等甚至你还可以为了某种特殊情况设计成不规则的图形来分割单元
{0.25,0.25}, //左下
{0.75,0.25}, //右下
{0.25,0.75}, //左上
{0.75,0.75} //右上
};
for (int i = min_x; i <= max_x; ++i)
{
for (int j = min_y; j <= max_y; ++j)
{
int count = 0; //开始遍历四个小格子,获得平均值
for (int MSAA_4 = 0; MSAA_4 < 4; ++MSAA_4)
{
if (insideTriangle(static_cast<float>(i+pos[MSAA_4][0]), static_cast<float>(j+pos[MSAA_4][1]),t.v))
++count;
}
if(count) //至少有一个小格子在三角形内
{
//此处是框架中代码,获得z,见原程序注释:
// * v[i].w() is the vertex view space depth value z.
// * Z is interpolated view space depth for the current pixel
// * zp is depth between zNear and zFar, used for z-buffer
auto[alpha, beta, gamma] = computeBarycentric2D(i + 0.5, j + 0.5, t.v);
float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
zp *= Z;
//end
if (depth_buf[get_index(i, j)] > zp)
{
depth_buf[get_index(i, j)] = zp;//更新深度
//这里注意,虽然说明上说"反转了z,保证都是正数,并且越大表示离视点越远",
//但经过我的查看,实际上并没有反转,因此还是按照-z近大远小来做,当然也可以在上面补一个负号不过没必要
//利用重心坐标插值各种值
auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1).normalized();
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);
//shadingcoords是由view_pos插值得到,也就是物体表面的点在相机坐标系的位置。他们会在shader中被用到,来计算光照等信息。
//此处是框架中代码,获得z,见原程序注释:
fragment_shader_payload payload(interpolated_color, interpolated_normal, interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
//end
// 设置颜色
set_pixel(Eigen::Vector2i(i, j), pixel_color * (count / 4.0));
}
}
}
}
}
此时你可以运行./Rasterizer output.png normal 来观察法向量实现结果。
结果:
下面就是修改函数 phong_fragment_shader() in main.cpp: 实现 Blinn-Phong 模型计算 Fragment Color.
这里要添加的地方就是一个for循环,每个点用一下Ld,Ls,La那仨公式就行。
无脑对着公式敲,非常简单。
循环内代码:
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += (Ld + Ls);
}
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += La;
另附一下整个函数的代码:
Eigen::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)
{
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += (Ld + Ls);
}
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += La;
//这里注意一下.cwiseProduct和.dot的用法。
return result_color * 255.f;
}
接下来就是修改函数 texture_fragment_shader() in main.cpp: 在实现 Blinn-Phong的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader.
这里要写一个if和一个for。if里写一句话:
if (payload.texture)
{ //payload是之前返回的一个结构体,texture是payload的成员,是个类,getcolor是公有函数,接收两个float,u,v坐标
return_color = payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y());
}
for里写的东西和之前完全一样:
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += (La + Ld + Ls);
附个完整的函数:
Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{ //payload是之前返回的一个结构体,texture是payload的成员,是个类,getcolor是公有函数,接收两个float,u,v坐标
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);
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 = texture_color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += (Ld + Ls);
}
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += La;
return result_color * 255.f;
}
接下来,修改函数 bump_fragment_shader() in main.cpp: 在实现 Blinn-Phong 的基础上,仔细阅读该函数中的注释,实现 Bump mapping.
这里就是凹凸贴图的实现,主要思路就是实现贴图对法线的扰动(计算导数)
这里框架里给了非常详细的注释,基本上约等于帮你把这部分写了,照着注释抄一下就好
注意的地方仍然注释了。
TODO后需要自行添加的代码:
// TODO: Implement bump mapping here 按照todo写:
// 这个地方是为了后面用局部坐标系,让n始终朝向001,课上讲过
float x = normal.x();
float y = normal.y();
float z = normal.z();
Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
Eigen::Vector3f b = normal.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln{-dU, -dV, 1};
Eigen::Vector3f result_color = (TBN * ln).normalized();
return result_color * 255.f;
完整函数代码:
Eigen::Vector3f bump_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;
float kh = 0.2, kn = 0.1;
// TODO: Implement bump mapping here 按照todo写:
// 这个地方是为了后面用局部坐标系,让n始终朝向001,课上讲过
float x = normal.x();
float y = normal.y();
float z = normal.z();
Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
Eigen::Vector3f b = normal.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln{-dU, -dV, 1};
Eigen::Vector3f result_color = (TBN * ln).normalized();
return result_color * 255.f;
}
结果:
下面就是最后一项任务:
修改函数 displacement_fragment_shader() in main.cpp: 在实现 Bump mapping 的基础上,实现 displacement mapping.
这里实现了位移贴图,并加上了光照。和之前的区别就在于一句话:
point += (kn * normal * payload.texture->getColor(u , v).norm());
真正改变了点的高度。
完整代码如下:
Eigen::Vector3f displacement_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;
float kh = 0.2, kn = 0.1;
float x = normal.x();
float y = normal.y();
float z = normal.z();
Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
Eigen::Vector3f b = normal.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln{-dU, -dV, 1};
//与凹凸贴图的区别就在于这句话
point += (kn * normal * payload.texture->getColor(u , v).norm());
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += (Ld + Ls);
}
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += La;
return result_color * 255.f;
}
结果:
注:看到很多其他博主做出来的图实际上要比说明里的图颜色浅,问题就在于把环境光照写在了循环里。
下面做一个对比:
环境光照写在外面:和说明完全一致
环境光照写在循环里:
腿部等有非常明显的区别。