作业要求:

作业效果:

我们需要做的:

关键词:法向量;插值计算;光照模型;凹凸贴图;位移映射;

1.在rasterizer.cpp中修改:

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
// TODO: From your HW3,
// 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();//把三角形面片的顶点坐标装入容器
int min_x = INT_MAX;
int max_x = INT_MIN;
int min_y = INT_MAX;
int max_y = INT_MIN;
for (auto point : v)
{
if (point[0] < min_x)min_x = point[0];
if (point[0] > max_x)max_x = point[0];
if (point[1] < min_y)min_y = point[1];
if (point[1] > max_y)max_y = point[1];
}
for (int y = min_y; y <= max_y; y++)
{
for (int x = min_x; x <= max_x; x++)
{
if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v))
{
//得到点的重心坐标
auto abg = computeBarycentric2D((float)x + 0.5, (float)y + 0.5, t.v);
float alpha = std::get<0>(abg);
float beta = std::get<1>(abg);
float gamma = std::get<2>(abg);
//z-buff插值
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 (z_interpolated < depth_buf[get_index(x, y)])
{
Eigen::Vector2i p = { (float)x,(float)y };
//颜色插值
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);
//纹理颜色插值
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);
fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
set_pixel(p, pixel_color);
depth_buf[get_index(x, y)] = z_interpolated;//更新z值
} }
}
} }

2.在main.cpp中修改:

分五步:

1.函数 get_projection_matrix() :将你自己之前的实验中实现的投影矩阵

此时运行就是法向量的结果——Normal shader.

igen::Matrix4f get_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_trans; //平移变换矩阵
Eigen::Matrix4f M_persp; //透视变换矩阵
Eigen::Matrix4f M_ortho; //正交变换矩阵
M_persp <<
zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -zFar * zNear,
0, 0, 1, 0; float alpha = 0.5 * eye_fov * MY_PI / 180.0f; //角度制转换
float yTop = -zNear * std::tan(alpha); //
float yBottom = -yTop;
float xRight = yTop * aspect_ratio;
float xLeft = -xRight; M_trans <<
1, 0, 0, -(xLeft + xRight) / 2,
0, 1, 0, -(yTop + yBottom) / 2,
0, 0, 1, -(zNear + zFar) / 2,
0, 0, 0, 1;
M_ortho <<
2 / (xRight - xLeft), 0, 0, 0,
0, 2 / (yTop - yBottom), 0, 0,
0, 0, 2 / (zNear - zFar), 0,
0, 0, 0, 1; M_ortho = M_ortho * M_trans;
projection = M_ortho * M_persp * projection; //矩阵乘法是从右到左
return projection;
}
//运行normal_fragment_shader
r.set_texture(Texture(Utils::PathFromAsset("model/spot/hmap.jpg")));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = normal_fragment_shader;

2.函数phong_fragment_shader(): 实现Blinn-Phong模型计算Fragment Color.

igen::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. 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);
} return result_color * 255.f;
}
//运行phong_fragment_shader
r.set_texture(Texture(Utils::PathFromAsset("model/spot/hmap.jpg")));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = phong_fragment_shader;

3.函数texture_fragment_shader() :在实现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
//获取当前片段纹理坐标处的纹理值
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);
//漫反射系数直接使用纹理颜色(归一化到[0,1]范围)
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}; //Blinn-Phong
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. 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);
}
//将颜色从[0,1]范围转回[0,255]范围
return result_color * 255.f;
}
 //运行texture_fragment_shader时注意要改纹理文件
//纹理文件hmap.jpg改为spot_texture.png
r.set_texture(Texture(Utils::PathFromAsset("model/spot/spot_texture.png")));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = texture_fragment_shader;

4.函数bump_fragment_shader() :在实现Blinn-Phong模型的基础上,实现凹凸贴图bump fragment shader.

核心原理:法线扰动不改变模型的实际几何形状,而是通过修改表面法线方向来模拟光照变化。当光线照射到这些 “虚拟” 的凹凸表面时,会产生明暗变化,从而欺骗眼睛感知到表面细节。

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; //扰动强度参数
//kh:控制高度变化的强度
//kn:控制法线扰动的强度
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 x = normal.x();
auto y = normal.y();
auto z = normal.z();
//计算切线向量 t
Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
// 计算副切线向量 b(法线与切线的叉积)
Eigen::Vector3f b = normal.cross(t);
//TBN矩阵: 将纹理坐标对应到模型空间中
Eigen::Matrix3f TBN;
TBN <<
t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.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()); // 纹理空间中的扰动法线(Z分量为1,表示向上)
Eigen::Vector3f ln{ -dU,-dV,1.0f };
// 将扰动法线从纹理空间转换到世界空间
normal = TBN * ln;
Eigen::Vector3f result_color = normal.normalized();//归一化 return result_color * 255.f;
}
//运行bump_fragment_shader
//纹理文件记得改回hmap.jpg
r.set_texture(Texture(Utils::PathFromAsset("model/spot/hmap.jpg")));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = bump_fragment_shader;

5.函数displacement_fragment_shader():在实现Blinn-Phong模型的基础上,实现位移映射Displacement fragment shader.

位移映射:与法线扰动不同,位移映射不仅改变表面法线,还实际移动顶点位置,从而创建更真实的凹凸效果,尤其是在边缘和轮廓处。

