欢迎来到NiceSpace!祝大家开心每一天!
  • C++
  • 图形学
3D图形学总结(九)—3D裁剪

    在渲染管线中,物体经过世界变换,相机变换,物体剔除,剩下的物体就是我们要进行渲染的,我们把这些物体加入渲染列表中,之后我们应该继续进行透视变化。

    但是进行透视变换之前,我们应该会想到有些物体虽然没有被剔除,但是只有一部分在视景体中,另一部分还在视景体外,这样组成物体网格的三角形加入渲染列表中后,有一些三角形是完全不在视景体中的,还有一些三角形是部分在视景体中的。对于那些完全不在视景体中的如果我们继续在渲染管线中渲染他们完全是浪费时间与效率。而另一些部分在视景体中的不处理也会产生我们不想要的结果,比如说三角形一部分在视景体中一部分在相机后面,如果直接进行透视变换后会导致错误的结果,三角形是反的。基于以上的这些所以在物体加入渲染列表后进行透视坐标前我们需要对在渲染列表中的三角形进行3D裁剪处理,以保证避免后续无用的计算和错误的结果。

    进行3D裁剪,我们主要根据远近,左右,上下裁剪面这6个面来进行计算,如图:


    根据图示我们可以看出裁剪需要进行的一些处理:

    首先,对于那些完全在视景体外的三角形我们完全干掉它,一个三角形v (v0, v1, v2),对于远近裁剪面,我们可以简单对三个点v0, v1, v2的z方向坐标与远近裁剪面的z坐标进行比较,3个点都大于远裁剪面的或者3个点都小于近裁剪面的剔除掉。对于左右裁剪面,我们可以根据相机的视平面宽度和视距来计算出左裁剪面斜率kl,,然后v的三个点的x坐标v0.x,v1.x,v2.x分别与左裁剪面的x坐标v0.z*kl,v1.z*kl,v2.z*kl做比较,三个点均大于则表示全在裁剪面外侧,右裁剪面同理。上下裁剪面的计算方式与左右裁剪面类似,只是将x分量变为了y分量,计算斜率时视平面宽度改成了视平面高度。

    将所有不在视景体中的三角形剔除后我们考虑部分在视景体中的三角形。

    对于远裁剪面,如果三角形有部分在远裁剪面外,我们可以不做处理,因为这种三角形映射视平面后并不会有什么问题,进行裁剪反而需要经过不少计算,裁剪后还会产生更多的三角形,降低效率。

    对于上下,左右裁剪面,如果三角形有部分在裁剪面外面,映射到视平面时也不会有什么问题,而裁剪需要更多的计算,还可能会导致渲染列表中的三角形数量增加,降低效率。而我们在进行对三角形进行着色渲染时,我们是需要对视口(屏幕)进行边界处理的,不在视口内的像素不进行渲染,所以我们可以不对这类的三角形进行处理,让他们最后映射到视口上,渲染时会进行处理。

    对于近裁剪面,这部分才是裁剪的重点部分,因为相机空间的三角形如果有部分在视平面后面后者相机后面,都会产生错乱的屏幕坐标,渲染后导致错误的画面,所以对于这类三角形需要对其进行近裁剪面的处理。

    近裁剪面裁剪:

    可以分为两种情况,如图:

    情形一:三角形有一个顶点在视景体中,两个顶点在近裁剪面外。这种情况比较简单,裁剪后并不会产生新的三角形。根据近裁剪面的坐标near_z可以很容易计算出新的顶点v01和v02的坐标,v01.x = v0.x + ((near_z - v0.z) / (v1.z - v0.z)) * (v1.x - v0.x),同理求y,顶点v02同理,求得后的v01和v02替代三角形顶点v1和v2

    情形二:三角形有两个顶点在视景体中,一个顶点在近裁剪面外。这种情形稍微复杂些,因为经过裁剪后三角形由3个顶点变成了4个顶点,成为了4边形,所以需要对这个四边形进行切割。首先我们可以根据情形一中的方法求得v01, v02顶点坐标,然后将v01顶点坐标覆盖原三角形v0顶点,然后新创建一个三角形面顶点分别为v2, v01, v02,将新的三角形面加入到渲染列表中,这样就完成了裁剪。

    源码:

