SkinnedModel 上的 XNA 三角形拾取

发布于 2024-12-18 13:47:32 字数 10436 浏览 3 评论 0原文

我的游戏使用 SkinnedModel 管道和 AnimationPlayer 来加载和显示模型。 我通过从管道构建顶点和索引列表来实现三角形的光线拾取。这是可行的,但是索引是静态的,仅适用于模型的初始姿势(当它行走时,手臂来回移动,它不够好,因为拾取只看到模型处于一个位置)

我已经改变了它通过在运行时获取顶点和索引来使用三角形进行光线拾取,然后能够通过动画播放器给出的骨转换来变换顶点。我现在遇到的问题是,我不太知道在收集顶点时如何转换顶点,

我将在下面发布代码,但我很确定它与以下行有关: 矩阵 worldMatrix = 方向 * Matrix.CreateTranslation(Position); 然后当获取特定顶点时: vert = Vector3.Transform(vert,boneTransforms [mesh.ParentBone.Index] * worldMatrix);

我发现省略 *Matrix.CreateTranslation(Position) 会使顶点接近,但不太正确。我不太擅长处理 3d 空间和变换,所以我们将不胜感激。

代码:

//这是我从类模型获取顶点的函数

public List<Vector3> GetVertices()
    {
        List<Vector3> vertices = new List<Vector3>();

        Matrix[] boneTransforms = AnimPlayer.GetSkinTransforms();
        Matrix worldMatrix = Orientation;// *Matrix.CreateTranslation(Position);

        foreach (ModelMesh mesh in Model.Meshes)
        {
            // There may be multiple parts in a mesh (different materials etc.) so loop through each
            foreach (ModelMeshPart part in mesh.MeshParts)
            {
                // The stride is how big, in bytes, one vertex is in the vertex buffer
                // We have to use this as we do not know the make up of the vertex
                int stride = part.VertexBuffer.VertexDeclaration.VertexStride;

                byte[] vertexData = new byte[stride * part.NumVertices];
                part.VertexBuffer.GetData(part.VertexOffset * stride, vertexData, 0, part.NumVertices, 1); // fixed 13/4/11

                //part.IndexBuffer.GetData<float>(part.StartIndex * sizeof(float),
                short[] indices = new short[part.PrimitiveCount * 3];
                part.IndexBuffer.GetData(part.StartIndex * sizeof(short), indices, 0, part.PrimitiveCount * 3);

                // Find minimum and maximum xyz values for this mesh part
                // We know the position will always be the first 3 float values of the vertex data
                Vector3 vert = new Vector3();
                for(int i = 0; i < indices.Length; i++)//for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
                {
                    vert.X = BitConverter.ToSingle(vertexData, (indices[i] * stride));
                    vert.Y = BitConverter.ToSingle(vertexData, (indices[i] * stride) + sizeof(float));
                    vert.Z = BitConverter.ToSingle(vertexData, (indices[i] * stride) + sizeof(float) * 2);

                    vert = Vector3.Transform(vert, boneTransforms[mesh.ParentBone.Index] * worldMatrix);

                    vertices.Add(vert);
                }
            }
        }

        return vertices;
    }

//这是获取拾取光线的函数

public static Ray GetPickRay(Viewport vp, Matrix projectionMatrix, Matrix viewMatrix, MouseState mouseState)
    {
        int mouseX = mouseState.X;
        int mouseY = mouseState.Y;

        Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
        Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);

        Matrix world = Matrix.CreateTranslation(0, 0, 0);//TODO: ?

        Vector3 nearPoint = vp.Unproject(nearsource, projectionMatrix, viewMatrix, Matrix.Identity);

        Vector3 farPoint = vp.Unproject(farsource, projectionMatrix, viewMatrix, Matrix.Identity);

        // Create a ray from the near clip plane to the far clip plane.
        Vector3 direction = farPoint - nearPoint;
        direction.Normalize();
        Ray pickRay = new Ray(nearPoint, direction);

        return pickRay;
    }

//这是用于检查相交的两个函数。

