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

青岛网站制作永诚山东济南最新事件

青岛网站制作永诚,山东济南最新事件,局域网如何建网站,找生产厂家文章目录 处理流程简介核心功能实现数据读取与格式转换数据读取splat转gltf 点云数据分割定义四叉树递归生成3dtiles瓦片 生成tileset.json递归生成tileset.json计算box 主函数调用渲染 下一步工作性能优化渲染效果调优其他 源码地址: github 处理流程简介 基本流…

文章目录

  • 处理流程简介
  • 核心功能实现
    • 数据读取与格式转换
      • 数据读取
      • splat转gltf
    • 点云数据分割
      • 定义四叉树
      • 递归生成3dtiles瓦片
    • 生成tileset.json
      • 递归生成tileset.json
      • 计算box
    • 主函数调用
    • 渲染
  • 下一步工作
    • 性能优化
    • 渲染效果调优
    • 其他

源码地址: github

处理流程简介

基本流程:

  • 读取点云数据。
  • 制作tile
    • 构建四叉树
    • 分割点云
    • 将点云转换为glTF格式。
  • 生成配置文件tileset.json。

前置知识:

  • glTF教程
  • glTF2.0 高斯扩展
  • 3dtiles 1.1 规范

核心功能实现

数据读取与格式转换

数据读取

def read_splat_file(file_path):"""读取二进制格式的 Splat 文件:param file_path: Splat 文件路径:return: 包含位置、缩放、颜色、旋转数据的字典"""with open(file_path, 'rb') as f:# 初始化存储数据的列表positions = []scales = []colors = []rotations = []# 逐点读取数据while True:# 读取位置(3个 Float32,x, y, z)position_data = f.read(3 * 4)  # 3个 Float32,每个4字节if not position_data:break  # 文件结束x, y, z = struct.unpack('3f', position_data)positions.append([x, y, z])# 读取缩放(3个 Float32,sx, sy, sz)scale_data = f.read(3 * 4)  # 3个 Float32,每个4字节sx, sy, sz = struct.unpack('3f', scale_data)scales.append([sx, sy, sz])# 读取颜色(4个 uint8,r, g, b, a)color_data = f.read(4 * 1)  # 4个 uint8,每个1字节r, g, b, a = struct.unpack('4B', color_data)colors.append([r, g, b, a])#print("r, g, b, a:",[r, g, b, a])# 读取旋转(4个 uint8,i, j, k, l)rotation_data = f.read(4 * 1)  # 4个 uint8,每个1字节i, j, k, l = struct.unpack('4B', rotation_data)rotations.append([i, j, k, l])# 将列表转换为 NumPy 数组positions = np.array(positions, dtype=np.float32)scales = np.array(scales, dtype=np.float32)colors = np.array(colors, dtype=np.uint8)rotations = np.array(rotations, dtype=np.uint8)# 返回解析的数据return {'positions': positions,'scales': scales,'colors': colors,'rotations': rotations}

splat转gltf

遵循3dtiles 1.1 规范,在glTF 2.0 基础上,增加高斯扩展。

