当前位置: 首页 > article >正文

基于 Vue + TS + Ant Design Vue 实现精细化菜单按钮权限授权组件腥

7.1 初识三维模型7.1.1 三维模型的数据载体随着计算机图形技术的发展我们或多或少都会见过或者听说过三维模型。笔者始终记得小时候第一次在电视上看到三维动画《变形金刚超能勇士》的震撼感受而现在我们已经可以在手机上玩三维游戏《王者荣耀》实时操作造型精美的英雄模型了。这些东西的背后都离不开三维模型数据他们往往是通过像Autodesk 3D Max这样的三维建模软件制作出来的。如同栅格数据和矢量数据一样三维模型数据也有形形色色的数据格式。这些不同的数据格式有时来源于不同的三维建模软件一些机构或者组织出于标准化的目的也会定义某种通用的三维模型数据格式。这些不同格式的数据文件构成了三维模型的数据载体。常用的三维数据格式如下表7.1所示名称 全称 特点PLY Polygon File Format 描述最简单OBJ Wavefront .obj file 经典通用性高3DS 3D Studio 广泛应用的经典格式MAX 3D Studio Max 3DMax专属格式glTF Graphics Language Transmission Format 现代自由开放适合OpenGL管线FBX Filmbox 现代而全面的格式游戏引擎中常用对这些三维数据格式我们可以做一个大致的认识因为后面可能会直接用到PLY是一种最简单的三维数据格式一般用其存储不带纹理的模型数据可通过文本和二进制两种形式来描述。OBJ是非常通用的三维模型数据格式与PLY相比增加了对材质的描述包括纹理信息。因此一个典型的OBJ格式的文件除了.obj文件同时还会附带一个.mtl文件用于描述材质。而在材质文件中就可以指定纹理图片的地址。很长一段时间内由于其对三维模型文件的描述比较全面通常用于不同三维建模软件的中转。3DS和MAX属于著名三维建模软件Autodesk 3ds Max的专属的文件数据格式。理论上来说3DS和MAX都属于商业三维数据格式但是Autodesk 3ds Max在三维建模上的使用非常广泛所以这两种数据格式也很常见。不同的是3DS现在已经基本能够各种三维软件所识别一些开源工具也能解析识别Max则是Autodesk 3ds Max所专用其他三维软件或者开源工具一般都不支持。Max还有一个笔者认为不太好的特点就是版本迭代太快低版本的Autodesk 3ds Max无法打开高版本的Max格式数据。glTF是一种自由开放无专利限制的适合传输和加载3D模型和场景的文件格式。相比较前面介绍三维数据格式来说glTF诞生的时间较晚可以采用了更为现代的图形技术来封装和组织这个格式。Khronos Group制定和维护了glTF数据格式的标准同时由于其也是OpenGL接口标准的指定者和维护者因此glTF特别适合OpenGL系列OpenGLOpenGL ESWebGL的图形渲染流水线所需要进行的处理。这个特点意味着glTF足够轻量化。目前glTF有1.0和2.0两个版本其中glTF2.0已经成为了ISO国际标准。FBX同样也是Autodesk公司开发的一种通用三维数据格式。与glTF一样FBX也是更为现代的三维数据格式比如支持显示效果更为真实的PBR材质。FBX广泛应用于游戏开发领域目前最火的三维游戏引擎Unity和Unreal都支持直接导入这种格式。Autodesk官方为FBX提供了开发包支持解析和修改该格式数据文件。除此之外也有一些开源第三方组件使用自己的方式兼容它。综合来说PLY、OBJ和3DS都属于比较早期的三维模型数据格式受限于当年的图形技术的认知而Max、glTF和FBX则设计得更为现代文件组织结构更为合理能提供更为强大的可视化效果。例如现代三维模型数据格式已经不仅仅是像早期三维模型数据格式那样只包含模型数据本身还会包括材质、动画、灯光甚至相机等其描述的对象可以是整个三维场景。7.1.2 从地形来认识三维模型PLY格式如果没有三维图形的基础知识上一小节的论述可能会让有的读者一头雾水。那么我们可以从GIS中的地形开始说起——在第6.3节中我们就已经使用过PLY格式的三维数据将其表达成不规则三角网地形。但是如图6.6所示的地形实在过于简陋有没有办法给这个白模赋予着色信息使其有更好的可视化效果呢一种最简单的可视化优化方案是可以结合第6.5节中晕渲图的实现创建一个带颜色信息的地形三维模型数据。如下例7.1所示//例7.1 DEM数据转换PLY三维模型#include#include#include#include#include#includeusing namespace std;struct VertexProperty {double x;double y;double z;uint8_t red;uint8_t green;uint8_t blue;};size_t vertexCount;vector vertexData;size_t faceCount;vector indices;//颜色查找表using F_RGB std::array ;vector tableRGB(256);//生成渐变色void Gradient(F_RGB start, F_RGB end, vector RGBList) {F_RGB d;for (int i 0; i 3; i) {d[i] (end[i] - start[i]) / RGBList.size();}for (size_t i 0; i RGBList.size(); i) {for (int j 0; j 3; j) {RGBList[i][j] start[j] d[j] * i;}}}//初始化颜色查找表void InitColorTable() {F_RGB blue({17, 60, 235}); //蓝色F_RGB green({17, 235, 86}); //绿色vector RGBList(60);Gradient(blue, green, RGBList);for (int i 0; i 60; i) {tableRGB[i] RGBList[i];}F_RGB yellow({235, 173, 17}); //黄色RGBList.clear();RGBList.resize(60);Gradient(green, yellow, RGBList);for (int i 0; i 60; i) {tableRGB[i 60] RGBList[i];}F_RGB red({235, 60, 17}); //红色RGBList.clear();RGBList.resize(60);Gradient(yellow, red, RGBList);for (int i 0; i 60; i) {tableRGB[i 120] RGBList[i];}F_RGB white({235, 17, 235}); //紫色RGBList.clear();RGBList.resize(76);Gradient(red, white, RGBList);for (int i 0; i 76; i) {tableRGB[i 180] RGBList[i];}}//根据高程选颜色inline int GetColorIndex(double z, double min_z, double max_z) {int temp (int)floor((z - min_z) * 255 / (max_z - min_z) 0.6);return temp;}void ReadDem() {string workDir getenv(GISBasic);string demPath workDir /../Data/Model/dem.tif;GDALDataset* dem (GDALDataset*)GDALOpen(demPath.c_str(), GA_ReadOnly);if (!dem) {cout Cant Open Image! endl;return;}int srcDemWidth dem-GetRasterXSize();int srcDemHeight dem-GetRasterYSize();//坐标信息double geoTransform[6] {0};dem-GetGeoTransform(geoTransform);double srcDx geoTransform[1];double srcDy geoTransform[5];double startX geoTransform[0] 0.5 * srcDx;double startY geoTransform[3] 0.5 * srcDy;double endX startX (srcDemWidth - 1) * srcDx;double endY startY (srcDemHeight - 1) * srcDy;size_t demBufNum (size_t)srcDemWidth * srcDemHeight;vector srcDemBuf(demBufNum, 0);int depth sizeof(float);dem-GetRasterBand(1)-RasterIO(GF_Read, 0, 0, srcDemWidth, srcDemHeight,srcDemBuf.data(), srcDemWidth, srcDemHeight,GDT_Float32, depth, srcDemWidth * depth);GDALClose(dem);double minZ *(std::min_element(srcDemBuf.begin(), srcDemBuf.end()));double maxZ *(std::max_element(srcDemBuf.begin(), srcDemBuf.end()));vertexCount (size_t)srcDemWidth * srcDemHeight;vertexData.resize(vertexCount);for (int yi 0; yi srcDemHeight; yi) {for (int xi 0; xi srcDemWidth; xi) {size_t m (size_t)srcDemWidth * yi xi;vertexData[m].x startX xi * srcDx;vertexData[m].y startY yi * srcDy;vertexData[m].z srcDemBuf[m];int index GetColorIndex(srcDemBuf[m], minZ, maxZ);vertexData[m].red (uint8_t)(tableRGB[index][0] 0.5);vertexData[m].green (uint8_t)(tableRGB[index][1] 0.5);vertexData[m].blue (uint8_t)(tableRGB[index][2] 0.5);}}faceCount (size_t)(srcDemHeight - 1) * (srcDemWidth - 1) * 2;// indices.resize(faceCount);for (int yi 0; yi srcDemHeight - 1; yi) {for (int xi 0; xi srcDemWidth - 1; xi) {size_t m (size_t)srcDemWidth * yi xi;indices.push_back(m);indices.push_back(m srcDemWidth);indices.push_back(m srcDemWidth 1);indices.push_back(m srcDemWidth 1);indices.push_back(m 1);indices.push_back(m);}}}void WriteDemModel() {string workDir getenv(GISBasic);string demPath workDir /../Data/Model/dst.ply;ofstream outfile(demPath);if (!outfile) {printf(write file error %s\n, demPath.c_str());return;}outfile ply\n;outfile format ascii 1.0\n;outfile comment CL generated\n;outfile element vertex to_string(vertexCount) \n;outfile property double x\n;outfile property double y\n;outfile property double z\n;outfile property uchar red\n;outfile property uchar green\n;outfile property uchar blue\n;outfile element face to_string(faceCount) \n;outfile property list uchar int vertex_indices\n;outfile end_header\n;outfile fixed;for (int vi 0; vi vertexCount; vi) {outfile vertexData[vi].x ;outfile vertexData[vi].y ;outfile vertexData[vi].z \n;outfile (int)vertexData[vi].red ;outfile (int)vertexData[vi].green ;outfile (int)vertexData[vi].blue \n;}for (size_t fi 0; fi faceCount; fi) {outfile 3;for (int ii 0; ii 3; ii) {int id indices[fi * 3 ii];outfile id;}outfile \n;}}int main() {GDALAllRegister(); //注册格式InitColorTable();ReadDem();WriteDemModel();return 0;}我们将生成PLY格式的三维数据导入到开源三维软件MeshLab中其显示的效果如下图7.1所示。可以看到虽然我们更换了一个地形数据但是其展示的效果与第6.5节中晕渲图的效果比较类似。其实准确来说二维晕渲图的可视化效果正是来自于三维渲染一定光照条件下的实现。图7.1 带颜色信息的地形三维模型数据例6.4使用PLY数据格式来表达不规则三角网地形而本例表达的则是规则格网地形。但是只要是保存为三维数据格式其存储的数据信息都是相同的都包含顶点信息和索引信息。其中顶点信息不再只包含位置信息了还包含了每个顶点的颜色信息RGB因此本例封装了一个顶点属性的结构体来表达一个顶点struct VertexProperty {double x;double y;double z;uint8_t red;uint8_t green;uint8_t blue;};我们生成PLY格式是文本格式通过记事本打开也可以直接看到位置信息和颜色信息如下图 7.2所示图7.2 PLY格式文件中的位置信息和颜色信息通过这个简单的例子就可以知道为什么需要三维模型数据分成顶点信息和索引信息来保存。在本例中是将DEM每个方形格网转换成两个三角形如果格网DEM为m行n列这意味着存在(m-1)?(n-1)个格网即2?(m-1)?(n-1)个三角形。如果我们以一个三角形顶点接着一个三角形顶点来描述三维模型文件那么就需要6?(m-1)?(n-1)个顶点。但其实DEM中的顶点个数很明确就是 m ?n个——这意味着至少存在这4到5倍的数据冗余。先描述顶点信息再描述索引信息这样可以兼容一些共顶点的情况因而可以最大化减少数据量毕竟一个索引比一个顶点的数据量更少。如下图7.3所示是本例生成的PLY格式文件中的索引信息。其中每一行代表一个面3表示绘制的是三角形后面三个数则表示顶点数据中每个顶点的索引编号。当然PLY格式也可以将每个面描述成四边形或者多边形。但是目前大多数图形API或渲染引擎都将三角形作为绘制的基本图元以三角形作为最小的绘制单位是效率最高的。图7.3 PLY格式文件中的索引信息7.1.3 地形和影像组成三维模型OBJ格式在上一节中展示了基于地形的三维模型的可视化图7.1但这种效果其实是一种风格化的效果。所谓风格化效果就是不一定写实但是由于抓住了事物对象主要特征我们可以很容易确信展示就是渲染的就是该事物对象例如卡通风格就是一种典型的风格化效果。与风格化效果相对应的就是写实效果写实效果能够让用户有更为真实的感受。在这里如果要让这个地形三维模型数据得到这种写实的效果那么就可以利用我们在第5章介绍过的栅格影像DOM将其铺在地形三维模型的表面而不是使用顶点着色。问题在于如何将这个影像铺在三维模型上呢这个时候我们就要用到除了位置和颜色之外的另一种顶点属性信息纹理坐标。在计算机图形中影像/图片数据在被传输到GPU后就被封装成一种名为“纹理”的数据对象。顶点的纹理坐标就是该顶点对应于这张纹理图片的位置一个三角形面片有三个顶点也就对应了纹理图片上的三个位置从而可以让我们取得纹理上的颜色值。而三角形内部的顶点的颜色值就直接从纹理上的三角形面片的区域取值内插得到。在这里说的内插过程有点像我们之前第5章中介绍的影像进行图像内插过程但并不完全准确。这涉及到计算机图像渲染流水线中光栅化的过程是一个很复杂的过程。本章我们只用关心三维模型数据本身可视化的问题我们后面再介绍。在这里我们只需要知道顶点信息可以附带纹理坐标信息从而将纹理图片的颜色值映射到模型上。如果我们要在一个三维模型中附带纹理和纹理坐标信息那么使用PLY格式的三维模型数据就不是很方便了。如下例7.2所示我们使用OBJ格式的三维模型数据来表达带纹理和纹理坐标信息的地形//例7.2 DEM数据转换OBJ三维模型#include#include#include#include#include#includeusing namespace std;struct VertexProperty {double x;double y;double z;double texCoordX;double texCoordY;};size_t vertexCount;vector vertexData;size_t faceCount;vector indices;void ReadDem() {string workDir getenv(GISBasic);string demPath workDir /../Data/Model/dem.tif;GDALDataset* dem (GDALDataset*)GDALOpen(demPath.c_str(), GA_ReadOnly);if (!dem) {cout Cant Open Image! endl;return;}int srcDemWidth dem-GetRasterXSize();int srcDemHeight dem-GetRasterYSize();//坐标信息double geoTransform[6] {0};dem-GetGeoTransform(geoTransform);double srcDx geoTransform[1];double srcDy geoTransform[5];double startX geoTransform[0] 0.5 * srcDx;double startY geoTransform[3] 0.5 * srcDy;double endX startX (srcDemWidth - 1) * srcDx;double endY startY (srcDemHeight - 1) * srcDy;size_t demBufNum (size_t)srcDemWidth * srcDemHeight;vector srcDemBuf(demBufNum, 0);int depth sizeof(float);dem-GetRasterBand(1)-RasterIO(GF_Read, 0, 0, srcDemWidth, srcDemHeight,srcDemBuf.data(), srcDemWidth, srcDemHeight,GDT_Float32, depth, srcDemWidth * depth);GDALClose(dem);double minZ *(std::min_element(srcDemBuf.begin(), srcDemBuf.end()));double maxZ *(std::max_element(srcDemBuf.begin(), srcDemBuf.end()));vertexCount (size_t)srcDemWidth * srcDemHeight;vertexData.resize(vertexCount);for (int yi 0; yi srcDemHeight; yi) {for (int xi 0; xi srcDemWidth; xi) {size_t m (size_t)srcDemWidth * yi xi;vertexData[m].x startX xi * srcDx;vertexData[m].y startY yi * srcDy;vertexData[m].z srcDemBuf[m];vertexData[m].texCoordX (double)xi / (srcDemWidth - 1);vertexData[m].texCoordY (double)yi / (srcDemHeight - 1);}}faceCount (size_t)(srcDemHeight - 1) * (srcDemWidth - 1) * 2;// indices.resize(faceCount);for (int yi 0; yi srcDemHeight - 1; yi) {for (int xi 0; xi srcDemWidth - 1; xi) {size_t m (size_t)srcDemWidth * yi xi;indices.push_back(m);indices.push_back(m srcDemWidth);indices.push_back(m srcDemWidth 1);indices.push_back(m srcDemWidth 1);indices.push_back(m 1);indices.push_back(m);}}}void WriteDemModel() {string workDir getenv(GISBasic);string demPath workDir /../Data/Model/dst.obj;ofstream outfile(demPath);if (!outfile) {printf(write file error %s\n, demPath.c_str());return;}outfile mtllib dst.mtl\n;outfile fixed;for (int vi 0; vi vertexCount; vi) {outfile v ;outfile vertexData[vi].x ;outfile vertexData[vi].y ;outfile vertexData[vi].z \n;}for (int vi 0; vi vertexCount; vi) {outfile vt ;outfile vertexData[vi].texCoordX ;outfile vertexData[vi].texCoordY \n;}outfile usemtl dst\n;for (size_t fi 0; fi faceCount; fi) {outfile f;for (int ii 0; ii 3; ii) {int id indices[fi * 3 ii] 1;outfile id / id;}outfile \n;}string mtlPath workDir /../Data/Model/dst.mtl;ofstream mtlfile(mtlPath);if (!mtlfile) {printf(write file error %s\n, mtlPath.c_str());return;}mtlfile newmtl dst\n;mtlfile illum 2\n;mtlfile map_Ka tex.jpg\n;mtlfile map_Kd tex.jpg\n;mtlfile map_Ks tex.jpg\n;mtlfile Ns 10.000\n;}int main() {GDALAllRegister(); //注册格式//设置Proj数据std::string projDataPath getenv(GISBasic);projDataPath /share/proj;CPLSetConfigOption(PROJ_LIB, projDataPath.c_str());ReadDem();WriteDemModel();return 0;}程序运行完成之后生成.obj格式后缀的三维模型文件其数据内容与.ply格式后缀的三维模型文件差不多都是由顶点数据和索引数据组成的只不过两者的数据组织形式不同。OBJ格式的数据描述形式是先描述顶点位置信息如下图7.4所示图7.4 OBJ格式文件中的位置信息接着描述顶点的纹理坐标信息如下图7.5所示图7.5 OBJ格式文件中的纹理坐标信息最后是索引数据信息如下图7.6所示。OBJ格式的索引数据的设计稍微复杂了一点将索引划分成顶点位置数据的索引以及顶点纹理坐标的索引。如果有顶点法向量数据还可以加上顶点法向量数据的索引。但这里不用进行区分直接都使用同一个索引值图7.6 OBJ格式文件中的顶点索引信息程序在生成.obj文件的同时生成了一个后缀名为.mtl的文件。mtl是英文单词material材质的缩写这个文件定义了OBJ格式文件的材质信息。在图7.4中我们可以看到.obj文件在第一行描述信息就引用了这个.mtlmtllib dst.mtl这一行描述信息表示dst.mtl文件是该.obj文件的材质文件。在这个材质文件中描述了一些材质参数其中就包括我们前面讲到的纹理图片文件如下图7.7所示tex.jpg文件就是我们使用的纹理图7.7 OBJ格式的材质文件材质是三维可视化最为重要的概念之一决定了物体对象以什么样的可视化效果渲染展示出来。例如同一个物体的质感体现是金属、木头还是塑料需要通过材质来进行定义。不过这个问题很复杂目前我们只需要知道通常在材质中使用纹理是材质的关键参数。三维物体不会仅仅只包含一个材质材质文件中可能会包含多个材质。每个材质通常与一段顶点索引数据相关联表示这一段图元是通过该材质进行渲染的。如图7.6所示在描述顶点索引信息之前使用了如下描述语句usemtl dst这表示以下三角面图元是通过材质文件dst.mtl中的dst材质来进行渲染的。最后将生成OBJ格式的三维数据导入到开源三维软件MeshLab中显示的三维渲染效果如下图7.8所示。可以看到相比例7.1的结果来说具有更好的写实效果可以看到突起的山峰以及峡谷的河流。这是因为使用了栅格影像DOM来作为纹理图片而DOM影像通常由光学影像拍摄真实的地形拍摄而来真实感效果更好。图7.8 带纹理的地形三维模型数据7.1.4 认识现代三维模型数据glTF格式三维模型数据是进行三维可视化的初始载体。在进行图形渲染的起始阶段会将硬盘中的三维模型数据读取到内存中然后再传入显存做进一步处理。因此三维数据格式总是会随着计算机图形技术的发展而发展要么会出现更新的三维模型数据的格式要么会在已有的三维模型数据上作扩展。相比较前面介绍的PLY格式或者OBJ格式glTF是一种更为现代的三维模型数据格式。这种现代性不仅仅是体现在时间上更是体现在多方面的现代三维数据格式包含的数据内容越来越多大多数与三维场景渲染相关的信息都可以进行定义和保存。现代三维数据格式数据定义的概念与三维渲染流程越来越适配很多早期的三维数据格式并没有考虑到三维渲染。现代三维数据格式往往会借用一些其他已经定义好的数据格式是一个数据文件的复合体。现代三维模型数据已经不单纯是一个数据资源更是一个数据资产asset。glTF的英文全称是GL Transmission Format从这个名称就可以看出这个三维数据格式的设计目的就是为了最大化数据传输的效率。这个数据传输不仅仅是指网络端到本地端数据传输也包括本地数据与内存传输以及内存到显存数据传输。对于数据处理这一类程序来说CPU或者GPU的运算速度已经足够快了数据传输反而是程序的性能瓶颈。因此glTF的设计思路尽可能轻量化最大程度减少3D资产的大小节约解析和使用这些资产所需的运行时处理的时间。glTF有glTF1.0和glTF2.0两个版本本书描述的内容以glTF2.0规范为准。通常来说glTF格式数据包含以下几个部分三维场景数据描述使用JSON来描述。JSONJavaScript Object Notation是一种轻量级的数据交换格式易于人类阅读和编写也易于机器解析和生成提升网络传输效率。另外JSON尤其适配三维场景这种树形结构数据的表达。缓冲区数据保存为二进制文件。缓冲区数据指的就是前面提到的顶点数据和顶点索引数据其数据量通常比较大以二进制的形式进行一次或者少数几次传输可以最大化减少数据预处理以及数据传输的性能损耗。在OpenGL的图形渲染中缓冲区数据读取后可以直接被其API接口调用。资源文件例如纹理数据可以使用jpg格式图片来表达jpg格式图片压缩比较高利于进行数据传输。另外还有一些三维图形专用的纹理格式例如DXT和KTX2也可以作为单独的文件被glTF使用。作为对照这里还是使用将DEM数据转换成三维模型的例子如下例7.3所示//例7.3 DEM数据转换glTF三维模型#include#include#include#include#includeusing namespace std;using namespace nlohmann;size_t pointNum 0;size_t binBufNum 0;size_t indicesNum 0;void CreateBinFile() {string workDir getenv(GISBasic);string demPath workDir /../Data/Model/dem.tif;GDALDataset *img (GDALDataset *)GDALOpen(demPath.c_str(), GA_ReadOnly);if (!img) {printf(Cant Open Image!);return;}int bufWidth img-GetRasterXSize(); //图像宽度int bufHeight img-GetRasterYSize(); //图像高度int bandNum img-GetRasterCount(); //波段数if (bandNum ! 1) {printf(DEM波段数不为1);return;}int depth GDALGetDataTypeSize(img-GetRasterBand(1)-GetRasterDataType()) /8; //图像深度//获取地理坐标信息double padfTransform[6];if (img-GetGeoTransform(padfTransform) CE_Failure) {printf(获取仿射变换参数失败);return;}double startX padfTransform[0];double dX padfTransform[1];double startY padfTransform[3];double dY padfTransform[5];//申请bufsize_t imgBufNum (size_t)bufWidth * bufHeight * bandNum;float *imgBuf new float[imgBufNum];//读取img-RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,GDT_Float32, bandNum, nullptr, bandNum * depth,bufWidth * bandNum * depth, depth);pointNum (size_t)bufWidth * bufHeight;size_t position_texture_num pointNum * 5;float *position_texture new float[position_texture_num];for (int yi 0; yi bufHeight; yi) {for (int xi 0; xi bufWidth; xi) {size_t n (size_t)(bufWidth * 5) * yi 5 * xi;position_texture[n] dX * xi;position_texture[n 1] dY * yi;size_t m (size_t)(bufWidth * bandNum) * yi bandNum * xi;position_texture[n 2] imgBuf[m];position_texture[n 3] float(xi) / (bufWidth - 1);position_texture[n 4] float(yi) / (bufHeight - 1);}}//释放delete[] imgBuf;imgBuf nullptr;string binPath workDir /../Data/Model/new.bin;ofstream binFile(binPath, std::ios::binary);binFile.write((char *)position_texture, position_texture_num * sizeof(float));size_t vertexBufNum position_texture_num * sizeof(float);binBufNum binBufNum vertexBufNum;int mod vertexBufNum % sizeof(uint16_t);if (mod ! 0) {int spaceNum sizeof(float) - mod;char *space new char[spaceNum];binBufNum binBufNum sizeof(char) * spaceNum;memset(space, 0, sizeof(char) * spaceNum);binFile.write(space, sizeof(char) * spaceNum);delete[] space;space nullptr;}indicesNum (size_t)(bufWidth - 1) * (bufHeight - 1) * 2 * 3;uint16_t *indices new uint16_t[indicesNum];for (int yi 0; yi bufHeight - 1; yi) {for (int xi 0; xi bufWidth - 1; xi) {uint16_t m00 (uint16_t)(bufWidth * yi xi);uint16_t m01 (uint16_t)(bufWidth * (yi 1) xi);uint16_t m11 (uint16_t)(bufWidth * (yi 1) xi 1);uint16_t m10 (uint16_t)(bufWidth * yi xi 1);size_t n (size_t)(bufWidth - 1) * yi xi;indices[n * 6] m00;indices[n * 6 1] m01;indices[n * 6 2] m11;indices[n * 6 3] m11;indices[n * 6 4] m10;indices[n * 6 5] m00;}}binFile.write((char *)indices, sizeof(uint16_t) * indicesNum);binBufNum binBufNum sizeof(uint16_t) * indicesNum;delete[] position_texture;position_texture nullptr;delete[] indices;indices nullptr;}int main() {GDALAllRegister();CPLSetConfigOption(GDAL_FILENAME_IS_UTF8, NO); //支持中文路径//设置Proj数据std::string projDataPath getenv(GISBasic);projDataPath /share/proj;CPLSetConfigOption(PROJ_LIB, projDataPath.c_str());ordered_json gltf;gltf[asset] {{generator, CL}, {version, 2.0}};gltf[scene] 0;gltf[scenes] {{{nodes, {0}}}};gltf[nodes] {{{mesh, 0}}};ordered_json positionJson;positionJson[POSITION] 1;positionJson[TEXCOORD_0] 2;ordered_json primitivesJson;primitivesJson {{{attributes, positionJson}, {indices, 0}, {material, 0}}};gltf[meshes] {{{primitives, primitivesJson}}};ordered_json pbrJson;pbrJson[baseColorTexture][index] 0;gltf[materials] {{{pbrMetallicRoughness, pbrJson}}};CreateBinFile();gltf[textures] {{{sampler, 0}, {source, 0}}};gltf[images] {{{uri, tex.jpg}}};gltf[samplers] {{{magFilter, 9729},{minFilter, 9987},{wrapS, 33648},{wrapT, 33648}}};gltf[buffers] {{{uri, new.bin}, {byteLength, binBufNum}}};ordered_json indicesBufferJson;indicesBufferJson[buffer] 0;indicesBufferJson[byteOffset] pointNum * 5 * 4;indicesBufferJson[byteLength] indicesNum * 2;indicesBufferJson[target] 34963;ordered_json positionBufferJson;positionBufferJson[buffer] 0;positionBufferJson[byteStride] sizeof(float) * 5;positionBufferJson[byteOffset] 0;positionBufferJson[byteLength] pointNum * 5 * 4;positionBufferJson[target] 34962;gltf[bufferViews] {indicesBufferJson, positionBufferJson};ordered_json indicesAccessors;indicesAccessors[bufferView] 0;indicesAccessors[byteOffset] 0;indicesAccessors[componentType] 5123;indicesAccessors[count] indicesNum;indicesAccessors[type] SCALAR;indicesAccessors[max] {18719};indicesAccessors[min] {0};ordered_json positionAccessors;positionAccessors[bufferView] 1;positionAccessors[byteOffset] 0;positionAccessors[componentType] 5126;positionAccessors[count] pointNum;positionAccessors[type] VEC3;positionAccessors[max] {770, 0.0, 1261.151611328125};positionAccessors[min] {0.0, -2390, 733.5555419921875};ordered_json textureAccessors;textureAccessors[bufferView] 1;textureAccessors[byteOffset] sizeof(float) * 3;textureAccessors[componentType] 5126;textureAccessors[count] pointNum;textureAccessors[type] VEC2;textureAccessors[max] {1, 1};textureAccessors[min] {0, 0};gltf[accessors] {indicesAccessors, positionAccessors, textureAccessors};string workDir getenv(GISBasic);string jsonFile workDir /../Data/Model/new.gltf;ofstream binFile(jsonFile, std::ios::binary);std::ofstream outFile(jsonFile);outFile std::setw(4) gltf std::endl;}例7.3最终得到的glTF格式使的三维模型文件如下图7.9所示也可以直接从本书的在线主页中获取。.gltf后缀的文件就是用于三维场景数据描述的JSON文件.bin后缀的文件就是储存缓存区数据的二进制文件.jpg文件就是三维模型用到的纹理图片。一些在线网站如https://gltf-viewer.donmccurdy.com提供了对glTF模型数据的浏览可以得到如图7.8所示的渲染效果。鲜院纳舱

