games101-作业3
由于此次试验需要加载模型,涉及到本地环节,如果是windows系统,需要对main函数中的路径稍作改变:
这么写需要:
#include "windows.h"
该段代码:
#include "windows.h"
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 = "\\..\\..\\test\\models\\spot\\";
char pathBuf[MAX_PATH];
char* p;
if (GetModuleFileNameA(NULL, pathBuf, MAX_PATH))
{
p = strrchr(pathBuf, '\\');
if (p)
{
*p = '\0';
std::string exe_path = pathBuf;
obj_path = exe_path + obj_path;
}
}
bool loadout = Loader.LoadFile("D:\\opencode\\games101\\test\\test\\models\\spot\\spot_triangulated_good.obj");
同时在windows下计算很卡,可能软件模拟计算量很大的原因;
使用c++17及以上版本,
\spot_triangulated_good.obj 中的描述了顶点信息
输入到三角形列表中:
但是 在使用的时候:
fragment_shader 即下面的着色模型:中去如何消费这个图片
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = normal_fragment_shader; 用于设置着色模型
着色模型
1.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;
}
这个函数并不是光照模型,只是根据不同法线返回不同颜色值。
第一行的代码,首先取出当前待着色像素点的法向量的X,Y,Z坐标并归一化,故此时X,Y,Z都在[-1,1]之间,加上(1.0f, 1.0f, 1.0f)后,变为[0,2],再除以2,即得[0,1],再分别乘以255即可得到各个颜色值了。
效果:
2.phong模型
Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
// 泛光、漫反射、高光系数
Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
// 灯光位置和强度
auto l1 = light{
{20, 20, 20}, {500, 500, 500}};
auto l2 = light{
{-20, 20, 0}, {500, 500, 500}};
std::vector<light> lights = {l1, l2};// 光照
Eigen::Vector3f amb_light_intensity{10, 10, 10};// 环境光强度
Eigen::Vector3f eye_pos{0, 0, 10};// 相机位置
float p = 150;
// ping point的信息
Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;// view space
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = {0, 0, 0};// 光照结果
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
// 遍历每一束光
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(), v = (eye_pos - point).normalized();// 光照方向和观察方向
Eigen::Vector3f h = (l + v).normalized();// 半程向量
Eigen::Vector3f I = light.intensity;// 光强
float r2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(I / r2) * std::max(0.0f, normal.dot(l));//cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array
Eigen::Vector3f Ls = ks.cwiseProduct(I / r2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += La + Ld + Ls;
}
//Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
//result_color += La;
return result_color * 255.f;
}
直接根据公式写就行,这里我认为环境光应该放在循环外,不过这样渲出来的结果相比说说明偏暗,但后面的displacement又要放在外面否则偏亮。
phong模型渲染结果:
3.texture模型
Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
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);
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};
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(), v = (eye_pos - point).normalized();// 光照方向和观察方向
Eigen::Vector3f h = (l + v).normalized();// 半程向量
Eigen::Vector3f I = light.intensity;// 光强
float r2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(I / r2) * std::max(0.0f, normal.dot(l));//cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array
Eigen::Vector3f Ls = ks.cwiseProduct(I / r2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += La + Ld + Ls;
}
// Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
// result_color += La;
return result_color * 255.f;
}
Eigen::Vector3f getColor(float u, float v)
{
// 限制(u, v)坐标范围
u = std::fmin(1, std::fmax(u, 0));
v = std::fmin(1, std::fmax(v, 0));
auto u_img = u * (width-1);
auto v_img = (1 - v) * (height-1);
auto color = image_data.at<cv::Vec3b>(v_img, u_img);// 四舍五入
return Eigen::Vector3f(color[0], color[1], color[2]);
}
更换一个材质:
4 displacement_fragment_shader
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 x = normal.x();
float y = normal.y();
float z = normal.z();
Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
Eigen::Vector3f b = normal.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln{-dU, -dV, 1};
//与凹凸贴图的区别就在于这句话
point += (kn * normal * payload.texture->getColor(u , v).norm());
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
Eigen::Vector3f l = (light.position - point).normalized(); // 光
Eigen::Vector3f v = (eye_pos - point).normalized(); // 眼
Eigen::Vector3f h = (l + v).normalized(); // 半程向量
double r_2 = (light.position - point).dot(light.position - point);
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l)); //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
result_color += (Ld + Ls);
}
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += La;
return result_color * 255.f;
}
draw函数(顶点、三角形处理阶段)
void rst::rasterizer::draw(std::vector<Triangle*>& TriangleList) {
float f1 = (50 - 0.1) / 2.0;// zfar和znear距离的一半
float f2 = (50 + 0.1) / 2.0;// zfar和znear的中心z坐标
Eigen::Matrix4f mvp = projection * view * model;// 计算MVP变换矩阵
// 遍历每个小三角形
for (const auto& t : TriangleList)
{
Triangle newtri = *t;
// 计算viewspace_pos,其中viewspace_pos的坐标是经过MV变换,没有经过P投影变换
// 所以默认在相机坐标系而不是世界坐标系
// 记录三角形顶点MV变换后坐标
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;
// 存入viewspace_pos
std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
return v.template head<3>();
});
// 得到经过mvp后的坐标
Eigen::Vector4f v[] = {
mvp * t->v[0],
mvp * t->v[1],
mvp * t->v[2]
};
// 换算齐次坐标
for (auto& vec : v) {
vec.x() /= vec.w();
vec.y() /= vec.w();
vec.z() /= vec.w();
}
// 计算在MV转换后各顶点的法向量
// 利用原来点法向量推出MV变换后法向量
// 因为光线作用是在view_space下进行的
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)
};
// 视口变换 得到顶点在屏幕上的坐标 即screen space
for (auto& vert : v)
{
vert.x() = 0.5 * width * (vert.x() + 1.0);
vert.y() = 0.5 * height * (vert.y() + 1.0);
// 为了Zbuffer保留Z值
// (透视)投影变换最后一步是从正交投影变换到正则立方体
// 而这一步就是把正则立方体的z值还原到正交投影时的z值,即原始z值
vert.z() = vert.z() * f1 + f2;
}
// 记录经过MVP视口变换后的顶点坐标
// 完成顶点变换,变换到屏幕空间
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>());
}
// 设置颜色
newtri.setColor(0, 148, 121.0, 92.0);
newtri.setColor(1, 148, 121.0, 92.0);
newtri.setColor(2, 148, 121.0, 92.0);
// 对这个小三角形进行光栅化
// 传入viewspace_pos的坐标,光线的作用是在viewspace下的
rasterize_triangle(newtri, viewspace_pos);
}
}
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4(); //v[0],v[1],v[2]分别为三角形的三个顶点,是四维向量
//比较三个顶点的横纵坐标,确定包围盒的边界并取整
double min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
double max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
double min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
double max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
min_x = static_cast<int>(std::floor(min_x));
min_y = static_cast<int>(std::floor(min_y));
max_x = static_cast<int>(std::ceil(max_x));
max_y = static_cast<int>(std::ceil(max_y));
//此处实现的是MSAA
std::vector<Eigen::Vector2f> pos
{ //对一个像素分割四份 当然你还可以分成4x4 8x8等等甚至你还可以为了某种特殊情况设计成不规则的图形来分割单元
{0.25,0.25}, //左下
{0.75,0.25}, //右下
{0.25,0.75}, //左上
{0.75,0.75} //右上
};
for (int i = min_x; i <= max_x; ++i)
{
for (int j = min_y; j <= max_y; ++j)
{
int count = 0; //开始遍历四个小格子,获得平均值
for (int MSAA_4 = 0; MSAA_4 < 4; ++MSAA_4)
{
if (insideTriangle(static_cast<float>(i+pos[MSAA_4][0]), static_cast<float>(j+pos[MSAA_4][1]),t.v))
++count;
}
if(count) //至少有一个小格子在三角形内
{
//此处是框架中代码,获得z,见原程序注释:
// * v[i].w() is the vertex view space depth value z.
// * Z is interpolated view space depth for the current pixel
// * zp is depth between zNear and zFar, used for z-buffer
auto[alpha, beta, gamma] = computeBarycentric2D(i + 0.5, j + 0.5, t.v);
float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
zp *= Z;
//end
if (depth_buf[get_index(i, j)] > zp)
{
depth_buf[get_index(i, j)] = zp;//更新深度
//这里注意,虽然说明上说"反转了z,保证都是正数,并且越大表示离视点越远",
//但经过我的查看,实际上并没有反转,因此还是按照-z近大远小来做,当然也可以在上面补一个负号不过没必要
//利用重心坐标插值各种值
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).normalized();
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);
//shadingcoords是由view_pos插值得到,也就是物体表面的点在相机坐标系的位置。他们会在shader中被用到,来计算光照等信息。
//此处是框架中代码,获得z,见原程序注释:
fragment_shader_payload payload(interpolated_color, interpolated_normal, interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
//end
// 设置颜色
set_pixel(Eigen::Vector2i(i, j), pixel_color * (count / 4.0));
}
}
}
}
}
完整代码:vs2022