在做这个作业之前,看到弹幕说这次作业难度相对于前面几次上升很多,真正做了之后确实有那么点感受,但是实际上当厘清渲染管线之后,其实挺简单的,其实我觉得本次作业最难的地方就是看懂框架源码,知道它在干什么,理解核心代码的功能,通过这个过程去掌握渲染管线,明白这个之后,将问题拆解,其实这次作业也就不那么难了==
断断续续搞了一个星期的时间,总算把相关课程和作业3弄完了,感觉收获真的超大,就是这节课作业做完之后,对整个图形的渲染管线有了基本的理解,之前很多懵懂的概念也都清晰了很多很多,
对这节课做个小总结,这节课主题就是着色,也就是如何计算物体的颜色,先是讲了phong光照模型,根据这个模型可以计算出物体顶点反射进入人眼的光(也就是人能看到的颜色),然后讲了3个着色方法(flat和gouraud都是在投影变换前就计算好颜色,而phong是光栅化阶段计算颜色,但顶点的一些属性的插值是要矫正到投影变换之前的),再然后总结了渲染管线,这个渲染管线非常重要,我感觉这个弄明白了,图形学算脚拇指迈入门了。然后就是介绍插值算法,重心坐标,数学真的博大精深。
最后就是介绍纹理映射,其实就是基于phong光照模型,纹理就是光照模型里面的反射系数(作业是选用的漫反射系数),其中如何得到顶点的纹理坐标没有细讲(作业直接提供了,感觉这个技术点挺麻烦的,虽然我不懂==),重点讲了在已知顶点纹理坐标后,如何对整个图形进行着色,分析了两个问题,即图形大纹理小的情况和图形小纹理大的情况,前者直接用双线性插值就能解决(但不可避免的会使图形模糊),后者麻烦很多,像素与像素之间的纹理坐标差距很大,一个像素可能会对应大面积的纹理,因此就要使用mipmap方法(范围查询,想到这个办法的人是真的牛逼,简单高效==),然后由于mipmap是一层一层的,也是离散的,因此再次使用插值使过渡平滑,真的牛逼。之后提了一些其他范围查询的方法,不过没有细讲(各向异性过滤和EWA过滤,分别解决的是区域条形和区域旋转的情况)。再然后就是提了下纹理的其他应用,比如天空盒记录环境光,用纹理模拟凹凸效果(bump方法和diplacment方法,前者是假凹凸,没有改变顶点位置,后者是真凹凸,改变了顶点的位置)。
在做作业前,一定一定要先把框架看懂,要不然真的是无从下手,其实我觉得这次作业最大的收获就是看懂框架。我是先自己琢磨了一两天,然后很关键的几个点去借鉴了大佬的博客,建议不要直接去看,思维会被束缚住,而且他们也有地方是错误的。建议先自己尽量去理解,实在理解不了再去其他人的博客看一些细节的地方。
先直接从main开始看,因为我是自己配的环境,在vs2019上跑的,所以把命令行相关的删掉了,关键的注释我已经写在代码里面了,总的来说,main函数的作用就是导入模型和纹理,设置着色器,然后调用draw函数,得到frambuffer数组,生成图像。
int main(int argc, const char** argv)
{
std::vector<Triangle*> TriangleList;
float angle = 140.0;
bool command_line = false;
std::string filename = "output.png";
objl::Loader Loader;
std::string obj_path = "./models/spot/";
// Load .obj File
//读入三维模型,数据存入TriangleList,路径有点不一样,要留心=
bool loadout = Loader.LoadFile("./models/spot/spot_triangulated_good.obj");
for(auto mesh:Loader.LoadedMeshes)
{
for(int i=0;i<mesh.Vertices.size();i+=3)
{
Triangle* t = new Triangle();
//读入的数据包括:
//一组顶点位置(对应三角形的三个顶点,已经配好了)
//对应顶点的法向量,已经单位化
//对应顶点的纹理映射坐标,已经单位化到[0,1]
for(int j=0;j<3;j++)
{
t->setVertex(j,Vector4f(mesh.Vertices[i+j].Position.X,mesh.Vertices[i+j].Position.Y,mesh.Vertices[i+j].Position.Z,1.0));
t->setNormal(j,Vector3f(mesh.Vertices[i+j].Normal.X,mesh.Vertices[i+j].Normal.Y,mesh.Vertices[i+j].Normal.Z));
t->setTexCoord(j,Vector2f(mesh.Vertices[i+j].TextureCoordinate.X, mesh.Vertices[i+j].TextureCoordinate.Y));
}
TriangleList.push_back(t);
}
}
rst::rasterizer r(700, 700);
//设置纹理
auto texture_path = "spot_texture.png";
r.set_texture(Texture(obj_path + texture_path));
//摄像机位置
Eigen::Vector3f eye_pos = { 0,0,10 };
//设置着色器
//vertex_shader是顶点着色器,这里实际上没用到这个,这次作业的5个着色器都是片元着色器,因为都是插值出了每个像素的法向量(phong着色)
//active_shader是激活的片元着色器
//顶点着色器:对点的顶点进行着色,一般是在透视变换之前,在draw函数中对顶点直接赋予相同的颜色,因此没有用到这个
//片元着色器:对像素进行着色,
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = texture_fragment_shader;
r.set_vertex_shader(vertex_shader);
r.set_fragment_shader(active_shader);
int key = 0;
int frame_count = 0;
while(key != 27)
{
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
r.set_model(get_model_matrix(angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));
//r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
r.draw(TriangleList);
cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
cv::imshow("image", image);
cv::imwrite(filename, image);
key = cv::waitKey(10);
if (key == 'a' )
{
angle -= 0.1;
}
else if (key == 'd')
{
angle += 0.1;
}
}
return 0;
}
然后就是关键的函数draw函数,其实框架的很多关键代码的作用都是我打断点测出来的==,总的来说,这个函数的作用就是计算三角形视口变换后的顶点信息,以及投影变换前的顶点信息(这个后面着色有用,着色要在投影变换前,因为投影变换会影响插值的准确性,要矫正回去),最终将二者作为参数送入rasterize_triangle函数,进行三角形面片的绘制。
void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {
//这里的50和0.1是根据透视投影那边的zfar和znear对应得到的
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)
{
//在该循环内部逐个绘制三角形newtri
Triangle newtri = *t;
//计算得到摄像机坐标系下的点坐标,并存入viewspace_pos
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 division
//注意,这里w没有除掉,因为后面插值需要用到w(但是实际上,toVector4函数把w变成1了,后面也没有真正用到w),作业2除掉了,实际上作业2里面的插值是有点问题的,并不算真正的矫正。
//透视除法是为了真正将点转换到立方体中,只有除了之后点才会是立方体中的点,因为推导投影过程中第四维发生了变化,这里要把它变回去
//然后这里的w,我推导了下,根据投影矩阵,它就是透视变换前,摄像机坐标下的z值,关键在投影矩阵第四行第三列
for (auto& vec : v) {
vec.x()/=vec.w();
vec.y()/=vec.w();
vec.z()/=vec.w();
}
//这里是计算摄像机坐标系下的法向量,法向量并不是直接乘上变换矩阵就行了的
//这里和顶点的运算不一样,多了个逆和转置,这里是用线性代数的知识推导出来的,见https://blog.csdn.net/Q_pril/article/details/123598746
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 transformation
//视图变换,转换到屏幕空间
for (auto & vert : v)
{
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
//就是把z还原成投影变换之前的。
//这里要加负号的,切记切记,要不然小奶牛位置不对,要么改这里,要么改投影矩阵
//这个问题如果作业2解决了(三角形顺序反了),就不用管了
vert.z() = -vert.z() * f1 + f2;
}
//这项前面是MVP+视点变换,现在已经将顶点转到了屏幕空间,接下来就是光栅化,把顶点信息存入newtri中
for (int i = 0; i < 3; ++i)
{
//screen space coordinates
newtri.setVertex(i, v[i]);
}
for (int i = 0; i < 3; ++i)
{
//view space normal
newtri.setNormal(i, n[i].head<3>());
}
//顶点颜色,后面有部分着色器要用到它,比如phong,diplacment。而texture没有用到,因为用的就是纹理的颜色。
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 position
//viewspace_pos是视点坐标,就是投影变换之前的坐标
rasterize_triangle(newtri, viewspace_pos);
}
}
之后就是rasterize_triangle函数了,如果认认真真做了作业2,其实这个函数的作用应该大致知道的。它的功能我就不赘述了,就是绘制三角形,关键点在于里面的插值算法,其实框架给的是有一定误差的,本质上都没有进行透视矫正(https://blog.csdn.net/Mine268/article/details/120273744),参见注释。哦对,还有一个很重要的地方,就是它这里插值之后,是把各个属性送入着色器,使用着色器进行着色的,可以把着色器看作一个函数,各个属性就是参数,其返回结果就是最终要着色的颜色。
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.
//toVector4()函数是框架自定义的。
//它只会把顶点坐标拷过去,w没拷过去,因此w就没有用了==,这就导致后面的插值是并不是真的插值。
//不过我试了一下,改和不改效果看不出来,可能是模型的原因
auto v = t.toVector4();
int min_x, max_x, min_y, max_y;
min_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
max_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));
min_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
max_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));
for (int x = min_x; x <= max_x; x++)
{
for (int y = min_y; y <= max_y; y++)
{
//判断像素中心点是否在连续三角形内,若在三角形内,就尝试对该像素进行着色,若深度测试通过,便着色
if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v))
{
// 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
//对z进行插值,这里的算法是透视矫正,https://blog.csdn.net/Mine268/article/details/120273744
//虽然这里的算法写的是对的,但是实际上,不是真正的透视矫正==
//因为w没拷过去,值为1(参见上面对toVector4函数的分析),它的值并不是摄像机坐标下的z值==
auto [alpha, beta, gamma] = computeBarycentric2D((float)x + 0.5, (float)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;
//深度测试,通过便着色,并同时将深度存入缓存
if (depth_buf[get_index(x, y)] > zp)
{
// TODO: Interpolate the attributes:
// auto interpolated_color
// auto interpolated_normal
// auto interpolated_texcoords
// auto interpolated_shadingcoords
//这里的插值都是直接在屏幕坐标系插的,按理说是应该返回到摄像机坐标系插的,这是因为投影会影响插值,具体原因可以去上面透视矫正的链接看,还是有点意思的。
//但是这里没有,直接在像素空间插了,应该是会有一定误差的。
Vector3f interpolated_color= interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
Vector3f interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1);
Vector2f interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
Vector3f 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;
//根据main函数中定义的片元着色器进行着色
//实际上,fragment_shader就已经是一个函数,传入参数然后最终返回计算的颜色
auto pixel_color = fragment_shader(payload);
//深度存入缓存
depth_buf[get_index(x, y)] = zp;
Vector2i point = { (float)x,(float)y};
//给像素赋予颜色
set_pixel(point, pixel_color);
}
}
}
}
写在上文rasterize_triangle解析里面了,直接回头看这个就行,关键是要学明白透视校正==老师上课没有讲,其实这个证明也不是特别好证明的,反正我是搞了大半天没搞出来。
直接把作业2的投影矩阵贴进去就行,此时如果要运行得到法向量着色结果,记得把main函数里面的着色器设置为法向量着色的那个,就是normal shader
Eigen::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();
// TODO: Implement this function
// Create the projection matrix for the given parameters.
// Then return it.
Eigen::Matrix4f persp_to_ortho, ortho, ortho_translate, ortho_scale;
float EyeAngelRadian = eye_fov / 180 * MY_PI;
float n, f, l, r, b, t;
n = -zNear;//观察源码,和老师讲的,应该需要往-z方向看
f = -zFar;
t = abs(n) * tan(EyeAngelRadian / 2);
b = -t;
r = aspect_ratio * t;
l = -r;
persp_to_ortho << n, 0, 0, 0,
0, n, 0, 0,
0, 0, n + f, -n * f,
0, 0, 1, 0;
ortho_translate << 1, 0, 0, -(r + l) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(n + f) / 2,
0, 0, 0, 1;
ortho_scale << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (n - f), 0,
0, 0, 0, 1;
ortho = ortho_scale * ortho_translate;
projection = ortho * persp_to_ortho * projection;
return projection;
}
接下来几个任务就很简单了,就是写main函数里面的着色器,我感觉公式搞明白,这部分敲代码还是比较好敲的,关键的地方在于一定要对带入光照模型的那几个向量作归一化,要不然效果是有问题的,我看有些博客是没有做归一化的,这个还是要细心一点。
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};
//计算环境光
Eigen::Vector3f ambient = { 0,0,0 };
ambient = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f diffuse = { 0,0,0 };
Eigen::Vector3f specular = { 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 l = light.position - point;
float r_square = l.norm();
r_square = r_square * r_square;
//向量单位化
l=l.normalized();
//计算漫反射光
diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square))*std::max(0.f, normal.dot(l));
//std::cout << diffuse;
Eigen::Vector3f v = (eye_pos - point).normalized();
Eigen::Vector3f h = (v+l).normalized();
//计算高光
specular= specular+ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)),p);
}
result_color = ambient + diffuse + specular;
return result_color * 255.f;
}
这部分写得比较快,没怎么遇到棘手的问题,感觉渲染部分主要是要搞懂原理,代码认真跟着算法敲下来一般问题不会特别多。
这部分是在phong着色基础上实现的,说白了就是把漫反射系数改成纹理映射的颜色,但是这里有个比较坑的点,卡了我老半天,就是在边界插值出来的纹理坐标可能会超出[0,1],需要在getColor函数中设定边界判定,要不然就会出现越界的bug。参考https://zhuanlan.zhihu.com/p/419872527
Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
//纹理着色器,就是把小奶牛的图片贴上去,核心思路就是改掉漫反射系数kd
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
// TODO: Get the texture value at the texture coordinates of the current fragment
//注意,这段话可能会报错,因为在边界插值出来的纹理坐标可能会超出[0,1],需要在getColor函数中设定边界判定
return_color= payload.texture->getColor(payload.tex_coords[0], payload.tex_coords[1]);
}
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 = payload.color;
Eigen::Vector3f point = payload.view_pos;
//查了下源码,模型载入的法向量就是单位向量
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = { 0, 0, 0 };
//计算环境光
Eigen::Vector3f ambient = { 0,0,0 };
ambient = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f diffuse = { 0,0,0 };
Eigen::Vector3f specular = { 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 l = light.position - point;
float r_square = l.norm();
r_square = r_square * r_square;
//向量单位化,normalized()是不会改变原有大小的。
l = l.normalized();
//计算漫反射光
diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square)) * std::max(0.f, normal.dot(l));
//std::cout << diffuse;
Eigen::Vector3f v = (eye_pos - point).normalized();
Eigen::Vector3f h = (v + l).normalized();
//计算高光
specular = specular + ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)), p);
}
result_color = ambient + diffuse + specular;
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;
float u = payload.tex_coords[0];
float v = payload.tex_coords[1];
float w = payload.texture->width;
float h = payload.texture->height;
// TODO: Implement bump mapping here
auto [x, y, z] = std::tuple{ normal[0],normal[1],normal[2] };
Vector3f t = { 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 dU = kh * kn * (payload.texture->h(u+1.0f/w,v)- payload.texture->h(u,v));
float dV = kh * kn * (payload.texture->h(u, v + 1.0f / h) - payload.texture->h(u, v));
Vector3f ln = {-dU,-dV,1};
normal = (TBN * ln).normalized();
// Let n = normal = (x, y, z)
// Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
// Vector b = n cross product t
// Matrix TBN = [t b n]
// dU = kh * kn * (h(u+1/w,v)-h(u,v))
// dV = kh * kn * (h(u,v+1/h)-h(u,v))
// Vector ln = (-dU, -dV, 1)
// Normal n = normalize(TBN * ln)
Eigen::Vector3f result_color = {0, 0, 0};
result_color = normal;
return result_color * 255.f;
}
和作业四同理,不再赘述,后面用到回来补。
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;
float u = payload.tex_coords[0];
float v = payload.tex_coords[1];
float w = payload.texture->width;
float h = payload.texture->height;
auto [x, y, z] = std::tuple{ normal[0],normal[1],normal[2] };
Vector3f t = { 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 dU = kh * kn * (payload.texture->h(u + 1.0f / w, v) - payload.texture->h(u, v));
float dV = kh * kn * (payload.texture->h(u, v + 1.0f / h) - payload.texture->h(u, v));
Vector3f ln = { -dU,-dV,1 };
point = point + kn * normal * payload.texture->h(u, v);
normal = (TBN * ln).normalized();
// TODO: Implement displacement mapping here
// Let n = normal = (x, y, z)
// Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
// Vector b = n cross product t
// Matrix TBN = [t b n]
// dU = kh * kn * (h(u+1/w,v)-h(u,v))
// dV = kh * kn * (h(u,v+1/h)-h(u,v))
// Vector ln = (-dU, -dV, 1)
// Position p = p + kn * n * h(u,v)
// Normal n = normalize(TBN * ln)
Eigen::Vector3f result_color = {0, 0, 0};
//计算环境光
Eigen::Vector3f ambient = { 0,0,0 };
ambient = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f diffuse = { 0,0,0 };
Eigen::Vector3f specular = { 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*
// components are. Then, accumulate that result on the *result_color* object.
Eigen::Vector3f l = light.position - point;
float r_square = l.norm();
r_square = r_square * r_square;
//向量单位化,normalized()是不会改变原有大小的。
l = l.normalized();
//计算漫反射光
diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square)) * std::max(0.f, normal.dot(l));
//std::cout << diffuse;
Eigen::Vector3f v = (eye_pos - point).normalized();
Eigen::Vector3f h = (v + l).normalized();
//计算高光
specular = specular + ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)), p);
}
result_color = ambient + diffuse + specular;
return result_color * 255.f;
}
最后作业做完真的还是蛮有成就感的,做这种图形学的作业,我发现我还是比较专注的,做那种开发的作业,我真的懒得做。。。可能我不是很适合搞开发吧。我觉得用数学推导出来然后用算法实现的感觉,老爽了==这段时间是少有的比较闲的时间,后面五月份之后可能会忙起来了,希望自己进度抓紧一点,赶快把games101搞搞完,争取图形学能早点入门。
https://blog.csdn.net/Q_pril/article/details/123598746(这篇博客我觉得总结的还是相当不错的,能感受到是下了功夫的)
https://games-cn.org/forums/topic/%e4%bd%9c%e4%b8%9a3%e6%9b%b4%e6%ad%a3%e5%85%ac%e5%91%8a/(作业3更正,做之前一定要看看这个)
https://blog.csdn.net/Mine268/article/details/120273744(透视矫正的推导)
https://blog.csdn.net/n5/article/details/100148540(透视矫正的推导)
https://zhuanlan.zhihu.com/p/419872527(插值出来的纹理坐标越界)