实现这个功能首先要了解坐标系统,我们将模型模型正确的显示在我们的屏幕上,需要进行坐标系的转化。
局部空间:通常是模型坐标系,模型上面实际的坐标就是模型坐标系上的定义的。
世界空间:是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));
}