rasterize_triangle(const Triangle& t) in rasterizer.cpp
//Screen space rasterizationvoid rst::rasterizer::rasterize_triangle(constTriangle& t,const std::array<Eigen::Vector3f,3>& view_pos) { // TODO: From your HW3, get the triangle rasterization code. // TODO: Inside your rasterization loop: // * 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 // 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; // TODO: Interpolate the attributes: // auto interpolated_color // auto interpolated_normal // auto interpolated_texcoords // auto interpolated_shadingcoords // Use: fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
// Use: payload.view_pos = interpolated_shadingcoords; // Use: Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
// Use: auto pixel_color = fragment_shader(payload);}
2. 实现投影矩阵
get_projection_matrix() in main.cpp
Eigen::Matrix4fget_projection_matrix(float eye_fov,float aspect_ratio,float zNear,float zFar){ // TODO: Use the same projection matrix from the previous assignments}
3. 实现 Blinn-Phong 模型计算 Fragment Color
phong_fragment_shader() in main.cpp
Eigen::Vector3fphong_fragment_shader(constfragment_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. }return result_color *255.f;}
4. 实现纹理映射
texture_fragment_shader() in main.cpp
在实现 Blinn-Phong 的基础上,将纹理颜色视为公式中的 kd ,实现 Texture Shading Fragment Shader。
Eigen::Vector3ftexture_fragment_shader(constfragment_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 } 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) { // 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. }return result_color *255.f;}
Eigen::Vector3fbump_fragment_shader(constfragment_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) Eigen::Vector3f result_color = {0,0,0}; result_color = normal;return result_color *255.f;}
6. 实现位移片元着色器
displacement_fragment_shader() in main.cpp
在实现 Bump mapping 的基础上,实现 displacement mapping。
Eigen::Vector3fdisplacement_fragment_shader(constfragment_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) 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. }return result_color *255.f;}
题解解析
1. 实现三角形渲染(HW2基础上增加颜色、法向量和纹理颜色坐标插值)
代码内容大致与HW2相同。
首先使用Bounding Box缩小渲染范围。
auto v =t.toVector4();float min_x = std::floor(std::min({v[0].x(),v[1].x(),v[2].x()}));float max_x = std::ceil(std::max({v[0].x(),v[1].x(),v[2].x()}));float min_y = std::floor(std::min({v[0].y(),v[1].y(),v[2].y()}));float max_y = std::ceil(std::max({v[0].y(),v[1].y(),v[2].y()}));// Iterating through each pixel in the bounding boxfor (float x = min_x; x <= max_x; x++) {for (float y = min_y; y <= max_y; y++) { ......//后文代码均在此处 }}
// Set the pixel color if it should be paintedEigen::Vector2i p = { (float)x,(float)y};set_pixel(p, pixel_color); //设置颜色depth_buf[get_index(x, y)] = z_interpolated;//更新z值
2. 投影变换矩阵
HW2已经写过,这里直接抄过来。
Eigen::Matrix4fget_projection_matrix(float eye_fov,float aspect_ratio,float zNear,float zFar){ // TODO: Use the same projection matrix from the previous assignments Eigen::Matrix4f projection = Eigen::Matrix4f::Identity(); Eigen::Matrix4f M = Eigen::Matrix4f ::Identity();float fov =0.5*eye_fov*MY_PI/180;float top =tan(fov) * zNear;float bottom =-top;float right = top * aspect_ratio;float left =-right; M <<2*abs(zNear) / (right - left),0, (right + left) / (right - left),0,0,2*abs(zNear) / (top - bottom), (top + bottom) / (top - bottom),0, 0, 0, (abs(zNear) + abs(zFar)) / (abs(zNear) - abs(zFar)), 2 * abs(zFar * zNear) / (abs(zNear) - abs(zFar)),
0,0,-1,0;return M;}
在作业框架中,已经事先生成了ambient, diffuse, and specular 反射系数(ka, kd, ks),需要注意的是,这个反射系数包含了环境光的;两个光源的位置和强度(l1, l2),环境光强度常数( amb_light_intensity )。与此同时,之前光栅化的信息保存在了 payload 中,包括物体材质颜色 color ,位置信息 point 和法线 normal 。
并且创建了 result_color 用于保存Blinn-Phong处理的结果。
对于每个光源,它计算到光源的方向向量( l )和相机的方向向量( v ),以及用于高光项的半向量(h)。衰减因子( r ),距离该点越远,光的强度就越低。代码中直接对 l 自身做一次点积处理获得向量 l 的模的平方。
// 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.auto v = eye_pos - point; //v为出射光方向(指向眼睛)auto l =light.position - point; //l为指向入射光源方向auto h = (v + l).normalized(); //h为半程向量即v+l归一化后的单位向量auto r =l.dot(l); //衰减因子auto ambient =ka.cwiseProduct(amb_light_intensity);auto diffuse =kd.cwiseProduct(light.intensity / r) * std::max(0.0f,normal.normalized().dot(l.normalized()));auto specular =ks.cwiseProduct(light.intensity / r) * std::pow(std::max(0.0f,normal.normalized().dot(h)), p);result_color += (ambient + diffuse + specular);
4. 完成纹理映射
这个部分和上part基本一致,只是改变了 漫反射系数 的 kd 数值。
我们只需要填充这个部分:
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::Vector3fgetColor(float u,float v){if (u <0) {u =0;}if (u >1) {u =1;}if (v <0) {v =0;}if (v >1) {v =1;}auto u_img = u * width;auto v_img = (1- v) * height;auto color =image_data.at<cv::Vec3b>(v_img, u_img);return Eigen::Vector3f(color[0],color[1],color[2]);}
至于原因目前还没深入研究 // TODO
5. 实现凹凸贴图
首先将贴图改回「hmap.jpg」,渲染函数改为「bump_fragment_shader」:
auto texture_path ="hmap.jpg";// auto texture_path = "spot_texture.png";...std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = bump_fragment_shader;
在Phong模型的基础上,通过TBN矩阵实现凹凸反射。、
什么是TBN:
将纹理坐标对应到模型空间的矩阵
The acronym TBN stands for Tangent, Bitangent, Normal, and it's used in the context of bump mapping or normal mapping in 3D computer graphics, including the fragment shader.
Tangent: A vector that is perpendicular to the surface of the object and aligned with the direction of increasing texture U-coordinate (along the width of the texture).
Bitangent (or Binormal): A vector perpendicular to the surface of the object and aligned with the direction of increasing texture V-coordinate (along the height of the texture).
Normal: A vector pointing directly out from the surface of the object, it is perpendicular to both the Tangent and Bitangent vectors.
// 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矩阵:
auto x =normal.x();auto y =normal.y();auto z =normal.z();Eigen::Vector3ft(x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z));Eigen::Vector3f b =normal.cross(t);Eigen::Matrix3f TBN; //TBN矩阵: 将纹理坐标对应到模型空间中TBN <<t.x(),b.x(),normal.x(),t.y(),b.y(),normal.y(),t.z(),b.z(),normal.z();
(u + 1.0f / w, v) 对当前点稍微右边的纹理进行采样, (u, v) 对当前点的纹理进行采样。 dV 同理。
kh,kn 用于控制纹理高度的变化。定义: float kh = 0.2, kn = 0.1; 。
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());
接下来,根据写出 (dU, dV) 的垂直向量 ln (扩充到三维)。
Eigen::Vector3f ln{ -dU,-dV,1.0f };
最后利用TBN矩阵转换到世界坐标,归一化,并且输出颜色到result_color:
normal = TBN * ln;Eigen::Vector3f result_color =normal.normalized();return result_color *255.f;
效果:
6. 实现位移片元着色器
在凹凸贴图的基础上做修改。
凹凸贴图不影响物体的形状,只是通过扰动法向量实现凹凸感。
位移片元着色器则会获取模型的顶点,然后通过 kn * normal * payload.texture->getColor(u, v).norm() 抬高或压低顶点 point ,方向是法向量的方向。