作业2
基础题目:
栅格化:在屏幕绘制一个实心三角形,函数 rasterize_triangle(const Triangle& t),需要找出当前三角形的边界框,然后遍历像素,查找当前像素是否在三角形内static bool insideTriangle(),
深度测试:如果在三角形内,代码已经自动计算z值(插值得到)存储在z_interpolated中,如果当前点更靠近相机,需要请设置像素颜色(使用 set_pixel 函数)并更新深度缓冲区
解:
首先创建三角形边界,分别为3个顶点的,xy坐标的最大最小值
auto v = t.toVector4();
float alpha, beta, gamma, lmin=INT_MAX, rmax=INT_MIN, tmax=INT_MIN, bmin=INT_MAX;
for(auto &k:v){
lmin = int(std::min(lmin,k.x()));
rmax = std::max(rmax,k.x());rmax = rmax == int(rmax) ? int(rmax)-1 : rmax;
tmax = std::max(tmax,k.y());tmax = tmax == int(tmax) ? int(tmax)-1 : tmax;
bmin = int(std::min(bmin,k.y()));
}
接着遍历每个像素,如果像素中心在三角形内,用插值计算深度,如果深度值 < 屏幕坐标的深度值,就更新颜色值和深度缓冲,总体来说使用了作业框架的API
for(float i = lmin; i <= rmax; i++){
for(float j = bmin; j <= tmax; j++){
if(insideTriangle(i, j, t.v)){
/* 计算z深度 */
std::tie(alpha, beta, gamma) = computeBarycentric2D(i+0.5, j+0.5, t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if(-z_interpolated < depth_buf[get_index(i,j)]){/* 当前深度 < 深度缓冲中的深度值 */
set_pixel({i,j,1},t.getColor());/* 设置当前像素颜色值 */
depth_buf[get_index(i,j)] = -z_interpolated;/* 更新深度缓冲 */
}
}
}
}
下一步判断坐标是否在三角形内部,根据老师课上讲的叉积判断,如果一个点在三角形所有边的同一侧,就为内部 。ver存储的是三个线框的向量(需要同一的顺时针或逆时针),ver2存储的是从xy坐标为终点到三个点的向量。循环比较每个边和xy向量的叉积,
比如顺时针绘制三角形,需要判断z值是否为负
static bool insideTriangle(int x, int y, const Vector3f* _v)
{
std::vector<Vector3f> ver, ver2;
ver.push_back({_v[1].x()-_v[0].x(),_v[1].y()-_v[0].y(),0}); ver2.push_back({x-_v[0].x(),y-_v[0].y(),0});
ver.push_back({_v[2].x()-_v[1].x(),_v[2].y()-_v[1].y(),0}); ver2.push_back({x-_v[1].x(),y-_v[1].y(),0});
ver.push_back({_v[0].x()-_v[2].x(),_v[0].y()-_v[2].y(),0}); ver2.push_back({x-_v[2].x(),y-_v[2].y(),0});
for(int i=0;i<3;i++){
if(ver[i].cross(ver2[i]).z() < 0)
return false;
}
return true;
}
图像颠倒和镜像翻转问题:
发现绘制的三角形是上下颠倒的,错误原因其实是opencv原点在左上角,而代码框架用的是左下,
注意opegl规范化设备坐标NDC是右手坐标系而非左手:direct和opencv采用的都是左手,因为定义opencv原点为左上,需要将左手坐标系翻转,导致+y向下的坐标系
比如传入一下坐标点:按照右手坐标系带入这组顶点,符合作业的输出结果,但是由于OpenCV是左手坐标系,带入完全会得到相反的结果,所以应该将图像翻转
{2, 0, -2},
{0, 2, -2},
{-2, 0, -2},
{3.5, -1, -5},
{2.5, 1.5, -5},
{-1, 0.5, -5}
经过查找网上的题解,需要更改透视矩阵,在转换的时候直接应用翻转
proj <<
zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, (zNear + zFar) / (zFar - zNear), (2 * zNear * zFar) / (zFar - zNear),
0, 0, -1, 0;
得到结果:
提高题目:
解决走样问题,实现MSAA:每一个像素建立4个采样点,需要维护一个 sample list,你得到的三角形不应该有不正常的黑边
解:
首先建立采样点偏移量,然后对每个像素采样4次,判断是否在三角形内,如果在就加上权重值,最后乘以平均权重值
黑边问题:黑边出现两个三角交界处,这是因为这个像素由两部分组成,假设靠前(深度值小)的仅仅覆盖了1/4的采样点,那么最终颜色会接近黑色,展现黑边
所以应该对每个像素的所有采样点中存储颜色,然后求平均值,
比如这里的颜色,由于靠后物体更新了3/4,靠前物体更新了1/4,所以求它们的平均值
通过4倍的后台深度缓冲区和颜色缓冲区,存储每个采样点的颜色和深度,
而对于正常的前台缓冲区,颜色为所有采样点的颜色平均值,深度为最小深度
std::vector<float> a{0.25,0.25,0.75,0.75,0.25};
mindep = INT_MAX;//像素的深度
eid = get_index(i,j)*4;//像素的索引
……
for(int k = 0; k < 4; k++){{
if(insideTriangle(i+a[k], j+a[k+1], t.v)){
if (-z_interpolated < depth_sample[eid + k])
{
depth_sample[eid + k] = -z_interpolated;//后台深度缓冲--采样点
frame_sample[eid + k] = t.getColor() / 4;//后台颜色缓冲--采样点
}
mindep = std::min(depth_sample[eid + k], mindep);
}
}
color = frame_sample[eid] + frame_sample[eid + 1] + frame_sample[eid + 2] +frame_sample[eid + 3];//像素颜色为采样点的平均值
set_pixel({i,j,1}, color);//仅更新1次像素颜色
depth_buf[get_index(i,j)] = std::min(depth_buf[get_index(i,j)], mindep);//深度为采样点中最小值
作业三
框架
这次作业框架通过Object Loader加载一个3维模型,并提供 了Vertex Shader 与 Fragment Shader着色器
- main入口点
-
active_shader通用函数绑定,表示当前绑定哪个着色器
-
- rasterizer屏幕光栅器类
- draw计算顶点数据
-
rasterize_triangle插值,并设置最终颜色
- obj_loader第三方.obj 文件加载库
- global全局常量数据
- shader着色器类
- 提供了,片段和顶点shader的数据加载
- texture纹理类
- 通过opencv,从图片生成纹理
- getColor查找纹理颜 色的接口
- triangle三角形网格类
题目:
我们需要编写的代码部分:
- rasterize_triangle实现插值
- phong_fragment_shader() 实现 Blinn-Phong 模型fragment shader
- texture_fragment_shader()纹理fragment shader
- bump_fragment_shader()凹凸贴图fragment shader
- displacement_fragment_shader()位移贴图的fragment shader
其中在运行可执行文件时,第二个参数是生成的图片文件名,第3个参数可以是texture,normal,phong,bump,shader其中之一
解:
首先填写了rasterize_triangle和调用normal后