在C++/OpenGL 2D游戏中获取纹理的GLFW像素坐标方法咨询
解决方案:纹理坐标到GLFW像素坐标的转换与点击检测实现
核心坐标系关系梳理
首先明确你用到的几个关键坐标系:
- 局部空间:纹理自身的顶点坐标(如
data.vertexPositions中的±0.5) - 世界空间:通过
transform矩阵(平移、旋转、缩放)转换后的纹理实际位置 - 裁剪空间/NDC:MVP矩阵变换后得到的标准化设备坐标,范围为[-1,1]
- GLFW屏幕像素坐标:鼠标输入的坐标系,原点在窗口左上角,X轴向右、Y轴向下
步骤1:将纹理顶点转换为标准化设备坐标(NDC)
你可以复用现有的calculateMvpMatrix函数,传入目标纹理的position、rotation、scale参数,得到完整的MVP矩阵后,将纹理的四个局部顶点代入计算,再通过透视除法得到NDC坐标:
// 目标纹理的参数(从DrawQuad调用时的参数保存而来) glm::vec3 texPos = {x, y, 0.0f}; float texRot = rotation; glm::vec3 texScale = {size.x, size.y, 1.0f}; glm::mat4 mvp = calculateMvpMatrix(texPos, texRot, texScale); // 纹理的四个局部顶点 std::vector<glm::vec4> localVertices = { glm::vec4(0.5f, -0.5f, 0.0f, 1.0f), glm::vec4(-0.5f, -0.5f, 0.0f, 1.0f), glm::vec4(-0.5f, 0.5f, 0.0f, 1.0f), glm::vec4(0.5f, 0.5f, 0.0f, 1.0f) }; std::vector<glm::vec2> ndcVertices; for (auto& v : localVertices) { glm::vec4 clipPos = mvp * v; // 透视除法得到NDC坐标 glm::vec2 ndc = glm::vec2(clipPos.x / clipPos.w, clipPos.y / clipPos.w); ndcVertices.push_back(ndc); }
步骤2:NDC坐标转换为GLFW像素坐标
通过窗口宽高将NDC的[-1,1]范围映射到屏幕像素范围,同时注意反转Y轴(NDC的Y轴向上,GLFW的Y轴向下):
int windowWidth, windowHeight; glfwGetWindowSize(window, &windowWidth, &windowHeight); std::vector<glm::vec2> screenPixelCoords; for (auto& ndc : ndcVertices) { float screenX = (ndc.x + 1.0f) * 0.5f * windowWidth; float screenY = (1.0f - ndc.y) * 0.5f * windowHeight; screenPixelCoords.push_back(glm::vec2(screenX, screenY)); }
此时screenPixelCoords中存储的就是纹理四个角对应的GLFW像素坐标。
步骤3:高效实现鼠标点击检测
逐像素检测效率较低,建议先做层级检测:
1. 轴对齐包围盒(AABB)快速筛选
用转换后的像素坐标计算纹理的最小/最大X、Y值,先判断鼠标点击是否在这个矩形范围内:
// 计算AABB边界 float minX = std::min({screenPixelCoords[0].x, screenPixelCoords[1].x, screenPixelCoords[2].x, screenPixelCoords[3].x}); float maxX = std::max({screenPixelCoords[0].x, screenPixelCoords[1].x, screenPixelCoords[2].x, screenPixelCoords[3].x}); float minY = std::min({screenPixelCoords[0].y, screenPixelCoords[1].y, screenPixelCoords[2].y, screenPixelCoords[3].y}); float maxY = std::max({screenPixelCoords[0].y, screenPixelCoords[1].y, screenPixelCoords[2].y, screenPixelCoords[3].y}); // 获取鼠标像素坐标 double mouseX, mouseY; glfwGetCursorPos(window, &mouseX, &mouseY); if (mouseX >= minX && mouseX <= maxX && mouseY >= minY && mouseY <= maxY) { // 进入下一步精确检测 }
2. 精确检测:鼠标坐标转世界空间对比
将鼠标像素坐标转换回世界空间,直接和纹理的世界空间范围对比(无需处理像素):
// 鼠标像素坐标转NDC float ndcMouseX = (2.0f * mouseX) / windowWidth - 1.0f; float ndcMouseY = 1.0f - (2.0f * mouseY) / windowHeight; // 反转Y轴 glm::vec4 ndcMouse = glm::vec4(ndcMouseX, ndcMouseY, 0.0f, 1.0f); // 用MVP逆矩阵将NDC鼠标转世界空间 glm::mat4 invMVP = glm::inverse(mvp); glm::vec4 worldMouse = invMVP * ndcMouse; worldMouse /= worldMouse.w; // 透视除法 // 纹理的世界空间范围(局部±0.5经过缩放后的范围) float halfWidth = texScale.x * 0.5f; float halfHeight = texScale.y * 0.5f; // 注意:如果纹理有旋转,需要用旋转后的边界判断,这里简化为无旋转情况 if (worldMouse.x >= texPos.x - halfWidth && worldMouse.x <= texPos.x + halfWidth && worldMouse.y >= texPos.y - halfHeight && worldMouse.y <= texPos.y + halfHeight) { // 点击命中该纹理,可获取纹理名称 }
关键注意事项
- 你的正交投影矩阵
glm::ortho(0.0f, 8.0f, 0.0f, 4.5f,...)是固定范围,若窗口宽高比不是16:9会导致拉伸,建议改为和窗口宽高关联:float aspectRatio = (float)windowWidth / windowHeight; glm::mat4 projection = glm::ortho(0.0f, 8.0f, 0.0f, 8.0f / aspectRatio, 0.1f, 100.0f); - 需保存每个纹理的
position、rotation、size参数,以便点击检测时重新计算MVP矩阵。
内容的提问来源于stack exchange,提问作者notfynnaf
相关产品推荐
相关产品推荐

