OpenGL射线投射器鼠标悬停网格高亮异常问题排查
问题排查与修正方案
你的核心问题出在射线方向和相机位置的空间转换不匹配,只有相机沿-Z轴(视图矩阵为单位矩阵)时,错误的变换结果刚好巧合符合预期,旋转相机后空间不匹配的问题就彻底暴露出来了。下面一步步拆解问题和修正方法:
1. 顶点着色器中的核心错误
先看你原来的顶点着色器代码:
rayDir = vec3(M * vec4(ray_dir,1.0)); cameraPos = vec3(V * M * vec4(cam_pos,1.0));
这里存在两个严重的空间转换逻辑错误:
- 射线方向(ray_dir):你在C++代码中计算的
ray_wor是世界空间的射线方向,但你用模型矩阵M把它转到了模型空间,而顶点输出的position是视图空间的坐标(V*M*vertex_position)。两个向量不在同一个空间,点积计算出的夹角完全没有意义。 - 相机位置(cam_pos):
cam_pos本身是世界空间的相机位置,视图矩阵V已经能把它转换到视图空间,但你多乘了模型矩阵M——相机和模型是独立的,这会导致相机位置被错误地应用了模型的平移/旋转变换,完全偏离了实际位置。
2. 片段着色器的可选优化
你原来计算vDir的代码:
vec3 vDir = normalize(position.xyz - cameraPos);
根据视图空间的定义,相机本身就在视图空间的原点,所以这个向量其实就是相机指向顶点的方向,可以直接简化为normalize(position.xyz),省去相机位置的计算步骤,既高效又避免额外错误。
修正后的完整代码
顶点着色器
#version 410 layout (location = 0) in vec3 vertex_position; layout (location = 2) in vec2 VertexUV; uniform mat4 P; uniform mat4 V = mat4(1.0); uniform mat4 M = mat4(1.0); out vec2 uv; out vec4 position; // 视图空间顶点位置 uniform vec3 ray_dir; // 世界空间射线方向 out vec3 rayDir_view; // 视图空间射线方向 void main () { // 把世界空间射线方向转换到视图空间(方向向量用w=0,避免平移影响) rayDir_view = vec3(V * vec4(ray_dir, 0.0)); gl_Position = P * V * M * vec4(vertex_position, 1); position = V * M * vec4(vertex_position, 1.0); // 视图空间顶点位置 uv = VertexUV; }
片段着色器
#version 410 out vec4 fragment_colour; // final colour of surface in vec4 position; // 视图空间顶点位置 in vec2 uv; uniform vec3 light_pos; uniform vec3 light_ambient; uniform sampler2D texture2D; in vec3 rayDir_view; // 视图空间射线方向 void main () { vec4 test = vec4(0, 0, 0, 0); // 视图空间中相机在原点,直接取顶点位置作为相机到顶点的方向 vec3 camToVertex = normalize(position.xyz); // 计算方向夹角的余弦,clamp避免精度问题导致acos报错 float cosAngle = dot(camToVertex, rayDir_view); cosAngle = clamp(cosAngle, -1.0, 1.0); float angle = degrees(acos(cosAngle)); if(angle < 5.0) { test = vec4(1, 0, 0, 1); } // 注意:如果light_pos是世界空间,需要转换到视图空间再计算距离 // vec3 light_pos_view = vec3(V * vec4(light_pos, 1.0)); // float intensity = (1.0 / length(position.xyz - light_pos_view)) + 0.25; float intensity = (1.0 / length(position.xyz - light_pos)) + 0.25; intensity = clamp(intensity, 0, 1); vec4 ambient = vec4(light_ambient, 1); fragment_colour = ((vec4(texture(texture2D, uv).rgb, 1.0) * intensity) * ambient) + test; }
额外补充说明
- 为什么相机沿-Z轴时正常?因为此时视图矩阵
V是单位矩阵,若模型矩阵M也是单位矩阵,错误的变换结果刚好和正确结果一致,暂时掩盖了问题。 - 光照计算的潜在问题:你当前的
light_pos如果是世界空间,直接和视图空间的position计算距离是错误的,需要像射线方向一样转换到视图空间,代码中已添加注释提示。
内容的提问来源于stack exchange,提问作者Calum McManus
相关产品推荐
相关产品推荐

