• 处理顶点——在3D空间中使用CatmullRom插值生成额外的顶点


    问题

    给定一个3D空间中的点序列,你想构建一个漂亮的,光滑曲线可以通过所有这些点。图5-32中的黑色曲线显示了这样条曲线,灰色线段表示使用简单的线性插值的情况,可参见教程5-9。

    image

    图5-32 通过5点的Catmul-Rom样条(spline)

    这在许多情况中是很用的。例如,你可以用它产生一个赛道,可参见教程5-17。当相机非常靠近模型或地形时,你可以使用Catmull-Rom插值产生额外的顶点,使模型或地形看起来更加光滑。

    解决方案

    如果你想在两个基点之间生成Catmull-Rom样条,你还需要知道两个邻近基点。在图5-32中,当你想在点1和点2之间生成曲线时,你需要知道基点0, 1, 2和3的坐标。

    XNA已经带有一维的Catmull-Rom功能。你可以将任意四个基点传入MathHelper. CatmullRom方法,这个方法可以为你计算第二和第三个点之间的曲线。你还需要传递在0和1范围内的第五个参数,表示你想要第二和第三个点之间的哪个点。

    在本教程中,你将这个功能扩展到3维并创建一个方法用来生成多个基点之间的一根样条。

    工作原理

    XNA提供了一维的单个值的Catmul-Rom插值算法。因为Vector3只是三个单个值的组合,所以你可以通过在Vector3的X, Y和Z分量上调用一维XNA方法实现Catmull-Rom插值 。下面的方法是XNA默认方法的3D扩展,它接受四个Vector3而不是四个单个值。

    private Vector3 CR3D(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, float amount) 
    {
        Vector3 result = new Vector3(); 
        
        result.X= MathHelper.CatmullRom(v1.X, v2.X, v3.X, v4.X, amount); 
        result.Y = MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amount); 
        result.Z = MathHelper.CatmullRom(v1.Z, v2.Z, v3.Z, v4.Z, amount); 
        
        return result; 
    } 

    这个方法会返回一个叫做result的Vector3,位于v2和v3之间的样条上。变量amount让你可以指定v2,result和v3之间的距离,当amount为0时返回v2,1返回v3。

    使用CR3D方法计算样条

    有了计算3维Catmull-Rom插值的方法,就可以生成样条的多个点了。给定3维空间中的四个基点v1,v2,v3和v4,下面的方法返回v2和v3之间的20个点的集合,第一个点位于v2。

    private List<Vector3> InterpolateCR(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4) 
    {
        List<Vector3> list = new List<Vector3>(); 
        
        int detail = 20; 
        for (int i = 0; i < detail; i++) 
        {
            Vector3 newPoint = CR3D(v1, v2, v3, v4, (float)i / (float)detail); 
            list.Add(newPoint); 
        }
        return list; 
    } 

    如果你想添加/移除v2和v3之间的点,可以增加/减少detail值(或更好的做法,将detail值作为这个方法的参数)。

    注意:点集合中的最后一个点不是v3。这是因为最后一个点调用CR3D方法时,传递的参数是19/20而不是1。

    你需要手动添加v3,例如,添加这行代码:list.Add(v3);。

    将样条的多个部分连接在一起

    前面的代码生成了位于四个点中间两点之间的样条,下面的代码表示如何扩展样条:

    points.Add(new Vector3(0,0,0)); 
    points.Add(new Vector3(2,2,0)); 
    points.Add(new Vector3(4,0,0)); 
    points.Add(new Vector3(6,6,0)); 
    points.Add(new Vector3(8,2,0)); 
    points.Add(new Vector3(10, 0, 0)); 
    
    List<Vector3> crList1 = InterpolateCR(points[0], points[1], points[2], points[3]); 
    List<Vector3> crList2 = InterpolateCR(points[1], points[2], points[3], points[4]);
    List<Vector3> crList3 = InterpolateCR(points[2], points[3], points[4], points[5]); 
    
    straightVertices = XNAUtils.LinesFromVector3List(points, Color.Red); 
    crVertices1 = XNAUtils.LinesFromVector3List(crList1, Color.Green); 
    crVertices2 = XNAUtils.LinesFromVector3List(crList2, Color.Blue); 
    crVertices3 = XNAUtils.LinesFromVector3List(crList3, Color.Yellow);

    首先在3D空间定义七个(译者注:原文如此,不过好像是六个)点的集合。然后,使用前四个点生成points [ 1]和points [ 2]之间的额外点。然后切换到下一个点获取points [2]和points [ 3]之间的额外点。最后获取了points[3]和points[4]之间的额外点。

    这意味着你最终获得了许多额外点,所有这些点都在从points[1]出发,经过points[2]和point[3],终止于points[4]的样条上(看一下图5-32加深理解)。

    因为你想绘制这些点,所以要从样条的这三个部分生成顶点。首先要将七个基点转换为顶点,这样就可以绘制线段了。使用下面的方法进行此步:

    public static VertexPositionColor[] LinesFromVector3List(List<Vector3> pointList, Color color) 
    {
        myVertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements); 
        VertexPositionColor[] vertices = new VertexPositionColor[pointList.Count]; 
        
        int i = 0; 
        foreach (Vector3 p in pointList) 
            vertices[i++] = new VertexPositionColor(p, color); 
        
        return vertices; 
    }

    上述方法只是简单地将集合中的每个Vector3转换为一个顶点,并将它和选择的颜色存储在一个数组中。

    你可以绘制位于这些顶点间的线段,可参加教程6-1:

    basicEffect.World = Matrix.Identity; 
    basicEffect.View = fpsCam.ViewMatrix; 
    basicEffect.Projection = fpsCam.ProjectionMatrix; 
    basicEffect.VertexColorEnabled = true; 
    basicEffect.TextureEnabled = false; 
    
    basicEffect.Begin(); 
    foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) 
    {
        pass.Begin(); 
        device.VertexDeclaration = myVertexDeclaration; 
        device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, straightVertices, 0, straightVertices.Length - 1); 
        device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, crVertices1, 0, crVertices1.Length - 1);
        device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, crVertices2, 0, crVertices2.Length- 1); 
        device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, crVertices3, 0, crVertices3.Length - 1); 
        pass.End(); 
    }
    basicEffect.End(); 
    代码

    这个教程介绍了两个方法。第一个是XNA自带的Catmull-Rom 插值算法在3维空间中的扩展:

    private Vector3 CR3D(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, float amount) 
    {
        Vector3 result = new Vector3(); 
        
        result.X = MathHelper.CatmullRom(v1.X,v2.X, v3.X, v4.X, amount); 
        result.Y = MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amount); 
        result.Z = MathHelper.CatmullRom(v1.Z, v2.Z, v3.Z, v4.Z, amount); 
        
        return result; 
    } 

    第二个方法可以计算通过四个基点的样条上额外点:

    private List<Vector3> InterpolateCR(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
    {
        List<Vector3> list = new List<Vector3>(); 
        
        int detail = 20; 
        for (int i = 0; i < detail; i++) 
        {
            Vector3 newPoint = CR3D(v1, v2, v3, v4, (float)i / (float)detail); 
            list.Add(newPoint); 
        }
        list.Add(v3); 
        return list; 
    } 

    image

  • 相关阅读:
    AngularJS $http模块POST请求
    thinkphp整合系列之融云即时通讯在线聊天
    Linux 常用命令
    Linux Shell脚本编写规范、例子
    Linux crontab定时执行任务 命令格式与详细例子
    Linux目录详细说明大全, 方便你以后合理规划及管理
    Linux 操作MySQL常用命令行
    SVN服务器搭建和使用(三)
    Linux下的SVN服务器搭建
    python 根据染色体起始终止点坐标来获取碱基序列
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120105.html
Copyright © 2020-2023  润新知