计算机图形学——渲染

255 阅读4分钟

渲染

渲染的作用:将视野空间中的三角形,映射到屏幕空间上。

步骤:

  • 变化
  • 光栅化
  • 着色

变化

通过三个变化来完成:

  • 模型变化(MmodelM_{model}

    • Scale矩阵(缩放矩阵)
      • HLPwUf.png
    • Translation矩阵(位移矩阵)
      • HLPdVP.png
    • Rotation矩阵(旋转矩阵)
      • 绕坐标轴:HLPcKs.png
      • 绕任意轴:HLPs2Q.png
  • 视图变化(MviewM_{view}

    • 相机移动到原点,向z-z方向看,yy方向为上。HLPWV0.png
    • 可以计算将原点移动到相机位置的矩阵,然后求逆。HLPgrn.png
  • 投影变化(MprojectM_{project}

    • 视锥(nn为最近可视距离,ff为最远可视距离):HLP2bq.png

    • fovV(垂直视角)Aspectration(屏幕宽/屏幕高)fovV(垂直视角)、Aspect ration(屏幕宽/屏幕高)l,r,b,tl, r, b, t参数转化HLPIGF.png

    • 将透视投影转为正交投影(MperseporthoM_{persep \rightarrow ortho }

      {n0000n0000AB0010}A=n+f,B=nf\left\{ \begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 & 1 & 0 \end{matrix} \right\} ,A=n+f,B=-nf

    • 将长方体的视锥([l,r][b,t][f,n][l, r] *[b, t] * [f,n])转换为正则立方体([1,1]3[-1,1]^3

      • HLPfaV.png
      • HLPh5T.png
      • 然后再正交化,形成标准立方体
    • 将标准立方体([1,1]3[-1,1]^3)投影到屏幕上([0,width][0,height][0,width]*[0,height]

      • 无视zz的值,因为是从33维降到22
      • HLP5PU.png

我们结合代码来看一下:

模型变化(略)

视图变化(将相机移动到原点):

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;
}

投影变化(不包括正交化、将标准立方体([1,1]3[-1,1]^3)投影到屏幕上([0,width][0,height][0,width]*[0,height])):

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;
}

正交化、将标准立方体([1,1]3[-1,1]^3)投影到屏幕上([0,width][0,height][0,width]*[0,height]):

        //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;
        }

光栅化

将二维屏幕中的矢量三角形像素化。

  • 取二维屏幕中那个三角形((x0,y0),(x1,y1),(x2,y2)(x_0,y_0),(x_1,y_1),(x_2,y_2))的包围盒,然后枚举这个包围盒内的所有像素点HLPo24.png
  • 判断像素点是否在三角形内(经典算法题,略)
  • 进行抗锯齿操作(嘤嘤嘤好复杂呀,下次补上)
  • 深度测试,对每个像素点,既要保存这个点的颜色,也要保存这个点的深度。
    • 如果当前枚举到的像素点在三角形内,且这个点的深度比当前像素点已有的深度近,则更新该像素点颜色与深度信息

实现代码:

//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!

以三角形为例,着色只考虑局部(光源射入角度、观测角度、三角形法向量、表面材质参数),不会考虑其他三角形对其光源射入的遮挡。

HLPTxJ.png

着色的时刻:

//深度较以往更近,更新颜色与深度
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)

局部光照模型:

  • 泛光模型:只考虑环境光,这是最简单的经验模型

    • Ienv=KaIaI_{env} =K_{a}I_{a}
    • KaK_a表示物体表面对环境光的反射率
    • IaI_a表示入射环境光的亮度
    • IenvI_{env}存储结果,即人眼所能看到从物体表面反射的环境光的亮度
    • HLPHM9.png
  • Lambert漫反射模型:在泛光模型的基础之上增加了漫反射项

    • 漫反射便是光从一定角度入射之后从入射点向四面八方反射,且每个不同方向反射的光的强度相等,而产生漫反射的原因是物体表面的粗糙,导致了这种物理现象的发生。

    • 首先考虑入射角度所造成的接收到的光强的损失HLPqq1.png

      只有当入射光线与平面垂直的时候才能完整的接受所有光的能量,而入射角度越倾斜损失的能量越大,具体来说,我们应该将光强乘上一个cosθ=ln\cos\theta=l n,其中 是ll入射光方向,nn为平面法线方向。

    • 除了入射角度之外,光源与照射点的距离也应该考虑,直观来说,离得越远当然强度也就越弱!HLPjIK.png

      图中中心为一个点光源,光线均匀的向周围发射,可以想象光源发射出来的能量其实是一定的,那么在任意两个圈上接受到的能量之和一定相等。而离圆心越远,圆的面积越大,单位面积所接受能量也就越弱,因此会将光强II 除上一个r2r^2

    • HLPbrR.png

    • kdk_d为漫反射系数,II入射光强,n,ln,l为法线向量和入射方向,rr是光源距离,maxmax是为了剔除夹角大于90°90°的光。

    • 注意漫反射光线强度是与出射方向无关的,因此无论人眼在哪观察接收到的强度都是一样的!

  • Phong反射模型:镜面反射

    • RR为镜面反射方向,vv为人眼观察方向
    • 除了考虑漫反射中提到的光源到反射点的距离rr之外,需要注意的是,观察方向在镜面反射时是很重要的,具体来说,只有当观察方向集中在反射方向周围很近的时候才能看见反射光,因此在镜面反射中会考虑 RRvv的夹角 α\alpha
    • HLPOVx.png
    • HLPXa6.png
  • Blinn-Phong反射模型

    • 我们将反射方向与人眼观察方向夹角替换成如下图所示的一个半程向量和法线向量的夹角
    • HLPzGD.png
    • HLiSRe.png
    • HLipxH.png

着色频率:

  • Flat Shading:每一个三角形平面作为一个着色单位

    • 模型数据大多以很多个三角面进行存储,因此也就记录了每个面的法线向量,利用每个面的法线向量进行一次Blinn-Phong反射光照模型的计算,将该颜色赋予整个面
    • HLiCMd.png
  • Gouraud Shading:对每个三角形的顶点进行一次着色

    • 我们只知道三角形的法线向量,如何得到每个顶点的法线向量呢?

    • 将所有共享这个点的面的法线向量加起来求均值,最后再标准化就得到了该顶点的法线向量了

    • 对于三角形内部的每一个点应该怎么办呢?对,就是利用重心坐标来插值:

      C=αC0+βC1+γC2C = \alpha C_0 + \beta C_1 + \gamma C_2

  • Phong Shading