核心原理:位移映射通过高度图(Height Map)修改顶点位置,使平坦的表面在渲染时看起来像有真实的几何起伏。这需要在着色阶段动态调整顶点位置,并相应地更新法线方向。

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) // 计算切线向量 t(沿纹理U方向)
auto x = normal.x();
auto y = normal.y();
auto z = normal.z();
Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
// 计算副切线向量 b(法线与切线的叉积,沿纹理V方向)
Eigen::Vector3f b = normal.cross(t);
// 构建TBN矩阵(将局部坐标转换到世界坐标)
Eigen::Matrix3f TBN;
TBN <<
t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.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 * normal * payload.texture->getColor(u, v).norm());
// 将扰动法线从纹理空间转换到世界空间
normal = TBN * ln;
normal.normalized(); //Phong 光照模型
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. 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); } return result_color * 255.f;
}
//运行displacement_fragment_shader
r.set_texture(Texture(Utils::PathFromAsset("model/spot/hmap.jpg")));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = displacement_fragment_shader;


[提高项 3分] :尝试更多模型: 找到其他可用的.obj 文件,提交渲染结果并把模型保存在 /models 目录下。这些模型也应该包含 Vertex Normal 信息。

[提高项 5分]:双线性纹理插值: 使用双线性插值进行纹理采样, 在 Texture类中实现一个新方法 Vector3f getColorBilinear(float u, float v) 并通过 fragment shader 调用它。为了使双线性插值的效果更加明显,你应该考虑选择更小的纹理图。请同时提交纹理插值与双线性纹理插值的结果,并进行比较。

GAMES101作业3的更多相关文章

  1. GAMES101作业2

    作业任务: 填写并调用函数 rasterize_triangle(const Triangle& t). 即实现光栅化 该函数的内部工作流程如下: 创建三角形的 2 维 bounding bo ...

  2. 【UE4】GAMES101 图形学作业2:光栅化和深度缓存

    总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...

  3. GAMES101课程 作业6 源代码概览

    GAMES101课程 作业6 源代码概览 Written by PiscesAlpaca(双鱼座羊驼) 一.概述 本篇将从main函数为出发点,按照各cpp文件中函数的调用顺序和层级嵌套关系,简单分析 ...

  4. 【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)

    总览 在这部分的课程中,我们将专注于使用光线追踪来渲染图像.在光线追踪中最重要的操作之一就是找到光线与物体的交点.一旦找到光线与物体的交点,就可以执行着色并返回像素颜色. 在这次作业中,我们要实现两个 ...

  5. 【UE4】GAMES101 图形学作业4:贝塞尔曲线

    总览 Bézier 曲线是一种用于计算机图形学的参数曲线. 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更 ...

  6. 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色

    总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...

  7. 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换

    总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...

  8. 【UE4】GAMES101 图形学作业0:矩阵初识

    作业描述 给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出变换后点的坐标(要求用齐次坐标进行计算). UE4 知识点 主要矩阵 FMatrix FBasisVec ...

  9. python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)

    类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...

  10. SQLServer2005创建定时作业任务

    SQLServer定时作业任务:即数据库自动按照定时执行的作业任务,具有周期性不需要人工干预的特点 创建步骤:(使用最高权限的账户登录--sa) 一.启动SQL Server代理(SQL Server ...

随机推荐

  1. linux 日常工作常用软件(持续更新)

    1.开发工具:jetbrain全家桶,先安装jetbrain toolbox,从其中安装,eclipse.dbeaver.sqlliteman.anypoint studio.spring tool ...

  2. 标准javabean

    1.javabean介绍 javabean,名为实体类,封装数据的类 前面我们写的类都是实体类,但我们写的不是标准的实体类 . 2.标准的javabean写法 如图 3.快捷键 一个成员变量就要写两个 ...

  3. 【Python】词频统计

    需求:一篇文章,出现了哪些词?哪些词出现得最多? 英文文本词频统计 英文文本:Hamlet 分析词频 统计英文词频分为两步: 文本去噪及归一化 使用字典表达词频 代码: #CalHamletV1.py ...

  4. 2012R2双网卡路由的设定

    目前T440服务器, os只能起步2012R2.intelWin联盟是实实在在的.在该os上,DB支持SQL2008. 你要安装SQL2005也可以.到网上查找攻略.那也是死去活来.还好.2008兼容 ...

  5. pycharm-pip安装scrapy、pywifi等模块报错解决方法

    之前学Python时,使用pycharm安装一些不常用的模块时,报错,安装不成功.找了很多方法,总算好了,总结一下: 一.大部分安装不成功的原因,都是原因pip安装源地址问题. 1.在项目pip.ex ...

  6. 团队项目:杰杰Bond团队成员介绍

    项目 内容 这个作业属于哪个课程 2025年春季软件工程(罗杰.任健) 这个作业的要求在哪里 [T.1] 团队项目:团队成员介绍 我在这个课程的目标是 学习软件工程相关知识,培养编程和团队协作能力,做 ...

  7. vscode安装离线插件autopep8

    商店 从上面的链接进去,在visual studio code一栏开始搜索,我要的是autopep8,所以搜索得到的是这样的: 点进去后,是这个界面,然后我是离线下载,要的是拓展包,所以是下面操作 下 ...

  8. MySQL的基本语法(增,删,改,查)

    MySQL的基本语法(增,删,改,查) MySQL中的(增)操作 创建数据库 CREATE DATABASE 库名; 例如: CREATE DATABASE db; 创建一个名为db的数据库. 创建列 ...

  9. linux vim增强使用

    目录 删除 编辑 删除 删除当前行 dd 删除当前行后面的所有行 dG 编辑 恢复为未修改前的状态 uu

  10. 【记录】BASE64|解决JS和C++中文传输乱码,内含两种语言的Base64编码解码的代码

    JS 解决方法来源于知乎新码笔记的文章 function b64Encode(str) { return btoa(unescape(encodeURIComponent(str))); } func ...