GAMES101:作业3记录

文章目录

  • 总览
  • 使用
  • 框架代码说明
  • 运行与结果
  • 代码实现
    • rasterize_triangle(const Triangle& t)的实现
    • get_projection_matrix()的实现
    • phong_fragment_shader()的实现
    • texture_fragment_shader()的实现
    • bump_fragment_shader()的实现
    • displacement_fragment_shader()的实现
    • 尝试其他的obj模型
    • 双线性插值

总览

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

而在本次实验中,你需要完成的任务是:

  • 修改函数 rasterize_triangle(const Triangle& t) in rasterizer.cpp: 在此处实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值。
  • 修改函数 get_projection_matrix() in main.cpp: 将你自己在之前的实验中实现的投影矩阵填到此处,此时你可以运行 ./Rasterizer output.png normal来观察法向量实现结果。
  • 修改函数 phong_fragment_shader() in main.cpp: 实现 Blinn-Phong 模型计算 Fragment Color.
  • 修改函数 texture_fragment_shader() in main.cpp: 在实现 Blinn-Phong的基础上,将纹理颜色视为公式中的 kd,实现 TextureShading Fragment Shader.
  • 修改函数 bump_fragment_shader() in main.cpp: 在实现 Blinn-Phong 的基础上,仔细阅读该函数中的注释,实现 Bump mapping.
  • 修改函数 displacement_fragment_shader() in main.cpp: 在实现 Bump mapping 的基础上,实现 displacement mapping
    这将会生成命名为 Rasterizer 的可执行文件。使用该可执行文件时,你传入的第二个参数将会是生成的图片文件名,而第三个参数可以是如下内容:

使用

texture: 使用代码中的 texture shader.
使用举例: ./Rasterizer output.png texture
normal: 使用代码中的 normal shader.
使用举例: ./Rasterizer output.png normal
phong: 使用代码中的 blinn-phong shader.
使用举例: ./Rasterizer output.png phong
bump: 使用代码中的 bump shader.
使用举例: ./Rasterizer output.png bump
displacement: 使用代码中的 displacement shader.
使用举例: ./Rasterizer output.png displacement
当你修改代码之后,你需要重新 make 才能看到新的结果。

框架代码说明

相比上次实验,我们对框架进行了如下修改:

  • 我们引入了一个第三方.obj 文件加载库来读取更加复杂的模型文件,这部分库文件在OBJ_Loader.h file. 你无需详细理解它的工作原理,只需知道这个库将会传递给我们一个被命名被 TriangleList 的 Vector,其中每个三角形都有对应的点法向量与纹理坐标。此外,与模型相关的纹理也将被一同加载。
    注意:如果你想尝试加载其他模型,你目前只能手动修改模型路径。
  • 我们引入了一个新的 Texture 类以从图片生成纹理,并且提供了查找纹理颜色的接口:Vector3f getColor(float u, float v)
  • 我们创建了 Shader.hpp 头文件并定义 fragment_shader_payload,其中包括了 Fragment Shader 可能用到的参数。目前 main.cpp 中有三个 Fragment Shader,其中 fragment_shader 是按照法向量上色的样例 Shader,其余两个将由你来实现。
  • 主渲染流水线开始于 rasterizer::draw(std::vector<Triangle> &TriangleList).我们再次进行一系列变换,这些变换一般由 Vertex Shader 完成。在此之后,我们调用函数 rasterize_triangle.
  • rasterize_triangle 函数与你在作业 2 中实现的内容相似。不同之处在于被设定的数值将不再是常数,而是按照 Barycentric Coordinates法向量、颜色、纹理颜色与底纹颜色 (Shading Colors) 进行插值。回忆我们上次为了计算z value 而提供的 [alpha, beta, gamma],这次你将需要将其应用在其他参数的插值上。你需要做的是计算插值后的颜色,并将 Fragment Shader 计算得到的颜色写入 framebuffer,这要求你首先使用插值得到的结果设置 fragment shader payload,并调用 fragment shader 得到计算结果。

运行与结果

在你按照上述说明将上次作业的代码复制到对应位置,并作出相应修改之后(请务必认真阅读说明),你就可以运行默认的 normal shader 并观察到如下结果:

在这里插入图片描述
实现 Blinn-Phong 反射模型之后的结果应该是:

