# Games101 作业
代码在 GitHub 中开源,包含原版没做过的作业和做过的作业,链接:https://github.com/Maikire/GAMES101-Homework
原版是在 Linux 环境下的项目
我是在 Windows 环境下做的,所以部分配置和代码有改动
# Assignment0
给定一个点 P=(2,1),将该点绕原点先逆时针旋转 45◦,再平移 (1,2),计算出变换后点的坐标(要求用齐次坐标进行计算)
int main() | |
{ | |
Vector3f p(2.0, 1.0, 1.0); | |
Matrix3f rp; | |
rp << cos(-45.0 / 180.0), -sin(-45.0 / 180.0), 1.0, | |
sin(-45.0 / 180.0), cos(-45.0 / 180.0), 2.0, | |
0, 0, 1.0; | |
Matrix3f rp1; | |
rp1 << cos(-45.0 / 180.0), -sin(-45.0 / 180.0), 0, | |
sin(-45.0 / 180.0), cos(-45.0 / 180.0), 0, | |
0, 0, 1.0; | |
Matrix3f rp2; | |
rp2 << 1.0, 0, 1.0, | |
0, 1.0, 2.0, | |
0, 0, 1.0; | |
cout << rp * p << endl; | |
cout << "==============================" << endl; | |
cout << rp2 * rp1 * p << endl; | |
return 0; | |
} |
# Assignment1
旋转三角形
Eigen::Matrix4f get_model_matrix(float rotation_angle) | |
{ | |
Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); | |
// TODO: Implement this function | |
// Create the model matrix for rotating the triangle around the Z axis. | |
// Then return it. | |
float rad = rotation_angle * MY_PI / 180.0; | |
model << cos(rad), -sin(rad), 0, 0, | |
sin(rad), cos(rad), 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1; | |
return model; | |
} | |
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, | |
float zNear, float zFar) | |
{ | |
// Students will implement this function | |
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity(); | |
// TODO: Implement this function | |
// Create the projection matrix for the given parameters. | |
// Then return it. | |
float rad = eye_fov * MY_PI / 180.0f; | |
float t = tan(rad / 2) * abs(zNear); | |
float r = t * aspect_ratio; | |
float l = -r; | |
float b = -t; | |
// 透视投影矩阵 | |
// GAMES101 和 OpenGL 中,相机的观察方向是 -z | |
// 如果最后一行是 [0, 0, 1, 0],那么变换后的 w 就等于原坐标的 z,所以 w 也是负值 | |
// 当 x 和 y 同时除以一个负数 w 时,它们在 NDC 空间中的符号都会反转 | |
//x 反转 = 左右镜像;y 反转 = 上下颠倒 | |
// 上下左右同时翻转 = 旋转 180 度 | |
// 所以用 [0, 0, -1, 0] 修正 | |
// 但是这样做会让 -z 反转到 +z,这会导致深度反转,所以将第三行整行 * -1 以抵消反转 | |
Eigen::Matrix4f persp; | |
persp << zNear, 0, 0, 0, | |
0, zNear, 0, 0, | |
0, 0, -(zNear + zFar), zNear* zFar, | |
0, 0, -1, 0; | |
// 平移 | |
Eigen::Matrix4f translate; | |
translate << 1, 0, 0, -(r + l) / 2, | |
0, 1, 0, -(t + b) / 2, | |
0, 0, 1, -(zNear + zFar) / 2, | |
0, 0, 0, 1; | |
// 缩放 | |
Eigen::Matrix4f scale; | |
scale << 2 / (r - l), 0, 0, 0, | |
0, 2 / (t - b), 0, 0, | |
0, 0, 2 / (zNear - zFar), 0, | |
0, 0, 0, 1; | |
// 先透视,再正交 | |
projection = scale * translate * persp; | |
return projection; | |
} |
# Assignment2
在屏幕上画出实心三角形
# 基础
1、计算包围盒
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 | |
// 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; | |
// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted. | |
float minX = t.v[0].x(); | |
float minY = t.v[0].y(); | |
float maxX = t.v[0].x(); | |
float maxY = t.v[0].y(); | |
for (size_t i = 1; i <= 2; i++) | |
{ | |
if (t.v[i].x() < minX) | |
{ | |
minX = t.v[i].x(); | |
} | |
if (t.v[i].y() < minY) | |
{ | |
minY = t.v[i].y(); | |
} | |
if (t.v[i].x() > maxX) | |
{ | |
maxX = t.v[i].x(); | |
} | |
if (t.v[i].y() > maxY) | |
{ | |
maxY = t.v[i].y(); | |
} | |
} | |
int minXi = std::floor(minX); | |
int maxXi = std::ceil(maxX); | |
int minYi = std::floor(minY); | |
int maxYi = std::ceil(maxY); | |
minXi = std::max(0, minXi); | |
minYi = std::max(0, minYi); | |
maxXi = std::min(width - 1, maxXi); | |
maxYi = std::min(height - 1, maxYi); | |
for (int i = minXi; i <= maxXi; i++) | |
{ | |
for (int j = minYi; j <= maxYi; j++) | |
{ | |
//rasterize_triangle_normal(t, Vector3f(i, j, 0), v); | |
rasterize_triangle_msaa_2x(t, Vector3f(i, j, 0), v); | |
} | |
} | |
} |
2、测试点是否在三角形内
void rst::rasterizer::rasterize_triangle_normal(const Triangle& t, const Vector3f& pix, const array<Vector4f, 3>& v) | |
{ | |
float x = pix.x() + 0.5f; | |
float y = pix.y() + 0.5f; | |
Vector3f p(x, y, 0); | |
// a = t.v[0] | |
// b = t.v[1] | |
// c = t.v[2] | |
Vector3f ab = t.v[1] - t.v[0]; | |
Vector3f ap = p - t.v[0]; | |
Vector3f bc = t.v[2] - t.v[1]; | |
Vector3f bp = p - t.v[1]; | |
Vector3f ca = t.v[0] - t.v[2]; | |
Vector3f cp = p - t.v[2]; | |
float z1 = ab.cross(ap).z(); | |
float z2 = bc.cross(bp).z(); | |
float z3 = ca.cross(cp).z(); | |
if ((z1 >= 0 && z2 >= 0 && z3 >= 0) || | |
(z1 <= 0 && z2 <= 0 && z3 <= 0)) | |
{ | |
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; | |
int index = get_index(pix.x(), pix.y()); | |
if (z_interpolated < depth_buf[index]) | |
{ | |
depth_buf[index] = z_interpolated; | |
set_pixel(pix, t.getColor()); | |
} | |
} | |
} |
# 提高:msaa2x 抗锯齿
在 rasterizer.hpp 中声明深度缓冲和计算的索引函数
std::vector<float> super_sampling_buf; | |
int get_index_msaa_2x(int x, int y); |
- 在
rasterizer.cpp中实现计算索引的函数get_index_msaa_2x - 在
rasterizer::clear函数中添加super_sampling_buf - 在
rasterizer::rasterizer函数中初始化super_sampling_buf
int rst::rasterizer::get_index_msaa_2x(int x, int y) | |
{ | |
return (height * 2 - 1 - y) * width * 2 + x; | |
} | |
void rst::rasterizer::clear(rst::Buffers buff) | |
{ | |
if ((buff & rst::Buffers::Color) == rst::Buffers::Color) | |
{ | |
std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0}); | |
} | |
if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth) | |
{ | |
std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity()); | |
std::fill(super_sampling_buf.begin(), super_sampling_buf.end(), std::numeric_limits<float>::infinity()); | |
} | |
} | |
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h) | |
{ | |
frame_buf.resize(w * h); | |
depth_buf.resize(w * h); | |
super_sampling_buf.resize(w * h * 4); | |
} |
实现抗锯齿
void rst::rasterizer::rasterize_triangle_msaa_2x(const Triangle& t, const Vector3f& pix, const array<Eigen::Vector4f, 3>& v) | |
{ | |
float x = pix.x(); | |
float y = pix.y(); | |
float in_pix = 0; | |
for (int i = 1; i <= 2; i++) | |
{ | |
x += i / 4.0; | |
for (int j = 1; j <= 2; j++) | |
{ | |
y += j / 4.0; | |
Vector3f p(x, y, 0); | |
Vector3f ab = t.v[1] - t.v[0]; | |
Vector3f ap = p - t.v[0]; | |
Vector3f bc = t.v[2] - t.v[1]; | |
Vector3f bp = p - t.v[1]; | |
Vector3f ca = t.v[0] - t.v[2]; | |
Vector3f cp = p - t.v[2]; | |
float z1 = ab.cross(ap).z(); | |
float z2 = bc.cross(bp).z(); | |
float z3 = ca.cross(cp).z(); | |
if ((z1 >= 0 && z2 >= 0 && z3 >= 0) || | |
(z1 <= 0 && z2 <= 0 && z3 <= 0)) | |
{ | |
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; | |
int index = get_index_msaa_2x(pix.x() * 2 + i, pix.y() * 2 + j); | |
if (z_interpolated < super_sampling[index]) | |
{ | |
super_sampling[index] = z_interpolated; | |
in_pix += 0.25; | |
} | |
} | |
} | |
y = pix.y(); | |
} | |
set_pixel(pix, get_pixel(pix) * (1 - in_pix) + t.getColor() * in_pix); | |
} |
# Assignment3
渲染、模型、纹理、法线
1、修改函数 rasterize_triangle() in rasterizer.cpp ,实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值
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: | |
// * 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); | |
auto v = t.toVector4(); | |
float minX = t.v[0].x(); | |
float minY = t.v[0].y(); | |
float maxX = t.v[0].x(); | |
float maxY = t.v[0].y(); | |
for (size_t i = 1; i <= 2; i++) | |
{ | |
if (t.v[i].x() < minX) | |
{ | |
minX = t.v[i].x(); | |
} | |
if (t.v[i].y() < minY) | |
{ | |
minY = t.v[i].y(); | |
} | |
if (t.v[i].x() > maxX) | |
{ | |
maxX = t.v[i].x(); | |
} | |
if (t.v[i].y() > maxY) | |
{ | |
maxY = t.v[i].y(); | |
} | |
} | |
int minXi = std::floor(minX); | |
int maxXi = std::ceil(maxX); | |
int minYi = std::floor(minY); | |
int maxYi = std::ceil(maxY); | |
minXi = std::max(0, minXi); | |
minYi = std::max(0, minYi); | |
maxXi = std::min(width - 1, maxXi); | |
maxYi = std::min(height - 1, maxYi); | |
for (int i = minXi; i <= maxXi; i++) | |
{ | |
for (int j = minYi; j <= maxYi; j++) | |
{ | |
rasterize_triangle_normal(t, view_pos, Vector2i(i, j), v); | |
} | |
} | |
} | |
void rst::rasterizer::rasterize_triangle_normal(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos, const Vector2i& pix, const array<Eigen::Vector4f, 3>& v) | |
{ | |
float x = pix.x() + 0.5f; | |
float y = pix.y() + 0.5f; | |
Vector3f p(x, y, 0); | |
Vector3f a = t.v[0].head<3>(); | |
Vector3f b = t.v[1].head<3>(); | |
Vector3f c = t.v[2].head<3>(); | |
Vector3f ab = b - a; | |
Vector3f ap = p - a; | |
Vector3f bc = c - b; | |
Vector3f bp = p - b; | |
Vector3f ca = a - c; | |
Vector3f cp = p - c; | |
float z1 = ab.cross(ap).z(); | |
float z2 = bc.cross(bp).z(); | |
float z3 = ca.cross(cp).z(); | |
if ((z1 >= 0 && z2 >= 0 && z3 >= 0) || | |
(z1 <= 0 && z2 <= 0 && z3 <= 0)) | |
{ | |
auto [alpha, beta, gamma] = computeBarycentric2D(x, y, 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; | |
int index = get_index(pix.x(), pix.y()); | |
if (zp < depth_buf[index]) | |
{ | |
depth_buf[index] = zp; | |
Vector3f interpolated_color = (alpha * t.color[0] / v[0].w() + beta * t.color[1] / v[1].w() + gamma * t.color[2] / v[2].w()) * Z; | |
Vector3f interpolated_normal = (alpha * t.normal[0] / v[0].w() + beta * t.normal[1] / v[1].w() + gamma * t.normal[2] / v[2].w()) * Z; | |
Vector2f interpolated_texcoords = (alpha * t.tex_coords[0] / v[0].w() + beta * t.tex_coords[1] / v[1].w() + gamma * t.tex_coords[2] / v[2].w()) * Z; | |
Vector3f interpolated_shadingcoords = (alpha * view_pos[0] / v[0].w() + beta * view_pos[1] / v[1].w() + gamma * view_pos[2] / v[2].w()) * Z; | |
fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr); | |
payload.view_pos = interpolated_shadingcoords; | |
Vector3f pixel_color = fragment_shader(payload); | |
set_pixel(pix, pixel_color); | |
} | |
} | |
} |
2、修改函数 get_projection_matrix() in main.cpp 将你自己在之前的实验中实现的投影矩阵填到此处
// 直接复制之前的代码即可 |
3、修改函数 phong_fragment_shader() in main.cpp 实现 Blinn-Phong 模型计算 Fragment Color
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<!--swig0-->; | |
auto l2 = light<!--swig1-->; | |
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.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. | |
Vector3f light_dir = (light.position - point).normalized(); | |
// 半兰伯特 | |
Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5); | |
// Blinn-Phong | |
Vector3f view_dir = (eye_pos - point).normalized(); | |
Vector3f halfDir = (light_dir + view_dir).normalized(); | |
Vector3f specular = ks * pow(normal.dot(halfDir), p); | |
// 环境光 | |
//cwiseProduct 逐元素相乘 | |
Vector3f ambient = ka.cwiseProduct(amb_light_intensity); | |
result_color += diffuse + specular + ambient; | |
} | |
return result_color * 255.f; | |
} |
4、修改函数 texture_fragment_shader() in main.cpp 在实现 Blinn-Phong 的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader
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 | |
float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f); | |
float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f); | |
return_color = payload.texture->getColor(u, v); | |
} | |
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. | |
Vector3f light_dir = (light.position - point).normalized(); | |
// 半兰伯特 | |
Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5); | |
// Blinn-Phong | |
Vector3f view_dir = (eye_pos - point).normalized(); | |
Vector3f halfDir = (light_dir + view_dir).normalized(); | |
Vector3f specular = ks * pow(normal.dot(halfDir), p); | |
// 环境光 | |
//cwiseProduct 逐元素相乘 | |
Vector3f ambient = ka.cwiseProduct(amb_light_intensity); | |
result_color += diffuse + specular + ambient; | |
} | |
return result_color * 255.f; | |
} |
5、修改函数 bump_fragment_shader() in main.cpp 在实现 Blinn-Phong 的 基础上,实现 Bump mapping
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(); | |
float y = normal.y(); | |
float z = normal.z(); | |
Vector3f t = Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z)); | |
Vector3f b = normal.cross(t); | |
Matrix3f TBN; | |
TBN << t, b, normal; | |
float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f); | |
float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f); | |
float u1 = std::clamp(u + 1 / width, 0.0f, 1.0f); | |
float v1 = std::clamp(v + 1 / height, 0.0f, 1.0f); | |
Vector3f h = payload.texture->getColor(u, v); | |
Vector3f h1 = payload.texture->getColor(u1, v); | |
Vector3f h2 = payload.texture->getColor(u, v1); | |
float dU = kh * kn * (h1.x() - h.x()); | |
float dV = kh * kn * (h2.x() - h.x()); | |
Vector3f ln = Vector3f(-dU, -dV, 1); | |
normal = (TBN * ln).normalized(); | |
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::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(); | |
float y = normal.y(); | |
float z = normal.z(); | |
Vector3f t = Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z)); | |
Vector3f b = normal.cross(t); | |
Matrix3f TBN; | |
TBN << t, b, normal; | |
float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f); | |
float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f); | |
float u1 = std::clamp(u + 1 / width, 0.0f, 1.0f); | |
float v1 = std::clamp(v + 1 / height, 0.0f, 1.0f); | |
Vector3f h = payload.texture->getColor(u, v); | |
Vector3f h1 = payload.texture->getColor(u1, v); | |
Vector3f h2 = payload.texture->getColor(u, v1); | |
float dU = kh * kn * (h1.x() - h.x()); | |
float dV = kh * kn * (h2.x() - h.x()); | |
Vector3f ln = Vector3f(-dU, -dV, 1); | |
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. | |
Vector3f light_dir = (light.position - point).normalized(); | |
// 半兰伯特 | |
Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5); | |
// Blinn-Phong | |
Vector3f view_dir = (eye_pos - point).normalized(); | |
Vector3f halfDir = (light_dir + view_dir).normalized(); | |
Vector3f specular = ks * pow(normal.dot(halfDir), p); | |
// 环境光 | |
//cwiseProduct 逐元素相乘 | |
Vector3f ambient = ka.cwiseProduct(amb_light_intensity); | |
result_color += diffuse + specular + ambient; | |
} | |
return result_color * 255.f; | |
} |
# Assignment4
贝赛尔曲线
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) | |
{ | |
// TODO: Implement de Casteljau's algorithm | |
int num = control_points.size() - 1; | |
if (num <= 0) | |
{ | |
return control_points[0]; | |
} | |
std::vector<cv::Point2f> temp_points(num); | |
for (size_t i = 0; i < num; i++) | |
{ | |
temp_points[i] = (1 - t) * control_points[i] + t * control_points[i + 1]; | |
} | |
return recursive_bezier(temp_points, 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 (float t = 0.0; t <= 1.0; t += 0.0001) | |
{ | |
cv::Point2f line_points = recursive_bezier(control_points, t); | |
window.at<cv::Vec3b>(line_points.y, line_points.x)[2] = 255; | |
} | |
} |
# Assignment5
光线追踪
1、修改 Render() in Renderer.cpp ,这里你需要为每个像素生成一条对应的光线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中
void Renderer::Render(const Scene& scene) | |
{ | |
std::vector<Vector3f> framebuffer(scene.width * scene.height); | |
float scale = std::tan(deg2rad(scene.fov * 0.5f)); | |
float imageAspectRatio = scene.width / (float)scene.height; | |
// Use this variable as the eye position to start your rays. | |
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 | |
float x; | |
float y; | |
// 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* | |
// 像素坐标以屏幕中心为原点,x 轴水平向右,y 轴竖直向上 | |
//x 从 [0, width] 映射到 [-1, 1] | |
x = (2 * (i + 0.5) / (float)scene.width - 1) * imageAspectRatio * scale; | |
//y 从 [0, height] 映射到 [1, -1] | |
y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale; | |
// Don't forget to normalize this direction! | |
Vector3f dir = normalize(Vector3f(x, y, -1)); | |
framebuffer[m++] = castRay(eye_pos, dir, scene, 0); | |
} | |
UpdateProgress(j / (float)scene.height); | |
} | |
// save framebuffer to file | |
FILE* fp = fopen("binary.ppm", "wb"); | |
(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height); | |
for (auto i = 0; i < scene.height * scene.width; ++i) { | |
static unsigned char color[3]; | |
color[0] = (char)(255 * clamp(0, 1, framebuffer[i].x)); | |
color[1] = (char)(255 * clamp(0, 1, framebuffer[i].y)); | |
color[2] = (char)(255 * clamp(0, 1, framebuffer[i].z)); | |
fwrite(color, 1, 3, fp); | |
} | |
fclose(fp); | |
} |
2、修改 rayTriangleIntersect() in Triangle.hpp , v0, v1, v2 是三角形的三个顶点, orig 是光线的起点, dir 是光线单位化的方向向量。 tnear, u, v 是你需要使用我们课上推导的 Moller-Trumbore 算法来更新的参数
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 S0 = orig - v0; | |
Vector3f S1 = crossProduct(dir, E2); | |
Vector3f S2 = crossProduct(S0, E1); | |
float k = 1.0f / dotProduct(S1, E1); | |
float t = k * dotProduct(S2, E2); | |
float b1 = k * dotProduct(S1, S0); | |
float b2 = k * dotProduct(S2, dir); | |
tnear = t; | |
u = b1; | |
v = b2; | |
bool res = t > 0 && b1 > 0 && b2 > 0 && (1 - b1 - b2) > 0; | |
return res; | |
} |
# Assignment6
BVH 加速结构
1、 Render() in Renderer.cpp 将你的光线生成过程粘贴到此处,并且按照新框架更新相应调用的格式
void Renderer::Render(const Scene& scene) | |
{ | |
std::vector<Vector3f> framebuffer(scene.width * scene.height); | |
float scale = tan(deg2rad(scene.fov * 0.5)); | |
float imageAspectRatio = scene.width / (float)scene.height; | |
Vector3f eye_pos(-1, 5, 10); | |
int m = 0; | |
for (uint32_t j = 0; j < scene.height; ++j) | |
{ | |
for (uint32_t 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; | |
// Don't forget to normalize this direction! | |
Vector3f dir = normalize(Vector3f(x, y, -1)); | |
framebuffer[m++] = scene.castRay(Ray(eye_pos, dir), 0); | |
} | |
UpdateProgress(j / (float)scene.height); | |
} | |
UpdateProgress(1.f); | |
// save framebuffer to file | |
FILE* fp = fopen("binary.ppm", "wb"); | |
(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height); | |
for (auto i = 0; i < scene.height * scene.width; ++i) { | |
static unsigned char color[3]; | |
color[0] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].x)); | |
color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y)); | |
color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z)); | |
fwrite(color, 1, 3, fp); | |
} | |
fclose(fp); | |
} |
2、 Triangle::getIntersection() in Triangle.hpp 将你的光线 - 三角形相交函数粘贴到此处,并且按照新框架更新相应相交信息的格式。
bool rayTriangleIntersect( | |
const Vector3f& v0, | |
const Vector3f& v1, | |
const Vector3f& v2, | |
const Vector3f& orig, | |
const Vector3f& dir, | |
float& tnear, | |
float& u, | |
float& v) | |
{ | |
const float EPSILON = 1e-8f; | |
Vector3f edge1 = v1 - v0; | |
Vector3f edge2 = v2 - v0; | |
Vector3f pvec = crossProduct(dir, edge2); | |
float det = dotProduct(edge1, pvec); | |
if (fabs(det) < EPSILON) | |
return false; | |
float invDet = 1.0f / det; | |
Vector3f tvec = orig - v0; | |
u = dotProduct(tvec, pvec) * invDet; | |
if (u < 0.0f || u > 1.0f) | |
return false; | |
Vector3f qvec = crossProduct(tvec, edge1); | |
v = dotProduct(dir, qvec) * invDet; | |
if (v < 0.0f || u + v > 1.0f) | |
return false; | |
tnear = dotProduct(edge2, qvec) * invDet; | |
if (tnear < EPSILON) | |
return false; | |
return true; | |
} | |
inline Intersection Triangle::getIntersection(Ray ray) | |
{ | |
Intersection inter; | |
if (dotProduct(ray.direction, normal) > 0) | |
return inter; | |
double u, v, t_tmp = 0; | |
Vector3f pvec = crossProduct(ray.direction, e2); | |
double det = dotProduct(e1, pvec); | |
if (fabs(det) < EPSILON) | |
return inter; | |
double det_inv = 1. / det; | |
Vector3f tvec = ray.origin - v0; | |
u = dotProduct(tvec, pvec) * det_inv; | |
if (u < 0 || u > 1) | |
return inter; | |
Vector3f qvec = crossProduct(tvec, e1); | |
v = dotProduct(ray.direction, qvec) * det_inv; | |
if (v < 0 || u + v > 1) | |
return inter; | |
t_tmp = dotProduct(e2, qvec) * det_inv; | |
// TODO find ray triangle intersection | |
float t; | |
float b1; | |
float b2; | |
if (rayTriangleIntersect(v0, v1, v2, ray.origin, ray.direction, t, b1, b2)) | |
{ | |
inter.happened = true; | |
inter.coords = (1 - b1 - b2) * v0 + b1 * v1 + b2 * v2; | |
inter.normal = normal; | |
inter.distance = t; | |
inter.obj = this; | |
inter.m = m; | |
} | |
return inter; | |
} |
3、 IntersectP() in the Bounds3.hpp 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,你需要按照课程介绍的算法实现求交过程
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 | |
// 时间 = 位移 / 速度 | |
float tMin_x = (pMin.x - ray.origin.x) * invDir.x; | |
float tMax_x = (pMax.x - ray.origin.x) * invDir.x; | |
float tMin_y = (pMin.y - ray.origin.y) * invDir.y; | |
float tMax_y = (pMax.y - ray.origin.y) * invDir.y; | |
float tMin_z = (pMin.z - ray.origin.z) * invDir.z; | |
float tMax_z = (pMax.z - ray.origin.z) * invDir.z; | |
if (!dirIsNeg[0]) std::swap(tMin_x, tMax_x); | |
if (!dirIsNeg[1]) std::swap(tMin_y, tMax_y); | |
if (!dirIsNeg[2]) std::swap(tMin_z, tMax_z); | |
float tEnter = std::max(tMin_x, std::max(tMin_y, tMin_z)); | |
float tExit = std::min(tMax_x, std::min(tMax_y, tMax_z)); | |
return tEnter <= tExit && tExit >= 0; | |
} |
4、 getIntersection() in BVH.cpp 建立 BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调用你实现的 Bounds3::IntersectP
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const | |
{ | |
// TODO Traverse the BVH to find intersection | |
if (node == nullptr) | |
{ | |
return Intersection(); | |
} | |
if (node->bounds.IntersectP(ray, ray.direction_inv, ray.dirIsNeg)) | |
{ | |
if (node->left == nullptr && node->right == nullptr) | |
{ | |
return node->object->getIntersection(ray); | |
} | |
Intersection isect1 = getIntersection(node->left, ray); | |
Intersection isect2 = getIntersection(node->right, ray); | |
if (isect1.happened && isect2.happened) | |
{ | |
return isect1.distance < isect2.distance ? isect1 : isect2; | |
} | |
else if (isect1.happened) | |
{ | |
return isect1; | |
} | |
else | |
{ | |
return isect2; | |
} | |
} | |
else | |
{ | |
return Intersection(); | |
} | |
} |
# Assignment7
路径追踪
1、你需要从上一次编程练习中直接拷贝以下函数到对应位置: Triangle::getIntersection() in Triangle.hpp 、 IntersectP() in the Bounds3.hpp 、 getIntersection() in BVH.cpp
2、在本次实验中,你只需要修改这一个函数 castRay() in Scene.cpp ,在其中实现 Path Tracing 算法
Vector3f Scene::castRay(const Ray& ray, int depth) const | |
{ | |
Intersection inter = intersect(ray); | |
if (!inter.happened) | |
{ | |
return Vector3f(0.0f, 0.0f, 0.0f); | |
} | |
// 如果光线直接打在光源上(且是直接从相机射出的光线) | |
if (inter.m->hasEmission()) | |
{ | |
// 间接光照打到光源的情况已在 L_dir 中采样计算过,所以深度大于 0 时不重复计算 | |
// 如果有 BSDF 等材质,这样写可能会漏掉一些路径。例如 相机 -> 镜子 -> 光源 | |
return depth == 0 ? inter.m->getEmission() : Vector3f(0.0f, 0.0f, 0.0f); | |
} | |
Vector3f L_dir(0.0f, 0.0f, 0.0f); | |
Vector3f L_indir(0.0f, 0.0f, 0.0f); | |
// 直射光采样 (L_dir) | |
Intersection light_pos; | |
float pdf_light = 0.0f; | |
sampleLight(light_pos, pdf_light); | |
Vector3f p = inter.coords; | |
Vector3f xx = light_pos.coords; | |
Vector3f ws = normalize(xx - p); | |
//Vector3f wo = normalize(-ray.direction); | |
Vector3f wo = normalize(ray.direction); | |
// 加上一个小偏移量避免自交引起的表面噪点 (Shadow acne) | |
Vector3f p_shifted = p + inter.normal * 0.0005f; | |
if (dotProduct(ray.direction, inter.normal) > 0) | |
{ | |
p_shifted = p - inter.normal * 0.0005f; | |
} | |
float dist_to_light = (xx - p).norm(); | |
Intersection test_inter = intersect(Ray(p_shifted, ws)); | |
// 如果交点距离光源足够近,说明没有被其它物体遮挡 | |
if (test_inter.happened && dist_to_light - test_inter.distance < 0.01f) | |
{ | |
Vector3f f_r = inter.m->eval(wo, ws, inter.normal); | |
float cosn = std::max(0.0f, dotProduct(ws, inter.normal)); | |
float cosnn = std::max(0.0f, dotProduct(-ws, light_pos.normal)); | |
L_dir = light_pos.emit * f_r * cosn * cosnn / (dist_to_light * dist_to_light) / pdf_light; | |
} | |
// 间接光追踪 (L_indir) | |
if (get_random_float() <= RussianRoulette) | |
{ | |
Vector3f wi = inter.m->sample(wo, inter.normal).normalized(); | |
Ray indir_ray(p_shifted, wi); | |
Intersection ininter = intersect(indir_ray); | |
// 如果打到了物体,并且打到的不是光源(避免 Double Counting) | |
if (ininter.happened && !ininter.m->hasEmission()) | |
{ | |
float pdf_indir = inter.m->pdf(wo, wi, inter.normal); | |
// 避免极端除 0 导致的亮点 (Fireflies) | |
if (pdf_indir > 0.0001f) | |
{ | |
Vector3f f_r_indir = inter.m->eval(wo, wi, inter.normal); | |
float cos_indir = std::max(0.0f, dotProduct(wi, inter.normal)); | |
// 递归调用 castRay 来得到弹射后路径对该点的辐射度 | |
Vector3f q_radiance = castRay(indir_ray, depth + 1); | |
L_indir = q_radiance * f_r_indir * cos_indir / pdf_indir / RussianRoulette; | |
} | |
} | |
} | |
return L_dir + L_indir; | |
} |
# Assignment8
质点弹簧系统
# 安装 freetype,配置 CMake
如果已有 freetype,就可以跳过这一步
如果没有 vcpkg,需要先克隆
git clone https://github.com/microsoft/vcpkg |
在 vcpkg 目录中打开 cmd 执行以下命令
vcpkg install freetype:x64-windows | |
vcpkg integrate install |
打开 Project -> CMake Settings,编辑 CMakePresets.json,在其中加入 CMAKE_TOOLCHAIN_FILE 项
{ | |
"version": 3, | |
"configurePresets": [ | |
{ | |
"name": "x64-debug", | |
"generator": "Ninja", | |
"binaryDir": "${sourceDir}/build", | |
"cacheVariables": { | |
"CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" | |
} | |
} | |
] | |
} |
# 实现
1、在 rope.cpp 中,实现 Rope 类的构造函数
Rope::Rope(Vector2D start, Vector2D end, int num_nodes, float node_mass, float k, vector<int> pinned_nodes) | |
{ | |
// TODO (Part 1): Create a rope starting at `start`, ending at `end`, and containing `num_nodes` nodes. | |
//Comment-in this part when you implement the constructor | |
for (int i = 0; i < num_nodes; i++) | |
{ | |
double x = start.x + (end.x - start.x) * (double)i / (double)(num_nodes - 1); | |
double y = start.y + (end.y - start.y) * (double)i / (double)(num_nodes - 1); | |
Vector2D position = Vector2D(x, y); | |
masses.push_back(new Mass(position, node_mass, false)); | |
} | |
for (int i = 0; i < num_nodes - 1; i++) | |
{ | |
springs.push_back(new Spring(masses[i], masses[i + 1], k)); | |
} | |
for (auto& i : pinned_nodes) | |
{ | |
masses[i]->pinned = true; | |
} | |
} |
2、在 Rope::simulateEuler() 中,首先实现胡克定律。遍历所有的弹簧,对弹簧两端的质点施加正确的弹簧力。对每个质点,累加所有的弹簧力。
void Rope::simulateEuler(float delta_t, Vector2D gravity) | |
{ | |
for (auto &s : springs) | |
{ | |
//TODO (Part 2): Use Hooke's law to calculate the force on a node | |
Vector2D dir = s->m2->position - s->m1->position; | |
Vector2D f = s->k * dir / dir.norm() * (dir.norm() - s->rest_length); | |
s->m1->forces = s->m1->forces + f; | |
s->m2->forces = s->m2->forces - f; | |
} | |
for (auto &m : masses) | |
{ | |
if (!m->pinned) | |
{ | |
// TODO (Part 2): Add the force due to gravity, then compute the new velocity and position | |
Vector2D a = m->forces / m->mass + gravity; | |
m->velocity += a * delta_t; | |
m->position += m->velocity * delta_t; | |
// TODO (Part 2): Add global damping | |
m->velocity += -0.0005 * m->velocity; | |
} | |
// Reset all forces on each mass | |
m->forces = Vector2D(0, 0); | |
} | |
} |
3、在 Rope::simulateVerlet() 中,实现显示 Verlet 方法积分的胡克定律
void Rope::simulateVerlet(float delta_t, Vector2D gravity) | |
{ | |
for (auto &s : springs) | |
{ | |
// TODO (Part 3): Simulate one timestep of the rope using explicit Verlet (solving constraints) | |
Vector2D dir = s->m2->position - s->m1->position; | |
Vector2D f = s->k * dir / dir.norm() * (dir.norm() - s->rest_length); | |
s->m1->forces = s->m1->forces + f; | |
s->m2->forces = s->m2->forces - f; | |
} | |
for (auto &m : masses) | |
{ | |
if (!m->pinned) | |
{ | |
Vector2D temp_position = m->position; | |
// TODO (Part 3.1): Set the new position of the rope mass | |
// TODO (Part 4): Add global Verlet damping | |
Vector2D a = m->forces / m->mass + gravity; | |
m->position = temp_position + (1 - 0.0005) * (temp_position - m->last_position) + a * delta_t * delta_t; | |
m->last_position = temp_position; | |
} | |
// Reset all forces on each mass | |
m->forces = Vector2D(0, 0); | |
} | |
} |