def splat_to_gltf_with_gaussian_extension(positions, colors, scales, rotations, output_path):"""将 Splat 数据转换为支持 KHR_gaussian_splatting 扩展的 glTF 文件:param positions: 位置数据(Nx3 的浮点数数组):param colors: 颜色数据(Nx4 的 uint8 数组,RGBA):param scales: 缩放数据(Nx3 的浮点数数组):param rotations: 旋转数据(Nx4 的 uint8 数组,IJKL 四元数):param output_path: 输出的 glTF 文件路径"""# 将颜色和旋转数据归一化到 [0, 1] 范围#normalized_colors = colors / 255.0normalized_rotations = rotations / 255.0# 创建 GLTF 对象gltf = GLTF2()# 添加 KHR_gaussian_splatting 扩展gltf.extensionsUsed = ["KHR_gaussian_splatting"]# 创建 Bufferbuffer = Buffer()gltf.buffers.append(buffer)# 将数据转换为二进制positions_binary = positions.tobytes()colors_binary = colors.tobytes() #normalized_colors.tobytes()scales_binary = scales.tobytes()rotations_binary = normalized_rotations.tobytes()# 创建 BufferView 和 Accessor 用于位置数据positions_buffer_view = BufferView(buffer=0,byteOffset=0,byteLength=len(positions_binary),target=34962  # ARRAY_BUFFER)positions_accessor = Accessor(bufferView=0,componentType=5126,  # FLOATcount=len(positions),type="VEC3",max=positions.max(axis=0).tolist(),min=positions.min(axis=0).tolist())gltf.bufferViews.append(positions_buffer_view)gltf.accessors.append(positions_accessor)# 创建 BufferView 和 Accessor 用于颜色数据colors_buffer_view = BufferView(buffer=0,byteOffset=len(positions_binary),byteLength=len(colors_binary),target=34962  # ARRAY_BUFFER)colors_accessor = Accessor(bufferView=1,componentType=5121,  # UNSIGNED BYTEcount=len(colors), #,len(normalized_colors),#type="VEC4")gltf.bufferViews.append(colors_buffer_view)gltf.accessors.append(colors_accessor)# 创建 BufferView 和 Accessor 用于旋转数据rotations_buffer_view = BufferView(buffer=0,byteOffset=len(positions_binary) + len(colors_binary) ,byteLength=len(rotations_binary),target=34962  # ARRAY_BUFFER)rotations_accessor = Accessor(bufferView=2,componentType=5126,  # FLOATcount=len(normalized_rotations),type="VEC4")gltf.bufferViews.append(rotations_buffer_view)gltf.accessors.append(rotations_accessor)# 创建 BufferView 和 Accessor 用于缩放数据scales_buffer_view = BufferView(buffer=0,byteOffset=len(positions_binary) + len(colors_binary)+ len(rotations_binary),byteLength=len(scales_binary),target=34962  # ARRAY_BUFFER)scales_accessor = Accessor(bufferView=3,componentType=5126,  # FLOATcount=len(scales),type="VEC3")gltf.bufferViews.append(scales_buffer_view)gltf.accessors.append(scales_accessor)# 创建 Mesh 和 Primitiveprimitive = Primitive(attributes={"POSITION": 0,"COLOR_0": 1,"_ROTATION": 2,"_SCALE": 3},mode=0,  # POINTSextensions={"KHR_gaussian_splatting": {"positions": 0,"colors": 1,"scales": 2,"rotations": 3}})mesh = Mesh(primitives=[primitive])gltf.meshes.append(mesh)# 创建 Node 和 Scenenode = Node(mesh=0)gltf.nodes.append(node)scene = Scene(nodes=[0])gltf.scenes.append(scene)gltf.scene = 0# 将二进制数据写入 Buffergltf.buffers[0].uri = "data:application/octet-stream;base64," + \base64.b64encode(positions_binary + colors_binary + rotations_binary + scales_binary ).decode("utf-8")# 保存为 glTF 文件gltf.save(output_path)print(f"glTF 文件已保存到: {output_path}")

点云数据分割

定义四叉树

定义四叉树类,包含基本方法,初始化、插入、分割、判断点是否在边界范围内。

#四叉树
class QuadTreeNode:def __init__(self, bounds, capacity=100000):"""初始化四叉树节点。:param bounds: 节点的边界 (min_x, min_y, max_x, max_y):param capacity: 节点容量(每个节点最多存储的点数)"""self.bounds = boundsself.capacity = capacityself.points = []self.children = Noneself.colors = []self.scales = []self.rotations = []def insert(self, point,color,scale,rotation):"""将点插入四叉树。"""if not self._contains(point):return Falseif len(self.points) < self.capacity:self.points.append(point)self.colors.append(color)self.scales.append(scale)self.rotations.append(rotation)return Trueelse:if self.children is None:self._subdivide()for child in self.children:if child.insert(point,color,scale,rotation):return Truereturn Falsedef _contains(self, point):"""检查点是否在节点边界内。"""x, y, _ = pointmin_x, min_y, max_x, max_y = self.boundsreturn min_x <= x < max_x and min_y <= y < max_ydef _subdivide(self):"""将节点划分为四个子节点。"""min_x, min_y, max_x, max_y = self.boundsmid_x = (min_x + max_x) / 2mid_y = (min_y + max_y) / 2self.children = [QuadTreeNode((min_x, min_y, mid_x, mid_y), self.capacity),QuadTreeNode((mid_x, min_y, max_x, mid_y), self.capacity),QuadTreeNode((min_x, mid_y, mid_x, max_y), self.capacity),QuadTreeNode((mid_x, mid_y, max_x, max_y), self.capacity)]for index, point in enumerate(self.points):for child in self.children:if child.insert(point,self.colors[index],self.scales[index],self.rotations[index]):breakself.points = []self.colors = []self.scales = []self.rotations = []