public static float? RayIntersectsModel(Ray ray, Person person, Matrix modelTransform,
                                     out bool insideBoundingSphere,
                                     out Vector3 vertex1, out Vector3 vertex2,
                                     out Vector3 vertex3)
    {
        vertex1 = vertex2 = vertex3 = Vector3.Zero;

        Matrix inverseTransform = Matrix.Invert(modelTransform);

        ray.Position = Vector3.Transform(ray.Position, inverseTransform);
        ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);

        insideBoundingSphere = true;

        float? closestIntersection = null;

        // Loop over the vertex data, 3 at a time (3 vertices = 1 triangle).
        Vector3[] vertices = person.GetVertices().ToArray();// tagData.Vertices.ToArray();

        for (int i = 0; i < vertices.Length; i += 3)
        {
            // Perform a ray to triangle intersection test.
            float? intersection;

            RayIntersectsTriangle(ref ray,
                                  ref vertices[i],
                                  ref vertices[i + 1],
                                  ref vertices[i + 2],
                                  out intersection);

            // Does the ray intersect this triangle?
            if (intersection != null)
            {
                // If so, is it closer than any other previous triangle?
                if ((closestIntersection == null) ||
                    (intersection < closestIntersection))
                {
                    // Store the distance to this triangle.
                    closestIntersection = intersection;

                    // Transform the three vertex positions into world space,
                    // and store them into the output vertex parameters.
                    Vector3.Transform(ref vertices[i],
                                      ref modelTransform, out vertex1);

                    Vector3.Transform(ref vertices[i + 1],
                                      ref modelTransform, out vertex2);

                    Vector3.Transform(ref vertices[i + 2],
                                      ref modelTransform, out vertex3);
                }
            }
        }

        return closestIntersection;
    }

    public static void RayIntersectsTriangle(ref Ray ray,
                                      ref Vector3 vertex1,
                                      ref Vector3 vertex2,
                                      ref Vector3 vertex3, out float? result)
    {
        // Compute vectors along two edges of the triangle.
        Vector3 edge1, edge2;

        Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
        Vector3.Subtract(ref vertex3, ref vertex1, out edge2);

        // Compute the determinant.
        Vector3 directionCrossEdge2;
        Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);

        float determinant;
        Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);

        // If the ray is parallel to the triangle plane, there is no collision.
        if (determinant > -float.Epsilon && determinant < float.Epsilon)
        {
            result = null;
            return;
        }

        float inverseDeterminant = 1.0f / determinant;

        // Calculate the U parameter of the intersection point.
        Vector3 distanceVector;
        Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);

        float triangleU;
        Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
        triangleU *= inverseDeterminant;

        // Make sure it is inside the triangle.
        if (triangleU < 0 || triangleU > 1)
        {
            result = null;
            return;
        }

        // Calculate the V parameter of the intersection point.
        Vector3 distanceCrossEdge1;
        Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);

        float triangleV;
        Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
        triangleV *= inverseDeterminant;

        // Make sure it is inside the triangle.
        if (triangleV < 0 || triangleU + triangleV > 1)
        {
            result = null;
            return;
        }

        // Compute the distance along the ray to the triangle.
        float rayDistance;
        Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
        rayDistance *= inverseDeterminant;

        // Is the triangle behind the ray origin?
        if (rayDistance < 0)
        {
            result = null;
            return;
        }

        result = rayDistance;
    }

//最后,如果有帮助的话,我的更新部分使这一切发生

Ray cursorRay = ModelHelper.GetPickRay(ScreenManager.GraphicsDevice.Viewport, projectionMatrix, viewMatrix, mouseState);

                //pickedModelName = null;
                Person pickedPerson = null;

                // Keep track of the closest object we have seen so far, so we can
                // choose the closest one if there are several models under the cursor.
                float closestIntersection = float.MaxValue;

                // Loop over all our models.
                for (int i = 0; i < People.Count; i++)
                {
                    bool insideBoundingSphere;
                    Vector3 vertex1, vertex2, vertex3;

                    // Perform the ray to model intersection test.
                    float? intersection = ModelHelper.RayIntersectsModel(cursorRay, People[i],
                                                             People[i].Orientation * Matrix.CreateTranslation(People[i].Position),
                                                             out insideBoundingSphere,
                                                             out vertex1, out vertex2,
                                                             out vertex3);

                    // Do we have a per-triangle intersection with this model?
                    if (intersection != null)
                    {
                        // If so, is it closer than any other model we might have
                        // previously intersected?
                        if (intersection < closestIntersection)
                        {
                            // Store information about this model.
                            closestIntersection = intersection.Value;

                            //pickedModelName = ModelFilenames[i];
                            pickedPerson = People[i];
                        }
                    }
                }

