文章目录

  • 一、引言
  • 二、OpenDrive概述
  • 三、OpenDrive重要节点介绍
    • 1.建模用到的主要节点及属性
    • 2.geometry节点
    • 3.lane的width节点
  • 四、根据解析得到的数据创建道路模型
    • 1.把reference line表示为Bezier曲线
      • (1)Bezier曲线介绍
      • (2)获取一条road reference line的信息
      • (3)根据reference line信息生成Bezier曲线数据
        • A. Line
        • B. Arc
    • 2.把Bezier曲线渲染为Mesh
  • 五、沿着道路随机生成建筑物
  • 六、给道路添加程序纹理

一、引言

前置知识:

Unity解析OSM数据,并生成简单模型

这次参考了两个开源项目:

  • OpenDRIVE2Unity3D
  • OpenDrive-Unity-Renderer

第二个项目比较好,是直接用的OpenDRIVE原本的格式。第一个项目用的数据经过处理,直接看不出来是怎么解析源数据的,生成模型的方法也许可以看看。

以下内容都是对OpenDrive-Unity-Renderer的解读。本次使用Unity解析OpenDRIVE生成路网的模型如下:


有路网和建筑模型,车跑上去效果还行,但是交叉口没有处理,还是模型重叠的状态。

二、OpenDrive概述

OpenDRIVE ®是一个开放的文件格式,用于道路网络的逻辑描述。它是由一组仿真专业人员开发并维护的,并得到了仿真行业的大力支持。它的首次公开露面是在2006年1月31日。

1.为什么要使用OpenDRIVE ®?

OpenDRIVE ®是独立于供应商,并且可以免费使用
OpenDRIVE ®包含了所有的主要功能路网
OpenDRIVE ®是一个具有广泛的国际用户群
OpenDRIVE ®是一个管理良好的格式,发展过程透明

OpenDRIVE被开发出来是为了创建一种标准的地图数据格式,方便在各种驾驶仿真模拟器中进行数据交换。

2. OpenDRIVE的特征:

  • XML格式
  • 层次结构
  • 道路几何形状的解析定义:(平面元素,横向/垂直轮廓,车道宽度等)
  • 各种类型的车道
  • 连接点和连接点组
  • 通道的逻辑互连
  • 标志和信号,包括 依存关系
  • 信号控制器(例如用于路口)
  • 路面特性(另请参见OpenCRG)
  • 道路和路边物体
  • 用户可定义的data beads
  • 等等

3. OSM和OpenDRIVE的比较:

可以看到,OpenDRIVE也是一种XML文件,是一种矢量地图。只不过,相比OSM地图,它包含的信息更多,结构也更复杂。



4.文件下载

这里可以下载OpenDRIVE的文件规范,对每个节点、每个节点的每个属性都做了详细的解释。还可以下载示例的OpenDRIVE数据。

三、OpenDrive重要节点介绍

这是我做的一个XML节点和属性的导图。“【】”表示这个节点一般有多个。


在OpenDRIVE中,所有的道路都由一条定义基本几何图形(弧线,直线等)的reference line组成。沿着reference line,可以定义道路的各种属性。例如:高程轮廓线、交通标志等。

可以在思维导图中看到,road节点是重点,其中geometry节点就定义了reference line,而lane节点重点定义了各车道的属性。


通过指定与reference line的横向距离来创建单独的车道。reference line通过连接clothoids(又名欧拉螺旋)或多项式来构建。

请注意圆弧段和直线是clothoids的特殊情况。使用clothoids的优点是,沿着reference line的曲率随路径长度线性变化,这就是为什么大多数道路都是由clothoids构造的。

三种线的曲率变化:

1.建模用到的主要节点及属性

在项目中直接把用到的属性划个重点,接下来我们主要就看看这些属性是什么意思。

  • geometry.x

  • geometry.y

  • geometry.length

  • geometry.hdg

  • geometry.arc.curvature

  • laneSection.right.lane[i].width.a

2.geometry节点

一连串道路 geometry的节点在x/y平面(plan view)上定义了道路reference line的layout。这些geometry节点必须按照升序排列(i.e. increasing s-position). 一个子节点包含了具体的geometric元素的数据。OpenDRIVE现在支持五种geometric元素:

  • straight lines
  • spirals
  • arcs
  • cubic polynomials
  • parametric cubic polynomials

geometry节点的属性:
这里的s-coordinate是指:

inertial是指:

我们看一个具体的例子:


这里的reference line的类型是line,并且起始点的坐标是(x,y)=(512.5,-2250),它的heading朝向的弧度约是1.57,line的长度为583。我们知道了道路参考线的起点、朝向、长度就可以确定这个参考线的位置了。

