Games101 -- 作业3
说明
本次作业主要是实现对一个obj文件表示的物体利用贴图进行渲染
rasterizer.cpp框架分析
和作业二类似,只不过颜色不再是固定值,而是通过纹理获得
//draw 函数
// Also pass view space vertice position
rasterize_triangle(newtri, viewspace_pos);
viewspace_pos为视口空间中顶点的位置,传入顶点位置是为了方便后续求解Blinn-Phong光照。
通过注释可以发现在这个函数中主要实现的就是求解一下四个插值
// TODO: Interpolate the attributes:
// auto interpolated_color // 颜色插值
// auto interpolated_normal //法线插值
// auto interpolated_texcoords //纹理贴图插值
// auto interpolated_shadingcoords //位置坐标插值
rasterize_triangle函数上方就是两个重载的插值函数,通过传入重心坐标,以及对应的坐标即可求得该pixel的四个插值
最后一部分说明了该框架是如何进行着色的,通过构建一个payload 然后调用fragment_shader()函数进行对像素的着色
fragment_shader是一个是std::function<>可以通过参数来控制调用的shader函数,这就是在命令行中输入的第3个参数决定的
payload的定义是在Shader.hpp文件中,本质是一个结构体,通过构造函数来初始化payload中的color,normal,tex_coods(纹理坐标),texture(使用的纹理贴图)
所以最终代码
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4();
//b-box 包围盒
int minx = std::min(v[0].x(),std::min(v[1].x(),v[2].x()));
int miny = std::min(v[0].y(),std::min(v[1].y(),v[2].y()));
int maxx = std::max(v[0].x(),std::max(v[1].x(),v[2].x()));
int maxy = std::max(v[0].y(),std::max(v[1].y(),v[2].y()));
Eigen::Vector3i P;
for(P.x() = minx;P.x()<=maxx;P.x()++)
{
for(P.y() = miny;P.y()<=maxy;P.y()++)
{
//barycentric 判断是否在三角形内
if(!insideTriangle(P.x(), P.y(), t.v)) continue;
int cur_index = get_index(P.x(),P.y());
//重心坐标
auto [alpha, beta, gamma] = computeBarycentric2D(P.x(), P.y(), 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(zp < depth_buf[cur_index])
{
depth_buf[cur_index] = zp;
// auto interpolated_color
auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1);
// auto interpolated_normal
auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1);
// auto interpolated_texcoords u,v
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
// auto interpolated_shadingcoords camer location -- r and l
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);
//pixel的点坐标 用于blinn-phong
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
Vector2i p2;
p2 << P.x(),P.y();
set_pixel(p2,pixel_color);
}
}
}
各个Shader
phone shading
可以看到在环境中存在两个光照,我们只需要在循环中计算Blinn-Phong的三个值相加即可
入射向量,观测向量和半程向量
Eigen::Vector3f l = (light.position-point).normalized();
Eigen::Vector3f v = (eye_pos-point).normalized();
Eigen::Vector3f h = (l+v).normalized();
光源到照射点的距离
float r = std::sqrt((light.position[0]-point[0])*(light.position[0]-point[0])+(light.position[1]-point[1])*(light.position[1]-point[1])+(light.position[2]-point[2])*(light.position[2]-point[2]));
求解漫反射,环境光照和高光
//ambient
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
//diffuse
Eigen::Vector3f Ld = kd.cwiseProduct((light.intensity/(r*r)) * std::max(0.0f,normal.dot(l)));
//specular
Eigen::Vector3f Ls = ks.cwiseProduct((light.intensity/(r*r)) * std::pow(std::max(0.0f,normal.dot(h)),p));
使用纹理贴图
纹理贴图的主要作用就是Blinn-Phong模型中的kd值,在对应的Shader函数中,可以看到相应的注释,我们需要做的就是利用payload获取当前pixel对应的kd值
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());
}
凹凸贴图
凹凸贴图存储的是当前点的高度值,通过课程的学习,可以通过高度求解切线,然后利用切线求解法线。通过注释也可以理解这样一个流程
//cur pixel normal 当前pixel的法线
Eigen::Vector3f n = normal;
float x = normal.x();
float y = normal.y();
float z = normal.z();
//计算矩阵
//由于结算法线是由(0,0,1)改变的,所以需要在计算之后进行变换
Eigen::Vector3f t = Eigen::Vector3f(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 = Eigen::Vector3f(n.y()*t.z()-n.z()*t.y(),-1.0*(n.x()*t.z()-n.z()*t.x()),n.x()*t.y()-n.y()*t.x());
Eigen::Matrix3f TBN;
TBN << t.x(),b.x(),n.x(),
t.y(),b.y(),n.y(),
t.z(),b.z(),n.z();
//get u,v获取当前点的u,v值即高度变换值
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
//get width and height 获取整个纹理贴图的宽和高
float w =payload.texture->width;
float h = payload.texture->height;
//calculate the changed normal
//计算当前点的变化量
float dU = kh * kn * (payload.texture->getColor(u+1.0f/w,v).norm() - payload.texture->getColor(u,v).norm());
float dV = kh * kn * (payload.texture->getColor(u,v+1.0f/h).norm() - payload.texture->getColor(u,v).norm());
//变化量的垂直向量就是改变后的法线向量
Eigen::Vector3f ln = Eigen::Vector3f(-1.0*dU,-1.0*dV,1.0);
//求解实际的法线向量
n = TBN*ln;
normal = n.normalized();
注意:为什么在求解dU和dV时,我们计算相邻点的高度值是+1/w或者+1/h。因为通过观察getColor函数可以发现最后实际计算颜色的位置是
计算dU 和 dV为什么采用norm
可以查看FAQ中的解释
displacement 纹理
课上讲过,这种纹理会实际改变点的位置,而注释中也给出点的计算方法
Position p = p + kn * n * h(u,v)
所以我们只需要在bumpshader的基础上,在求得改变后的normal之后更改点的位置,然后再利用Blinn-Phone模型求解着色情况。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了