在这里插入图片描述
实现纹理之后的结果应该是:

在这里插入图片描述
实现 Bump Mapping 后,你将看到可视化的凹凸向量:
在这里插入图片描述
实现 Displacement Mapping 后,你将看到如下结果:

在这里插入图片描述


代码实现

我们先看一下rasterizer::draw的实现:

void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {float f1 = (50 - 0.1) / 2.0;float f2 = (50 + 0.1) / 2.0;Eigen::Matrix4f mvp = projection * view * model;for (const auto& t:TriangleList){Triangle newtri = *t;std::array<Eigen::Vector4f, 3> mm {(view * model * t->v[0]),(view * model * t->v[1]),(view * model * t->v[2])};//得到相机下的四维坐标std::array<Eigen::Vector3f, 3> viewspace_pos;std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {return v.template head<3>();});//得到相机下的三维坐标Eigen::Vector4f v[] = {mvp * t->v[0],mvp * t->v[1],mvp * t->v[2]};//计算屏幕坐标//Homogeneous divisionfor (auto& vec : v) {vec.x()/=vec.w();vec.y()/=vec.w();vec.z()/=vec.w();           }//进行了齐次除法,也就是透视除法,视锥体被变换到一个立方体Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();Eigen::Vector4f n[] = {inv_trans * to_vec4(t->normal[0], 0.0f),inv_trans * to_vec4(t->normal[1], 0.0f),inv_trans * to_vec4(t->normal[2], 0.0f)};//计算变换以后得新法线的方向//Viewport transformationfor (auto & vert : v){vert.x() = 0.5*width*(vert.x()+1.0);vert.y() = 0.5*height*(vert.y()+1.0);vert.z() = -vert.z() * f1 + f2;//这里要添加一个负号}for (int i = 0; i < 3; ++i){//screen space coordinatesnewtri.setVertex(i, v[i]);}for (int i = 0; i < 3; ++i){//view space normalnewtri.setNormal(i, n[i].head<3>());}newtri.setColor(0, 148,121.0,92.0);newtri.setColor(1, 148,121.0,92.0);newtri.setColor(2, 148,121.0,92.0);// Also pass view space vertice positionrasterize_triangle(newtri, viewspace_pos);}
}

① 经过MV变换我们得到view space的点,存在mm里,而后传递给viewspace_pos,后面传递给了rasterize_triangle函数作为着色点的位置用于计算光源到着色点的单位向量。
② 经过MVP变换我们得到投影空间的点,这时候已经投影到了平面,不过这时候投影的原点在矩形的中心,保存在了v,然后对这个齐次坐标进行了齐次除法(x,y,z分量除以第四个分量)w坐标记录了原本的z值,但是后面似乎没有用到。
③ MV变换后的点法向量和原来的法向量不同,中间差了一个矩阵: ( M V ) − T (MV)^{-T} (MV)T,这个推导在这篇文章里有写:计算机图形学五:局部光照模型(Blinn-Phong 反射模型)与着色方法(Phong Shading)
④ 为了变换到当前的屏幕的空间(原点在左下角,x轴向右,y轴向上),再次对MVP变换后的点做了一次变换:

for (auto & vert : v)
{vert.x() = 0.5*width*(vert.x()+1.0);vert.y() = 0.5*height*(vert.y()+1.0);vert.z() = -vert.z() * f1 + f2;//这里要添加一个负号
}

下面是从论坛截的一些同学的评论,觉得也挺好。作业3 interpolated_shadingcoords

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:在rasterizer::draw()函数里,使用newtri记录三角形的指针,并重新定义了三角形的属性:①法线:在相机空间viewspace(只进行MV变换)通过setNormal()函数实现;②顶点位置:在screen space(屏幕空间),通过setVertex()函数实现,③viewspace下的三角形顶点的坐标通过参数的形式传递给了rasterize_triangle()函数,用作viewspace下使用重心插值公式渲染像素点的坐标interpolated_shadingcoords(详情看rasterize_triangle()函数)

rasterize_triangle(const Triangle& t)的实现

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//zp计算的像素在zNear和zFar之间的深度,使用在z-buffer算法里// float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());//alpha beta gamma通过插值函数求出// 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;//view_pos: 这里使用的是相机下看到的坐标// 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();// TODO : Find out the bounding box of current triangle.int bounding_box_left_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));int bounding_box_right_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));int bounding_box_bottom_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));int bounding_box_top_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));for (int x = bounding_box_left_x; x <= bounding_box_right_x; x++) {for (int y = bounding_box_bottom_y; y <= bounding_box_top_y; y++) {if (insideTriangle(x + 0.5, y + 0.5, t.v)) {// If so, use the following code to get the interpolated z value.   auto[alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 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; //zp是正确的深度值if (zp <  depth_buf[get_index(x, y)]) {depth_buf[get_index(x, y)] = zp;//auto interpolated_colorauto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1.0);auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1.0);auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1.0);auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1.0);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(Eigen::Vector2i(x, y),  pixel_color);}}}} 
} 

这里使用toVector4函数将t的w值全部变为了1,这里本来需要存储深度值的,但是这几行代码还是把z的值求出来了(范围在0.1到50):

auto[alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 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; //zp是正确的深度值

首先这里使用了computeBarycentric2D进行插值,这里使用了二维点来插值得到了在view平面上的alpha,beta和gamma,这里使用了平面的重心坐标公式,这里直接给出可以和上面的代码比较(具体可以到computeBarycentric2D这个函数下面查看):

在这里插入图片描述

具体可以查看这篇文章:

计算机图形学三(补充):重心坐标(barycentric coordinates)详解及其作用

但是这肯定是有误差的,我们希望的还是在真实的三维空间找到它的深度,这时候就需要用到透视校正插值了,这里给出透视校正插值的公式,具体可以看这篇文章的推导了解透视校正插值:【重心坐标插值、透视矫正插值】原理以及用法见解(GAMES101深度测试部分讨论)

Z t = 1 α Z A + β Z B + γ Z C = 1 Z_t=\frac{1}{\frac{\alpha}{Z_A}+\frac{\beta}{Z_B}+\frac{\gamma}{Z_C}}=1 Zt=ZAα+ZBβ+ZCγ1=1

由于使用的三角形在toVector4函数的作用下三个顶点的深度都变为1,所以这里等价于:

Z t = 1 α + β + γ = 1 Z_t=\frac{1}{\alpha+\beta+\gamma}=1 Zt=α+β+γ1=1

然后计算真实的深度(后面两行的代码)

在这里插入图片描述
z p = ( α z 0 1 + β z 1 1 + γ z 2 1 ) = α z 0 + β z 1 + γ z 2 zp = (\alpha \frac{z_0}{1}+\beta \frac{z_1}{1} + \gamma\frac{z_2}{1})=\alpha z_0 + \beta z_1 + \gamma z_2 zp=(α1z0+β1z1+γ1z2)=αz0+βz1+γz2

依然计算出了0.1到50 的深度(但我觉得这个深度不是真正的深度,真正的深度应该和摄像机的位置有关?不过下面的计算不影响,我们只是用这个“深度”判断是否更靠近相机)。

关于透视校正插值可以看这篇文章:

计算机图形学六:正确使用重心坐标插值(透视矫正插值(Perspective-Correct Interpolation))和图形渲染管线总结

注意这一句:

set_pixel(Eigen::Vector2i(x, y),  pixel_color);

和之前的渲染三角形不一样,参数列表的第一个参数是Eigen::Vector2i类型的,我复制作业2的代码提示数组的维度不一样报错了,原来的是Eigen::Vector3f的类型。

另外和作业2一样,insideTriangle函数的参数列表里的x和y我们还是使用了float类型,因为我们在rasterize_triangle(const Triangle& t)中使用了x+0.5和y+0.5作为这个函数的第一和第二个参数(像素的中心)。

static bool insideTriangle(float x, float y, const Vector4f* _v){// 这里还是一样进行了参数列表的修改Vector3f v[3];for(int i=0;i<3;i++)v[i] = {_v[i].x(),_v[i].y(), 1.0};Vector3f f0,f1,f2;f0 = v[1].cross(v[0]);f1 = v[2].cross(v[1]);f2 = v[0].cross(v[2]);Vector3f p(x,y,1.);if((p.dot(f0)*f0.dot(v[2])>0) && (p.dot(f1)*f1.dot(v[0])>0) && (p.dot(f2)*f2.dot(v[1])>0))return true;return false;
}

get_projection_matrix()的实现

首先在main.cpp插入我们之前写的投影矩阵的代码,直接复制以前的就好:

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{// TODO: Use the same projection matrix from the previous assignmentsEigen::Matrix4f projection = Eigen::Matrix4f::Identity();float t = abs(zNear) * tan(eye_fov * MY_PI / (180 * 2));float r = aspect_ratio * t;float l = -r;float b = -t;float n = -zNear;float f = -zFar;projection(0, 0) = 2 * n / (r - l);projection(0, 2) = (l + r) / (l - r);projection(1, 1) = 2 * n / (t - b);projection(1, 2) = (b + t) / (b - t);projection(2, 2) = (f + n) / (n - f);projection(2, 3) = 2 * f * n / (f - n);projection(3, 2) = 1.0;projection(3, 3) = 0.0;return projection;}

为了不让我们的图形覆盖的顺序颠倒,我们需要修改rasterizer.cpp中的void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList)

找到这一行:

vert.z() = vert.z() * f1 + f2;

右边的vert.z()前面添加一个负号

vert.z() = -vert.z() * f1 + f2;

否则可能会出现和之前一样的牛牛的屁屁对着我们的情况(三角形的覆盖是顺序有问题,这里是左右手系对应的问题,因为我的投影矩阵使用的还是右手系也就是闫老师说的那个矩阵,但是框架代码使用的是左手系,需要颠倒一下)。

我们编译,在命令行输入命令

./Rasterizer output.png normal

可以得到:
在这里插入图片描述
我们看下纹理映射实现的源码:

Eigen::Vector3f normal_fragment_shader(const fragment_shader_payload& payload)
{Eigen::Vector3f return_color = (payload.normal.head<3>().normalized() + Eigen::Vector3f(1.0f, 1.0f, 1.0f)) / 2.f;Eigen::Vector3f result;result << return_color.x() * 255, return_color.y() * 255, return_color.z() * 255;return result;
}

因为发现的方向的三个分量的范围是[-1,1],我们加上1就变为[0,2],再除以2就归一化为[0,1],也就变成了RGB三个颜色分量的范围。

phong_fragment_shader()的实现

使用Phong Shading,详细可以参考计算机图形学五:局部光照模型(Blinn-Phong 反射模型)与着色方法(Phong Shading),公式如下:
在这里插入图片描述

这里根据公式写代码即可:

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* Eigen::Vector3f r = light.position - point;Eigen::Vector3f l = r.normalized();Eigen::Vector3f v = (eye_pos - point).normalized();Eigen::Vector3f h = (l + v).normalized();Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0.0, normal.normalized().dot(l));Eigen::Vector3f specular = ks.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0.0, pow(normal.normalized().dot(h), p));// components are. Then, accumulate that result on the *result_color* object.result_color = result_color + ambient + diffuse + specular;  }return result_color * 255.f;
} 

