渲染
渲染的作用:将视野空间中的三角形,映射到屏幕空间上。
步骤:
- 变化
- 光栅化
- 着色
变化
通过三个变化来完成:
-
模型变化()
-
视图变化()
-
投影变化()
我们结合代码来看一下:
模型变化(略)
视图变化(将相机移动到原点):
Eigen::Vector3f eye_pos = {0,0,5};
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos)
{
Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
Eigen::Matrix4f translate;
translate << 1,0,0,-eye_pos[0],
0,1,0,-eye_pos[1],
0,0,1,-eye_pos[2],
0,0,0,1;
view = translate*view;
return view;
}
投影变化(不包括正交化、将标准立方体()投影到屏幕上()):
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
double theta = (eye_fov / 2) / 180.0 * M_PI;
double t = -tan(theta) * fabs(zNear);
double b = -t;
double r = aspect_ratio * t;
double l = -r;
double f = zFar;
double n = zNear;
Eigen::Matrix4f ortho, orthoA, orthoB;
orthoA << 2/(r - l), 0, 0, 0, 0, 2/(t - b), 0, 0, 0, 0, 2/(n-f), 0, 0, 0, 0, 1;
orthoB << 1, 0, 0, -(r + l) / 2, 0, 1, 0, -(t + b) / 2, 0, 0, 1, -(n + f) / 2, 0, 0, 0, 1;
ortho = orthoA * orthoB;
Eigen::Matrix4f persp2ortho;
double A = zNear + zFar;
double B = -zNear * zFar;
persp2ortho << zNear, 0, 0, 0, 0, zNear, 0, 0, 0, 0, A, B, 0, 0, 1, 0;
projection = ortho * persp2ortho;
return projection;
}
正交化、将标准立方体()投影到屏幕上():
//Homogeneous division 正交化
for (auto& vec : v) {
vec /= vec.w();
}
//Viewport transformation
//将[-1, 1] * [-1, 1] * [-1, 1] 到 [0, width] * [0, height]
for (auto & vert : v)
{
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
vert.z() = vert.z() * f1 + f2;
}
光栅化
将二维屏幕中的矢量三角形像素化。
- 取二维屏幕中那个三角形()的包围盒,然后枚举这个包围盒内的所有像素点
- 判断像素点是否在三角形内(经典算法题,略)
- 进行抗锯齿操作(嘤嘤嘤好复杂呀,下次补上)
- 深度测试,对每个像素点,既要保存这个点的颜色,也要保存这个点的深度。
- 如果当前枚举到的像素点在三角形内,且这个点的深度比当前像素点已有的深度近,则更新该像素点颜色与深度信息
实现代码:
//t为screen空间里的三角形
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
//寻找到包围盒
int MaxY = max(v[0].y(), max(v[1].y(), v[2].y())) + 0.5;
int MinY = min(v[0].y(), min(v[1].y(), v[2].y())) - 0.5;
int MaxX = max(v[0].x(), max(v[1].x(), v[2].x())) + 0.5;
int MinX = min(v[0].x(), min(v[1].x(), v[2].x())) - 0.5;
//枚举包围盒中所有像素点
for(int x = MinX; x <= MaxX; x++)
for(int y = MinY; y <= MaxY; y++)
if(insideTriangle(x, y, t.v)) //判断像素点是否在三角形内
{
//std::tuple X(0, 0, 0);
std::tuple<float, float, float> tmp = computeBarycentric2D(x, y, t.v);
double alpha = std::get<0>(tmp);
double beta = std::get<1>(tmp);
double gamma = std::get<2>(tmp);
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(x, y)])
{
depth_buf[get_index(x, y)] = z_interpolated;
set_pixel(Eigen::Vector3f(x, y, 0), t.getColor());
}
}
}
着色(shading)
此时考虑的是局部光照模型
Shading is local,No shader will be generated!
以三角形为例,着色只考虑局部(光源射入角度、观测角度、三角形法向量、表面材质参数),不会考虑其他三角形对其光源射入的遮挡。
着色的时刻:
//深度较以往更近,更新颜色与深度
if(z_interpolated < depth_buf[get_index(x, y)])
{
//更新像素点的深度
depth_buf[get_index(x, y)] = z_interpolated;
//更新像素点的颜色
set_pixel(Eigen::Vector3f(x, y, 0), t.getColor());
}
将光分成三类:
-
镜面反射(Specular)
-
漫反射(Diffuse)
-
环境光(Ambient)
局部光照模型:
-
泛光模型:只考虑环境光,这是最简单的经验模型
-
Lambert漫反射模型:在泛光模型的基础之上增加了漫反射项
-
漫反射便是光从一定角度入射之后从入射点向四面八方反射,且每个不同方向反射的光的强度相等,而产生漫反射的原因是物体表面的粗糙,导致了这种物理现象的发生。
-
只有当入射光线与平面垂直的时候才能完整的接受所有光的能量,而入射角度越倾斜损失的能量越大,具体来说,我们应该将光强乘上一个,其中 是入射光方向,为平面法线方向。
-
除了入射角度之外,光源与照射点的距离也应该考虑,直观来说,离得越远当然强度也就越弱!
图中中心为一个点光源,光线均匀的向周围发射,可以想象光源发射出来的能量其实是一定的,那么在任意两个圈上接受到的能量之和一定相等。而离圆心越远,圆的面积越大,单位面积所接受能量也就越弱,因此会将光强 除上一个
-
为漫反射系数,入射光强,为法线向量和入射方向,是光源距离,是为了剔除夹角大于的光。
-
注意漫反射光线强度是与出射方向无关的,因此无论人眼在哪观察接收到的强度都是一样的!
-
-
Phong反射模型:镜面反射
-
Blinn-Phong反射模型
着色频率: