图形学课程作业代码归档
作业0:虚拟机的配置和VSCode基本使用
本次作业主要是实现一个homogeneous coordinate下的点(2,1)的旋转45度和平移(1,2),十分简单,主要目的是在虚拟机环境下的编译(但是虚拟机太卡了建议自行配置link文件,只需要下载eigen3和openCV即可)
#include<cmath>
#include<eigen3/Eigen/Core>
#include<eigen3/Eigen/Dense>
#include<iostream>
int main() {
float sqrt2_div_2 = sqrt(2) / 2;
Eigen::Vector3f P(2.0f, 1.0f, 1.0f);
Eigen::Matrix3f rotate_and_trans;
rotate_and_trans << sqrt2_div_2, sqrt2_div_2, 1.0, -sqrt2_div_2, sqrt2_div_2, 2.0, 0.0, 0.0, 1.0;
P = rotate_and_trans * P;
//std::cout << P << std::endl;
return 0;
}
注意线性变换是从右往左看的哦(经过手算和输出后答案是对的)。
作业1:旋转与投影
本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵,打分如下:
[5 分] 正确构建模型矩阵;
[5 分] 正确构建透视投影矩阵;
[10 分] 你的代码可以在现有框架下正确运行,并能看到变换后的三角形;
[10 分] 当按 A 键与 D 键时,三角形能正确旋转。或者正确使用命令行得 到旋转结果图像;
[提高项 5 分] 在 main.cpp 中构造一个函数,该函数的作用是得到绕任意过原点的轴的旋转变换矩阵。
Eigen::Matrix4f get_rotation(Vector3f axis, float angle)
具体代码(不会包含有模板,需要自行判断在哪里,后续一致)
模型矩阵:指对于z轴旋转的函数,根据我GAMES101的文章内写有三个轴旋转矩阵,具体代码如下
Eigen::Matrix4f get_model_matrix(float angle){ Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); angle = angle * 180 / 3.14; model << cos(angle) , -sin(angle) , 0 , 0 , sin(angle) , cos(angle) , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 ; // TODO: Implement this function // Create the model matrix for rotating the triangle around the Z axis. // Then return it. return model; }
正确构建透视投影矩阵:如下列代码所示,注意return的矩阵乘法中,第二项不return可能在本地显示的会更好一些。
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { // Students will implement this function float n = -zNear; float f = -zFar; float t = n * std::tan(0.5f * eye_fov / 180.f * MY_PI); float b = -t; float r = aspect_ratio * t; float l = -r; Eigen::Matrix4f perspective_to_orthogonal = Eigen::Matrix4f::Identity(); perspective_to_orthogonal << zNear , 0 , 0 , 0 , 0 , zNear , 0 , 0 , 0 , 0 , zNear + zFar , -zNear * zFar, 0 , 0 , -1 , 0 ; Eigen::Matrix4f move_to_center = Eigen::Matrix4f::Identity(); move_to_center << 1 , 0 , 0 , -(r-l)/2.f , 0 , 1 , 0 , -(t-b)/2.f , 0 , 0 , 1 , -(n-f)/2.f , 0 , 0 , 0 , 1 ; Eigen::Matrix4f scale_to_canonical = Eigen::Matrix4f::Identity(); scale_to_canonical << 2.f/(r-l) , 0 , 0 , 0 , 0 , 2.f/(t-b) , 0 , 0 , 0 , 0 , 2.f/(n-f) , 0 , 0 , 0 , 0 , 1 ; // TODO: Implement this function // Create the projection matrix for the given parameters. // Then return it. return scale_to_canonical * move_to_center * perspective_to_orthogonal; }
提高部分:其公式选自于Rodrigues’ Rotation Formula,因此只需要按照对应公示即可(注:由于没有参加到实时批改,因此此部分正确性不敢保证)
Eigen::Matrix4f get_rotation(Vector3f axis, float angle) { Eigen::Matrix4f rotation,dual_matrix; dual_matrix << 0 , -axis.z() , axis.y() , 0 , axis.z() , 0 , -axis.x(), 0 , -axis.y() , axis.x() , 0 , 0 , 0 , 0 , 0 , 1 ; rotation << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; Vector4f A; A << axis.x(), axis.y(), axis.z(), 0; Eigen::Matrix4f I = Eigen::Matrix4f::Identity(); rotation = std::cos(angle / 180.f * MY_PI) * I+ (1 - std::cos(angle / 180.f * MY_PI)) * A * A.transpose()+ std::sin(angle / 180.f * MY_PI) * dual_matrix; return rotation; }
作业2:光栅化三角形
- [5 分] 正确地提交所有必须的文件,且代码能够编译运行。
- [20 分] 正确实现三角形栅格化算法。
- [10 分] 正确测试点是否在三角形内。
- [10 分] 正确实现 z-buffer 算法, 将三角形按顺序画在屏幕上。
- [提高项 5 分] 用 super-sampling 处理 Anti-aliasing : 你可能会注意 到,当我们放大图像时,图像边缘会有锯齿感。我们可以用 super-sampling 来解决这个问题,即对每个像素进行 2 * 2 采样,并比较前后的结果 (这里 并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每 一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个 sample list。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。
具体代码
关于rasterizer的代码:
void rst::rasterizer::rasterize_triangle(const Triangle& t) { auto v = t.toVector4(); // TODO : Find out the bounding box of current triangle. // iterate through the pixel and find if the current pixel is inside the triangle float xmax = std::max(t.v[0].x(), std::max(t.v[1].x(), t.v[2].x())); float ymax = std::max(t.v[0].y(), std::max(t.v[1].y(), t.v[2].y())); float xmin = std::min(t.v[0].x(), std::min(t.v[1].x(), t.v[2].x())); float ymin = std::min(t.v[0].y(), std::min(t.v[1].y(), t.v[2].y())); xmax = (int)xmax + 1, xmin = (int)xmin - 1, ymax = (int)ymax + 1, ymin = (int)ymin - 1; //std::cout << xmax << ymax << xmin << ymin << std::endl; for (int i = xmin; i <= xmax; ++i) { for (int j = ymin; j <= ymax; ++j) { // If so, use the following code to get the interpolated z value. //auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v); //float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w()); //float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w(); //z_interpolated *= w_reciprocal; if (insideTriangle((float)i + 0.5f, (float)j + 0.5f, t.v)) { float alpha, beta, gamma; auto k = computeBarycentric2D(i, j, t.v); std::tie(alpha, beta, gamma) = k; //static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector3f* v) float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w()); float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w(); z_interpolated *= w_reciprocal; // TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted. } } } }
关于判定点是否在三角形内:使用向量叉积进行判定
static bool insideTriangle(int x, int y, const Vector3f* _v) { // TODO : Implement this function to check if the point (x, y) //is inside the triangle represented by _v[0], _v[1], _v[2] Eigen::Vector2f p(x, y); Eigen::Vector2f AB = _v[1].head(2) - _v[0].head(2); Eigen::Vector2f BC = _v[2].head(2) - _v[1].head(2); Eigen::Vector2f CA = _v[0].head(2) - _v[2].head(2); Eigen::Vector2f AP = p - _v[0].head(2); Eigen::Vector2f BP = p - _v[1].head(2); Eigen::Vector2f CP = p - _v[2].head(2); return (AB[0] * AP[1] - AB[1] * AP[0] > 0) && (BC[0] * BP[1] - BC[1] * BP[0] > 0) && (CA[0] * CP[1] - CA[1] * CP[0] > 0); }
关于z-buffer的实现:在(1)中_TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted._处添加下面的代码
if (depth_buf[get_index(i, j)] > z_interpolated) { Vector3f color = t.getColor(); Vector3f point((float)i, (float)j, z_interpolated); depth_buf[get_index(i, j)] = z_interpolated; set_pixel(point, color); }
提高部分——超采样(MSAA),实际上就是把每一个格子细分称成四个格子,需要的代码如下
首先是需要增加一个预处理的格子细分:
std::vector<Eigen::Vector2f> memo{ {0.25,0.25}, {0.75,0.25}, {0.25,0.75}, {0.75,0.75}, };
然后将循环体更换为下面的部分
std::vector<Eigen::Vector2f> memo { {0.25,0.25}, {0.75,0.25}, {0.25,0.75}, {0.75,0.75}, }; float minDepth = FLT_MAX; int count = 0; for (int l = 0; l < 4; l++) { if (insideTriangle((float)i + memo[l][0], (float)j + memo[l][1], t.v)) { float alpha, beta, gamma; auto k = computeBarycentric2D((float)i + memo[l][0], (float)j + memo[l][1], t.v); std::tie(alpha, beta, gamma) = k; float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w()); float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w(); z_interpolated *= w_reciprocal; minDepth = std::min(minDepth, z_interpolated); count++; } } if (count != 0) { if (depth_buf[get_index(i, j)] > minDepth) { Vector3f color = t.getColor() * count / 4.0; Vector3f point((float)i, (float)j, minDepth); depth_buf[get_index(i, j)] = minDepth; set_pixel(point, color); } }
作业3:Pipeline and Shading
- [5 分] 提交格式正确,包括所有需要的文件。代码可以正常编译、执行。
- [10 分] 参数插值: 正确插值颜色、法向量、纹理坐标、位置 (Shading Position) 并将它们传递给 fragment_shader_payload.
- [20 分]Blinn-phong 反射模型: 正确实现 phong_fragment_shader 对应的 反射模型。
- [5 分] Texture mapping: 将 phong_fragment_shader 的代码拷贝到 texture_fragment_shader, 在此基础上正确实现 Texture Mapping.
- [10 分] Bump mapping 与 Displacement mapping: 正确实现 Bump mapping 与 Displacement mapping.
- [Bonus 3 分] 尝试更多模型: 找到其他可用的.obj 文件,提交渲染结果并 把模型保存在 /models 目录下。这些模型也应该包含 Vertex Normal 信息。
- [Bonus 5 分] 双线性纹理插值: 使用双线性插值进行纹理采样, 在 Texture 类中实现一个新方法 Vector3f getColorBilinear(float u, float v) 并 通过 fragment shader 调用它。为了使双线性插值的效果更加明显,你应该 考虑选择更小的纹理图。请同时提交纹理插值与双线性纹理插值的结果,并进行比较
具体代码
参数插值
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) { // TODO: From your HW3, get the triangle rasterization code. // TODO: Inside your rasterization loop: // TODO : Find out the bounding box of current triangle. // iterate through the pixel and find if the current pixel is inside the triangle // * 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 v = t.toVector4(); float xmax = std::max(t.v[0].x(), std::max(t.v[1].x(), t.v[2].x())); float ymax = std::max(t.v[0].y(), std::max(t.v[1].y(), t.v[2].y())); float xmin = std::min(t.v[0].x(), std::min(t.v[1].x(), t.v[2].x())); float ymin = std::min(t.v[0].y(), std::min(t.v[1].y(), t.v[2].y())); xmax = (int)xmax + 1, xmin = (int)xmin, ymax = (int)ymax + 1, ymin = (int)ymin; for (int i = xmin; i <= xmax; ++i) { for (int j = ymin; j <= ymax; ++j) { if (insideTriangle((float)i + 0.5f, (float)j + 0.5f, t.v)) { auto [alpha, beta, gamma] = computeBarycentric2D((float)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; // 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); if (zp < depth_buf[get_index(i, j)]){ depth_buf[get_index(i, j)] = zp; auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);// color auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1).normalized();// normal auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);// texture auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);// shadingcoords fragment_shader_payload payload(interpolated_color, interpolated_normal, interpolated_texcoords, texture ? &*texture : nullptr); payload.view_pos = interpolated_shadingcoords; auto pixel_color = fragment_shader(payload); set_pixel(Eigen::Vector2i(i, j), pixel_color); } } } } }
Blinn-Phong 模型
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) { // 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 light_dir = light.position - point; Eigen::Vector3f view_dir = eye_pos - point; float r = light_dir.dot(light_dir); // ambient Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity); // diffuse Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r); Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized())); // specular Eigen::Vector3f h = (light_dir + view_dir).normalized(); Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r); Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p); result_color += (La + Ld + Ls); } return result_color * 255.f; }
纹理映射
注意此处需要修改getcolor相关的函数,使得$u,v\in[0,1]$
Eigen::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->getColorBilinear(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) { // 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 light_dir = light.position - point; Eigen::Vector3f view_dir = eye_pos - point; float r = light_dir.dot(light_dir); // ambient Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity); // diffuse Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r); Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized())); // specular Eigen::Vector3f h = (light_dir + view_dir).normalized(); Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r); Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p); result_color += (La + Ld + Ls); } 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 // 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) float x = normal.x(), y = normal.y(), 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.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); float 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; Eigen::Vector3f result_color = normal.normalized(); return result_color * 255.f; }
位移贴图
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) float x = normal.x(), y = normal.y(), 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.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); float 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 * normal * payload.texture->getColor(u, v).norm()); normal = TBN * ln; normal = normal.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 light_dir = light.position - point; Eigen::Vector3f view_dir = eye_pos - point; float r = light_dir.dot(light_dir); // ambient Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity); // diffuse Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r); Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized())); // specular Eigen::Vector3f h = (light_dir + view_dir).normalized(); Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r); Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p); result_color += (La + Ld + Ls); } return result_color * 255.f; }
双线性插值
//in texture.hpp Eigen::Vector3f getColorBilinear(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; int umin = (int)u_img; int umax = std::min(width, (int)u_img + 1); int vmin = (int)v_img; int vmax = std::min(height, (int)v_img + 1); auto row_1 = image_data.at<cv::Vec3b>(vmax, umin); auto row_2 = image_data.at<cv::Vec3b>(vmax, umax); //row //column auto column_1 = image_data.at<cv::Vec3b>(vmin, umin); auto column_2 = image_data.at<cv::Vec3b>(vmin, umax); float row = (u_img - umin) / (umax - umin); float column = (v_img - vmax) / (vmin - vmax); auto bottom_color = (1 - row) * row_1 + row * row_2; auto top_color = (1 - row) * column_1 + row * column_2; auto color = (1 - column) * bottom_color + column * top_color; return Eigen::Vector3f(color[0], color[1], color[2]); }
作业4:Bézier 曲线
[5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行
[20 分] De Casteljau 算法: 对于给定的控制点,你的代码能够产生正确的 Bézier 曲线
[5 分] 奖励分数: 实现对 Bézier 曲线的反走样。(对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。)
实际上本次作业就是De Casteljau 算法的实现
具体代码
De Casteljau算法的实现
具体数学推导见我的另外一篇文章,注意是递归的方法,可能会比较慢。
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t)
{
// TODO: Implement de Casteljau's algorithm
if (control_points.size() == 2)
{
return control_points[0] + t * (control_points[1] - control_points[0]);
}
std::vector<cv::Point2f> vec;
for (int i = 0; i < control_points.size() - 1; i++)
{
vec.push_back(control_points[i] + t * (control_points[i + 1] - control_points[i]));
}
return recursive_bezier(vec, t);
}
反走样方法
void bezier(const std::vector<cv::Point2f>& control_points, cv::Mat& window)
{
// TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
// recursive Bezier algorithm.
for (double t = 0.0; t <= 1.0; t += 0.0001)
{
cv::Point2f point = recursive_bezier(control_points, t);
window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
//anti-aliasing
float x = point.x - std::floor(point.x);
float y = point.y - std::floor(point.y);
int x_flag = x < 0.5f ? -1 : 1;
int y_flag = y < 0.5f ? -1 : 1;
// 距离采样点最近的4个坐标点
cv::Point2f p00 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y) + 0.5f);
cv::Point2f p01 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y) + 0.5f);
cv::Point2f p10 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);
cv::Point2f p11 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);
std::vector<cv::Point2f> vec;
vec.push_back(p01);
vec.push_back(p10);
vec.push_back(p11);
// 计算最近的坐标点与采样点距离
cv::Point2f distance = p00 - point;
float len = sqrt(distance.x * distance.x + distance.y * distance.y);
// 对边缘点进行着色
for (auto p : vec)
{
cv::Point2f d = p - point;
float l = sqrt(d.x * d.x + d.y * d.y);
float percnet = len / l;
cv::Vec3d color = window.at<cv::Vec3b>(p.y, p.x);
color[1] = std::max(color[1], (double)255 * percnet);
window.at<cv::Vec3b>(p.y, p.x) = color;
}
}
}
main函数中的内容更改
//naive_bezier(control_points, window);
bezier(control_points, window);
作业5:光线与三角形相交
- [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。
- [10 分] 光线生成: 正确实现光线生成部分,并且能够看到图像中的两个球体。
- [15 分] 光线与三角形相交: 正确实现了 Moller-Trumbore 算法,并且能够看到图像中的地面。
具体代码
光线生成
void Renderer::Render(const Scene& scene)
{
//前面省略
Vector3f eye_pos(0);
int m = 0;
for (int j = 0; j < scene.height; ++j)
{
for (int i = 0; i < scene.width; ++i)
{
// generate primary ray direction
// TODO: Find the x and y positions of the current pixel to get the direction
// vector that passes through it.
// Also, don't forget to multiply both of them with the variable *scale*, and
// x (horizontal) variable with the *imageAspectRatio*
float x = (2 * (i + 0.5) / (float)scene.width - 1) * imageAspectRatio * scale;
float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
Vector3f direction(x, y, -1);
direction = normalize(direction); // Don't forget to normalize this direction!
framebuffer[m++] = castRay(eye_pos, direction, scene, 0);
}
UpdateProgress(j / (float)scene.height);
}
//后面省略
}
光线与三角形求交
bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
const Vector3f& dir, float& tnear, float& u, float& v)
{
// TODO: Implement this function that tests whether the triangle
// that's specified bt v0, v1 and v2 intersects with the ray (whose
// origin is *orig* and direction is *dir*)
// Also don't forget to update tnear, u and v.
Vector3f E1 = v1 - v0;
Vector3f E2 = v2 - v0;
Vector3f S = orig - v0;
Vector3f S1 = crossProduct(dir, E2);
Vector3f S2 = crossProduct(S, E1);
float k = dotProduct(S1, E1);
tnear = 1.0f / k * dotProduct(S2, E2);
u = 1.0f / k * dotProduct(S1, S);
v = 1.0f / k * dotProduct(S2, dir);
if (tnear > 0 && v >= 0 && v <= 1 && u >= 0 && u <= 1) return true;
else return false;
}
作业6:加速结构
- [5 points] 提交格式正确,包含所有需要的文件;代码可以在虚拟机下正确 编译运行。
- [20 points] 包围盒求交:正确实现光线与包围盒求交函数。
- [15 points] BVH 查找:正确实现 BVH 加速的光线与场景求交。
- [加分项 20 points] SAH 查找:自学 SAH(Surface Area Heuristic) , 正 确实现 SAH 加速,并且提交结果图片,并在 README.md 中说明 SVH 的实现 方法,并对比 BVH、SVH 的时间开销。(可参考 http://15462.courses.cs.cmu.edu/fall2015/lecture/acceleration/slide_024,也可以查找其他资料)
具体代码
包围盒求交
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& dirIsNeg) const
{
// invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
// dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
// TODO test if ray bound intersects
//x
float xmin = (pMin.x - ray.origin.x) * invDir.x, xmax = (pMax.x - ray.origin.x) * invDir.x;
//y
float ymin = (pMin.y - ray.origin.y) * invDir.y, ymax = (pMax.y - ray.origin.y) * invDir.y;
//z
float zmin = (pMin.z - ray.origin.z) * invDir.z, zmax = (pMax.z - ray.origin.z) * invDir.z;;
//判断是否反向
if (!dirIsNeg[0]){
float t = xmin, xmin = xmax, xmax = t;
}
if (!dirIsNeg[1]){
float t = ymin, ymin = ymax, ymax = t;
}
if (!dirIsNeg[2]){
float t = zmin, zmin = zmax, zmax = t;
}
//进出包围盒的时间
float T_Enter = std::max(xmin, std::max(ymin, zmin));
float T_Exit = std::min(xmax, std::min(ymax, zmax));
if (T_Enter <= T_Exit && T_Exit >= 0) return true;
return false;
}
BVH树的查找
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
// TODO Traverse the BVH to find intersection
// invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
// dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
// TODO test if ray bound intersects
// 命名采用的是求交的名字,此处是简单的前序遍历
Vector3f invDir = Vector3f{ 1.0f / ray.direction.x, 1.0f / ray.direction.y, 1.0f / ray.direction.z };
std::array<int, 3> dirIsNeg = { ray.direction.x > 0, ray.direction.y > 0, ray.direction.z > 0 };
if (!node->bounds.IntersectP(ray, invDir, dirIsNeg))
{
return {};
}
if (node->left == nullptr && node->right == nullptr)
{
return node->object->getIntersection(ray);
}
Intersection BVH_Left = getIntersection(node->left, ray);
Intersection BVH_Right = getIntersection(node->right, ray);
return BVH_Left.distance < BVH_Right.distance ? BVH_Left : BVH_Right;
}
SAH查找(暂略)
作业7:路径追踪
(史上最难,因为我第一次学习写多线程。。)
- [5 points] 提交格式正确,包含所有需要的文件;代码可以在虚拟机下正确 编译运行。
- [45 points] Path Tracing:正确实现 Path Tracing 算法,并提交分辨率 不小于 512*512,采样数不小于 8 的渲染结果图片。
- [加分项 10 points] 多线程:将多线程应用在 Ray Generation 上,注意实现时可能涉及的冲突。
- [加分项 10 points] Microfacet:正确实现 Microfacet 材质,并提交可 体现 Microfacet 性质的渲染结果。
具体代码
Path Tracing主要代码
//in Scene.cpp
// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
/*
intersect(const Ray ray)in Scene.cpp: 求一条光线与场景的交点
sampleLight(Intersection pos, float pdf)inScene.cpp: 在场景的所有光源上按面积uniform、地、sample、一个点,并
计算该 sample 的概率密度
sample(const Vector3f wi, const Vector3f N) in Material.cpp: 按照该材质的性质,给定入射方向与法向量,用某种分
布采样一个出射方向
pdf(const Vector3f wi, const Vector3f wo, const Vector3f N)inMaterial.cpp: 给定一对入射、出射方向与法向量,计
算 sample 方法得到该出射 方向的概率密度
eval(const Vector3f wi, const Vector3f wo, const Vector3f N)inMaterial.cpp: 给定一对入射、出射方向与法向量,计
算这种情况下的 f_r 值
可能用到的变量有:
RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率
*/
// TO DO Implement Path Tracing Algorithm here
/*
shade(p, wo)
sampleLight(inter, pdf_light)
Get x, ws, NN, emit from inter
Shoot a ray from p to x
If the ray is not blocked in the middle
L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws, NN) / |x-p|^2 / pdf_light
L_indir = 0.0
Test Russian Roulette with probability RussianRoulette
wi = sample(wo, N)
Trace a ray r(p, wi)
If ray r hit a non-emitting object at q
L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N) / pdf(wo, wi, N) / RussianRoulette
Return L_dir + L_indir
*/
//sampleLight(inter, pdf_light)
/*
Intersection的类成员
bool happened;
Vector3f coords;
Vector3f tcoords;
Vector3f normal;
Vector3f emit;
double distance;
Object* obj;
Material* meterial;
*/
Intersection Light_Inter;
float Light_PDF = 0.0f;
sampleLight(Light_Inter, Light_PDF);
Intersection Object_Inter = intersect(ray);
//Check
if (!Object_Inter.happened)
{
return {};
}
if (Object_Inter.meterial->hasEmission())
{
return Object_Inter.meterial->getEmission();
}
//Get object, ws, NN, emit from inter
Vector3f Object_To_Light = Light_Inter.coords - Object_Inter.coords;
Vector3f Object_To_Light_Direction = Object_To_Light.normalized();
float Distance_square = Object_To_Light.x * Object_To_Light.x + Object_To_Light.y * Object_To_Light.y + Object_To_Light.z * Object_To_Light.z;
//Shoot a ray from p to x
Ray Object_To_Light_Ray = { Object_Inter.coords, Object_To_Light_Direction };
Intersection t = intersect(Object_To_Light_Ray);
//L_dir = 0.0
Vector3f L_dir = { 0, 0, 0 };
//If the ray is not blocked in the middle
if (t.distance - Object_To_Light.norm() > -EPSILON)
{
//L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws, NN) / |x-p|^2 / pdf_light
L_dir = Light_Inter.emit * Object_Inter.meterial->eval(ray.direction, Object_To_Light_Direction, Object_Inter.normal) * dotProduct(Object_To_Light_Direction, Object_Inter.normal) * dotProduct(-Object_To_Light_Direction, Light_Inter.normal) / Distance_square / Light_PDF;
}
//L_indir = 0.0
Vector3f L_indir = { 0, 0, 0 };
//Test Russian Roulette with probability RussianRoulette
//俄罗斯轮盘赌
if (get_random_float() > RussianRoulette)
{
return L_dir;
}
//wi = sample(wo, N)
Vector3f Object_To_Next_Object = Object_Inter.meterial->sample(ray.direction, Object_Inter.normal).normalized();
//Trace a ray r(p, wi)
Ray Object_To_Next_Ray = { Object_Inter.coords, Object_To_Next_Object };
Intersection nextObjInter = intersect(Object_To_Next_Ray);
//If ray r hit a non-emitting object at q
if (nextObjInter.happened && !nextObjInter.meterial->hasEmission())
{
//L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N) / pdf(wo, wi, N) / RussianRoulette
float pdf = Object_Inter.meterial->pdf(ray.direction, Object_To_Next_Object, Object_Inter.normal);
L_indir = castRay(Object_To_Next_Ray, depth + 1) * Object_Inter.meterial->eval(ray.direction, Object_To_Next_Object, Object_Inter.normal) * dotProduct(Object_To_Next_Object, Object_Inter.normal) / pdf / RussianRoulette;
}
//Return L_dir + L_indir
return L_dir + L_indir;
}
需要迁移的内容见HM6,直接C/V
- Triangle::getIntersection in Triangle.hpp
- IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp(*注意检查 t_enter = t_exit *)
- getIntersection(BVHBuildNode* node, const Ray ray)inBVH.cpp
关于多线程
为了保证数据不产生竞争,需要灵活地使用mutex和锁,同时数据传输也需要进行一次打包防止中断,使用的是std::Thread的部分内容和晚上查阅的材料
关于Render.hpp
// // Created by goksu on 2/25/20. // #pragma once #include "Scene.hpp" #include <functional> #include <queue> struct hit_payload { float tNear; uint32_t index; Vector2f uv; Object* hit_obj; }; struct Thread_Data_List { int m; Vector3f eye_pos; Vector3f dir; int depth; int SPP; unsigned int id; }; struct Threads { Thread_Data_List Thread_data; std::function<void(Thread_Data_List)> Thread_Function; }; class Renderer { public: void Render(const Scene& scene); void CastRayTask(Thread_Data_List); //多线程相关 void Run(int id); void Add_Thread(const Threads&); private: Scene* renderer_scene; std::vector<Vector3f> framebuffer; //多线程相关 bool Exit_Thread; unsigned int Thread_ID; std::queue<Threads> Thread_Queue; };
对应的.cpp文件
void Renderer::Render(const Scene& scene) { Exit_Thread = false; std::vector<std::thread*> threads; for (int i = 0; i < MAX_THREAD_SIZE; i++) { std::thread* t = new std::thread([=] {Run(i); }); threads.push_back(t); } renderer_scene = (Scene*)(&scene); framebuffer.resize(scene.width * scene.height); float scale = tan(deg2rad(scene.fov * 0.5)); float imageAspectRatio = scene.width / (float)scene.height; Vector3f eye_pos(278, 273, -800); int m = 0; Thread_ID = 0; // change the spp value to change sample ammount int SPP = 200; std::cout << "SPP: " << SPP << "\n"; for (uint32_t j = 0; j < scene.height; ++j) { for (uint32_t i = 0; i < scene.width; ++i) { for (int k = 0; k < SPP; ++k) { // generate primary ray direction float x = (2 * (i + get_random_float()) / (float)scene.width - 1) * imageAspectRatio * scale; float y = (1 - 2 * (j + get_random_float()) / (float)scene.height) * scale; Vector3f dir = normalize(Vector3f(-x, y, 1)); Thread_Data_List Thread_Data = Thread_Data_List{ m++, eye_pos, dir, 0, SPP }; auto Thread_Function = std::bind(&Renderer::CastRayTask, this, std::placeholders::_1); Threads Ready_Thread = { Thread_Data, Thread_Function }; Add_Thread(Ready_Thread); } } } Exit_Thread = true; for (auto& t : threads) { //上一个线程结束后,再开启下一个线程 t->join(); } //后续不变 } void Renderer::Add_Thread(const Threads& t) { while (true) { std::unique_lock Lock(_lock); if (Thread_Queue.size() < MAX_QUEUE_SIZE) { Thread_Queue.push(t); Lock.unlock(); _no_empty.notify_all(); break; } else { _no_full.wait(Lock); } } } void Renderer::Run(int id) { while (true) { //线程上锁防止竞争 std::unique_lock Lock(_lock); if (!Thread_Queue.empty()) { Threads t = Thread_Queue.front(); Thread_Queue.pop(); Lock.unlock(); t.Thread_data.id = id; t.Thread_Function(t.Thread_data); //唤醒 _no_full.notify_all(); } else if (Exit_Thread) { Lock.unlock(); break; } else { _no_empty.wait_for(Lock, std::chrono::milliseconds(50)); } } } void Renderer::CastRayTask(Thread_Data_List data) { // ProtoType // framebuffer[m] += scene.castRay(Ray(eye_pos, dir), 0) / spp; std::unique_lock Lock(_buf_lock); framebuffer[data.m] += renderer_scene->castRay(Ray(data.eye_pos, data.dir), data.depth) / data.SPP; int line = (Thread_ID++) / (renderer_scene->width * data.SPP); Lock.unlock(); }
作业8:弹簧质点系统
- [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。
- [5 分] 连接绳子约束,正确的构造绳子
- [5 分] 半隐式欧拉法
- [5 分] 显式欧拉法
- [10 分] 显式 Verlet
- [5 分] 阻尼
由于虚拟机早已删除,同时windows下无法使用<unistd.h>,因此暂时咕咕咕,啥时候补的话,看心情咯