我们编译,在命令行输入命令

./Rasterizer output.png phong

在这里插入图片描述

texture_fragment_shader()的实现

纹理shader的实现其实就是用u,v去纹理图去找对应的颜色,我们需要知道该点像素的u和v,需要使用重心公式,其实现方式为:

在这里插入图片描述
详细原理可以看:计算机图形学七:纹理映射(Texture Mapping)及Mipmap技术

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 fragmentfloat u = payload.tex_coords[0];float v = payload.tex_coords[1];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.// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* Eigen::Vector3f r = light.position - point;Eigen::Vector3f l = r.normalized();Eigen::Vector3f v = (eye_pos - point).normalized();Eigen::Vector3f h = (l + v).normalized();Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0.0, normal.dot(l));Eigen::Vector3f specular = ks.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0.0, pow(normal.dot(h), p));// components are. Then, accumulate that result on the *result_color* object.result_color = result_color + ambient + diffuse + specular; }return result_color * 255.f;
}

和Phong Shading差不多,只不过这里我们使用的颜色要从纹理获得(实现的原理也很简单,通过u和v进行颜色的映射),而不是想Phong Shading里使用的是模型自己的颜色,关键代码在这里:

if (payload.texture)
{// TODO: Get the texture value at the texture coordinates of the current fragmentfloat u = payload.tex_coords[0];float v = payload.tex_coords[1];return_color = payload.texture->getColor(u, v);
}