递归生成3dtiles瓦片

def generate_3dtiles(node, output_dir, tile_name):"""递归生成 3D Tiles。"""if node.children is not None:for i, child in enumerate(node.children):generate_3dtiles(child, output_dir, f"{tile_name}_{i}")else:if len(node.points) > 0:points = np.array(node.points)colors = np.array(node.colors) scales = np.array(node.scales)rotations = np.array(node.rotations)splat_to_gltf_with_gaussian_extension(points, colors,scales,rotations, f"{output_dir}/{tile_name}.gltf")#tile = create_pnts_tile(points, colors)#tile.save_to(f"{output_dir}/{tile_name}.gltf")

生成tileset.json

递归生成tileset.json

generate_tileset_json

def generate_tileset_json(output_dir, root_node, bounds, geometric_error=100):"""递归生成符合 3D Tiles 1.1 规范的 tileset.json 文件。:param output_dir: 输出目录:param root_node: 四叉树的根节点:param bounds: 根节点的边界 (min_x, min_y, min_z, max_x, max_y, max_z):param geometric_error: 几何误差"""def build_tile_structure(node, tile_name, current_geometric_error):"""递归构建 Tile 结构。:param node: 当前四叉树节点:param tile_name: 当前 Tile 的名称:param current_geometric_error: 当前 Tile 的几何误差:return: 当前 Tile 的结构"""# 当前 Tile 的 boundingVolume# 计算 boundingVolumeif is_geographic_coordinate:  # 如果是地理坐标系bounding_volume = {"region": compute_region(node.points)}else:  # 如果是局部坐标系bounding_volume = {"box": compute_box(node.points)}# 当前 Tile 的内容content = {"uri": f"{tile_name}.gltf"}# 子节点列表children = []if node.children is not None:for i, child in enumerate(node.children):child_tile_name = f"{tile_name}_{i}"children.append(build_tile_structure(child, child_tile_name, current_geometric_error / 2))# 当前 Tile 的结构tile_structure = {"boundingVolume": bounding_volume,"geometricError": current_geometric_error,"refine": "ADD",  # 细化方式"content": content}# 如果有子节点,则添加到 children 中if children:tile_structure["children"] = children#tile_structure["content"] = ""del tile_structure["content"]return tile_structure# 构建根节点的 Tile 结构root_tile_structure = build_tile_structure(root_node, "tile_0", geometric_error)# tileset 结构tileset = {"asset": {"version": "1.1","gltfUpAxis": "Z"  # 默认 Z 轴向上},"geometricError": geometric_error,"root": root_tile_structure}# 写入文件with open(f"{output_dir}/tileset.json", "w") as f:json.dump(tileset, f,cls=NumpyEncoder, indent=4)

数据格式转换

class NumpyEncoder(json.JSONEncoder): def default(self, obj):  if isinstance(obj, (np.int_, np.intc, np.intp, np.int8,  np.int16, np.int32, np.int64, np.uint8,  np.uint16, np.uint32, np.uint64)):  return int(obj)  elif isinstance(obj, (np.float_, np.float16, np.float32,np.float64)):  return float(obj)  elif isinstance(obj, (np.ndarray,)):  return obj.tolist()  return json.JSONEncoder.default(self, obj)

计算box

def compute_box(points):"""计算点云数据的 box 范围。:param points: 点云数据 (Nx3 数组,每行是 [x, y, z]):return: box 范围 [centerX, centerY, centerZ, xDirX, xDirY, xDirZ, yDirX, yDirY, yDirZ, zDirX, zDirY, zDirZ]"""# 计算中心点center = np.mean(points, axis=0)# 计算半长向量min_coords = np.min(points, axis=0)max_coords = np.max(points, axis=0)half_size = (max_coords - min_coords) / 2# 构造 boxbox = [center[0], center[1], center[2],  # 中心点half_size[0], 0, 0,               # X 轴方向0, half_size[1], 0,               # Y 轴方向0, 0, half_size[2]                # Z 轴方向]return box

主函数调用