/*相机坐标空间内3D裁剪
先对上下左右远近裁剪面进行简单接受/剔除
然后对近裁剪面进行裁剪*/
void ClipPolysRenderlist(RENDERLIST4DV2_PTR renderlist, CAM4DV1_PTR cam, int cull_flags)
{
	int last_poly_index = renderlist->num_polys;
 
	//x与y方向裁剪面斜率
	float z_factorx = 0.5 * cam->viewplane_width / cam->view_dist;
	float z_factory = 0.5 * cam->viewplane_height / cam->view_dist;
	float z_test[3] = {0};
	float t_param1 = 0, t_param2 = 0;//参数化
	float x1, y1, x2, y2;//交点
	float tu1, tv1, tu2, tv2;//纹理坐标
	float nx1, ny1, nz1, nx2, ny2, nz2;//顶点法线
	int nums_in = 0;
	int index = 0;//循环索引
	int v0 = 0, v1 = 1, v2 = 2; //三角形顶点索引
	VECTOR4D u, v, n;//计算法线临时变量
	POLYF4DV2 temp_poly;//裁剪新增的面
 
	for (int poly = 0; poly < last_poly_index; poly++)
	{
		POLYF4DV2_PTR cur_poly = &renderlist->poly_data[poly];
		//判断是否裁剪平面
		if ((cur_poly == NULL) || (!(cur_poly->state & POLY4DV2_STATE_ACTIVE))
			|| (cur_poly->state & POLY4DV2_STATE_BACKFACE) || (cur_poly->state & POLY4DV2_STATE_CLIPPED))
			continue;
 
		if (cull_flags & CULL_OBJECT_X_PLANE) //左右裁剪面
		{
			for (index = 0; index < 3; index++)
			{
				z_test[index] = z_factorx * cur_poly->tvlist[index].z;
			}
			//裁剪
			if (((cur_poly->tvlist[0].x > z_test[0]) &&
				(cur_poly->tvlist[1].x > z_test[1]) &&
				(cur_poly->tvlist[2].x > z_test[2])) ||
				(cur_poly->tvlist[0].x < -z_test[0]) &&
				(cur_poly->tvlist[1].x < -z_test[1]) &&
				(cur_poly->tvlist[2].x < -z_test[2]))
			{
				SET_BIT(cur_poly->state, POLY4DV2_STATE_CLIPPED);
				continue;
			}
		}
		if (cull_flags & CULL_OBJECT_Y_PLANE) //上下裁剪面
		{
			for (int i = 0; i < 3; i++)
			{
				z_test[i] = z_factory * cur_poly->tvlist[i].z;
			}
			//裁剪
			if (((cur_poly->tvlist[0].y > z_test[0]) &&
				(cur_poly->tvlist[1].y > z_test[1]) &&
				(cur_poly->tvlist[2].y > z_test[2])) ||
				(cur_poly->tvlist[0].y < -z_test[0]) &&
				(cur_poly->tvlist[1].y < -z_test[1]) &&
				(cur_poly->tvlist[2].y < -z_test[2]))
			{
				SET_BIT(cur_poly->state, POLY4DV2_STATE_CLIPPED);
				continue;
			}
		}
		if (cull_flags & CULL_OBJECT_Z_PLANE) //远近裁剪面
		{
			//裁剪
			if (((cur_poly->tvlist[0].z > cam->far_clip_z) &&
				(cur_poly->tvlist[1].z > cam->far_clip_z) &&
				(cur_poly->tvlist[2].z > cam->far_clip_z)) ||
				(cur_poly->tvlist[0].z < cam->near_clip_z) &&
				(cur_poly->tvlist[1].z < cam->near_clip_z) &&
				(cur_poly->tvlist[2].z < cam->near_clip_z))
			{
				SET_BIT(cur_poly->state, POLY4DV2_STATE_CLIPPED);
				continue;
			}
		}
		//对近裁剪面进行3D裁剪
		//判断有几个顶点在近裁剪面内
		nums_in = 0;
		for (index = 0; index < 3; index++)
		{
			if (cur_poly->tvlist[index].z >= cam->near_clip_z)
				nums_in++;
		}
		//如果有一个顶点在近裁剪面内部
		if (nums_in == 1)
		{
			//判断是哪个顶点在内部
			if (cur_poly->tvlist[0].z >= cam->near_clip_z)
			{
				v0 = 0; v1 = 1; v2 = 2;
			}
			else if (cur_poly->tvlist[1].z >= cam->near_clip_z)
			{
				v0 = 1; v1 = 2; v2 = 0;
			}
			else
			{
				v0 = 2; v1 = 0; v2 = 1;
			}
			//v0->v1求得参数t
			t_param1 = (cam->near_clip_z - cur_poly->tvlist[v0].z) / (cur_poly->tvlist[v1].z - cur_poly->tvlist[v0].z);
			//交点
			x1 = cur_poly->tvlist[v0].x + (cur_poly->tvlist[v1].x - cur_poly->tvlist[v0].x) * t_param1;
			y1 = cur_poly->tvlist[v0].y + (cur_poly->tvlist[v1].y - cur_poly->tvlist[v0].y) * t_param1;
			//覆盖v1
			cur_poly->tvlist[v1].x = x1;
			cur_poly->tvlist[v1].y = y1;
			cur_poly->tvlist[v1].z = cam->near_clip_z;
			//重新计算顶点法线
			nx1 = cur_poly->tvlist[v0].nx + (cur_poly->tvlist[v1].nx - cur_poly->tvlist[v0].nx) * t_param1;
			ny1 = cur_poly->tvlist[v0].ny + (cur_poly->tvlist[v1].ny - cur_poly->tvlist[v0].ny) * t_param1;
			nz1 = cur_poly->tvlist[v0].nz + (cur_poly->tvlist[v1].nz - cur_poly->tvlist[v0].nz) * t_param1;
			cur_poly->tvlist[v1].nx = nx1;
			cur_poly->tvlist[v1].ny = ny1;
			cur_poly->tvlist[v1].nz = nz1;
			//v0->v2求得参数t
			t_param2 = (cam->near_clip_z - cur_poly->tvlist[v0].z) / (cur_poly->tvlist[v2].z - cur_poly->tvlist[v0].z);
			//交点
			x2 = cur_poly->tvlist[v0].x + (cur_poly->tvlist[v2].x - cur_poly->tvlist[v0].x) * t_param2;
			y2 = cur_poly->tvlist[v0].y + (cur_poly->tvlist[v2].y - cur_poly->tvlist[v0].y) * t_param2;
			//覆盖v2
			cur_poly->tvlist[v2].x = x2;
			cur_poly->tvlist[v2].y = y2;
			cur_poly->tvlist[v2].z = cam->near_clip_z;
			//法线
			nx2 = cur_poly->tvlist[v0].nx + (cur_poly->tvlist[v2].nx - cur_poly->tvlist[v0].nx) * t_param2;
			ny2 = cur_poly->tvlist[v0].ny + (cur_poly->tvlist[v2].ny - cur_poly->tvlist[v0].ny) * t_param2;
			nz2 = cur_poly->tvlist[v0].nz + (cur_poly->tvlist[v2].nz - cur_poly->tvlist[v0].nz) * t_param2;
			cur_poly->tvlist[v2].nx = nx2;
			cur_poly->tvlist[v2].ny = ny2;
			cur_poly->tvlist[v2].nz = nz2;
			//判断纹理
			if (cur_poly->attr & POLY4DV2_ATTR_SHAD_MODE_TEXTURE)
			{
				//v1交点纹理
				tu1 = cur_poly->tvlist[v0].u0 + t_param1 * (cur_poly->tvlist[v1].u0 - cur_poly->tvlist[v0].u0);
				tv1 = cur_poly->tvlist[v0].v0 + t_param1 * (cur_poly->tvlist[v1].v0 - cur_poly->tvlist[v0].v0);
				//覆盖v1
				cur_poly->tvlist[v1].u0 = tu1;
				cur_poly->tvlist[v1].v0 = tv1;
				//v2交点纹理
				tu2 = cur_poly->tvlist[v0].u0 + t_param2 * (cur_poly->tvlist[v2].u0 - cur_poly->tvlist[v0].u0);
				tv2 = cur_poly->tvlist[v0].v0 + t_param2 * (cur_poly->tvlist[v2].v0 - cur_poly->tvlist[v0].v0);
				//覆盖v2
				cur_poly->tvlist[v2].u0 = tu2;
				cur_poly->tvlist[v2].v0 = tv2;
			}
			//重新计算法线长度
			VECTOR4D_SUB(&cur_poly->tvlist[1].v, &cur_poly->tvlist[0].v, &u);
			VECTOR4D_SUB(&cur_poly->tvlist[2].v, &cur_poly->tvlist[0].v, &v);
			VECTOR4D_CROSS(&u, &v, &n);
			cur_poly->nlength = VECTOR4D_Length(&n);
		}
		else if (nums_in == 2)
		{
			//将当前面复制到新增面中
			memcpy((void*)&temp_poly, (void*)cur_poly, sizeof(POLYF4DV2));
 
			if (cur_poly->tvlist[0].z < cam->near_clip_z)
			{
				v0 = 0; v1 = 1; v2 = 2;
			}
			else if (cur_poly->tvlist[1].z < cam->near_clip_z)
			{
				v0 = 1; v1 = 2; v2 = 0;
			}
			else
			{
				v0 = 2; v1 = 0; v2 = 1;
			}
 
			//求得参数t
			t_param1 = (cam->near_clip_z - cur_poly->tvlist[v0].z) / (cur_poly->tvlist[v1].z - cur_poly->tvlist[v0].z);
			t_param2 = (cam->near_clip_z - cur_poly->tvlist[v0].z) / (cur_poly->tvlist[v2].z - cur_poly->tvlist[v0].z);
			//v0->v1交点
			x1 = cur_poly->tvlist[v0].x + t_param1 *(cur_poly->tvlist[v1].x - cur_poly->tvlist[v0].x);
			y1 = cur_poly->tvlist[v0].y + t_param1 *(cur_poly->tvlist[v1].y - cur_poly->tvlist[v0].y);
			//v0->v2交点
			x2 = cur_poly->tvlist[v0].x + t_param2 *(cur_poly->tvlist[v2].x - cur_poly->tvlist[v0].x);
			y2 = cur_poly->tvlist[v0].y + t_param2 *(cur_poly->tvlist[v2].y - cur_poly->tvlist[v0].y);
			//v0->v1法线
			nx1 = cur_poly->tvlist[v0].nx + (cur_poly->tvlist[v1].nx - cur_poly->tvlist[v0].nx) * t_param1;
			ny1 = cur_poly->tvlist[v0].ny + (cur_poly->tvlist[v1].ny - cur_poly->tvlist[v0].ny) * t_param1;
			nz1 = cur_poly->tvlist[v0].nz + (cur_poly->tvlist[v1].nz - cur_poly->tvlist[v0].nz) * t_param1;
			//v0->v2法线
			nx2 = cur_poly->tvlist[v0].nx + (cur_poly->tvlist[v2].nx - cur_poly->tvlist[v0].nx) * t_param2;
			ny2 = cur_poly->tvlist[v0].ny + (cur_poly->tvlist[v2].ny - cur_poly->tvlist[v0].ny) * t_param2;
			nz2 = cur_poly->tvlist[v0].nz + (cur_poly->tvlist[v2].nz - cur_poly->tvlist[v0].nz) * t_param2;
			
			//v0->v1交点与法线覆盖v0
			cur_poly->tvlist[v0].x = x1;
			cur_poly->tvlist[v0].y = y1;
			cur_poly->tvlist[v0].z = cam->near_clip_z;
			cur_poly->tvlist[v0].nx = nx1;
			cur_poly->tvlist[v0].ny = ny1;
			cur_poly->tvlist[v0].nz = nz1;
 
			//v0->v1交点覆盖新面的v1
			temp_poly.tvlist[v1].x = x1;
			temp_poly.tvlist[v1].y = y1;
			temp_poly.tvlist[v1].z = cam->near_clip_z;
			temp_poly.tvlist[v1].nx = nx1;
			temp_poly.tvlist[v1].ny = ny1;
			temp_poly.tvlist[v1].nz = nz1;
			//v0->v2交点覆盖新面的v0
			temp_poly.tvlist[v0].x = x2;
			temp_poly.tvlist[v0].y = y2;
			temp_poly.tvlist[v0].z = cam->near_clip_z;
			temp_poly.tvlist[v0].nx = nx2;
			temp_poly.tvlist[v0].ny = ny2;
			temp_poly.tvlist[v0].nz = nz2;
 
			if (cur_poly->attr & POLY4DV2_ATTR_SHAD_MODE_TEXTURE)
			{
				//v1交点纹理
				tu1 = cur_poly->tvlist[v0].u0 + t_param1 * (cur_poly->tvlist[v1].u0 - cur_poly->tvlist[v0].u0);
				tv1 = cur_poly->tvlist[v0].v0 + t_param1 * (cur_poly->tvlist[v1].v0 - cur_poly->tvlist[v0].v0);
				//v2交点纹理
				tu2 = cur_poly->tvlist[v0].u0 + t_param2 * (cur_poly->tvlist[v2].u0 - cur_poly->tvlist[v0].u0);
				tv2 = cur_poly->tvlist[v0].v0 + t_param2 * (cur_poly->tvlist[v2].v0 - cur_poly->tvlist[v0].v0);
 
				cur_poly->tvlist[v0].u0 = tu1;
				cur_poly->tvlist[v0].v0 = tv1;
 
				temp_poly.tvlist[v1].u0 = tu1;
				temp_poly.tvlist[v1].v0 = tv1;
				temp_poly.tvlist[v0].u0 = tu2;
				temp_poly.tvlist[v0].v0 = tv2;
			}
			//重新计算法线长度
			VECTOR4D_SUB(&cur_poly->tvlist[1].v, &cur_poly->tvlist[0].v, &u);
			VECTOR4D_SUB(&cur_poly->tvlist[2].v, &cur_poly->tvlist[0].v, &v);
			VECTOR4D_CROSS(&u, &v, &n);
			cur_poly->nlength = VECTOR4D_Length(&n);
 
			VECTOR4D_SUB(&temp_poly.tvlist[1].v, &temp_poly.tvlist[0].v, &u);
			VECTOR4D_SUB(&temp_poly.tvlist[2].v, &temp_poly.tvlist[0].v, &v);
			VECTOR4D_CROSS(&u, &v, &n);
			temp_poly.nlength = VECTOR4D_Length(&n);
 
			//将新面加入到渲染列表中
			InsertPolyToRenderlist(renderlist, &temp_poly);
		}
	}
}

 

随机文章
一个简单的CSS加载动画 基于Assimp的骨骼动画实现 Django本地配置ckeditor(windows系统) Apache+mod_wsgi本地部署Django(Windows系统) 3D图形学总结(七)—Gouraud着色和仿射纹理映射
推荐文章