• 简单软光栅实现


    最近在看UE5的Lumen渲染优化,其中针对像素级三角形的优化:软光栅处理。遂自行找个案例来实现一下软光栅流程巩固光栅化的相关知识(当然仅是软光栅的简单实现)。

    在这里插入图片描述

    一、光栅化流程

    首先来一张硬光栅流程,也是本次实现的总纲。

    在这里插入图片描述
    上边是传统光栅化的着色器流程,或许我们来看一下RTR4中给出的渲染管线流程更清晰一些:
    在这里插入图片描述
    可以看到RTR对渲染管线的总结非常精简,只有4个阶段(我们后续实现的时候几乎与这几步相同):

    • Application:应用阶段
    • Geometry:几何阶段
    • Rasterization:光栅
    • Pixel Processing Stage:像素处理

    2.1 Application

    应用阶段,在CPU上的一些预备工作,组织光栅器所需要的数据并尽可能在执行光栅器任务前将数据进行优化。这个阶段可以做很多复杂的工作,但是对于光栅化渲染器本身我们更需要更关注后面的阶段。
    我们本次主要是使用立方体作为光栅化数据。

    typedef struct { float x, y, z, w; } vector_t;
    typedef vector_t point_t;
    typedef struct { float r, g, b; } color_t;
    typedef struct { float u, v; } texcoord_t;
    typedef struct { point_t pos; texcoord_t tc; color_t color; float rhw; } vertex_t;
    vertex_t mesh[8] = {
    	{ { -1, -1,  1, 1 }, { 0, 0 }, { 1.0f, 0.2f, 0.2f }, 1 },
    	{ {  1, -1,  1, 1 }, { 0, 1 }, { 0.2f, 1.0f, 0.2f }, 1 },
    	{ {  1,  1,  1, 1 }, { 1, 1 }, { 0.2f, 0.2f, 1.0f }, 1 },
    	{ { -1,  1,  1, 1 }, { 1, 0 }, { 1.0f, 0.2f, 1.0f }, 1 },
    	{ { -1, -1, -1, 1 }, { 0, 0 }, { 1.0f, 1.0f, 0.2f }, 1 },
    	{ {  1, -1, -1, 1 }, { 0, 1 }, { 0.2f, 1.0f, 1.0f }, 1 },
    	{ {  1,  1, -1, 1 }, { 1, 1 }, { 1.0f, 0.3f, 0.3f }, 1 },
    	{ { -1,  1, -1, 1 }, { 1, 0 }, { 0.2f, 1.0f, 0.3f }, 1 },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.2 Geometry

    几何阶段,顾名思义主要处理几何相关的任务,负责将输入的顶点数据经过一系列变换最终变成屏幕顶点数据,它又包含了4个小阶段:
    在这里插入图片描述

    整个Geometry阶段都绕不开坐标系和坐标系变换,如果对相关概念不熟悉可以先看这几篇:

    我们首先来看一下此部分软光栅对应的实现代码与流程:

    2.2.1 Vertex Shading与Projection

    此部分对应我们的软光栅可以理解为顶点数据的空间转换,可见下图:

    在这里插入图片描述

    从图中可以看出其实就是MVP转换,具体原理见下图就不在赘述。

    在这里插入图片描述

    我们主要来看一下软光栅中纯代码实现这些如何处理的。

    因为定义的正方体已经处于世界空间,所以我们的Model矩阵为单位矩阵即可,具体代码实现如下:

    typedef struct { float m[4][4]; } matrix_t;
    void matrix_set_identity(matrix_t *m) {
    	m->m[0][0] = m->m[1][1] = m->m[2][2] = m->m[3][3] = 1.0f; 
    	m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
    	m->m[1][0] = m->m[1][2] = m->m[1][3] = 0.0f;
    	m->m[2][0] = m->m[2][1] = m->m[2][3] = 0.0f;
    	m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来我们看一下View矩阵,即将场景统一转换到视空间,可见下图:

    在这里插入图片描述
    具体View矩阵代码实现如下:

    // 设置摄像机矩阵
    void matrix_set_lookat(matrix_t *m, const vector_t *eye, const vector_t *at, const vector_t *up) {
    	vector_t xaxis, yaxis, zaxis;
    
    	vector_sub(&zaxis, at, eye);
    	vector_normalize(&zaxis);
    	vector_crossproduct(&xaxis, up, &zaxis);
    	vector_normalize(&xaxis);
    	vector_crossproduct(&yaxis, &zaxis, &xaxis);
    
    	m->m[0][0] = xaxis.x;
    	m->m[1][0] = xaxis.y;
    	m->m[2][0] = xaxis.z;
    	m->m[3][0] = -vector_dotproduct(&xaxis, eye);
    
    	m->m[0][1] = yaxis.x;
    	m->m[1][1] = yaxis.y;
    	m->m[2][1] = yaxis.z;
    	m->m[3][1] = -vector_dotproduct(&yaxis, eye);
    
    	m->m[0][2] = zaxis.x;
    	m->m[1][2] = zaxis.y;
    	m->m[2][2] = zaxis.z;
    	m->m[3][2] = -vector_dotproduct(&zaxis, eye);
    	
    	m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
    	m->m[3][3] = 1.0f;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    之后是Perspective矩阵的处理,此处我们使用透视投影,具体参数见下图:

    在这里插入图片描述

    具体Perspective矩阵代码实现如下:

    void matrix_set_perspective(matrix_t *m, float fovy, float aspect, float zn, float zf) {
    	float fax = 1.0f / (float)tan(fovy * 0.5f);
    	matrix_set_zero(m);
    	m->m[0][0] = (float)(fax / aspect);
    	m->m[1][1] = (float)(fax);
    	m->m[2][2] = zf / (zf - zn);
    	m->m[3][2] = - zn * zf / (zf - zn);
    	m->m[2][3] = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    具体的MVP矩阵运算代码封装实现如下:

    typedef struct { 
    	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) {
    	matrix_t m;
    	matrix_mul(&m, &ts->world, &ts->view);
    	matrix_mul(&ts->transform, &m, &ts->projection);
    }
    
    // 初始化,设置屏幕长宽
    void transform_init(transform_t *ts, int width, int height) {
    	float aspect = (float)width / ((float)height);
    	matrix_set_identity(&ts->world);
    	matrix_set_identity(&ts->view);
    	matrix_set_perspective(&ts->projection, 3.1415926f * 0.5f, aspect, 1.0f, 500.0f);
    	ts->w = (float)width;
    	ts->h = (float)height;
    	transform_update(ts);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.2.2 Clipping

    当顶点从Object Space变换到Clip Space后(即经过MVP转换后),首先需要将顶点数据转换为基础图元,因为后续需要对基础图元进行一系列的剔除、裁剪处理,而仅有顶点信息是无法正确执行这些任务的,这一步常称为Primitive Assembly。

    接着需要对组织好的图元数据进行剔除、裁剪,这两个步骤都是发生在Clip Space,注意Clip Space的坐标范围为:
    在这里插入图片描述
    当然了,一般的剔除规则如下:

    • Primitive完全处于Frustum内,保留
    • Primitive完全处于任意一个Frustum平面外,剔除
    • Primitive部分处于Frustum外,裁剪,产生新顶点,变为新的图元,不过这一步可选,上下左右平面天然支持延迟到Screen Space,或者不裁剪(后面插值的时候稍作处理即可),而近平面也可以通过增加一个flag来延迟/取消裁剪近平面:flag可以区分一个顶点处于Clip还是Screen Space,因此可以帮助后面做透视正确的插值,也能避免因为w < 0而导致的翻转投影的问题,

    当然了,我们的实现方式就是简单的第一个规则,具体细节可以参考的clip、Interp相关函数代码实现:

    // 检查齐次坐标同 cvv 的边界用于视锥裁剪
    int transform_check_cvv(const vector_t *v) {
    	float w = v->w;
    	int check = 0;
    	if (v->z < 0.0f) check |= 1;
    	if (v->z >  w) check |= 2;
    	if (v->x < -w) check |= 4;
    	if (v->x >  w) check |= 8;
    	if (v->y < -w) check |= 16;
    	if (v->y >  w) check |= 32;
    	return check;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    裁剪本质就是线段和平面求交然后产生新的顶点形成新图元,有许多经典的裁剪算法可以参考:

    2.2.3 Screen Mapping

    紧接着将经过剔除/裁剪的图元从Clip Space变换到NDC,这一步称为Perspective Division,即透视除法,通过将xyz分别除以w分量来实现,做这一步的目的是将坐标从四维的齐次坐标系变换回三维的归一化设备坐标系,NDC的坐标范围是:
    在这里插入图片描述
    接着需要将坐标从NDC空间变换到Screen Space,是一个简单的映射过程,即将xy从 [−1,1] 映射到 [0,width][0, height] 的范围,至于y轴是否flip可以自行选择哪种Convention。

    具体代码实现如下:

    // 归一化,得到屏幕坐标
    void transform_homogenize(const transform_t *ts, vector_t *y, const vector_t *x) {
    	float rhw = 1.0f / x->w;
    	y->x = (x->x * rhw + 1.0f) * ts->w * 0.5f;
    	y->y = (1.0f - x->y * rhw) * ts->h * 0.5f;
    	y->z = x->z * rhw;
    	y->w = 1.0f;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    映射到Screen Space之后,可以按需进行Backface Culling,即背面剔除,其实背面剔除可以发生在View Space( n⋅v<0 ),也可以发生在Clip Space( |v0,v1,v2|<0),也可以发生在Screen Space( S△ < 0 ),计算方法略有不同,但几何含义是一致的,唯一需要关注的是三角形顶点的顺序。背面剔除相关资料:

    2.3 Rasterization

    当图元变换到屏幕空间后就需要进行光栅化,就是将连续的点线面和均匀的栅格(像素)进行求交,计算出哪些栅格覆盖了点线面。

    2.3.1 线段光栅化算法

    常用线段光栅化的算法有如下几种:

    2.3.2 多边形光栅化算法

    常用多边形光栅化的算法有如下几种:

    2.3.3 光栅化实现

    线实现

    Bresenham是目前较为常用的线段算法,因为可以用整数计算。线段算法可以用来绘制WireFrame。

    因此我们使用Bresenham,具体实现代码如下:

    // 画点
    void device_pixel(device_t *device, int x, int y, IUINT32 color) {
    	if (((IUINT32)x) < (IUINT32)device->width && ((IUINT32)y) < (IUINT32)device->height) {
    		device->framebuffer[y][x] = color;
    	}
    }
    
    // 绘制线段
    void device_draw_line(device_t *device, int x1, int y1, int x2, int y2, IUINT32 c) {
    	int x, y, rem = 0;
    	if (x1 == x2 && y1 == y2) {
    		device_pixel(device, x1, y1, c);
    	}	else if (x1 == x2) {
    		int inc = (y1 <= y2)? 1 : -1;
    		for (y = y1; y != y2; y += inc) device_pixel(device, x1, y, c);
    		device_pixel(device, x2, y2, c);
    	}	else if (y1 == y2) {
    		int inc = (x1 <= x2)? 1 : -1;
    		for (x = x1; x != x2; x += inc) device_pixel(device, x, y1, c);
    		device_pixel(device, x2, y2, c);
    	}	else {
    		int dx = (x1 < x2)? x2 - x1 : x1 - x2;
    		int dy = (y1 < y2)? y2 - y1 : y1 - y2;
    		if (dx >= dy) {
    			if (x2 < x1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
    			for (x = x1, y = y1; x <= x2; x++) {
    				device_pixel(device, x, y, c);
    				rem += dy;
    				if (rem >= dx) {
    					rem -= dx;
    					y += (y2 >= y1)? 1 : -1;
    					device_pixel(device, x, y, c);
    				}
    			}
    			device_pixel(device, x2, y2, c);
    		}	else {
    			if (y2 < y1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
    			for (x = x1, y = y1; y <= y2; y++) {
    				device_pixel(device, x, y, c);
    				rem += dx;
    				if (rem >= dy) {
    					rem -= dy;
    					x += (x2 >= x1)? 1 : -1;
    					device_pixel(device, x, y, c);
    				}
    			}
    			device_pixel(device, x2, y2, c);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    面绘制

    绘制代码流程如下,详见注释:

    // 主渲染函数
    void device_render_trap(device_t *device, trapezoid_t *trap) {
    	scanline_t scanline;
    	int j, top, bottom;
    	top = (int)(trap->top + 0.5f);
    	bottom = (int)(trap->bottom + 0.5f);
    	for (j = top; j < bottom; j++) {
    		if (j >= 0 && j < device->height) {
    			trapezoid_edge_interp(trap, (float)j + 0.5f);
    			trapezoid_init_scan_line(trap, &scanline, j);
    			device_draw_scanline(device, &scanline);
    		}
    		if (j >= device->height) break;
    	}
    }
    
    // 根据 render_state 绘制原始三角形
    void device_draw_primitive(device_t *device, const vertex_t *v1, 
    	const vertex_t *v2, const vertex_t *v3) {
    	point_t p1, p2, p3, c1, c2, c3;
    	int render_state = device->render_state;
    
    	// 按照 Transform 变化
    	transform_apply(&device->transform, &c1, &v1->pos);
    	transform_apply(&device->transform, &c2, &v2->pos);
    	transform_apply(&device->transform, &c3, &v3->pos);
    
    	// 裁剪,注意此处可以完善为具体判断几个点在 cvv内以及同cvv相交平面的坐标比例
    	// 进行进一步精细裁剪,将一个分解为几个完全处在 cvv内的三角形
    	if (transform_check_cvv(&c1) != 0) return;
    	if (transform_check_cvv(&c2) != 0) return;
    	if (transform_check_cvv(&c3) != 0) return;
    
    	// 归一化
    	transform_homogenize(&device->transform, &p1, &c1);
    	transform_homogenize(&device->transform, &p2, &c2);
    	transform_homogenize(&device->transform, &p3, &c3);
    
    	// 纹理或者色彩绘制
    	if (render_state & (RENDER_STATE_TEXTURE | RENDER_STATE_COLOR)) {
    		vertex_t t1 = *v1, t2 = *v2, t3 = *v3;
    		trapezoid_t traps[2];
    		int n;
    
    		t1.pos = p1; 
    		t2.pos = p2;
    		t3.pos = p3;
    		t1.pos.w = c1.w;
    		t2.pos.w = c2.w;
    		t3.pos.w = c3.w;
    		
    		vertex_rhw_init(&t1);	// 初始化 w
    		vertex_rhw_init(&t2);	// 初始化 w
    		vertex_rhw_init(&t3);	// 初始化 w
    		
    		// 拆分三角形为0-2个梯形,并且返回可用梯形数量
    		n = trapezoid_init_triangle(traps, &t1, &t2, &t3);
    
    		if (n >= 1) device_render_trap(device, &traps[0]);
    		if (n >= 2) device_render_trap(device, &traps[1]);
    	}
    	 
    	if (render_state & RENDER_STATE_WIREFRAME) 
    	{		// 线框绘制
    		device_draw_line(device, (int)p1.x, (int)p1.y, (int)p2.x, (int)p2.y, device->foreground);
    		device_draw_line(device, (int)p1.x, (int)p1.y, (int)p3.x, (int)p3.y, device->foreground);
    		device_draw_line(device, (int)p3.x, (int)p3.y, (int)p2.x, (int)p2.y, device->foreground);
    	}
    }
    
    
    
    void draw_plane(device_t *device, int a, int b, int c, int d) {
    	vertex_t p1 = mesh[a], p2 = mesh[b], p3 = mesh[c], p4 = mesh[d];
    	p1.tc.u = 0, p1.tc.v = 0, p2.tc.u = 0, p2.tc.v = 1;
    	p3.tc.u = 1, p3.tc.v = 1, p4.tc.u = 1, p4.tc.v = 0;
    	device_draw_primitive(device, &p1, &p2, &p3);
    	device_draw_primitive(device, &p3, &p4, &p1);
    }
    
    void draw_box(device_t *device, float theta) {
    	matrix_t m;
    	matrix_set_rotate(&m, -1, -0.5, 1, theta);
    	device->transform.world = m;
    	transform_update(&device->transform);
    	draw_plane(device, 0, 1, 2, 3);
    	draw_plane(device, 7, 6, 5, 4);
    	draw_plane(device, 0, 4, 5, 1);
    	draw_plane(device, 1, 5, 6, 2);
    	draw_plane(device, 2, 6, 7, 3);
    	draw_plane(device, 3, 7, 4, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    其中,上述流程主要是对Mesh数据进行分类执行上述几步的步骤,在光栅化的事后还需要对三角形进行三角形分割,具体可以参照此篇文章:三角形的光栅化
    具体三角形分割算法如下:

    // 根据三角形生成 0-2 个梯形,并且返回合法梯形的数量
    int trapezoid_init_triangle(trapezoid_t *trap, const vertex_t *p1, 
    	const vertex_t *p2, const vertex_t *p3) {
    	const vertex_t *p;
    	float k, x;
    
    	if (p1->pos.y > p2->pos.y) p = p1, p1 = p2, p2 = p;
    	if (p1->pos.y > p3->pos.y) p = p1, p1 = p3, p3 = p;
    	if (p2->pos.y > p3->pos.y) p = p2, p2 = p3, p3 = p;
    	if (p1->pos.y == p2->pos.y && p1->pos.y == p3->pos.y) return 0;
    	if (p1->pos.x == p2->pos.x && p1->pos.x == p3->pos.x) return 0;
    
    	if (p1->pos.y == p2->pos.y) {	// triangle down
    		if (p1->pos.x > p2->pos.x) p = p1, p1 = p2, p2 = p;
    		trap[0].top = p1->pos.y;
    		trap[0].bottom = p3->pos.y;
    		trap[0].left.v1 = *p1;
    		trap[0].left.v2 = *p3;
    		trap[0].right.v1 = *p2;
    		trap[0].right.v2 = *p3;
    		return (trap[0].top < trap[0].bottom)? 1 : 0;
    	}
    
    	if (p2->pos.y == p3->pos.y) {	// triangle up
    		if (p2->pos.x > p3->pos.x) p = p2, p2 = p3, p3 = p;
    		trap[0].top = p1->pos.y;
    		trap[0].bottom = p3->pos.y;
    		trap[0].left.v1 = *p1;
    		trap[0].left.v2 = *p2;
    		trap[0].right.v1 = *p1;
    		trap[0].right.v2 = *p3;
    		return (trap[0].top < trap[0].bottom)? 1 : 0;
    	}
    
    	trap[0].top = p1->pos.y;
    	trap[0].bottom = p2->pos.y;
    	trap[1].top = p2->pos.y;
    	trap[1].bottom = p3->pos.y;
    
    	k = (p3->pos.y - p1->pos.y) / (p2->pos.y - p1->pos.y);
    	x = p1->pos.x + (p2->pos.x - p1->pos.x) * k;
    
    	if (x <= p3->pos.x) {		// triangle left
    		trap[0].left.v1 = *p1;
    		trap[0].left.v2 = *p2;
    		trap[0].right.v1 = *p1;
    		trap[0].right.v2 = *p3;
    		trap[1].left.v1 = *p2;
    		trap[1].left.v2 = *p3;
    		trap[1].right.v1 = *p1;
    		trap[1].right.v2 = *p3;
    	}	else {					// triangle right
    		trap[0].left.v1 = *p1;
    		trap[0].left.v2 = *p3;
    		trap[0].right.v1 = *p1;
    		trap[0].right.v2 = *p2;
    		trap[1].left.v1 = *p1;
    		trap[1].left.v2 = *p3;
    		trap[1].right.v1 = *p2;
    		trap[1].right.v2 = *p3;
    	}
    
    	return 2;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    三角形插值光栅处理

    在光栅化的同时,我们需要知道三角形内部任意一点像素所代表的顶点数据,实现方法就是插值,可以利用重心坐标系来计算,具体实现:
    a Practical Implementation (The Rasterization Stage)

    插值需要关注的一个点就是如何做到Perspective-Correct Interpolation,因为直接在屏幕空间对图元数据进行线性的插值是透视不正确的,需要改成对rhw(1/w)进行插值来纠正透视错误的问题,插值完后再乘以w来还原,详见这篇文章的推导过程:

    lowk_persp_interp_techrep

    想要观察透视正确和透视不正确的插值可以在Fragment Shader中输出uv颜色来对比直接插值和对rhw插值的区别。

    具体实现代码如下:

    typedef struct { vertex_t v, v1, v2; } edge_t;
    typedef struct { float top, bottom; edge_t left, right; } trapezoid_t;
    typedef struct { vertex_t v, step; int x, y, w; } scanline_t;
    
    
    void vertex_rhw_init(vertex_t *v) {
    	float rhw = 1.0f / v->pos.w;
    	v->rhw = rhw;
    	v->tc.u *= rhw;
    	v->tc.v *= rhw;
    	v->color.r *= rhw;
    	v->color.g *= rhw;
    	v->color.b *= rhw;
    }
    
    void vertex_interp(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float t) {
    	vector_interp(&y->pos, &x1->pos, &x2->pos, t);
    	y->tc.u = interp(x1->tc.u, x2->tc.u, t);
    	y->tc.v = interp(x1->tc.v, x2->tc.v, t);
    	y->color.r = interp(x1->color.r, x2->color.r, t);
    	y->color.g = interp(x1->color.g, x2->color.g, t);
    	y->color.b = interp(x1->color.b, x2->color.b, t);
    	y->rhw = interp(x1->rhw, x2->rhw, t);
    }
    
    void vertex_division(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float w) {
    	float inv = 1.0f / w;
    	y->pos.x = (x2->pos.x - x1->pos.x) * inv;
    	y->pos.y = (x2->pos.y - x1->pos.y) * inv;
    	y->pos.z = (x2->pos.z - x1->pos.z) * inv;
    	y->pos.w = (x2->pos.w - x1->pos.w) * inv;
    	y->tc.u = (x2->tc.u - x1->tc.u) * inv;
    	y->tc.v = (x2->tc.v - x1->tc.v) * inv;
    	y->color.r = (x2->color.r - x1->color.r) * inv;
    	y->color.g = (x2->color.g - x1->color.g) * inv;
    	y->color.b = (x2->color.b - x1->color.b) * inv;
    	y->rhw = (x2->rhw - x1->rhw) * inv;
    }
    
    void vertex_add(vertex_t *y, const vertex_t *x) {
    	y->pos.x += x->pos.x;
    	y->pos.y += x->pos.y;
    	y->pos.z += x->pos.z;
    	y->pos.w += x->pos.w;
    	y->rhw += x->rhw;
    	y->tc.u += x->tc.u;
    	y->tc.v += x->tc.v;
    	y->color.r += x->color.r;
    	y->color.g += x->color.g;
    	y->color.b += x->color.b;
    }
    
    // 按照 Y 坐标计算出左右两条边纵坐标等于 Y 的顶点
    void trapezoid_edge_interp(trapezoid_t *trap, float y) {
    	float s1 = trap->left.v2.pos.y - trap->left.v1.pos.y;
    	float s2 = trap->right.v2.pos.y - trap->right.v1.pos.y;
    	float t1 = (y - trap->left.v1.pos.y) / s1;
    	float t2 = (y - trap->right.v1.pos.y) / s2;
    	vertex_interp(&trap->left.v, &trap->left.v1, &trap->left.v2, t1);
    	vertex_interp(&trap->right.v, &trap->right.v1, &trap->right.v2, t2);
    }
    
    // 根据左右两边的端点,初始化计算出扫描线的起点和步长
    void trapezoid_init_scan_line(const trapezoid_t *trap, scanline_t *scanline, int y) {
    	float width = trap->right.v.pos.x - trap->left.v.pos.x;
    	scanline->x = (int)(trap->left.v.pos.x + 0.5f);
    	scanline->w = (int)(trap->right.v.pos.x + 0.5f) - scanline->x;
    	scanline->y = y;
    	scanline->v = trap->left.v;
    	if (trap->left.v.pos.x >= trap->right.v.pos.x) scanline->w = 0;
    	vertex_division(&scanline->step, &trap->left.v, &trap->right.v, width);
    }
    
    // 绘制扫描线
    void device_draw_scanline(device_t *device, scanline_t *scanline) {
    	IUINT32 *framebuffer = device->framebuffer[scanline->y];
    	float *zbuffer = device->zbuffer[scanline->y];
    	int x = scanline->x;
    	int w = scanline->w;
    	int width = device->width;
    	int render_state = device->render_state;
    	for (; w > 0; x++, w--) {
    		if (x >= 0 && x < width) {
    			float rhw = scanline->v.rhw;
    			if (rhw >= zbuffer[x]) {	
    				float w = 1.0f / rhw;
    				zbuffer[x] = rhw;
    				if (render_state & RENDER_STATE_COLOR) {
    					float r = scanline->v.color.r * w;
    					float g = scanline->v.color.g * w;
    					float b = scanline->v.color.b * w;
    					int R = (int)(r * 255.0f);
    					int G = (int)(g * 255.0f);
    					int B = (int)(b * 255.0f);
    					R = CMID(R, 0, 255);
    					G = CMID(G, 0, 255);
    					B = CMID(B, 0, 255);
    					framebuffer[x] = (R << 16) | (G << 8) | (B);
    				}
    				if (render_state & RENDER_STATE_TEXTURE) {
    					float u = scanline->v.tc.u * w;
    					float v = scanline->v.tc.v * w;
    					IUINT32 cc = device_texture_read(device, u, v);
    					framebuffer[x] = cc;
    				}
    			}
    		}
    		vertex_add(&scanline->v, &scanline->step);
    		if (x >= width) break;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    其中,设计到纹理的处理,我们可以简单的看一下纹理的设置与读取,实现代码可见下代码:

    // 设置当前纹理
    void device_set_texture(device_t *device, void *bits, long pitch, int w, int h) {
    	char *ptr = (char*)bits;
    	int j;
    	assert(w <= 1024 && h <= 1024);
    	for (j = 0; j < h; ptr += pitch, j++) 	// 重新计算每行纹理的指针
    		device->texture[j] = (IUINT32*)ptr;
    	device->tex_width = w;
    	device->tex_height = h;
    	device->max_u = (float)(w - 1);
    	device->max_v = (float)(h - 1);
    }
    
    void init_texture(device_t *device) {
    	static IUINT32 texture[256][256];
    	int i, j;
    	for (j = 0; j < 256; j++) {
    		for (i = 0; i < 256; i++) {
    			int x = i / 32, y = j / 32;
    			texture[j][i] = ((x + y) & 1)? 0xff0000 : 0xffffff;
    		}
    	}
    	device_set_texture(device, texture, 256 * 4, 256, 256);
    }
    
    // 根据坐标读取纹理
    IUINT32 device_texture_read(const device_t *device, float u, float v) {
    	int x, y;
    	u = u * device->max_u;
    	v = v * device->max_v;
    	x = (int)(u + 0.5f);
    	y = (int)(v + 0.5f);
    	x = CMID(x, 0, device->tex_width - 1);
    	y = CMID(y, 0, device->tex_height - 1);
    	return device->texture[y][x];
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    可以看出来纹理的处理其实很简单就是二维数组的读写,当然GPU做了很多工作让纹理的处理十分高效。

    以上就是软光栅全部的具体工作原理。

    在这里插入图片描述

    其中相关数学库如下代码实现:

    //=====================================================================
    // 数学库
    //=====================================================================
    typedef struct { float m[4][4]; } matrix_t;
    typedef struct { float x, y, z, w; } vector_t;
    typedef vector_t point_t;
    
    int CMID(int x, int min, int max) { return (x < min)? min : ((x > max)? max : x); }
    
    // 计算插值:t 为 [0, 1] 之间的数值
    float interp(float x1, float x2, float t) { return x1 + (x2 - x1) * t; }
    
    // | v |
    float vector_length(const vector_t *v) {
    	float sq = v->x * v->x + v->y * v->y + v->z * v->z;
    	return (float)sqrt(sq);
    }
    
    // z = x + y
    void vector_add(vector_t *z, const vector_t *x, const vector_t *y) {
    	z->x = x->x + y->x;
    	z->y = x->y + y->y;
    	z->z = x->z + y->z;
    	z->w = 1.0;
    }
    
    // z = x - y
    void vector_sub(vector_t *z, const vector_t *x, const vector_t *y) {
    	z->x = x->x - y->x;
    	z->y = x->y - y->y;
    	z->z = x->z - y->z;
    	z->w = 1.0;
    }
    
    // 矢量点乘
    float vector_dotproduct(const vector_t *x, const vector_t *y) {
    	return x->x * y->x + x->y * y->y + x->z * y->z;
    }
    
    // 矢量叉乘
    void vector_crossproduct(vector_t *z, const vector_t *x, const vector_t *y) {
    	float m1, m2, m3;
    	m1 = x->y * y->z - x->z * y->y;
    	m2 = x->z * y->x - x->x * y->z;
    	m3 = x->x * y->y - x->y * y->x;
    	z->x = m1;
    	z->y = m2;
    	z->z = m3;
    	z->w = 1.0f;
    }
    
    // 矢量插值,t取值 [0, 1]
    void vector_interp(vector_t *z, const vector_t *x1, const vector_t *x2, float t) {
    	z->x = interp(x1->x, x2->x, t);
    	z->y = interp(x1->y, x2->y, t);
    	z->z = interp(x1->z, x2->z, t);
    	z->w = 1.0f;
    }
    
    // 矢量归一化
    void vector_normalize(vector_t *v) {
    	float length = vector_length(v);
    	if (length != 0.0f) {
    		float inv = 1.0f / length;
    		v->x *= inv; 
    		v->y *= inv;
    		v->z *= inv;
    	}
    }
    
    // c = a + b
    void matrix_add(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    	int i, j;
    	for (i = 0; i < 4; i++) {
    		for (j = 0; j < 4; j++)
    			c->m[i][j] = a->m[i][j] + b->m[i][j];
    	}
    }
    
    // c = a - b
    void matrix_sub(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    	int i, j;
    	for (i = 0; i < 4; i++) {
    		for (j = 0; j < 4; j++)
    			c->m[i][j] = a->m[i][j] - b->m[i][j];
    	}
    }
    
    // c = a * b
    void matrix_mul(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    	matrix_t z;
    	int i, j;
    	for (i = 0; i < 4; i++) {
    		for (j = 0; j < 4; j++) {
    			z.m[j][i] = (a->m[j][0] * b->m[0][i]) +
    						(a->m[j][1] * b->m[1][i]) +
    						(a->m[j][2] * b->m[2][i]) +
    						(a->m[j][3] * b->m[3][i]);
    		}
    	}
    	c[0] = z;
    }
    
    // c = a * f
    void matrix_scale(matrix_t *c, const matrix_t *a, float f) {
    	int i, j;
    	for (i = 0; i < 4; i++) {
    		for (j = 0; j < 4; j++) 
    			c->m[i][j] = a->m[i][j] * f;
    	}
    }
    
    // y = x * m
    void matrix_apply(vector_t *y, const vector_t *x, const matrix_t *m) {
    	float X = x->x, Y = x->y, Z = x->z, W = x->w;
    	y->x = X * m->m[0][0] + Y * m->m[1][0] + Z * m->m[2][0] + W * m->m[3][0];
    	y->y = X * m->m[0][1] + Y * m->m[1][1] + Z * m->m[2][1] + W * m->m[3][1];
    	y->z = X * m->m[0][2] + Y * m->m[1][2] + Z * m->m[2][2] + W * m->m[3][2];
    	y->w = X * m->m[0][3] + Y * m->m[1][3] + Z * m->m[2][3] + W * m->m[3][3];
    }
    
    void matrix_set_identity(matrix_t *m) {
    	m->m[0][0] = m->m[1][1] = m->m[2][2] = m->m[3][3] = 1.0f; 
    	m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
    	m->m[1][0] = m->m[1][2] = m->m[1][3] = 0.0f;
    	m->m[2][0] = m->m[2][1] = m->m[2][3] = 0.0f;
    	m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
    }
    
    void matrix_set_zero(matrix_t *m) {
    	m->m[0][0] = m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
    	m->m[1][0] = m->m[1][1] = m->m[1][2] = m->m[1][3] = 0.0f;
    	m->m[2][0] = m->m[2][1] = m->m[2][2] = m->m[2][3] = 0.0f;
    	m->m[3][0] = m->m[3][1] = m->m[3][2] = m->m[3][3] = 0.0f;
    }
    
    // 平移变换
    void matrix_set_translate(matrix_t *m, float x, float y, float z) {
    	matrix_set_identity(m);
    	m->m[3][0] = x;
    	m->m[3][1] = y;
    	m->m[3][2] = z;
    }
    
    // 缩放变换
    void matrix_set_scale(matrix_t *m, float x, float y, float z) {
    	matrix_set_identity(m);
    	m->m[0][0] = x;
    	m->m[1][1] = y;
    	m->m[2][2] = z;
    }
    
    // 旋转矩阵
    void matrix_set_rotate(matrix_t *m, float x, float y, float z, float theta) {
    	float qsin = (float)sin(theta * 0.5f);
    	float qcos = (float)cos(theta * 0.5f);
    	vector_t vec = { x, y, z, 1.0f };
    	float w = qcos;
    	vector_normalize(&vec);
    	x = vec.x * qsin;
    	y = vec.y * qsin;
    	z = vec.z * qsin;
    	m->m[0][0] = 1 - 2 * y * y - 2 * z * z;
    	m->m[1][0] = 2 * x * y - 2 * w * z;
    	m->m[2][0] = 2 * x * z + 2 * w * y;
    	m->m[0][1] = 2 * x * y + 2 * w * z;
    	m->m[1][1] = 1 - 2 * x * x - 2 * z * z;
    	m->m[2][1] = 2 * y * z - 2 * w * x;
    	m->m[0][2] = 2 * x * z - 2 * w * y;
    	m->m[1][2] = 2 * y * z + 2 * w * x;
    	m->m[2][2] = 1 - 2 * x * x - 2 * y * y;
    	m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
    	m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;	
    	m->m[3][3] = 1.0f;
    }
    
    // 设置摄像机
    void matrix_set_lookat(matrix_t *m, const vector_t *eye, const vector_t *at, const vector_t *up) {
    	vector_t xaxis, yaxis, zaxis;
    
    	vector_sub(&zaxis, at, eye);
    	vector_normalize(&zaxis);
    	vector_crossproduct(&xaxis, up, &zaxis);
    	vector_normalize(&xaxis);
    	vector_crossproduct(&yaxis, &zaxis, &xaxis);
    
    	m->m[0][0] = xaxis.x;
    	m->m[1][0] = xaxis.y;
    	m->m[2][0] = xaxis.z;
    	m->m[3][0] = -vector_dotproduct(&xaxis, eye);
    
    	m->m[0][1] = yaxis.x;
    	m->m[1][1] = yaxis.y;
    	m->m[2][1] = yaxis.z;
    	m->m[3][1] = -vector_dotproduct(&yaxis, eye);
    
    	m->m[0][2] = zaxis.x;
    	m->m[1][2] = zaxis.y;
    	m->m[2][2] = zaxis.z;
    	m->m[3][2] = -vector_dotproduct(&zaxis, eye);
    	
    	m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
    	m->m[3][3] = 1.0f;
    }
    
    // D3DXMatrixPerspectiveFovLH
    void matrix_set_perspective(matrix_t *m, float fovy, float aspect, float zn, float zf) {
    	float fax = 1.0f / (float)tan(fovy * 0.5f);
    	matrix_set_zero(m);
    	m->m[0][0] = (float)(fax / aspect);
    	m->m[1][1] = (float)(fax);
    	m->m[2][2] = zf / (zf - zn);
    	m->m[3][2] = - zn * zf / (zf - zn);
    	m->m[2][3] = 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
  • 相关阅读:
    multiprocessing 让子进程忽略信号,手动关闭子进程
    了解OLED显示屏的工作原理
    JAVA电子病历系统计算机毕业设计Mybatis+系统+数据库+调试部署
    [洛谷] P1143 进制转换
    Vmware下的虚拟机NAT连接后仍然木有网络
    理解LoadRunner,基于此工具进行后端性能测试的详细过程(下)
    一文速通 css3 2D转换【内含开发常用效果实现】
    vulfocus靶场tomcat-cve_2017_12615 文件上传
    手把手教你用Java获取IP归属地
    云南民族文化旅游网页设计制作 简单静态HTML网页作品 我的家乡网页作业成品 学生旅游网站模板
  • 原文地址:https://blog.csdn.net/qq_35312463/article/details/126050433