这里的tex_coords是通过rasterize_triangle()函数而来(回想我们在这个函数进行了纹理、法线、纹理坐标、颜色的插值得到像素点的最终渲染的属性,最后生成一个payload作为参数给渲染器帮我们着色,tex_coords在这个函数里是interpolated_texcoords,它是通过三角形的三个顶点的纹理坐标得来的,而三角形的三个纹理坐标我们是在main函数通过setTexCoord函数实现的)。

我们可以查看getColor源码的实现,注意这里为了防止数组的越界,我们需要给u和v设定范围限制(对应前面的四句if代码)。

Eigen::Vector3f getColor(float u, float v)
{if(u<0) u=0;if(v<0) v=0;if(u>1) u=1;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]);
}

我们编译,在命令行输入命令

./Rasterizer output.png texture

在这里插入图片描述

bump_fragment_shader()的实现

bump mapping的思想是在像素的表面进行微小的高度变化(使用纹理)计算扰动点的法向量(只在渲染计算的时候做,实际并没有改变像素的高度)

在这里插入图片描述

二维的实现

在这里插入图片描述

这里如图像素对应的切向量的是(1,dp),所以法向量就是(-dp,1).。

三维的实现也是同样的,沿u方向和v方向的的切向量分别是(1,0,dp/du)和(0,1,dp/dv)(u和v是纹理坐标的方向,对应的是x轴和y轴,u和v方向垂直)。所以法向量要和这两个切向量垂直为(-dp/du,-dp/dv,1),读者可以检验一下法向量和这两个切向量的垂直性。
在这里插入图片描述

根据注释写代码:这里使用的TBN矢量中,N是法线方向(normal),代码里取的是 ( x , y , z ) (x,y,z) (x,y,z),B和T分别在切线空间里,注释提示我们T这里取的是 ( x y ( x 2 + z 2 ) , x 2 + z 2 , z y x 2 + z 2 ) (\frac{xy}{\sqrt(x^2+z^2)},\sqrt{x^2+z^2},\frac{zy}{\sqrt{x^2+z^2}}) (( x2+z2)xy,x2+z2 ,x2+z2 zy),尽管这个T的模是1,我们可以很容易地检验这是一个单位向量:

( x y x 2 + z 2 ) 2 + ( x 2 + z 2 ) 2 + ( z y x 2 + z 2 ) 2 = x 2 y 2 x 2 + z 2 + x 2 + z 2 + z 2 y 2 x 2 + z 2 = x 2 y 2 + x 4 + 2 x 2 z 2 + z 4 + z 2 y 2 x 2 + z 2 = x 2 y 2 + x 4 + x 2 z 2 + x 2 z 2 + z 4 + z 2 y 2 x 2 + z 2 = x 2 ( x 2 + y 2 + z 2 ) + z 2 ( x 2 + y 2 + z 2 ) x 2 + z 2 ( x 2 + y 2 + z 2 = 1 ) = x 2 + z 2 x 2 + z 2 = 1 \begin{aligned} &\left(\frac{xy}{\sqrt{x^2+z^2}}\right)^2+\left(\sqrt{x^2+z^2}\right)^2+(\frac{zy}{\sqrt{x^2+z^2}})^2\\ =&\frac{x^2y^2}{x^2+z^2}+x^2+z^2+\frac{z^2y^2}{x^2+z^2}\\ =&\frac{x^2y^2+x^4+2x^2z^2+z^4+z^2y^2}{x^2+z^2}\\ =&\frac{x^2y^2+x^4+x^2z^2+x^2z^2+z^4+z^2y^2}{x^2+z^2}\\ =&\frac{x^2(x^2+y^2+z^2)+z^2(x^2+y^2+z^2)}{x^2+z^2}\quad(x^2+y^2+z^2=1)\\ =&\frac{x^2+z^2}{x^2+z^2}=1\end{aligned} =====(x2+z2 xy)2+(x2+z2 )2+(x2+z2 zy)2x2+z2x2y2+x2+z2+x2+z2z2y2x2+z2x2y2+x4+2x2z2+z4+z2y2x2+z2x2y2+x4+x2z2+x2z2+z4+z2y2x2+z2x2(x2+y2+z2)+z2(x2+y2+z2)(x2+y2+z2=1)x2+z2x2+z2=1