3.lane的width节点

lanes节点由多个laneSection节点组成。若不定义新的lane section节点,它定义的数值就始终有效,适用于接下来的road(Each lane section is valid until the next lane section is defined)。所以每条road至少有一个从s=0.0m起始的lane section。


一个lane section至少包含left/center/right三种节点中的一个。lane节点被包含在left/center/right节点中。车道用数字ID来区分,这些ID有如下特点:

  • 唯一
  • 连续 (i.e. without gaps),
  • starting from 0 on the reference line
  • 向左侧递增 (positive t-direction)
  • 向右侧递减 (negative t-direction)

每条road的lane的数量是不限的。 reference line被定义为 lane zero,且不允许有width节点,因为它的宽度总为0。

除了center里面的lane,其他lane至少有一个width节点。和lane section一样,如果不定义下一个width节点的话,它定义的数值就一直适用于接下来的lane。如果一个lane有多个width节点,它们必须按照升序排列。

width节点的属性如下:

看看具体数据:

上述的lane的width节点中,只有属性“a”的值不为0。观察了下整个xml文件,发现几乎所有的width节点的属性都一样,只有a有值。那么a的含义是什么呢?

查文档后发现,在给定点处的实际宽度是用三阶多项式函数计算的,这个函数看起来像这样:width=a+b∗ds+c∗ds2+d∗ds3width = a + b*ds + c*ds^2 + d*ds^3width=a+b∗ds+c∗ds2+d∗ds3其中,width就是给定点(位于reference line上的点?)处的车道宽度。a,b,c,d是常数系数。ds是the distance along the reference line between the start of the entry and the actual position.总体来讲,如果车道宽度变化比较复杂,那么计算也比较复杂。但是在本xml文件中,对车道的宽度做了简化,所有宽度都为10,ds相乘的系数都为0。

四、根据解析得到的数据创建道路模型

对于每条道路,已知它们的参考线起点坐标、参考线方向、长度。在这个xml文件中只有两种参考线类型,line和arc。我们可以把line和arc都用贝塞尔曲线来表示。根据参考线的信息,得到相应贝塞尔曲线上点的坐标,从而确定最终的模型顶点,渲染出模型。

(PS:其实我们也可以不用贝塞尔曲线,使用和OSM数据生成模型一样的方法。因为不管是line还是arc,实际上都是由线段拼接表示的,我们得到线段的顶点后,完全可以直接用这些顶点生成Mesh。)

按照这个思路,我们可以创建一个脚本BezierCurvePath,用来单独绘制每条road。在绘制road时,分为两个步骤:首先把这条road的参考线转为贝塞尔曲线上的点;再根据贝塞尔曲线上的点确定模型顶点,进行渲染。

1.把reference line表示为Bezier曲线

(1)Bezier曲线介绍

在这个网站可以调整控制点,并实时看到Bezier曲线的效果。

搬一个数学总结:

(2)获取一条road reference line的信息

