SharpGL - 使用选择和拾取检测OpenGL元素上的鼠标点击

5
我在使用SharpGL库实现WPF中的2D图形。我已经成功地在屏幕上绘制了一些基本对象,并且需要检测这些对象上的鼠标点击事件。
我查看了一个OpenGL教程,介绍如何对图形对象执行选择和拾取操作,但我没有成功使其工作。在我的测试应用程序中,我在屏幕上绘制了三角形,并在发生鼠标点击事件时以GL_SELECT模式绘制同样的三角形,希望能够检测是否点击了任何一个三角形。然而,命中测试总是返回选择缓存中的所有元素。我不确定这是否是正确的方法。
我知道PickMatrix中的宽度和高度参数不正确,而且我也不确定那里的正确值是什么。它是整个视图的宽度和高度吗?
private void OpenGLControl_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
    ////  Get the OpenGL object.
    OpenGL gl = args.OpenGL;

    //set background to white
    gl.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    ////  Clear the color and depth buffer.
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
    DrawScene();
    gl.Flush();
}

private void DrawScene()
{
    OpenGL gl = openGLControl.OpenGL;
    gl.Color(1.0, 0.0, 0.0);
    DrawTriangle(-0.2, 0.6, 0.0, 0.8, 0.2, 0.6);

    gl.Color(0.0, 1.0, 0.0);
    DrawTriangle(-0.2, 0.2, 0.0, 0.4, 0.2, 0.2);

    gl.Color(0.0, 0.0, 1.0);
    DrawTriangle(-0.2, -0.2, 0.0, 0.0, 0.2, -0.2);
}

private void SelectObjects(double mouseDownX, double mouseDownY)
{
    OpenGL gl = openGLControl.OpenGL;
    int BUFSIZE = 512;

    uint[] selectBuf = new uint[BUFSIZE];

    gl.SelectBuffer(BUFSIZE, selectBuf);
    gl.RenderMode(OpenGL.GL_SELECT);

    gl.InitNames();
    gl.PushName(0);

    int[] viewport = new int[4];
    gl.GetInteger(OpenGL.GL_VIEWPORT, viewport);
    
    //how to define the width and height of an element?
    gl.PickMatrix(mouseDownX, (double)(viewport[3] - mouseDownY), 50.0, 50.0, viewport);

    gl.LoadIdentity();

    gl.LoadName(1);
    gl.Color(1.0, 0.0, 0.0);
    DrawTriangle(-0.2, 0.6, 0.0, 0.8, 0.2, 0.6);

    gl.LoadName(2);
    gl.Color(0.0, 1.0, 0.0);
    DrawTriangle(-0.2, 0.2, 0.0, 0.4, 0.2, 0.2);

    gl.LoadName(3);
    gl.Color(0.0, 0.0, 1.0);
    DrawTriangle(-0.2, -0.2, 0.0, 0.0, 0.2, -0.2);

    gl.Flush(); 
    int hits = gl.RenderMode(OpenGL.GL_RENDER);
    processHits(hits, selectBuf);
}

private void processHits(int hits, uint[] buffer)
{
    uint bufferIterator = 0;
    for (uint i = 0; i < hits; i++)
    {
        uint numberOfNamesInHit = buffer[bufferIterator];
        Console.WriteLine("hit: " + i + " number of names in hit " + numberOfNamesInHit);

        uint lastNameIndex = bufferIterator + 2 + numberOfNamesInHit;
        for (uint j = bufferIterator + 3; j <= lastNameIndex; j++)
        {
            Console.WriteLine("Name is " + buffer[j]);
        }

        bufferIterator = bufferIterator + numberOfNamesInHit + 3;
    }
}

private void OnMouseClick(object sender, MouseEventArgs e)
{
    System.Windows.Point position = e.GetPosition(this);
    SelectObjects(position.X, position.Y); 
}

输出结果始终相同:

命中:0 在命中中的名称数为1

名称为1

命中:1 在命中中的名称数为1

名称为2

命中:2 在命中中的名称数为1

名称为3

2个回答

3

宽度和高度是以像素为单位的拾取区域大小。要检测鼠标指针下的对象,1x1应该就足够了(您希望检测的是一个1像素乘1像素的屏幕矩形下的内容)。

gluPickMatrix会将当前矩阵与拾取矩阵相乘。您的PickMatrix后跟LoadIdentity没有任何意义,因为glLoadIdentity将当前矩阵重置为单位矩阵。

为使示例代码正常工作,在渲染之前,请设置您的矩阵:

glLoadIdentity();
setupMyProjMatrix();

在选择之前,先设置与选取矩阵相同的矩阵,并进行预乘操作:
glLoadIdentity();
glPickMatrix();
setupMyProjMatrix();

在你的样例中,setupMyProjMatrix() 没有做任何事情。
无论如何,你应该避免在OpenGL中使用拾取。它是一个已经被弃用的功能(在现代GL中已删除),非常缓慢,并且有时不太可靠,这取决于供应商。你应该自己计算命中测试。
你永远不应该查询任何GL内容(glGet系列)。它会导致CPU停顿。
对于糟糕的英语,我感到抱歉。

谢谢您的解释,我删除了glLoadIdentitiy这一行代码,现在它可以正常工作了 :) - mobearette

2

正如Olivier所说的,避免使用已经废弃的功能。取而代之的是,您可以通过两种不同的方式进行拾取:

  • 通过射线测试挑选所有前向面多边形,其中您从眼点通过像素中心(在近平面距离处)发射一条射线,直到它击中多边形或超出选择范围。这种解决方案对于复杂场景来说并不是很可扩展,但实现起来相当容易,并提供了一定程度的灵活性以处理透明度等。

  • 您还可以通过将所有对象渲染为带颜色ID的离屏纹理来在视图空间中进行拾取。然后,您只需读取选择2D坐标的像素值,并将颜色ID映射回该选择坐标下的对象。这里的缺点是更难选择多个对象,并且渲染批次必须具有正确的剔除和着色设置。优点是该解决方案依赖于分辨率,并不那么依赖场景复杂性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接