相关文章:

基于 Vue + TS + Ant Design Vue 实现精细化菜单按钮权限授权组件腥

7.1 初识三维模型 7.1.1 三维模型的数据载体 随着计算机图形技术的发展,我们或多或少都会见过或者听说过三维模型。笔者始终记得小时候第一次在电视上看到三维动画《变形金刚:超能勇士》的震撼感受;而现在我们已经可以在手机上玩三维游戏《…...

探秘书匠策AI:毕业论文“通关秘籍”大揭秘

在学术的漫漫征途中,毕业论文宛如一座巍峨的高峰,横亘在众多学子面前。它不仅是对多年学习成果的全面检验,更是开启未来学术或职业大门的关键钥匙。然而,毕业论文的撰写过程充满了挑战,从选题时的迷茫,到资…...

【车载嵌入式】TBOX:智能汽车的“云端神经中枢”与数据引擎

1. TBOX:智能汽车的“云端神经中枢”是什么? 想象一下你的爱车突然有了“超能力”——能自动报告故障、远程启动空调、甚至预测保养时间。这些神奇功能的背后,都离不开一个藏在仪表盘下方的小盒子:TBOX(Telematics BOX…...

从电子琴到智能家居:无源蜂鸣器如何玩出花样?附ESP32播放《超级玛丽》主题曲代码

无源蜂鸣器的创意交响曲:从电子琴到游戏音效的ESP32实战指南 在创客和物联网开发的世界里,声音交互常常是项目中最容易被忽视却又最能提升用户体验的环节。无源蜂鸣器这个看似简单的元件,实际上蕴藏着惊人的创意潜力——它不只是发出单调的&q…...

MyBatis-Plus lambdaQuery条件构造器:EQ、NE、GT等操作符实战解析

1. 为什么需要lambdaQuery条件构造器 如果你用过MyBatis,肯定遇到过这样的场景:为了查询某个状态的数据,不得不写一堆if判断来拼接SQL。比如查询用户列表,要根据不同条件筛选,代码里全是"if(name!null){sql"…...

从零构建:基于UWB与MiniFly的室内无人机协同定位系统

1. 为什么选择UWB与MiniFly组合? 室内无人机定位一直是个技术难题。GPS信号穿墙能力差,光学定位受光线影响大,而UWB(超宽带)技术就像是为室内环境量身定制的定位方案。我最早接触这个组合是在一次创客马拉松上&#xf…...

【生产环境亲测】HANA2.0高可用切换实战指南

SLES 15 SP3 + HANA 2.0 SPS06 生产级 HA 手工切换全流程 | 维护模式规范 | 零数据丢失 | Pacemaker 集群运维 文章标签 SAP HANA SLES 15 SP3 高可用切换 Pacemaker SAP Basis 运维实战 数据库维护 一、前言 在 SLES 15 SP3 + SAP HANA 2.0 SPS06 + Pacemaker/Corosync 高可…...

RAGFlow服务报错排查:如何快速解决429 Too Many Requests错误

RAGFlow服务429错误全链路诊断与高可用架构设计实战 第一次在RAGFlow日志里看到"HTTP 429 Too Many Requests"时,我正端着咖啡准备验收新上线的智能文档分析系统。监控大屏突然变红的那一刻,整个运维团队的手指都悬在了键盘上方——这个看似简…...

空气质量指数背后的科学:从污染物浓度到健康影响的完整解读

空气质量指数背后的科学:从污染物浓度到健康影响的完整解读 清晨推开窗户,你是否曾因灰蒙蒙的天空而犹豫今天的晨跑计划?天气预报中那个看似简单的AQI数值,实则凝结了环境科学家们数十年的研究成果。这个介于0到500之间的数字&am…...

UniCloud前端网页托管+阿里云:如何绕过小程序审核,用H5快速迭代你的应用?

UniCloud阿里云混合开发实战:H5动态更新与小程序无缝整合方案 微信小程序审核周期长、更新受限是许多开发者面临的痛点。本文将介绍一种创新解决方案:通过UniCloud前端网页托管结合阿里云域名服务,构建可动态更新的H5应用,再借助小…...

蓝桥杯第15届单片机满分

1. 为什么会在第 5 位显示出 8&#xff1f;freq_jiaofreqseg_jiao;//频率数据的最终结果 if(freq_jiao<0) {wrong1;//频率界面数码管显示LL,表示此状态错误 } else wrong0;而在 serviceT1 的中断里&#xff0c;每 1000ms 更新一次 freq&#xff1a;当测试系统改变输入频率&a…...

AutoGen框架下Memory与RAG的深度整合:打造高效智能体记忆系统

1. AutoGen框架中的Memory机制解析 第一次接触AutoGen的Memory功能时&#xff0c;我就像发现了一个新大陆。想象一下&#xff0c;你家的智能音箱突然能记住你上次说"把空调调到25度"&#xff0c;下次直接说"跟上次一样"就能自动调节——这就是Memory的魔力…...

AI原生研发转型落地难?(SITS2026闭门报告首次解密:92%企业卡在“伪敏捷+真人工”陷阱)

第一章&#xff1a;AI原生研发的文化变革&#xff1a;从认知断层到组织跃迁 2026奇点智能技术大会(https://ml-summit.org) 当大模型不再仅是“调用API的工具”&#xff0c;而成为代码生成、测试覆盖、架构推演与运维决策的默认协作者&#xff0c;研发团队的认知基线正经历一…...

GFF3格式完全解析:从基因组注释到可视化实战教程

GFF3格式完全解析&#xff1a;从基因组注释到可视化实战教程 基因组注释是生物信息学分析中的核心环节&#xff0c;而GFF3作为当前主流的注释格式&#xff0c;其结构化设计能够精准描述基因、转录本、外显子等元素的层级关系。本文将带您深入理解GFF3的规范细节&#xff0c;并通…...

AI原生研发的“冰山协议”:SITS2026首次公开未写入文档的8项隐性契约(含法律、运维、伦理三维度合规 checklist)

第一章&#xff1a;SITS2026专家解读&#xff1a;AI原生研发的核心挑战 2026奇点智能技术大会(https://ml-summit.org) AI原生研发并非简单地将大模型API嵌入传统系统&#xff0c;而是重构软件生命周期的范式——从需求建模、架构设计、代码生成到验证运维&#xff0c;全部以L…...

3分钟掌握M3U8视频下载:N_m3u8DL-CLI-SimpleG终极指南

3分钟掌握M3U8视频下载&#xff1a;N_m3u8DL-CLI-SimpleG终极指南 【免费下载链接】N_m3u8DL-CLI-SimpleG N_m3u8DL-CLIs simple GUI 项目地址: https://gitcode.com/gh_mirrors/nm3/N_m3u8DL-CLI-SimpleG 你是否曾经遇到过心爱的在线视频无法保存的烦恼&#xff1f;那些…...

USB运动控制五轴雕刻机系统完全开源资料:PCB生产支持,多版本C++源码,五轴联动与RTCP...

USB运动控制 (五轴雕刻机系统)全部开源 不保留任何关键技术&#xff0c;PCB可直接生产&#xff0c;C6.0源码&#xff0c;从13.7-18.2所有版本&#xff0c;本产品为可复制资料&#xff0c;支持五轴联动&#xff0c;支持RTCP算法&#xff0c;全部开源。1、为电子资料 2、PCB底板原…...

Qwen3-ASR-1.7B效果实测:1.7B参数量带来的上下文联想能力提升验证

Qwen3-ASR-1.7B效果实测&#xff1a;1.7B参数量带来的上下文联想能力提升验证 1. 语音识别新标杆&#xff1a;Qwen3-ASR-1.7B深度解析 语音识别技术正在经历一场静默的革命。当我们还在为0.6B参数模型的准确率感到惊喜时&#xff0c;Qwen3-ASR-1.7B已经以近乎三倍的参数量重新…...

梦幻动漫魔法工坊在内容创作中的应用:快速生成文章配图与插画

梦幻动漫魔法工坊在内容创作中的应用&#xff1a;快速生成文章配图与插画 1. 为什么内容创作者需要AI动漫生成工具 在当今内容爆炸的时代&#xff0c;视觉元素已成为吸引读者的关键因素。研究表明&#xff0c;带有高质量配图的文章能获得94%以上的浏览量提升。但对于大多数文…...

Vivado2020.2与Modelsim2020.4联合仿真实战:从安装到避坑指南

1. 环境准备与安装避坑指南 刚接触FPGA开发的朋友们&#xff0c;肯定对Vivado和Modelsim这对黄金搭档不陌生。但说实话&#xff0c;我第一次用Vivado2020.2和Modelsim2020.4做联合仿真时&#xff0c;差点被各种坑给劝退。今天我就把踩过的坑和解决方案都整理出来&#xff0c;让…...

OpenClaw配置优化:Qwen3-4B模型响应速度提升30%的技巧

OpenClaw配置优化&#xff1a;Qwen3-4B模型响应速度提升30%的技巧 1. 为什么需要优化OpenClaw的性能 上周我在本地部署了OpenClaw对接Qwen3-4B模型&#xff0c;准备用它来处理日常的文档整理工作。最初的体验让我既惊喜又头疼——惊喜的是这个组合确实能完成复杂的自动化任务…...

Coze插件开发实战:如何将现有API快速封装并发布到扣子商店

1. 从零开始理解Coze插件开发 第一次接触Coze插件开发时&#xff0c;我也被各种概念绕得头晕。简单来说&#xff0c;这就像给手机安装APP——扣子商店是应用市场&#xff0c;插件就是里面的各种APP。而我们要做的&#xff0c;就是把自家开发的API服务打包成这样一个"APP&q…...

用LLM提高语音转文本的准确率

语音转文本转换&#xff0c;也称为自动语音识别&#xff08;ASR&#xff09;或音频转录&#xff0c;是将口语音频转换为书面文本的过程&#xff0c;生成的文本称为转录稿。虽然基于 Transformer 的模型现已广泛应用于语音转文本转换&#xff0c;但对于较小或资源匮乏的语言&…...

突破性AI语音转换实战指南:RVC从入门到精通的完整路径

突破性AI语音转换实战指南&#xff1a;RVC从入门到精通的完整路径 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrieval-based-Voice-Convers…...

Go语言怎么做SSE推送_Go语言Server-Sent Events教程【速学】

Go写SSE handler需设Content-Type和Cache-Control头、逐行写data:并双换行、每次调Flush&#xff1b;须禁用Read/WriteTimeout、设IdleTimeout&#xff1b;用chansync.Map实现安全广播&#xff0c;监听r.Context().Done()防泄漏。Go怎么写一个能发SSE的HTTP handlerGo原生不带S…...

告别会议记录焦虑:TMSpeech 如何用离线语音识别重塑你的工作效率

告别会议记录焦虑&#xff1a;TMSpeech 如何用离线语音识别重塑你的工作效率 【免费下载链接】TMSpeech 腾讯会议摸鱼工具 项目地址: https://gitcode.com/gh_mirrors/tm/TMSpeech 你是否曾在重要会议中因为分心记录而错过关键讨论&#xff1f;是否担心云端语音识别服务…...

2026年心脑血管疾病专科风云榜:谁是健康守护者?

随着现代生活节奏的加快和生活方式的变化&#xff0c;心脑血管疾病的发病率逐年上升。根据《中国心血管健康与疾病报告2025》显示&#xff0c;我国40岁以上人群中心脑血管疾病患者已超过3亿人。面对这一严峻形势&#xff0c;如何找到值得信赖的心脑血管疾病专科医院&#xff0c…...

网盘直链下载助手:八大平台一键获取真实下载地址的终极解决方案

网盘直链下载助手&#xff1a;八大平台一键获取真实下载地址的终极解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云…...

写段代码教会你什么是HOOK技术?HOOK技术能干什么?褂

为 HagiCode 添加 GitHub Pages 自动部署支持 本项目早期代号为 PCode&#xff0c;现已正式更名为 HagiCode。本文记录了如何为项目引入自动化静态站点部署能力&#xff0c;让内容发布像喝水一样简单。 背景/引言 在 HagiCode 的开发过程中&#xff0c;我们遇到了一个很现实的问…...

【算法日记 11】贪心之美:用“相邻交换法”秒杀乱序求极值问题

&#x1f680;【算法日记 11】贪心之美&#xff1a;用“相邻交换法”秒杀乱序求极值问题 &#x1f4cd; 场景引入&#xff1a;百醇的终极摆放艺术 今天遇到了一道看似毫无头绪的排列极值题&#xff1a;题目大意&#xff1a;有 NNN 根百醇&#xff0c;每根有长度 AiA_iAi​ 和美…...