图形学笔记 -- OpenGL 毕业报告 / 模拟显卡 GPU,C++ 实现光栅化渲染器

OpenGL 毕业报告 / 模拟显卡 GPU,C++ 实现光栅化渲染器。 主要参考 mini3d 代码,重写了一遍,加深理解。

实现一个固定管线的软件渲染器。普通坐标(Ordinary Coordinate 笛卡尔坐标)和齐次坐标(Homogeneous Coordinate)。 标准观察体(canonical view volume, CVV)投影变换

当年还用不了 D3D 和 OGL,开发游戏,做图形实现软件渲染是必备技能,当年机型差,连浮点数都用不了,要用定点数来计算,矩阵稍不注意就越界了。

数学基础部分

typedef struct Matrix { float m[4][4]; } matrix_t;
typedef struct Vector { float x, y, z, w; } vector_t;
typedef vector_t point_t;

inline int clamp(int x, int min, int max) { return (x < min) ? min : ((x > max) ? max : x); }

// 计算插值:t 为 [0, 1] 之间的数值
inline float interp(float x1, float x2, float t) { return x1 + (x2 - x1) * t; }

// | v |
float vector_length(const vector_t *v);
// z = x + y
void vector_add(vector_t *z, const vector_t *x, const vector_t *y);
// z = x - y
void vector_sub(vector_t *z, const vector_t *x, const vector_t *y);
// 矢量点乘
float vector_dotproduct(const vector_t *x, const vector_t *y);
// 矢量叉乘
void vector_crossproduct(vector_t *z, const vector_t *x, const vector_t *y);
// 矢量插值,t 取值 [0, 1]
void vector_interp(vector_t *z, const vector_t *x1, const vector_t *x2, float t);
// 矢量归一化
void vector_normalize(vector_t *v);

// c = a + b
void matrix_add(matrix_t *c, const matrix_t *a, const matrix_t *b);
// c = a - b
void matrix_sub(matrix_t *c, const matrix_t *a, const matrix_t *b);
// c = a * b
void matrix_mul(matrix_t *c, const matrix_t *a, const matrix_t *b);
// c = a * f
void matrix_scale(matrix_t *c, const matrix_t *a, float f);
// y = x * m
void matrix_apply(vector_t *y, const vector_t *x, const matrix_t *m);
// 单位矩阵
void matrix_set_identity(matrix_t *m);
// 0 矩阵
void matrix_set_zero(matrix_t *m);
// 平移变换
void matrix_set_translate(matrix_t *m, float x, float y, float z);
// 缩放变换
void matrix_set_scale(matrix_t *m, float x, float y, float z);
// 旋转矩阵
void matrix_set_rotate(matrix_t *m, float x, float y, float z, float theta);
// 设置摄像机
void matrix_set_lookat(matrix_t *m, const vector_t *eye, const vector_t *at, const vector_t *up);
// D3DXMatrixPerspectiveFovLH
void matrix_set_perspective(matrix_t *m, float fovy, float aspect, float zn, float zf);

坐标变换

typedef struct Transform {
    matrix_t world;         // 世界坐标变换
    matrix_t view;          // 摄影机坐标变换
    matrix_t projection;    // 投影变换
    matrix_t transform;     // transform = world * view * projection
    float w, h;             // 屏幕大小
} transform_t;

// 矩阵更新,计算 transform = world * view * projection
void transform_update(transform_t *ts);
// 初始化,设置屏幕长宽
void transform_init(transform_t *ts, int width, int height);
// 将矢量 x 进行 project
void transform_apply(const transform_t *ts, vector_t *y, const vector_t *x);
// 检查齐次坐标同 cvv 的边界用于视锥裁剪
int transform_check_cvv(const vector_t *v);
// 归一化,得到屏幕坐标
void transform_homogenize(const transform_t *ts, vector_t *y, const vector_t *x);

几何计算

几何计算:顶点、扫描线、边缘、矩形、步长计算。

typedef struct Color { float r, g, b; } color_t;
typedef struct TexCoord { float u, v; } texcoord_t;
typedef struct Vertex { point_t pos; texcoord_t tc; color_t color; float rhw; } vertex_t;

typedef struct Edge { vertex_t v, v1, v2; } edge_t;
typedef struct Trapezoid { float top, bottom; edge_t left, right; } trapezoid_t;
typedef struct Scanline { vertex_t v, step; int x, y, w; } scanline_t;

void vertex_rhw_init(vertex_t *v);
void vertex_interp(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float t);
void vertex_division(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float w);
void vertex_add(vertex_t *y, const vertex_t *x);

// 根据三角形生成 0-2 个梯形,并且返回合法梯形的数量
int trapezoid_init_triangle(trapezoid_t *trap, const vertex_t *p1, const vertex_t *p2, const vertex_t *p3);
// 按照 Y 坐标计算出左右两条边纵坐标等于 Y 的顶点
void trapezoid_edge_interp(trapezoid_t *trap, float y);
// 根据左右两边的端点,初始化计算出扫描线的起点和步长
void trapezoid_init_scan_line(const trapezoid_t *trap, scanline_t *scanline, int y);

渲染设备

#define RENDER_STATE_WIREFRAME      1        // 渲染线框
#define RENDER_STATE_TEXTURE        2        // 渲染纹理
#define RENDER_STATE_COLOR          4        // 渲染颜色