My game is using the SkinnedModel pipeline and AnimationPlayer to load and display models.
I've implemented ray picking with triangles by building a list of vertices and indices from the pipeline. This works, however the indices are static are only work for the initial pose of the model (seeing as it walks, arms moving back and forth, its not quite good enough since the picking sees the model in one position only)

I've changed it to ray picking with triangles by getting the vertices and indices at runtime, which is then able to transform the vertices by the boneTransforms given from the AnimationPlayer. The Issue I'm having now is that I don't quite know how to transform the vertices when they're being gathered

I'll post the code below, but I'm pretty sure it has something to do with the line:
Matrix worldMatrix = Orientation * Matrix.CreateTranslation(Position);
Then later when the specific vertex is acquired:
vert = Vector3.Transform(vert, boneTransforms[mesh.ParentBone.Index] * worldMatrix);

I've found that omitting *Matrix.CreateTranslation(Position) gets the vertices close, but not quite right. I'm not very skilled at dealing with 3d space and transforms, so help would be appreciated.

Code:

//Here is my function for acquiring the vertices from the class' model

public List<Vector3> GetVertices()
    {
        List<Vector3> vertices = new List<Vector3>();

        Matrix[] boneTransforms = AnimPlayer.GetSkinTransforms();
        Matrix worldMatrix = Orientation;// *Matrix.CreateTranslation(Position);

        foreach (ModelMesh mesh in Model.Meshes)
        {
            // There may be multiple parts in a mesh (different materials etc.) so loop through each
            foreach (ModelMeshPart part in mesh.MeshParts)
            {
                // The stride is how big, in bytes, one vertex is in the vertex buffer
                // We have to use this as we do not know the make up of the vertex
                int stride = part.VertexBuffer.VertexDeclaration.VertexStride;

                byte[] vertexData = new byte[stride * part.NumVertices];
                part.VertexBuffer.GetData(part.VertexOffset * stride, vertexData, 0, part.NumVertices, 1); // fixed 13/4/11

                //part.IndexBuffer.GetData<float>(part.StartIndex * sizeof(float),
                short[] indices = new short[part.PrimitiveCount * 3];
                part.IndexBuffer.GetData(part.StartIndex * sizeof(short), indices, 0, part.PrimitiveCount * 3);

                // Find minimum and maximum xyz values for this mesh part
                // We know the position will always be the first 3 float values of the vertex data
                Vector3 vert = new Vector3();
                for(int i = 0; i < indices.Length; i++)//for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
                {
                    vert.X = BitConverter.ToSingle(vertexData, (indices[i] * stride));
                    vert.Y = BitConverter.ToSingle(vertexData, (indices[i] * stride) + sizeof(float));
                    vert.Z = BitConverter.ToSingle(vertexData, (indices[i] * stride) + sizeof(float) * 2);

                    vert = Vector3.Transform(vert, boneTransforms[mesh.ParentBone.Index] * worldMatrix);

                    vertices.Add(vert);
                }
            }
        }

        return vertices;
    }

//Here is the function to get the pick ray

public static Ray GetPickRay(Viewport vp, Matrix projectionMatrix, Matrix viewMatrix, MouseState mouseState)
    {
        int mouseX = mouseState.X;
        int mouseY = mouseState.Y;

        Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
        Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);

        Matrix world = Matrix.CreateTranslation(0, 0, 0);//TODO: ?

        Vector3 nearPoint = vp.Unproject(nearsource, projectionMatrix, viewMatrix, Matrix.Identity);

        Vector3 farPoint = vp.Unproject(farsource, projectionMatrix, viewMatrix, Matrix.Identity);

        // Create a ray from the near clip plane to the far clip plane.
        Vector3 direction = farPoint - nearPoint;
        direction.Normalize();
        Ray pickRay = new Ray(nearPoint, direction);

        return pickRay;
    }

//Here are the two functions for checking the intersection.