但是这里我认为是有问题的,但是它和N并不垂直。

N ⋅ T = ( x , y , z ) ⋅ ( x y ( x 2 + z 2 ) , x 2 + z 2 , z y x 2 + z 2 ) = 2 x 2 y + 2 z 2 y x 2 + z 2 \begin{aligned}&N\cdot T\\ =&(x,y,z)\cdot(\frac{xy}{\sqrt(x^2+z^2)},\sqrt{x^2+z^2},\frac{zy}{\sqrt{x^2+z^2}})\\ =&\frac{2x^2y+2z^2y}{\sqrt{x^2+z^2}}\end{aligned} ==NT(x,y,z)(( x2+z2)xy,x2+z2 ,x2+z2 zy)x2+z2 2x2y+2z2y

正确的应该是: T = ( − x y ( x 2 + z 2 ) , x 2 + z 2 , − z y x 2 + z 2 ) T=(-\frac{xy}{\sqrt(x^2+z^2)},\sqrt{x^2+z^2},-\frac{zy}{\sqrt{x^2+z^2}}) T=(( x2+z2)xy,x2+z2 ,x2+z2 zy),这个博客画了这个T在三维坐标下的图(TBN三个矢量应该是相互垂直的!),画得还是比较清楚的【图源games101——作业3】:
在这里插入图片描述

而B向量就是T和N的叉乘的结果了

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();Eigen::Vector3f t = {-x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),-z*y/sqrt(x*x+z*z)};Eigen::Vector3f b = 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.0 / w, v).norm() - payload.texture->getColor(u, v).norm());float dV = kh * kn * (payload.texture->getColor(u, v + 1.0 / h).norm() - payload.texture->getColor(u, v). norm());
Eigen::Vector3f ln = {-dU, -dV, 1};
normal = (TBN * ln).normalized();Eigen::Vector3f result_color = {0, 0, 0};result_color = normal;return result_color * 255.f;
}

我们用下面的代码得到全局的TBN矩阵(我们最终的法向量还是要在相机空间里表示的,这是一个全局的变换矩阵):

Eigen::Vector3f t = {-x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),-z*y/sqrt(x*x+z*z)};
Eigen::Vector3f b = 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();

然后我们需要计算再沿纹理图u和v方向的像素的变化量,计算导数用的是差分的方式再乘上一个系数:

float dU = kh * kn * (payload.texture->getColor(u + 1.0 / w, v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1.0 / h).norm() - payload.texture->getColor(u, v). norm());

注释提示这样求扰动后的法向量:

// Vector ln = (-dU, -dV, 1)
// Normal n = normalize(TBN * ln)

是先求扰动后的法向量(但没有归一化)再转换为相机空间的法向量,然后再归一化,而不是像ppt说的先对法向量归一化再转换到相机空间,猜测把归一化放到后面能减少一定的浮点误差,提前归一化要进行开平方根操作会造成变换到相机空间的误差。

Eigen::Vector3f ln = {-dU, -dV, 1};
normal = (TBN * ln).normalized();

我们编译,在命令行输入命令

./Rasterizer output.png bump

在这里插入图片描述

displacement_fragment_shader()的实现

位移贴图displacement mapping除了类似凹凸贴图,改变了法向量,而且实际移动了三维空间中点的位置,进而直接影响 Blinn-Phong 模型中的 l l l v v v

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();Eigen::Vector3f t = {x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z)};Eigen::Vector3f b = 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.0/w, v).norm() - payload.texture->getColor(u, v).norm());float dV = kh * kn * (payload.texture->getColor(u, v + 1.0 / h).norm() - payload.texture->getColor(u, v). norm());Eigen::Vector3f ln = {-dU, -dV, 1};point = point + kn * normal * payload.texture->getColor(u, v).norm();normal = (TBN * ln).normalized();Eigen::Vector3f result_color = {0, 0, 0};for (auto& light : lights){// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* Eigen::Vector3f r = light.position - point;Eigen::Vector3f l = r.normalized();Eigen::Vector3f v = (eye_pos - point).normalized();Eigen::Vector3f h = (l + v).normalized();Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0, normal.dot(l));Eigen::Vector3f specular = ks.cwiseProduct(light.intensity) / (r.dot(r)) * std::max((float)0, pow(normal.dot(h), p));// components are. Then, accumulate that result on the *result_color* object.result_color = result_color + ambient + diffuse + specular; }return result_color * 255.f;
}