public List<BezierCurveData> curveDatas = new List<BezierCurveData>();void Start(){float length_s = 0f,x = 0f, y = 0f;float angle = 0f,curvature = 0f,patchWidth = 0f; int lanes = 0;       string path = Application.dataPath+ "/XMLFiles/cloverleaf.xml";XmlSerializer serializer = new XmlSerializer(typeof(OpenDRIVE));string xml = File.ReadAllText(path);using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml))){openDrive = (OpenDRIVE)serializer.Deserialize(stream);//这个脚本只能处理一个road,所以设置了一个roadVariable来指定road的index//reference line起点的x坐标x = openDrive.roads[roadVariable].plainView.geometry.x;//reference line起点的y坐标y = openDrive.roads[roadVariable].plainView.geometry.y;//reference line的长度length_s = openDrive.roads[roadVariable].plainView.geometry.length;//reference line 的heading角度angle = openDrive.roads[roadVariable].plainView.geometry.hdg;//生成道路模型的宽度:车道数x10cubeThickness = openDrive.roads[roadVariable].lanes.laneSection.right.lane.Length * 10;           //如果reference是arc,存储曲率try    {curvature = openDrive.roads[roadVariable].plainView.geometry.arc.curvature;}catch (NullReferenceException e){}}CityGenerator(x, y, length_s, angle, curvature);}
(3)根据reference line信息生成Bezier曲线数据
A. Line

void CityGenerator(float x, float y, float len, float angle, float curvature)
{   float x_end = 0.0f, y_end = 0.0f; if (curvature != 0.0f) {......}else{//根据length和theta角度算出endPosx_end = len * Mathf.Cos (angle);y_end = len * Mathf.Sin (angle);//按照长度,将line等分为3段,这样就可以得到4个点了float add_pointx = x_end / 3, add_pointy = y_end / 3;float point1x, point2x, point3x, point4x;float point1y, point2y, point3y, point4y;point1x = x;point2x = point1x + add_pointx;point3x = point2x + add_pointx;point4x = point3x + add_pointx;point1y = y;point2y = point1y + add_pointy;point3y = point2y + add_pointy;point4y = point3y + add_pointy;//定义Bezier曲线的4个控制点var curve = new BezierCurveData ();curve.points = new Vector3[4]{new Vector3 (point1x, 0.1f, point1y),new Vector3 (point2x, 0.1f, point2y),new Vector3 (point3x, 0.1f, point3y),new Vector3 (point4x, 0.1f, point4y)};         curveDatas.Add (curve);}
}
B. Arc

关于曲率的介绍

先搞懂这些角度、点的含义:

在下面这张图中,我们可以看到坐标系原点在右下角,x轴和z轴垂直,构成了水平面,这和Unity场景中的俯视图看到的坐标系是一样的。在opendrive的数据中,reference line的起点所在坐标系也是平面坐标系,是用x和y来表示的,这里的y和unity中的z等同,都是水平面上与x轴垂直的轴。


我自定义了一个reference line,它的xml节点属性值如下:起点的坐标为(x,y)=(0,0),hdg=1.57,转换为角度为90度,这条arc的弧长为1374m。这里的hdg,指的就是这条弧在起点处的切线,与x轴的夹角。从图上也可以看出来是90度。

<road id="3" name="R3" length="1374.446786" junction="-1"><planView><geometry s="0.0" x="0" y="0" hdg="1.570796" length="1374.446786"><!-- hdgDegrees="90.0"--><arc curvature="-0.001143" /><!-- radius="875.0"--></geometry></planView><lanes><laneSection s="0.0"><right><lane id="-1" type="driving" level="false"><width sOffset="0.0" a="10.0" b="0.0" c="0.0" d="0.0" /></lane></right></laneSection></lanes>
</road>

在下面这段代码中,startAngle就是hdg的值,roadLength就是弧长,radius根据曲率的绝对值计算得到,值为875,(在xml的注释中已经写出)。如果曲率的值小于0,那么这条arc的方向就是顺时针的。centerX和centerY是arc所在圆的圆心的横纵坐标。

if (curvature != 0.0f) {float startAngle = angle;float roadLength = len;float radius = 1.0f / Mathf.Abs (curvature);bool clockwise = curvature < 0.0f;float centerX = x - radius * Mathf.Cos (startAngle - HALF_PI) * (clockwise ? -1.0f : 1.0f);float centerY = y - radius * Mathf.Sin (startAngle - HALF_PI) * (clockwise ? -1.0f : 1.0f);......
}

计算圆心需要考虑startAngle、clockwise的值,我们就用最简单的图上的这个arc为例:startAngle=HALF_PI,clockwise为true,所以centerX=x−radius∗Cos(0)∗(−1)=x+radiuscenterX=x-radius*Cos(0)*(-1)=x+radiuscenterX=x−radius∗Cos(0)∗(−1)=x+radius centerY=y−radius∗Sin(0)∗(−1)=ycenterY=y-radius*Sin(0)*(-1)=ycenterY=y−radius∗Sin(0)∗(−1)=y也就得到了图上所示的center点的坐标。

在得到如上信息后,我们可以考虑如何创建多个折线段来表示这么长的弧线,每个折线段可以用一条贝塞尔曲线来表示,该贝塞尔曲线的中间两个点分别和起点终点重合。可以用下面getNewCurve函数来构建具体的贝塞尔曲线:我们只需要输入该折线段的起点和终点的vector。

BezierCurveData getNewCurve(Vector3 prev_vector, Vector3 end_vector)
{var curve_data = new BezierCurveData();curve_data.points = new Vector3[4]{prev_vector, prev_vector,end_vector, end_vector};return curve_data;
}

为了获得这些小折线段的起点(start_vector )和终点(second_vector),我们可以使用一个while循环,当这些小折线段的总长度没有达到弧线长度时,就不断更新start_vectorsecond_vector

 ......//将 r 减去一个车道宽度(10)float r = radius * (clockwise ? 1.0f : -1.0f);   Vector3 first_vector = new Vector3 ();//定义整个 arc 的第一个折线段起点if (roadLength < 750) {r = -r;first_vector = getCurvePart (startAngle, r - 10, centerX, centerY);} elsefirst_vector = getCurvePart (startAngle, r - 10, centerX, centerY); //these are two vectors starting and ending point of the curvevar end_vector = new Vector3 (x, 0.01f, y);var start_vector = first_vector;float distance = 0f;//折线段总长度while (true) {//每次增加一点角度,使得second_vector不断向end_vector靠近。startAngle = startAngle + 0.05f;var second_vector = getCurvePart (startAngle, r - 10, centerX, centerY);var curve_new = new BezierCurveData ();//循环的结束条件:总长度超过弧长distance += Vector3.Distance (start_vector, second_vector);if (distance >= roadLength) {curve_new = getNewCurve (start_vector, end_vector);curveDatas.Add (curve_new);start_vector = second_vector;break;}curve_new = getNewCurve (start_vector, second_vector);curveDatas.Add (curve_new);start_vector = second_vector;}
}

在每次循环中,递增startAngle使得second_vector不断向end_vector靠近,这是通过getCurvePart函数实现的:(start_x,start_y)构成的向量和(center_x,center_y)构成的向量相加,得到我们的目标向量(所代表的的点在arc上)。

Vector3 getCurvePart(float start_angle, float r, float center_x, float center_y)
{float start_x = 0.0f,start_y = 0.0f;start_x = r * Mathf.Cos(start_angle);start_y = r * Mathf.Sin(start_angle);return new Vector3(start_x + center_x, 0.1f, start_y + center_y);
}

2.把Bezier曲线渲染为Mesh

得到curveData数组后,我们遍历每一条曲线,生成一个curve游戏对象,再生成具体的Mesh,作为curve游戏对象的子物体。对于line来说,curveData数组只有一个元素,对于arc来说,有很多个元素。

void CreateMesh()
{for (int i = 0; i < curveDatas.Count; i++){if (curveMeshes.Count > i){    //此函数会在Update中实时调用,已经生成对象后就直接调用对象的CreateMesh函数curveMeshes[i].CreateMesh();}else{ //首次遍历时,先创建curveMesh对象实例var curveMesh = BezierCurveMesh.Instantiate(i, this);curveMeshes.Insert(i, curveMesh);curveMesh.CreateMesh();}}
}

如果想知道具体是怎么生成的Mesh,可以参考这个开源项目:unity-procedural-mesh-bezier-curve。本项目的这部分代码就是引用于它(也可以在本项目中直接查看相关代码,不过这部分和路网生成关系不大,就不放在这里解读了)。

五、沿着道路随机生成建筑物

(1)准备好建筑物的prefab共15个


(2)沿着道路随机摆放建筑物

可以观察到:建筑与路面之间有一定的距离;建筑不会摆放到道路上去;建筑都朝向路面。

关于实现方法,简单来说就是要分成line和arc类型来处理。在处理每种路面的时候也要考虑线段的方向,从而确定房屋摆放的位置。这部分细节比较多,如果需要优化或者修改,可以详细看看。

六、给道路添加程序纹理

道路模型没有按照车道区分,所以多车道其实也是一个模型。而单车道和多车道,外表是不同的。


本项目对于单车道和多车道使用的是同一张图片,但是对于多车道,在其上绘制了白色的线,作为新的贴图。

void Texture1(float x, float y, float len, float angle, float curvature, float patchWidth, int lanes,float []lane)
{Texture2D texture = new Texture2D((int)patchWidth, (int)len);Material m_Material;//Fetch the Renderer from the GameObjectRenderer m_Renderer = GameObject.FindGameObjectWithTag("Road").GetComponent<Renderer>();m_Renderer.material = new Material(Shader.Find("Standard"));m_Material = m_Renderer.material;//Make sure to enable the Keywordsm_Material.EnableKeyword ("_NORMALMAP");m_Material.EnableKeyword("_DETAIL_MULX2");m_Material.SetTexture("_MainTex", textures);m_Material.SetFloat ("_Metallic", 0.3f);m_Material.SetFloat ("_Metallic/_Smoothness", 0.0f);//【绘制白色车道线】int a1 = (int)lane[0];int j = 0,b1=0;for (int i= 1; i< lanes; i++) {int l=0;while(l<len){b1 = l+80;//每条短线长 80for ( j = l; j < b1; j++)       {Color32 color = new Color32(255,255,255,255);texture.SetPixel (a1, j, color);texture.filterMode = FilterMode.Point;              }           l=j+100; //短线间间隔 100}a1 = a1 + (int)lane[i];}m_Material.SetTexture("_DetailAlbedoMap", texture);m_Material.SetTextureScale("_MainTex", new Vector2(1, 2));materials = m_Material;texture.Apply();
}

Unity解析OpenDRIVE地图数据,并生成路网模型相关推荐

  1. echart 乡镇地图 地图数据在线生成工具_自定义样式生成工具

    最近有点时间,做了个echart地图在线生成图片,支持省.市.县区.乡镇四级地图查看的功能,做ppt或者设计可能会用到,本人不是专业UI,页面简陋,功能还算比较全, 加了点新功能,调整了IE浏览器访问 ...

  2. OpenDrive格式地图数据解析

    OpenDrive地图解析代码可以参考,https://github.com/liuyf5231/opendriveparser OpenDrive地图文件格式为xml,详细的介绍可以参考 http: ...

  3. Unity 解析视频流数据

    首先在AndroidStudio中将数据转换为yuv图片数据,然后传到Unity,Unity解析Yuv图片数据并显示 MediaCodec mCodec = MediaCodec.createDeco ...

  4. 高德SD地图数据生产自动化技术的路线与实践(道路篇)

    一.背景及现状 近些年,国内道路交通及相关设施的基础建设日新月异.广大用户日常出行需求旺盛,对所使用到的电子地图产品的数据质量和现势性提出了更高的要求.传统的地图数据采集和生产过程,即通过采集设备实地 ...

  5. python爬虫爬取腾讯网站——实时疫情数据并生成Excel表格

    一.基本介绍: 开发背景:自从2020年新冠疫情发生后,至今为止的相关疫情数据新闻已经是非常的巨大了,我们无时不在在用数据尝试帮助我们解剖全球的疫情状况.由此可见,新冠疫情数据的新闻报道数量与国内疫情 ...

  6. Python实现OSM地图数据解析——OSM2GMNS

    转载 OSM2GMNS相关说明文档: 详细用户文档:https://osm2gmns.readthedocs.io: Github链接:https://github.com/jiawei92/OSM2 ...

  7. 集中式自动驾驶控制域中的高精地图数据解析算法

    作者 | Aimme 出品 | 焉知 下一代自动驾驶架构设计中已经倾向于以软件定义汽车的方式集合,主要涉及中央集中处理单元的方式会存在较大的改变.这一改变主要体现在外围传感单元将不再有单独的分布式处理 ...

  8. Python实现OSM地图数据解析——OSM2Rail

    目录 1. 简介 2. 功能特色 2.1 自动下载OpenStreetMap地图数据包 2.2 提供更精确的铁路网络数据解析 2.3 扩展GMNS标准 3. 快速上手 4. 效果展示 5. 相关阅读 ...

  9. 基于高德离线地图数据实现GPS经纬度逆解析城市信息

    一.获取离线数据:        如果你只是想实现逆解析功能,那该步骤可以跳过,因为地图数据已经收集打包好了.直接文末找git路径就好了. 准备工作:python  numpy h5py   open ...

最新文章

  1. Win32 API消息函数:GetMessagePos
  2. LINUX samba的安装使用
  3. 个人小应用服务器安装搭建,HP 360p Gen9 使用winpe安装centos[一]
  4. 使用DirectX截屏
  5. 使用jsp和tld实现javaweb开发
  6. 北斗导航 | Python实现读取GPS(txt)数据并画轨迹
  7. feature改变属性表的值
  8. C++vector容器-插入和删除
  9. sql中索引不会被用到的几种情况
  10. 计算机表格复制粘贴后不变,excel表格复制粘贴后格式不变
  11. 史上最简单的玻尔兹曼机模型讲解
  12. 说说emit(上)基本操作
  13. html 字体图标 颜色怎么改,关于css:如何设置Font Awesome Icons的图标颜色,大小和阴影的样式...
  14. php自定义按钮,vue实现自定义按钮的方法介绍(附代码)
  15. 【工具】js脚本下载百度文库生成word文本 + python爬取百度文库
  16. DXF文件结构与示意
  17. Flutter高仿微信-第57篇-添加好友
  18. 和导师的微信聊天翻车现场,你一定也经历过!
  19. 在python平台上利用pymol来查找PDB文件中蛋白质的相互作用位点
  20. handlebars使用

热门文章

  1. 成为杰出人物的路线图_如何成为杰出的初级开发人员
  2. Ubuntu之操作系统安装
  3. 运行安装包出现installer integrity check has failed错误快速解决
  4. 捷联惯导基础知识解析之一(姿态表示方法基础知识)
  5. [20150911]关于增量检查点.txt
  6. matlab在数学中的应用论文,探究数学建模中MATLAB的运用
  7. 本人64位windows下安装最新版XGBoost,附操作步骤图
  8. 算法设计与分析第二章课后作业
  9. 进一步简化你的 ViewHolder
  10. easyui_layout折叠