到新东家第一件事,是编写一个软光栅。写软光栅是图形学里的基础,没啥好说的。基本功能如下:

  1. 画线
  2. 实现MVP变换
  3. 画面(基于扫描线算法画三角形)
  4. 基本光照(Ground光照)
  5. UV贴图(以及UV透视校正)
  6. 裁剪
  7. 背面消隐(隐藏顺时针面,逆时针面或双面渲染可选)
  8. 模型加载

       软光栅编写有些讲究,但其实难度不算特别大,主要难度集中在裁剪,其次UV稍微难点但也非常简单了。MVP手工推导很容易出错,但是,GLM里啥都有,有问题对对即可。

       而且,传统的软光栅其实已经有些脱节,现代的话应该编写一些更现代一些,也就是支持着色器的软光栅。简单的比如ssloy/tinyrenderer,还有隔壁某位dalao的作品,复杂点的比如OpenSWR

       我写软光栅的时候,使用一台CPU E8600,内存4G,显卡GT630的搓机,还作死的用了SDL作为画点的基础库,写出来的程序跑起来那真是卡的出奇,大约7~8FPS。不过,我之后用了一些小优化的方法,还是把帧数提高到了40~200FPS,渣机不是很稳定,但还是大于30了。这些方法里有些有意义,有些没意义,还有些很二,不过我还是记录一下。

1. 画点方式优化(提升明显)

       一般画点的方式,应该类似

SDL_SetRenderDrawColor(mRenderer, r, g, b, a);
SDL_RenderDrawPoint(mRenderer, x, y);

       直接画点效率是很低的,实现深度测试的时候,大家可能都实现了一个类似logic_device/virtualScreen这样的数据结构,存储屏幕上每个点的颜色值和深度值,最后将这个数据结构里的每个点画在真正的屏幕上。优化点来了。既然已经有了虚拟设备,这个虚拟设备如果转换成一张texture,再直接贴到窗口上,类似于显卡缓冲区,效率会不会提高

mDrawOptiSurface = SDL_GetWindowSurface(mWindow);

for (int i = 0; i < mVitrualScreen.width - 1; i ++) {
    for (int j = 0; j < mVitrualScreen.height - 1; j++) {

        putPixelUV4DrawOpti(i, j, mVitrualScreen.color[i][j]);

    }
}


mDrawOptiTexture = SDL_CreateTextureFromSurface(mRenderer, mDrawOptiSurface);

SDL_RenderCopy(mRenderer, mDrawOptiTexture, NULL, NULL);

SDL_DestroyTexture(mDrawOptiTexture);

// ==========================================

void MyApplication::putPixelUV4DrawOpti(int x, int y, Uint32 pixel) {
    int bpp = mDrawOptiSurface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to set */
    Uint8 *p = (Uint8 *)mDrawOptiSurface->pixels + y * mDrawOptiSurface->pitch + x * bpp;

    switch(bpp) {
        case 1:
            *p = pixel;
            break;

        case 2:
            *(Uint16 *)p = pixel;
            break;

        case 3:
            if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
                p[0] = (pixel >> 16) & 0xff;
                p[1] = (pixel >> 8) & 0xff;
                p[2] = pixel & 0xff;
            } else {
                p[0] = pixel & 0xff;
                p[1] = (pixel >> 8) & 0xff;
                p[2] = (pixel >> 16) & 0xff;
            }
            break;

        case 4:
            *(Uint32 *)p = pixel;
            break;
    }
}

       结果,帧数7->30+,基本一次达标。

2. 局部重绘(提升较大,没有意义)

       局部重绘这个优化方法顾名思义,每次只重新绘制屏幕上改变的部分,这个对于直接setPixel提升是异常巨大的,对于使用贴图的玩法提升也很明显。不过,既然是做软光栅,clear整个window是应该的,所以这个方法没有意义

       结果,帧数30+ -> 40~100,重绘范围越小提升越明显(其实软光栅本来就是重绘范围越小越快)。

3. release not debug

       你没看错,就是改一下IDE的设置,把程序从debug改成release。这样性能真的能飞越。对于SDL等一些release下多线程神器优化的库更明显。

       结果,帧数100+ -> 200+。

4. 换台好点的机子(不现实)

       我把程序转移到我的开发机上(配置i7 7700k,16g,gtx1070ti),帧数直接炸裂。

       结果,帧数200+ -> 1000+。

5. 降低精度(提升不明显)

       把double变为float之后,性能提升,但是真的不是很多。

6. 裁剪方式

       老老实实的将三角形裁剪成更多视野内的小三角形的方法其实速度并不快。更加简单粗暴的裁剪方式是将裁剪空间外的像素直接舍弃。这种实现不光简单,而且可以并行化,实际上显卡驱动采用的方式是后者,但是会先在一个比window大得多的光栅上进行裁剪防止溢出错误,然后只显示window。