public static float? RayIntersectsModel(Ray ray, Person person, Matrix modelTransform,
                                     out bool insideBoundingSphere,
                                     out Vector3 vertex1, out Vector3 vertex2,
                                     out Vector3 vertex3)
    {
        vertex1 = vertex2 = vertex3 = Vector3.Zero;

        Matrix inverseTransform = Matrix.Invert(modelTransform);

        ray.Position = Vector3.Transform(ray.Position, inverseTransform);
        ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);

        insideBoundingSphere = true;

        float? closestIntersection = null;

        // Loop over the vertex data, 3 at a time (3 vertices = 1 triangle).
        Vector3[] vertices = person.GetVertices().ToArray();// tagData.Vertices.ToArray();

        for (int i = 0; i < vertices.Length; i += 3)
        {
            // Perform a ray to triangle intersection test.
            float? intersection;

            RayIntersectsTriangle(ref ray,
                                  ref vertices[i],
                                  ref vertices[i + 1],
                                  ref vertices[i + 2],
                                  out intersection);

            // Does the ray intersect this triangle?
            if (intersection != null)
            {
                // If so, is it closer than any other previous triangle?
                if ((closestIntersection == null) ||
                    (intersection < closestIntersection))
                {
                    // Store the distance to this triangle.
                    closestIntersection = intersection;

                    // Transform the three vertex positions into world space,
                    // and store them into the output vertex parameters.
                    Vector3.Transform(ref vertices[i],
                                      ref modelTransform, out vertex1);

                    Vector3.Transform(ref vertices[i + 1],
                                      ref modelTransform, out vertex2);

                    Vector3.Transform(ref vertices[i + 2],
                                      ref modelTransform, out vertex3);
                }
            }
        }

        return closestIntersection;
    }

    public static void RayIntersectsTriangle(ref Ray ray,
                                      ref Vector3 vertex1,
                                      ref Vector3 vertex2,
                                      ref Vector3 vertex3, out float? result)
    {
        // Compute vectors along two edges of the triangle.
        Vector3 edge1, edge2;

        Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
        Vector3.Subtract(ref vertex3, ref vertex1, out edge2);

        // Compute the determinant.
        Vector3 directionCrossEdge2;
        Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);

        float determinant;
        Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);

        // If the ray is parallel to the triangle plane, there is no collision.
        if (determinant > -float.Epsilon && determinant < float.Epsilon)
        {
            result = null;
            return;
        }

        float inverseDeterminant = 1.0f / determinant;

        // Calculate the U parameter of the intersection point.
        Vector3 distanceVector;
        Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);

        float triangleU;
        Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
        triangleU *= inverseDeterminant;

        // Make sure it is inside the triangle.
        if (triangleU < 0 || triangleU > 1)
        {
            result = null;
            return;
        }

        // Calculate the V parameter of the intersection point.
        Vector3 distanceCrossEdge1;
        Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);

        float triangleV;
        Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
        triangleV *= inverseDeterminant;

        // Make sure it is inside the triangle.
        if (triangleV < 0 || triangleU + triangleV > 1)
        {
            result = null;
            return;
        }

        // Compute the distance along the ray to the triangle.
        float rayDistance;
        Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
        rayDistance *= inverseDeterminant;

        // Is the triangle behind the ray origin?
        if (rayDistance < 0)
        {
            result = null;
            return;
        }

        result = rayDistance;
    }

//And lastly, if it helps any, the section of my Update that makes it all happen

Ray cursorRay = ModelHelper.GetPickRay(ScreenManager.GraphicsDevice.Viewport, projectionMatrix, viewMatrix, mouseState);

                //pickedModelName = null;
                Person pickedPerson = null;

                // Keep track of the closest object we have seen so far, so we can
                // choose the closest one if there are several models under the cursor.
                float closestIntersection = float.MaxValue;

                // Loop over all our models.
                for (int i = 0; i < People.Count; i++)
                {
                    bool insideBoundingSphere;
                    Vector3 vertex1, vertex2, vertex3;

                    // Perform the ray to model intersection test.
                    float? intersection = ModelHelper.RayIntersectsModel(cursorRay, People[i],
                                                             People[i].Orientation * Matrix.CreateTranslation(People[i].Position),
                                                             out insideBoundingSphere,
                                                             out vertex1, out vertex2,
                                                             out vertex3);

                    // Do we have a per-triangle intersection with this model?
                    if (intersection != null)
                    {
                        // If so, is it closer than any other model we might have
                        // previously intersected?
                        if (intersection < closestIntersection)
                        {
                            // Store information about this model.
                            closestIntersection = intersection.Value;

                            //pickedModelName = ModelFilenames[i];
                            pickedPerson = People[i];
                        }
                    }
                }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文