作业来自官网
文章目录
总览
在这次编程任务中,我们会进一步模拟现代图形技术。我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与 Fragment Shader,并且支持了纹理映射。
1 rasterize_triangle(const Triangle& t)
在此处实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值。
解答
大体上和上一个作业差不多,注意插值算法,源代码框架已经给出了interpolate
插值函数,调用即可。
注意
auto[]是C++17的结构体绑定的用法, 简单理解就是结构体的成员变量会按顺序赋给auto中括号里的序列。
可以在vscode的C++扩展里更改配置,让其支持C++17的语法。
//还要带插值
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
//获取t的3个顶点的向量值,存储到数组中。
auto v = t.toVector4();
Vector4f v_[3];
for(int i = 0;i<3;i++){
v_[i]={
v[i].x(),v[i].y(),v[i].z(),v[i].w()};
}
// 先找包围盒
float min_x,max_x,max_y,min_y;
min_x=std::min(std::min(v[0].x(),v[1].x()),v[2].x());
min_y=std::min(std::min(v[0].y(),v[1].y()),v[2].y());
max_x=std::max(std::max(v[0].x(),v[1].x()),v[2].x());
max_y=std::max(std::max(v[0].y(),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.5f,(float)y+0.5f,v_)){
//获得重心坐标的参数
auto[alpha, beta, gamma] = computeBarycentric2D((float)x+0.5f,(float)y+0.5f, t.v);
//获得zp(此部分在原代码框架中给出)
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(zp<depth_buf[get_index(x,y)]){
// TODO: Interpolate the attributes:
auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1.0f);
//法线要归一化
auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1.0f).normalized();
auto interpolated_texcoords = interpolate(alpha,beta,gamma,t.tex_coords[0],t.tex_coords[1],t.tex_coords[2],1.0f);
auto interpolated_shadingcoords = interpolate(alpha,beta,gamma,view_pos[0],view_pos[1],view_pos[2],1.0f);
fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
// Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
auto pixel_color = fragment_shader(payload);
depth_buf[get_index(x,y)]=zp;
set_pixel(Vector2i(x,y),pixel_color);
}
}
}
}
}
2 get_projection_matrix()
将你自己在之前的实验中实现的投影矩阵填到此处,此时你可以运行 ./Rasterizer output.png normal 来观察法向量实现结果。
解答
把以前的粘过来即可。
注意
远平面拉伸时把最后一行向量改成了(0,0,-1,0)修改了以前上下颠倒的问题。
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
//求zNear和eye_fov高度
float height=tan(eye_fov/2)*abs(zNear)*2.0;
//由寬高比求寬度
float width = aspect_ratio*height;
//縮放到正則立方體
projection<<
2.0/width,0,0,0,
0,2.0f/height,0,0,
0,0,2.0f/(zFar-zNear),0,
0,0,0,1;
//將立方體中心平移到原點(xy方向無需平移)
Eigen::Matrix4f tmp=Eigen::Matrix4f::Identity();
tmp<<1,0,0,0,
0,1,0,0,
0,0,1,-(zNear+zFar)/2.0,
0,0,0,1;
projection=projection*tmp;
//進行遠平面的拉伸
tmp<<zNear,0,0,0,
0,zNear,0,0,
0,0,zNear+zFar,-zNear*zFar,
0,0,-1,0;
projection=projection*tmp;
return projection;
}
3 phong_fragment_shader()
实现 Blinn-Phong 模型计算 Fragment Color。
![](/qrcode.jpg)
解答
- 计算的是最后呈现的rgb颜色,只需要按照公式计算环境光、漫反射、高光然后相加即可。
- 光源的结构体第二个向量是光强,3维向量代表rgb通道的分量。
注意
- 注意环境光不要放在循环里,它算一遍即可。
- 高光最后要进行p次幂。
- 是和0max之后在p次幂,不是先p次幂再max…(弄错了之后我后来扰动法线的时候一堆不对的光点。)
Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
//环境光Ambient系数
Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
//漫反射Diffuse系数
Eigen::Vector3f kd = payload.color;
//高光Specular系数
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};
Eigen::Vector3f diffuse={
0,0,0};
Eigen::Vector3f specular={
0,0,0};
for (auto& light : lights)
{
//获得物体与光源距离的平方
float r_squared=(light.position-payload.view_pos).squaredNorm();
//计算每一个光的漫反射
diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
//获取入射与反射光的半程向量
auto h=((light.position-point).normalized()+(eye_pos-point).normalized()).normalized();
//计算高光(这里一定要注意p次幂)
specular+=ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
}
//计算环境光(注意cwiseProduct函数是对应点相乘)
ambient=ka.cwiseProduct(amb_light_intensity);
//将三种光相加,获得结果
result_color=ambient+diffuse+specular;
return result_color * 255.f;
}
结果
环境光:
漫反射:
高光:
相加后的最终Bling-Phone模型结果:
4 texture_fragment_shader()
在实现 Blinn-Phong的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader.
解答
- 使用uv坐标调用纹理get_color函数。
- uv坐标是rasterize_triangle函数中插值得到的。
- 用对应点纹理的颜色替换漫反射kd系数。
注意
有些uv不在0到1内(可能是插值的锅),容易出现段错误,加个限定范围就好了。
Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {
0, 0, 0};
if (payload.texture)
{
// std::cout<<payload.tex_coords.x()<<" "<<payload.tex_coords.y()<<std::endl;
//这里一直段错误,一输出发现uv坐标居然有大于1的,所以这么处理(应该是纹理坐标插值时有一点微弱的超界)
return_color=payload.texture->getColor(MIN(payload.tex_coords.x(),1),MIN(payload.tex_coords.y(),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 = texture_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};
Eigen::Vector3f diffuse={
0,0,0};
Eigen::Vector3f specular={
0,0,0};
for (auto& light : lights)
{
//获得物体与光源距离的平方
float r_squared=(light.position-payload.view_pos).squaredNorm();
//计算每一个光的漫反射
diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
//获取入射与反射光的半程向量
auto h=(light.position-point+eye_pos-point).normalized();
//计算高光
specular+=ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
}
//计算环境光(注意cwiseProduct函数是对应点相乘)
ambient=ka.cwiseProduct(amb_light_intensity);
//将三种光相加,获得结果
result_color=ambient+diffuse+specular;
return result_color * 255.f;
}
结果
5 bump_fragment_shader()
在实现 Blinn-Phong 的基础上,仔细阅读该函数中的注释,实现 Bump mapping。
解答
重点 TBN矩阵和凹凸贴图
这里是我这次作业最卡人的地方!!!
- 先根据原本的法线来获得TBN向量,形成TBN变换矩阵,这个矩阵是将切线空间坐标转换到模型空间中用的!(TBN其实是切线空间的基向量,这一步实际上应该是做了一个基变换。)
- 注意是凹凸贴图,不是法线贴图,所以要先求du、dv然后才能获得法线。
注意
- 注意归一化。
- 最后获得的是扰动过的法线,在下一个函数中,就是基于此再计算的。
- main函数里少东西,大概,没设置hmap纹理。后面位移贴图也有这问题。
Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload& 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;
//n是原来模型空间的法线
Eigen::Vector3f n=normal;
float x = n.x(),y=n.y(),z=n.z();
//计算得到法线空间的基向量t和b(法线空间中,n与模型空间一致)
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=n.cross(t);
Eigen::Matrix3f TBN;
TBN.col(0)=t;
TBN.col(1)=b;
TBN.col(2)=n;
// std::cout<<"TBN矩阵:"<<TBN<<std::endl;
float u=payload.tex_coords.x(),v=payload.tex_coords.y();
float w=payload.texture->width,h=payload.texture->height;
//注意这里是.norm 求范数
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);
//将它转换到模型空间中
// std::cout<<"转换前的法线:"<<normal<<std::endl;
normal = (TBN*ln).normalized();
// std::cout<<"转换后的法线:"<<normal<<std::endl;
Eigen::Vector3f result_color = {
0, 0, 0};
result_color = normal;
return result_color * 255.f;
}
结果
6 displacement_fragment_shader()
在实现 Bump mapping 的基础上,实现 displacement mapping。
解答
- 所谓位移着色器,就是要确实的让每个点先移动,然后再计算着色。这个移动的根据,就是原本的(不是计算后的,我这错了之后凹凸的过分)法线和凹凸贴图。
- 注意这些把bump着色器和phone着色器拼在一起即可。
注意
这里才真的能看出之前算的对不对。
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;
//n是原来模型空间的法线
Eigen::Vector3f n=normal;
float x = n.x(),y=n.y(),z=n.z();
//计算得到法线空间的基向量t和b(法线空间中,n与模型空间一致)
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=n.cross(t);
Eigen::Matrix3f TBN;
TBN.col(0)=t;
TBN.col(1)=b;
TBN.col(2)=n;
// std::cout<<"TBN矩阵:"<<TBN<<std::endl;
float u=payload.tex_coords.x(),v=payload.tex_coords.y();
float w=payload.texture->width,h=payload.texture->height;
//注意这里是.norm 求范数
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);
ln.normalize();
//将它转换到模型空间中
// std::cout<<"转换前的法线:"<<normal<<std::endl;
point = point + kn*n*(payload.texture->getColor(u,v).norm());
normal = (TBN*ln).normalized();
// TODO: Implement displacement mapping here
// Position p = p + kn * n * h(u,v)
Eigen::Vector3f result_color = {
0, 0, 0};
Eigen::Vector3f ambient={
0,0,0};
Eigen::Vector3f diffuse={
0,0,0};
Eigen::Vector3f specular={
0,0,0};
for (auto& light : lights)
{
//获得物体与光源距离的平方
float r_squared=(light.position-point).squaredNorm();
//计算每一个光的漫反射
diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
//获取入射与反射光的半程向量
auto h=((light.position-point).normalized()+(eye_pos-point).normalized()).normalized();
//计算高光(这里一定要注意p次幂)
Eigen::Vector3f light_dir = light.position - point;
Eigen::Vector3f view_dir = eye_pos - point;
specular+= ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
}
//计算环境光(注意cwiseProduct函数是对应点相乘)
ambient=ka.cwiseProduct(amb_light_intensity);
//将三种光相加,获得结果
result_color=ambient + diffuse + specular;
return result_color * 255.f;
}
结果
最开始两次由于point计算和高光计算有偏差,得到了奇妙的效果。
修改后: