【GAMES101】作业3 Pipeline and Shading
之前一篇文章完成了作业3的任务1-4,留下5、6两个任务分别为凹凸贴图和位移贴图,由于这两个问题没有在课程上细说,所以实现起来需要自己额外花费一些功夫,通过这篇文章详细讲解一下这两个问题的解决步骤。
目录
5、修改bump_fragment_shader函数,实现Bump Mapping
6、修改函数displacement_fragment_shader,实现displacement mapping(位移贴图)
凹凸贴图原理
先给出课程中关于凹凸贴图的说明:所谓凹凸贴图,是指在着色的时候扰动物体表面法向量,达到在计算的时候改变着色值的效果,黑色的圆滑曲线为物体表面,黄色曲线为凹凸贴图,可以看出来,原本需要着色的位置法线向量为p,但是经过凹凸贴图修改后,法线向量变成了n,自然会改变漫反射和高光的值,达到修改着色的作用。
作业程序实现
5、修改bump_fragment_shader函数,实现Bump Mapping
所以本质问题是怎么计算每个面修改后的法线向量,在程序框架中,已经以注释的方式给出了计算过程。
// TODO: Implement bump mapping here
// Let n = normal = (x, y, z)
// Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
// Vector b = n cross product t
// Matrix TBN = [t b n]
// dU = kh * kn * (h(u+1/w,v)-h(u,v))
// dV = kh * kn * (h(u,v+1/h)-h(u,v))
// Vector ln = (-dU, -dV, 1)
// Normal n = normalize(TBN * ln)
根据注释内容可以将具体代码完善出来,大概可以看得出来,求法线向量的过程中最关键的一步是求解TBN矩阵,这个矩阵由三个列向量t,b,n组成,具体求TBN矩阵的方法我打算放到最后讨论(因为太难了,现在还不会)。
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
// Let n = normal = (x, y, z)
// Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
// Vector b = n cross product t
// Matrix TBN = [t b n]
// dU = kh * kn * (h(u+1/w,v)-h(u,v))
// dV = kh * kn * (h(u,v+1/h)-h(u,v))
// Vector ln = (-dU, -dV, 1)
// Normal n = normalize(TBN * ln)
auto n = normal.normalized();
auto x = n.x();
auto y = n.y();
auto z = n.z();
Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
Eigen::Vector3f b = n.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), n.x(),
t.y(), b.y(), n.y(),
t.z(), b.z(), n.z();
auto u = payload.tex_coords.x();
auto v = payload.tex_coords.y();
auto w = payload.texture->width;
auto h = payload.texture->height;
auto dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm());
auto dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln(-dU, -dV, 1.0f);
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
result_color = normal;
return result_color * 255.f;
}
完善代码并运行,我们可以得到一个看起来凹凸不平的牛牛
6、修改函数displacement_fragment_shader,实现displacement mapping(位移贴图)
相较于Bump Mapping,位移贴图的代码注释仅仅增加了一句
// Position p = p + kn * n * h(u,v)
这句话是什么意思呢,这就涉及到位移贴图的原理,位移贴图使用与凹凸贴图一样的纹理,不同之处在于,位移贴图实实在在地修改了顶点的位置(point),根据修改之后的顶点位置,考虑光照因素,需要再次使用Blinn-Phong模型进行计算。
整个代码只需要把Bump Mapping和Blinn-Phong模型融合在一起,再增加一行代码
point += kn * n * (payload.texture->getColor(u, v).norm());
用于修改三角形的顶点Vertices
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;
// TODO: Implement displacement mapping here
// Let n = normal = (x, y, z)
// Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
// Vector b = n cross product t
// Matrix TBN = [t b n]
// dU = kh * kn * (h(u+1/w,v)-h(u,v))
// dV = kh * kn * (h(u,v+1/h)-h(u,v))
// Vector ln = (-dU, -dV, 1)
// Position p = p + kn * n * h(u,v)
// Normal n = normalize(TBN * ln)
auto n = normal.normalized();
auto x = n.x();
auto y = n.y();
auto z = n.z();
Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
Eigen::Vector3f b = n.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), n.x(),
t.y(), b.y(), n.y(),
t.z(), b.z(), n.z();
auto u = payload.tex_coords.x();
auto v = payload.tex_coords.y();
auto w = payload.texture->width;
auto h = payload.texture->height;
auto dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm());
auto dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln(-dU, -dV, 1.0f);
point += kn * n * (payload.texture->getColor(u, v).norm());
normal = (TBN * ln).normalized();
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 l = light.position - point;
Eigen::Vector3f v = eye_pos - point;
Eigen::Vector3f h = l.normalized() + v.normalized();
Eigen::Vector3f n = normal.normalized();
auto r_square = l.dot(l);
auto ambient = ka.cwiseProduct(amb_light_intensity);
auto diffuse = kd.cwiseProduct(light.intensity / r_square) * std::max(0.0f, n.dot(l.normalized()));
auto specular = ks.cwiseProduct(light.intensity / r_square) * std::pow(std::max(0.0f, n.dot(h.normalized())), p);
result_color += (ambient + diffuse + specular);
}
return result_color * 255.f;
}
最后我们就可以得到一个凹凸不平并且考虑光照强度的牛牛了。
TBN矩阵原理
最后来仔细讨论一下什么是TBN矩阵,以及是如何通过该矩阵求出修改后的法线向量的。