#define DEVICE_KEYS_SIZE            512

// 渲染设备
struct Device {
    transform_t transform;      // 坐标变换器
    int width;                  // 窗口宽度
    int height;                 // 窗口高度
    UINT32 **framebuffer;       // 像素缓存:framebuffer[y] 代表第 y 行
    float **zbuffer;            // 深度缓存:zbuffer[y] 为第 y 行指针
    UINT32 **texture;           // 纹理:同样是每行索引
    int tex_width;              // 纹理宽度
    int tex_height;             // 纹理高度
    float max_u;                // 纹理最大宽度:tex_width - 1
    float max_v;                // 纹理最大高度:tex_height - 1
    int render_state;           // 渲染状态
    UINT32 background;          // 背景颜色
    UINT32 foreground;          // 线框颜色

public:
    void draw_plane(int a, int b, int c, int d);
    void draw_box(float theta);
    void camera_at_zero(float x, float y, float z);
    void init_texture();

    // 设备初始化,fb 为外部帧缓存,非 NULL 将引用外部帧缓存(每行 4 字节对齐)
    void device_init(int width, int height, void *fb);
    // 删除设备
    void device_destroy(Device *device);
    // 设置当前纹理
    void device_set_texture(void *bits, long pitch, int w, int h);
    // 清空 framebuffer 和 zbuffer
    void device_clear(int mode);
    // 画点
    void device_pixel(int x, int y, UINT32 color);
    // 绘制线段
    void device_draw_line(int x1, int y1, int x2, int y2, UINT32 c);
    // 根据坐标读取纹理
    UINT32 Device_texture_read(float u, float v);

    // 渲染实现
    // 绘制扫描线
    void device_draw_scanline(scanline_t *scanline);
    // 主渲染函数
    void device_render_trap(trapezoid_t *trap);
    // 根据 render_state 绘制原始三角形
    void device_draw_primitive(const vertex_t *v1, const vertex_t *v2, const vertex_t *v3);
};

Win32 窗口

Win32 窗口及图形绘制:为 device 提供一个 DibSection 的 FB。

class Window {
    int screen_w, screen_h;
    int screen_mx, screen_my, screen_mb;
    HWND screen_handle;        // 主窗口 HWND
    HDC screen_dc;            // 配套的 HDC
    HBITMAP screen_hb;        // DIB
    HBITMAP screen_ob;        // 老的 BITMAP
    unsigned char *screen_fb;    // frame buffer
    long screen_pitch;

public:
    static int device_exit;
    static int device_keys[DEVICE_KEYS_SIZE];    // 当前键盘按下状态

public:
    Window() {
        screen_mx = 0, screen_my = 0, screen_mb = 0;
        screen_handle = NULL;
        screen_dc = NULL;
        screen_hb = NULL;
        screen_ob = NULL;
        screen_fb = NULL;
        screen_pitch = 0;
        device_exit = 0;
        memset(device_keys, 0, sizeof(int) * DEVICE_KEYS_SIZE);
    }

    int screen_init(int w, int h, const TCHAR *title);    // 屏幕初始化
    int screen_close(void);                                // 关闭屏幕
    void screen_update(void);                            // 显示 FrameBuffer
    unsigned char *getScreenFrameBuffer() { return screen_fb; }

    // win32 event handler
    static LRESULT win_events(HWND, UINT, WPARAM, LPARAM);
    void win_dispatch(void);                            // 处理消息
};

主程序

#include "mini3d.h"
#include "window.h"

#define DEVICE_WIDTH    800
#define DEVICE_HEIGHT   600

int main(void)
{
    TCHAR *title = _T("Mini3d (software render tutorial) - ")
        _T("Left/Right: rotation, Up/Down: forward/backward, Space: switch state");

    Window window;
    if (window.screen_init(DEVICE_WIDTH, DEVICE_HEIGHT, title))
        return -1;

    Device device;
    device.device_init(DEVICE_WIDTH, DEVICE_HEIGHT, window.getScreenFrameBuffer());
    device.camera_at_zero(3, 0, 0);

    device.init_texture();
    device.render_state = RENDER_STATE_TEXTURE;

    int kbhit = 0;
    float theta = 1;
    float pos = 3.5;
    int states[] = { RENDER_STATE_TEXTURE, RENDER_STATE_COLOR, RENDER_STATE_WIREFRAME };
    int indicator = 0;

    while (window.device_exit == 0 && window.device_keys[VK_ESCAPE] == 0) {
        window.win_dispatch(); // 事件分发

        device.device_clear(1);
        device.camera_at_zero(pos, 0, 0);

        if (window.device_keys[VK_UP]) pos -= 0.01f;
        if (window.device_keys[VK_DOWN]) pos += 0.01f;
        if (window.device_keys[VK_LEFT]) theta += 0.01f;
        if (window.device_keys[VK_RIGHT]) theta -= 0.01f;

        if (window.device_keys[VK_SPACE]) {
            if (kbhit == 0) {
                kbhit = 1;
                if (++indicator >= 3) indicator = 0;
                device.render_state = states[indicator];
            }
        } else {
            kbhit = 0;
        }

        device.draw_box(theta);
        window.screen_update();
        Sleep(1);
    }
    return 0;
}

Refers


参考资料快照
参考资料快照

本文短链接:
If you have any questions or feedback, please reach out .