和bump mapping最主要的区别在于:

point = point + kn * normal * payload.texture->getColor(u, v).norm();

改变了顶点的坐标(沿着原来的发现的方向),顶点的坐标等于原顶点的坐标加法线向量乘系数kn乘纹理在该位置的颜色的模长,然后也是使用phong shading来渲染

我们编译,输入命令:

./Rasterizer output.png displacement

在这里插入图片描述

尝试其他的obj模型

参考的Games101|作业3 + shading + 双线性插值 + 疑惑

需要安装meshlab,参考ubuntu安装meshlab,全网最简单的方法

记得改路径

从github上下载obj模型,导入meshlab,

记得该eye_pos!!!没有改可能会出现段错误,图形跑到700*700的框框外面去了,找不到对应的颜色。

输出时应该是下面这样(输出为obj)

在这里插入图片描述
然后文件夹下除了obj还有obj.mtl格式的文件,没有的话texture可能出问题(我这里保存的是teapot2.obj和teapot.obj.mtl)

在这里插入图片描述
记得main函数修改路径:obj_path、loadout、texture_path。

normal shader

在这里插入图片描述

phong shader

在这里插入图片描述

bump shader 有点过于粗糙的感觉

在这里插入图片描述
texture shader
在这里插入图片描述

displacement shader
在这里插入图片描述

双线性插值

找个压缩图片的网站,我们把小奶牛的texture压缩为512*512的(原来是1024*1024的)保存下来,在把texture的路径改为这个512*512的。
OKTools-图片压缩

在这里插入图片描述
找到四个顶点,先沿宽度方向插值,再沿高度方向插值。

getColorBilinear

