[英]Modern OpenGL: Object picking (C#, OpenTK)
我正在尝试使用 C# 和 OpenTK 在 OpenGL 中实现 object 拾取。 为此,我基于两个来源编写了 class:
https://www.bfilipek.com/2012/06/select-mouse-opengl.html
目前我的代码仅用于计算鼠标指针与 (0,0,0) 的任意测试坐标的距离,但一旦工作,迭代场景中的对象以找到匹配项并不需要太多。
方法是在鼠标指针下方在近剪裁平面和远剪裁平面之间定义一条射线。 然后找到该射线上最接近被测点的点,并返回两者之间的距离。 当鼠标指针直接位于 (0,0,0) 上方时,该值应为零,并随着它向任何方向移开而增加。
任何人都可以帮助解决这个问题吗? 它执行没有错误,但返回的距离显然不正确。 我了解原理,但不了解计算的细节。
尽管我在网上找到了几乎可以做到的各种示例,但它们通常使用不同的语言或框架和/或使用过时的方法和/或不完整或不起作用。
public class ObjectPicker{
public static float DistanceFromPoint(Point mouseLocation, Vector3 testPoint, Matrix4 modelView, Matrix4 projection)
{
Vector3 near = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 0), modelView, projection); // start of ray
Vector3 far = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 1), modelView, projection); // end of ray
Vector3 pt = ClosestPoint(near, far, testPoint); // find point on ray which is closest to test point
return Vector3.Distance(pt, testPoint); // return the distance
}
private static Vector3 ClosestPoint(Vector3 A, Vector3 B, Vector3 P)
{
Vector3 AB = B - A;
float ab_square = Vector3.Dot(AB, AB);
Vector3 AP = P - A;
float ap_dot_ab = Vector3.Dot(AP, AB);
// t is a projection param when we project vector AP onto AB
float t = ap_dot_ab / ab_square;
// calculate the closest point
Vector3 Q = A + Vector3.Multiply(AB, t);
return Q;
}
private static Vector3 UnProject(Vector3 screen, Matrix4 modelView, Matrix4 projection)
{
int[] viewport = new int[4];
OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
Vector4 pos = new Vector4();
// Map x and y from window coordinates, map to range -1 to 1
pos.X = (screen.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
pos.Y = 1 - (screen.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
pos.Z = screen.Z * 2.0f - 1.0f;
pos.W = 1.0f;
Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(modelView) * projection );
Vector3 pos_out = new Vector3(pos2.X, pos2.Y, pos2.Z);
return pos_out / pos2.W;
}
}
它是这样调用的:
private void GlControl1_MouseMove(object sender, MouseEventArgs e)
{
float dist = ObjectPicker.DistanceFromPoint(new Point(e.X,e.Y), new Vector3(0,0,0), model, projection);
this.Text = dist.ToString(); // display in window caption for debugging
}
我知道矩阵是如何传入的(根据上面的代码)。 我很确定这些矩阵的内容一定是正确的,因为渲染效果很好,而且我可以成功地旋转/缩放。 这是顶点着色器 FWIW:
string vertexShaderSource =
"# version 330 core\n" +
"layout(location = 0) in vec3 aPos;" +
"layout(location = 1) in vec3 aNormal;" +
"uniform mat4 model; " +
"uniform mat4 view;" +
"uniform mat4 projection;" +
"out vec3 FragPos;" +
"out vec3 Normal;" +
"void main()" +
"{" +
"gl_Position = projection * view * model * vec4(aPos, 1.0);" +
"FragPos = vec3(model * vec4(aPos, 1.0));" +
"Normal = vec3(model * vec4(aNormal, 1.0))";
"}";
我使用 Arcball 的实现进行旋转。 缩放是使用翻译完成的,如下所示:
private void glControl1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
zoom += (float)e.Delta / 240;
view = Matrix4.CreateTranslation(0.0f, 0.0f, zoom);
SetMatrix4(Handle, "view", view);
glControl1.Invalidate();
}
每个顶点坐标由 model 视图矩阵变换。 这会将坐标从 model 空间转换到视图空间。 然后每个顶点坐标由投影矩阵变换。 这从视图空间转换为剪辑空间。 透视分割将剪辑空间坐标转换为规范化的设备空间。
如果要从规范化设备空间转换为 model 空间,则必须执行相反的操作。 这意味着您必须通过逆投影矩阵和逆 model 视图矩阵进行变换:
Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(projection) * Matrix4.Invert(modelView));
分别
Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(modelView * projection));
请注意,OpenTK 矩阵必须从左到右相乘。 请参阅OpenGL 4.2 LookAt 矩阵的答案仅适用于眼睛 position 的 -z 值。
在这里回答我自己的问题,以便我可以发布工作代码以造福其他用户,但至少有一半的答案是由 Rabbid76 提供的,我非常感谢他的帮助。
我的原始代码中有两个错误:
Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(modelView) * projection );
其中两个矩阵以错误的顺序相乘,并且投影矩阵没有反转。
float dist = ObjectPicker.DistanceFromPoint(new Point(eX,eY), new Vector3(0,0,0), model, projection);
我传入的是 model 矩阵而不是模型视图矩阵(这是 model 和视图矩阵的乘积)。
这有效:
private void GlControl1_MouseMove(object sender, MouseEventArgs e)
{
float dist = ObjectPicker.DistanceFromPoint(new Point(e.X,e.Y), new Vector3(0,0,0), model * view, projection);
// do something with the result
}
public class ObjectPicker{
public static float DistanceFromPoint(Point mouseLocation, Vector3 testPoint, Matrix4 modelView, Matrix4 projection)
{
int[] viewport = new int[4];
OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
Vector3 near = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 0), modelView, projection); // start of ray (near plane)
Vector3 far = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 1), modelView, projection); // end of ray (far plane)
Vector3 pt = ClosestPoint(near, far, testPoint); // find point on ray which is closest to test point
return Vector3.Distance(pt, testPoint); // return the distance
}
private static Vector3 ClosestPoint(Vector3 A, Vector3 B, Vector3 P)
{
Vector3 AB = B - A;
float ab_square = Vector3.Dot(AB, AB);
Vector3 AP = P - A;
float ap_dot_ab = Vector3.Dot(AP, AB);
// t is a projection param when we project vector AP onto AB
float t = ap_dot_ab / ab_square;
// calculate the closest point
Vector3 Q = A + Vector3.Multiply(AB, t);
return Q;
}
private static Vector3 UnProject(Vector3 screen, Matrix4 modelView, Matrix4 projection)
{
int[] viewport = new int[4];
OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
Vector4 pos = new Vector4();
// Map x and y from window coordinates, map to range -1 to 1
pos.X = (screen.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
pos.Y = 1 - (screen.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
pos.Z = screen.Z * 2.0f - 1.0f;
pos.W = 1.0f;
Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(projection) * Matrix4.Invert(modelView) );
Vector3 pos_out = new Vector3(pos2.X, pos2.Y, pos2.Z);
return pos_out / pos2.W;
}
}
自从发布这个问题以来,我了解到该方法通常称为光线投射,并找到了一些很好的解释:
Anton Gerdelan的 Ray Casting 鼠标拾取
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.