OPENGL 射线拾取法

鉴于之前OPENGL的基础功能应用开发差不多了,所以就准备加入交互模式,但是opengl使用射线拾取方法的网上资料也不多,故此主要参考了DX9的方法并记录之。

环境如之前,矩阵运算库GLM

虽然opengl有名字栈空间的方法选择,但是这种方式拾取的是范围的,一点都不精确。

因为之前接触过DX9所以自然就知道射线拾取方法,毕竟这个是可以精确到对应面片的,同时这个方法和FPS射击类游戏中的弹轨命中是如此的相似。

原理:就是在鼠标点击的投影视口内以点击坐标为起点产生一个垂直于视口面的射线,然后检测与这条射线所通过的空间内的物体是否有相交。所以就产生了两个问题需要解决:

1.由鼠标点击产生的射线

2.相交检测,及交点是否在对应面片内

一、坐标变换

常规的方法就是鼠标点击了,鼠标点选的是最终投影在视口上的二维坐标,故要通过透视和视口矩阵逆变化成一个世界坐标,具体方法opengl 有 gluUnProject方法,DX虽然没有提供API但是有直接的逆运算过程。

下面直接给出两种方式的代码,实际测试下来两种方法偏差在0.002左右:

//转换鼠标位置到3维空间
glm::vec3 getViewPos(float x, float y, glm::mat4 pro, glm::mat4 view)
{
	GLint viewPort[4] = { 0, 0, SCR_WIDTH, SCR_HEIGHT };
	GLdouble modelView[16] = {
		view[0][0],
		view[0][1],
		view[0][2],
		view[0][3],
		view[1][0],
		view[1][1],
		view[1][2],
		view[1][3],
		view[2][0],
		view[2][1],
		view[2][2],
		view[2][3],
		view[3][0],
		view[3][1],
		view[3][2],
		view[3][3]
	};
	GLdouble projection[16] = {
		pro[0][0],
		pro[0][1],
		pro[0][2],
		pro[0][3],
		pro[1][0],
		pro[1][1],
		pro[1][2],
		pro[1][3],
		pro[2][0],
		pro[2][1],
		pro[2][2],
		pro[2][3],
		pro[3][0],
		pro[3][1],
		pro[3][2],
		pro[3][3]
	};
	//将glm::mat4类型转化为方法所需的数组类型


	int mouse_x = x;
	int mouse_y = SCR_HEIGHT - y - 1;
	GLfloat win_x = (float)mouse_x;
	GLfloat win_y = (float)mouse_y;
	GLfloat win_z;
	GLdouble object_x, object_y, object_z;

	glReadBuffer(GL_BACK);
	glReadPixels(mouse_x, mouse_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &win_z);
	//使用gluUnProject方法将结果传入object_x,object_y,object_z中
	gluUnProject((GLdouble)win_x, (GLdouble)win_y, (GLdouble)win_z, modelView, projection, viewPort, &object_x, &object_y, &object_z);
	glm::vec3 p = glm::vec3(object_x, object_y, object_z);
	std::cout << "1.mouse =>3D Coord " << p.x << ",y:" << p.y << ",z:" << p.z << std::endl;

    //方法二直接逆运算
	float z = 1.0f;
	float xr = (SCR_WIDTH) / 2.0f;
	float yr = (SCR_HEIGHT) / 2.0f;
	x = (mouse_x - xr)/ xr;
	y = (mouse_y - yr) / yr;

	glm::vec3 ray_nds = glm::vec3(x, y, z);
	glm::vec4 ray_clip = glm::vec4(ray_nds.x, ray_nds.y, ray_nds.z, 1.0f);
	glm::vec4 ray_eye = glm::inverse(pro) * ray_clip;
	glm::vec4 ray_world = glm::inverse(view) * ray_eye;
	if (ray_world.w != 0.0)
	{
		ray_world.x /= ray_world.w;
		ray_world.y /= ray_world.w;
		ray_world.z /= ray_world.w;
	}
	p = glm::vec3(ray_world.x, ray_world.y, ray_world.z);
	std::cout << "2.mouse =>3D Coord " << p.x << ",y:" << p.y << ",z:" << p.z << std::endl;
	return p;
}

通过上面的计算就已经有了射线的起点和方向了。

二、相交检测

先上一些数学概念的几何应用理解,这将有助于观看计算过程的多方面理解:

向量的点乘,也叫向量的内积、数量积,对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作,点乘的结果是一个标量。
点乘的几何意义是可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影


两个向量的叉乘,又叫向量积、外积、叉积,叉乘的运算结果是一个向量而不是一个标量。
并且两个向量的叉积与这两个向量组成的坐标平面垂直。

叉乘几何意义
在三维几何中,向量a和向量b的叉乘结果是一个向量,更为熟知的叫法是法向量,该向量垂直于a和b向量构成的平面。

相交检测就涉及到数学了,高中的矩阵相关的几何知识,证明过程笔者就不赘述,毕竟水平有限,同时网上有很多相关的数学原理说明,就不班门弄斧了,可以参考一下链接,除了数学原理过程,包括代码照抄就可以了,笔者是直接ctrl+C   ctrl+V 以下链接中的代码。

射线和三角形的相交检测:https://www.cnblogs.com/graphics/archive/2010/08/09/1795348.html

判断点是否在三角形内 :https://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html
笔者采用的是方法三的重心法,所以也是直拷贝的第三种方式的代码。

当然抄完上面的代码是不能直接用的,还需要做个整合,重新大概理解一下上面链接中的原理如下:

1.射线在坐标中的矢量表达式1(射线参数方程)

2.面在空间中的定义表达式2(面参数方程)

有了这两个表达式之后,相交的理解就是 1的表达式值域 与 2的表达式值域中存在同一个值,

换句话说存在一个值使表达式1和表达式2同时满足。

同时上面抄的代码中,已经计算出了射线方程的参数t,这个t 就是交点处的t,通过t即可以算出交点,有了交点就可以和模型面片计算校验了,代码如下:

//面片拣选算法
bool hasPickingFace(glm::vec3 d, glm::vec3 cameraPos, glm::vec3 pos, unsigned *indicess, unsigned indlen, float* vaGrps)
{
	int index = 0;
	/*  首先遍历顶点索引数组,获取每个面片的顶点位置信息  因为采用的三角网格模型,所以每次遍历3个顶点  */
	for (int i = 0; i < indlen; i += 3) {

		/*注意因为默认都是模型在(0,0,0)时的坐标,所以都要加上pos,变换到模型所在位置*/
		index = indicess[i] * 3;
		glm::vec3 v1 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;
		index = indicess[i + 1] * 3;
		glm::vec3 v2 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;
		index = indicess[i + 2] * 3;
		glm::vec3 v3 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;

		std::cout << "..................................cur test  :" << i / 3 << std::endl;
		std::cout << "V1: " << v1[0] << "," << v1[1] << "," << v1[2] << std::endl;
		std::cout << "V2: " << v2[0] << "," << v2[1] << "," << v2[2] << std::endl;
		std::cout << "V3: " << v3[0] << "," << v3[1] << "," << v3[2] << std::endl;


		float T, U, V;
		if (IntersectTriangle(cameraPos, d, v1, v2, v3, &T, &U, &V)) {

			glm::vec3 intpos = cameraPos + glm::vec3(d.x * T, d.y * T, d.z * T);
			if (PointinTriangle(v1, v2, v3, intpos)) {
				return true;
			}
		}
	}
	//遍历所有面片仍未返回说明这个网格与鼠标无交点,返回false
	return false;
}

好了,有了上面的基础,接下来就可以愉快的玩耍了,增加扩展功能了!

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值