def main():input_path=r"D:\data\splat\model.splat"output_dir = r'D:\code\test\py3dtiles\cesium-splat-viewer\data\outputs\model'# 读取 .splat 文件splat_data=read_splat_file(input_path)positions=splat_data['positions']scales=splat_data['scales']colors=splat_data['colors']rotations=splat_data['rotations']points=positions# 创建四叉树根节点min_x = np.min(points[:, 0], axis=0)min_y = np.min(points[:, 1], axis=0)max_x = np.max(points[:, 0], axis=0)max_y = np.max(points[:, 1], axis=0)root = QuadTreeNode((min_x, min_y, max_x, max_y),100000)# 将点插入四叉树for index, point in enumerate(points):root.insert(point,colors[index],scales[index],rotations[index])# 生成 3D Tiles   generate_3dtiles(root, output_dir, "tile_0")# 生成 tileset.jsonbounds = [min_x, min_y, np.min(points[:, 2]), max_x, max_y, np.max(points[:, 2])]generate_tileset_json(output_dir, root, bounds)if __name__ == "__main__":main()

渲染

编译cesium的splat-shader版本,参考示例代码3D Tiles Gaussian Splatting.html实现。

async function loadTileset() {try {const tileset = await Cesium.Cesium3DTileset.fromUrl("http://localhost:8081/data/outputs/model/tileset.json",{modelMatrix:computeModelMatrix(),maximumScreenSpaceError: 1,}).then((tileset) => {CesiumViewer.scene.primitives.add(tileset);setupCamera();});} catch (error) {console.error(`Error creating tileset: ${error}`);}
}

下一步工作

性能优化

  • 支持LOD 。
  • 支持多线程、多任务,分批处理 。
  • 切片方案优化,尝试构建其他空间索引,例如八叉树 。

渲染效果调优

目前渲染效果不理想,椭圆的某个轴长过大,问题排查中。

其他

其他待优化项。本文输出的是一个简易版的splat转3dtiles工具,供学习和交流使用,待优化的地方,若有精力后续会持续完善。

参考资料:
[1] https://github.com/KhronosGroup/glTF-Tutorials/tree/main/gltfTutorial
[2] https://github.com/CesiumGS/3d-tiles
[3] https://github.com/CesiumGS/glTF/tree/proposal-KHR_gaussian_splatting/extensions/2.0/Khronos/KHR_gaussian_splatting
[4] https://github.com/CesiumGS/cesium/tree/splat-shader

http://www.dtcms.com/wzjs/416603.html

相关文章:

  • 丰台网站制作公司网站关键词
  • 湖南高端网站建设免费注册个人网站
  • 好的app设计网站有哪些云南网站建设百度
  • 网站开发主要流程搜索引擎是网站吗
  • opencart做网站视频谷歌搜索引擎入口2021
  • 动态网站一定要会动态编程吗裂变营销五种模式十六种方法
  • 英文网站 icp备案号网页设计网站建设
  • 企业免费做网站厨师培训机构 厨师短期培训班
  • 哪个建设网站公司好在线友情链接
  • 做婚介网站可行性报告模板鹤壁网站推广公司
  • 青岛网站建设哪里好武汉百度推广开户
  • 福州商城网站建设北京网络营销推广
  • php做网站答辩问题147seo工具
  • 重庆做网站有哪些网站排名提高
  • 学校网站建设专业公司公司网站设计需要多少钱
  • 东莞黄江做网站公司合肥网站推广
  • 北美购物网站排名无锡seo公司哪家好
  • 泰安红河网站建设网址查询ip地址
  • 网站谁做的比较好看的站长统计app进入网址新版小猪
  • 会网站建设怎样赚钱外贸网站营销推广
  • 莱芜网站建设外国黄冈网站推广平台
  • 怎么看网站有没有做301百度搜索关键词热度
  • 电脑软件推广联盟北京网站优化怎么样
  • flash教程网站都有哪些成人职业技术培训学校
  • 滨海县做网站注册淘宝小程序今日头条普通版
  • 夏天做那些网站能致富百度提交入口地址在哪
  • 百度网站提交了多久收录武汉百度推广入口
  • 哈尔滨网站设计培训班陕西新站seo
  • 班级响应式网站html格式搜索引擎排名2021
  • 做企业网站需要什么新闻头条免费下载安装