OpenGL点选模型三角面片上的顶点

实现这个功能首先要了解坐标系统,我们将模型模型正确的显示在我们的屏幕上,需要进行坐标系的转化。

局部空间:通常是模型坐标系,模型上面实际的坐标就是模型坐标系上的定义的。

世界空间:是3D场景的全局坐标系,所有的3D模型在渲染前都会被转换到世界空间中,这样它们就可以彼此交互并放置在一个共享的环境中。世界空间通常是一个固定不变的坐标系统,模型坐标系中的顶点通过模型矩阵被转换到世界空间中。

观察空间:观察空间也被称为相机空间或视图空间。这是相对于观察者(通常是虚拟相机)的坐标系。观察空间中的坐标是相对于相机的位置、方向和朝向的。视图矩阵负责将世界空间中的坐标转换到观察空间中。

裁剪空间:裁剪空间是经过投影变换后的一个标准化坐标系统。投影矩阵负责将观察空间中的坐标转换到裁剪空间中。裁剪空间中的坐标范围通常是 [−1,1] 之间。

屏幕空间:屏幕空间是最终输出到屏幕上的坐标系。它是2D坐标系统,坐标范围通常是从(0, 0)到(width, height),其中(0, 0)是屏幕的左下角,(width, height)是屏幕的右上角。屏幕空间中的坐标用于绘制像素。

坐标空间和各个矩阵的概念可以看坐标系统 - LearnOpenGL CN (learnopengl-cn.github.io)

射线法拾取三角形的顶点:

1、获取屏幕空间中的点击坐标,通常是像素坐标。

2、将屏幕坐标转化为裁剪空间坐标。

3、将裁剪空间中的坐标反投影到观察空间中。

4、射线相交检测。

//定义点(向量)
class Point {
public:
    double x, y, z;

    Point(double _x = 0, double _y = 0, double _z = 0) : x(_x), y(_y), z(_z) {}

    Point operator-(const Point& v) const {
        return Point(x - v.x, y - v.y, z - v.z);
    }

    // 向量点乘
    double dotProduct(const Point& v) const {
        return x * v.x + y * v.y + z * v.z;
    }

    // 向量叉乘
    Point crossProduct(const Point& v) const {
        return Point(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x);
    }
    
    //向量的模
    double length() const {
        return std::sqrt(x * x + y * y + z * z);
    }
};

// 定义面
class Face {
public:
    Point p0, p1, p2;

    Face(Point _p0 = Point(), Point _p1 = Point(), Point _p2 = Point())
        : p0(_p0), p1(_p1), p2(_p2) {}
};


//射线起点、射线方向、三角形三个顶点、交点
bool rayIntersectsTriangle(glm::vec3 rayOrigin, glm::vec3 rayDirection,glm::vec3 v0, glm::vec3 v1, glm::vec3 v2, glm::vec3& intersectionPoint){
	// Möller–Trumbore 相交检测算法
	float epsilon = 0.0001f;
	
	glm::vec3 e1 = v1 - v0;
	glm::vec3 e2 = v2 - v0;
	glm::vec3 h = glm::cross(rayDirection, e2);
	float a = glm::dot(e1, h);

	if (a > -epsilon && a < epsilon) {
		// 射线平行于三角形
		return false;
	}

	float f = 1.0f / a;
	glm::vec3 s = rayOrigin - v0;
	float u = f * glm::dot(s, h);

	if (u < 0.0f || u > 1.0f) {
		return false;
	}
	
	glm::vec3 q = glm::cross(s, e1);
	float v = f * glm::dot(rayDirection, q);
	if (v < 0.0f ||( u + v > 1.0f)){
		return false;
	}
	float t = f * glm::dot(e2, q);//表示射线与三角形交点沿射线方向的距离
	if (t > 0.00001) {
		intersectionPoint = rayOrigin + t * rayDirection;
		return true;
	}
	return false;
}

// 射线与模型相交检测
bool rayIntersectsModelPoint(const std::vector<Face>& modelFaces, glm::vec3 rayOrigin, glm::vec3 rayDirection, Point& p) {
    double minDistance = std::numeric_limits<double>::max();
    bool foundIntersection = false;

    for (const auto& face : modelFaces) {
        glm::vec3 v0(face.p0.x, face.p0.y, face.p0.z);
        glm::vec3 v1(face.p1.x, face.p1.y, face.p1.z);
        glm::vec3 v2(face.p2.x, face.p2.y, face.p2.z);

        glm::vec3 e1 = v1 - v0;
        glm::vec3 e2 = v2 - v0;

        glm::vec3 faceNormal = glm::cross(e1, e2);
        glm::vec3 normalizedFaceNormal = glm::normalize(faceNormal);
        float m = glm::dot(normalizedFaceNormal, rayDirection);

        if (m < 0) {
            glm::vec3 intersectionPoint;
            if (rayIntersectsTriangle(rayOrigin, rayDirection, v0, v1, v2, intersectionPoint)) {
                double distance = glm::length(intersectionPoint - rayOrigin);
                if (distance < minDistance) {
                    minDistance = distance;
                    glm::vec3 closestPointOnTriangle = glm::mix(v0, glm::mix(v1, v2, 1.0f - v), u);
                    if (u < 0.01) {
                        p = face.p0;
                    } else if (v < 0.01) {
                        p = face.p1;
                    } else if (1.0f - u - v < 0.01) {
                        p = face.p2;
                    } else {
                        // Use the barycentric coordinates to interpolate between the triangle's vertices
                        p = Point(closestPointOnTriangle.x, closestPointOnTriangle.y, closestPointOnTriangle.z);
                    }
                    foundIntersection = true;
                }
            }
        }
    }

    return foundIntersection;
}


bool SelectPoint(vector<Face>& modelFaces, float mouseX, float mouseY, Point<>& p)
{
    //获取归一化设备坐标(NDC)中的点击坐标
	float ndcx = (2.0f * mouseX) / SCR_WIDTH - 1.0f;
	float ndcy = 1.0f - (2.0f * mouseY) / SCR_HEIGHT;

    //构建裁剪空间中的射线起点(近裁剪面)
	glm::vec4 rayClip_near = glm::vec4(ndcx, ndcy, -1.0, 1.0f);

    //构建模型视图投影矩阵
	glm::mat4 mvp = global_projection * global_view * global_model;

    //反投影裁剪空间中的射线起点:
	glm::vec4 rayWorld = glm::inverse(mvp) * rayClip_near;
	rayWorld /= rayWorld.w;//齐次坐标转换为笛卡尔坐标

	//射线起点(世界空间)
	glm::vec3 rayOrigin = glm::vec3(rayWorld);

	//射线终点(远裁剪面)
	glm::vec4 rayClip_far = glm::vec4(ndcx, ndcy, 1.0, 1.0f);
	glm::vec4 world_far = glm::inverse(mvp) * rayClip_far;
	world_far /= world_far.w;
	glm::vec3 rayEnd = glm::vec3(world_far);

	//计算射线方向
	glm::vec3 rayDirection = glm::normalize(rayEnd - rayOrigin);
	glm::vec3 end = rayOrigin + rayDirection * 100.0f;//假设射线最大长度为100
	
	return (raySelectModelPoint(vector<Face>& modelFaces, rayOrigin, rayDirection, p));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值