Eigen::Vector3f getColorBilinear(float u, float v){if(u<0) u=0;if(v<0) v=0;if(u>1) u=1;if(v>1) v=1;auto u_img = u * width;auto v_img = (1 - v) * height;float ul = floor(u_img);float uh = ceil(u_img);float vl = floor(v_img);float vh = ceil(v_img);float s = (u_img - ul) / (uh - ul);float t = (v_img - vl) / (vh - vl);// 双线性插值auto color_o = image_data.at<cv::Vec3b>(v_img, u_img);auto color_00 = image_data.at<cv::Vec3b>(vl, ul);//color的序号按照u,v的顺序来,大值对应h,小值对应lauto color_01 = image_data.at<cv::Vec3b>(vh, ul);auto color_10 = image_data.at<cv::Vec3b>(vl, uh);auto color_11 = image_data.at<cv::Vec3b>(vh, uh);auto color_lerp1 = (1 - s) * color_00 + s * color_10;auto color_lerp2 = (1 - s) * color_01 + s * color_11;auto color = (1 - t) * color_lerp1 + t * color_lerp2 ;return Eigen::Vector3f(color[0], color[1], color[2]);}};

记得把texture_fragment_shader获取颜色的方法改为双线性插值的方法getColorBilinear,主要是这一段代码:

if (payload.texture)
{// TODO: Get the texture value at the texture coordinates of the current fragmentfloat u = payload.tex_coords[0];float v = payload.tex_coords[1];// return_color = payload.texture->getColor(u, v);return_color = payload.texture->getColorBilinear(u, v);}

测试:使用双线性插值:

在这里插入图片描述
不使用双线性插值:

在这里插入图片描述
可以看到效果还是很明显的。

另外关于作业3框架的一些问题可以参考这篇知乎的文章,写的很好:《GAMES101》作业框架问题详解,尽管作业里的深度插值用的不是真实的深度,但是我们还是得到了正确的效果(题目也说了,深度处理为了正值,离相机越近深度是越小的,深度相当于做了一个线性的映射到0.1到50,不过深度的大小情况是不变的,该靠近相机的还是靠近相机,所以我们最后还是可以看到这只牛牛)。

其他记录:

+讨论区 › 作业3更正公告

在这里插入图片描述

  • 讨论区 › [Lecture 9]为什么离视角近的像素点,对应的纹理区域越小?

在这里插入图片描述

  • 讨论区 › 关于几个思路问题(学习习惯与学习方法)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • displacement 原理问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/299037.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Https加密超文本传输协议的运用

证书是自定义颁发。私立密钥&#xff08;因此访问某些网站会显示网站不安全&#xff09; 拖入docker-compose docker-harbor 安装包mv docker-com... docker-compose mv docker-compose /usr/local/bin/ chmod 777 /usr/local/bin/docker-compose安装dockertar -xf harbor…

一文搞懂设计模式之建造者模式

大家好&#xff0c;我是晴天&#xff0c;我们又见面了&#xff0c;这周我们继续学习一文搞懂设计模式系列&#xff0c;本周将一起学习建造者模式&#xff08;生成器模式&#xff09; 什么是建造者模式 建造者模式&#xff08;也称为生成器模式&#xff09;是一种创建型设计模式…

【MySQL】:事务(下)

事务 一.MVCC机制(读-写)1.预备知识1.三个记录隐藏字段2.undo日志 2.模拟MVCC3.Read View4.整体流程 二.RC与RR的本质区别1.当前读和快照读在RR级别下的区别1.测试一2.测试二 2.RR 与 RC的本质区别 数据库并发的场景有三种&#xff1a; 读-读 &#xff1a;不存在任何问题&…

【六大排序详解】中篇 :选择排序 与 堆排序

选择排序 与 堆排序 选择排序 选择排序 与 堆排序1 选择排序1.1 选择排序原理1.2 排序步骤1.3 代码实现 2 堆排序2.1 堆排序原理2.1.1 大堆与小堆2.1.2 向上调整算法2.1.3 向下调整算法 2.2 排序步骤2.3 代码实现 3 时间复杂度分析 Thanks♪(&#xff65;ω&#xff65;)&#…

Vue在页面上添加水印

第一步&#xff1a;在自己的项目里创建一个js文件&#xff1b;如图所示我在在watermark文件中创建了一个名为waterMark.js文件。 waterMark.js /** 水印添加方法 */ let setWatermark (str1, str2) > {let id 1.23452384164.123412415if (document.getElementById(id) …

WPF中数据绑定转换器Converter

使用场景&#xff1a;ViewModel中的数据如果跟View中的数据类型不匹配。 下面是以int类型调控是否可见为例子 步骤一&#xff1a;创建转换器类 在xaml中查看Converter的定义可以知道Converter是一个接口类型&#xff0c;因此转换器的类定义需要使用这个接口 internal class Vi…

计算机视觉技术-使用图像增广进行训练

让我们使用图像增广来训练模型。 这里&#xff0c;我们使用CIFAR-10数据集&#xff0c;而不是我们之前使用的Fashion-MNIST数据集。 这是因为Fashion-MNIST数据集中对象的位置和大小已被规范化&#xff0c;而CIFAR-10数据集中对象的颜色和大小差异更明显。 CIFAR-10数据集中的前…

如何使用Docker部署Dashy并无公网ip远程访问管理界面

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起&#xff0c;形成自己的导航…

不同参数规模大语言模型在不同微调方法下所需要的显存总结

原文来自DataLearnerAI官方网站&#xff1a; 不同参数规模大语言模型在不同微调方法下所需要的显存总结 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051703254378255 大模型的微调是当前很多人都在做的事情。微调可以让大语言模型适应特定领域的任…

WordPress主题大前端DUX v8.3源码下载

DUX主题8.3版本更新内容&#xff1a; 新增&#xff1a;Cloudflare Turnstile 免费验证功能 新增&#xff1a;子菜单页面模版&#xff0c;支持多级页面 新增&#xff1a;手机端文章内表格自动出现横向滚动条&#xff0c;可集体或单独设置滚动宽度 新增&#xff1a;标签云页面模版…

springboot 共享自习室座位管理系统 -计算机毕业设计源码55732

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设共享自习室座位管理系…

零基础入门网络安全必看的5本书籍(附PDF)

书中自有黄金屋&#xff0c;书中自有颜如玉。很多人学习一门技术都会看大量的书籍&#xff0c;经常也有朋友询问&#xff1a;零基础刚入门&#xff0c;应该看哪些书&#xff1f;应该怎么学&#xff1f;等等问题。今天就整理了5本零基础入门网络安全必看书籍&#xff0c;希望能帮…