模型姿态问题原因及解决——以obj格式为例

在Cesium中加载模型时一个需要注意的地方就是模型的姿态问题,我们在本篇文章及下一篇与大家进行探讨。

一、背景概述

cesium-1.47,gltf 2.0
我们知道目前市面上有许多种3d格式,各大厂商纷纷开发自己的数据格式以争取话语权。
而cesium支持多种格式的三维模型,主要有dae,gltf,glb,czml以及3d-tiles,它们的文件组织格式和坐标系统不尽相同,所以我们需要进行探讨。顺便说一下踩过的坑。
在本篇文章中,我们主要探讨的是obj格式的模型,为什么是obj呢?因为之前的几篇中写到了osgb-->obj-->gltf的格式转换路线,obj正是其中的关键环节。在这个地方主要出现了两个问题,一是纹理贴错了,需要做一个镜像;二是模型的姿态在转换到gltf是总是错误的,我用的是obj2gltf工具。那么我们就这两个问题分别进行探讨,以姿态为主,纹理的问题我只提供一个简单的方法。

注:3d-tiles的事情以后再讨论

二、解决方法

1、对于纹理镜像的问题提供一个简单思路:
在进行dds-->png的转化中,我们使用了工具DDS4J。看他的源码发现其实是以横排的像素作为单位进行转换的,那么我们在此就可以做一点文章了。

ddsImageDecoder.java------>convertToPNG(....)

修改前:

DdsHeader header = dds.getHeader();
        FormatDecoder decoder = Decoders.getDecoder(dds);
        ImageInfo imageInfo = new ImageInfo(header.getDwWidth(), header.getDwHeight(), 8, true);
        PngWriter pngWriter = new PngWriter(outputStream, imageInfo);
        ImageLineInt imageLine = new ImageLineInt(imageInfo);
        for (int[] ints : decoder) {
            swizzle(ints, swizzle);
            ImageLineHelper.setPixelsRGBA8(imageLine, ints);
            pngWriter.writeRow(imageLine);
        }

        pngWriter.end();

修改后:

 DdsHeader header = dds.getHeader();
        FormatDecoder decoder = Decoders.getDecoder(dds);
        ImageInfo imageInfo = new ImageInfo(header.getDwWidth(), header.getDwHeight(), 8, true);
        PngWriter pngWriter = new PngWriter(outputStream, imageInfo);
        ImageLineInt imageLine = new ImageLineInt(imageInfo);

        // 镜像
        List<int[]> data=new ArrayList<>();
        for (int[] ints:decoder){
            data.add(ints);
        }
        for (int i=data.size();i>0;i--){
            int[] linedata=data.get(i-1);
            swizzle(linedata,swizzle);
            ImageLineHelper.setPixelsRGBA8(imageLine,linedata);
            pngWriter.writeRow(imageLine);
        }
 pngWriter.end();

 

2、解决姿态问题的方法:使用软件blender对模型进行旋转操作
[1]手动方式解决:
file-->import-->obj导入模型;在右上角的outliner操作框中可以看到scene中加了几个object,这就是我们的模型,请把自带的cube右键删除掉;选中我们模型里的任意一个object,按键盘R键(旋转的快捷键),然后输入xyz字母中的一个作为约束轴,最后输入角度值(可以为负),回车确定即完成旋转操作;以同上的操作对该模型的其他object进行旋转;file-->export-->obj导出模型。


[2]代码解决:
blender是可以进行二次开发的,可以使用python脚本(3.x)扩展blender功能,但是也有一些不能由python脚本实现,需要使用C/C++进行开发。
有两种方式来进行开发,一种是使用内置的python控制台来写代码,这种比较简单,但是不适合较大任务;所以另外blender提供文本编辑器(Text Editor)来进行开发,所以我们切换到文本编辑器进行开发,其中也提供了常见功能的代码例子以供参阅。

下面是我的一些代码,主要就是实现了旋转的功能。注意,在写代码前请先配置好python 3.x环境。

# 目标1:读取文件夹下所有obj文件
# 目标2:对obj模型中所有mesh进行旋转(x轴 -90度)
# 目标3:对旋转后obj模型进行保存(原文件名 + "-RotateX-90")

import bpy,os

InputFileDirectory="E:/obj/obj";
OutputFileDirectory="E:/obj/obj/RotateX-90Out";
Files=os.listdir(InputFileDirectory);
for file in Files:
	if file.endswith(".obj"):
		bpy.ops.import_scene.obj(filepath=(InputFileDirectory+"/"+file));     # 导入模型
		bpy.ops.transform.rotate(axis=(-90,0,0));   # 以x为轴旋转-90度
		str=file.split('.');
		OutPath=str[0]+'-RotateX-90.obj';
		bpy.ops.export_scene.obj(filepath=(OutputFileDirectory+"/"+OutPath));   # 输出模型
		# 删除模型
		override = bpy.context.copy();
		override['selected_bases'] = list(bpy.context.scene.object_bases);
		bpy.ops.object.delete(override);

		print(file+" is over");

注:输出在菜单栏 window-->Toggle System console 查看

       如果想对gltf模型直接进行操作,请先导入gltf导入导出模块,才能对其进行操作。不过我测试用的时候总是丢失纹理,所以我还是选择了对obj文件进行旋转操作。

       对于blender中的任意按钮或菜单,鼠标悬停后可以看到其对应的python命令,右键单击可以进入API文档查看该命令的具体参数和使用方法。

 

三、理论依据和原因追究

以上呢都是关于问题的描述以及如何去解决这个问题,那么除此之外,我们还要去弄清楚这到底是怎么回事,那么我们一起来看一下。

在我们这里遇到的模型姿态问题实际上是坐标轴朝向问题。glTF使用满足右手准则的三维空间笛卡尔坐标系,其中Y轴表示物体的上方向、Z轴垂直屏幕指向屏幕外、X轴为Y轴与Z轴向量的叉积。而大部分建模软件(如blender,3ds Max)和人们平常认知中都是以Z轴向上的坐标系,所以就导致了转化出来的 glTF场景中三维物体的底层顶点数据是以Z轴正方向为上方向的。最重要的是Cesium也是以Z轴向上,X轴向外,Y轴为XZ叉积的右手坐标系,那么就会显示出我们这里说的姿态问题。就这个问题的解决还有其他的方法:

1、使用blender导入obj模型后,再导出模型,但是导出的模型选项应为 -Z Froward, Y Up

在bpy.ops.export_scene.obj(.....) 参数中修改即可,API在此

2、在gltf文件的节点树中增加一个根节点以实现坐标系统的旋转,该节点的matrix属性应为[0,0,1,0, 0,1,0,0, -1,0,0,0, 0,0,0,1],

起初我以为这个方法很简单,写个符合json格式的转换的对象填进去就行了,实际上好像并不是这么简单。根据我的询问和摸索,这种方法需要对原来的gltf文件做如下改动:

(1) 在源文件中的"scenes"对象中查看"nodes"节点,把这些节点记录一下一会还要用,比如说为[0,1,2,3,4,5,6],然后将内容改为[8];

(2) 在源文件中的"nodes"节点数组中加入两个对象。一是在开头处加一个子节点数组对象,如{ "children": [1,2,3,4,5,6,7] };之后在末尾处加一个转换矩阵的对象,如{ "name": "Y_UP_Transform","matrix": [0,0,1,0,0,1,0,0,-1,0,0,0,0,0,0,1],"children" : [0] }

我来大概解释一下为什么,因为scenes是我们的场景,也就是要渲染的东西。修改前后分别渲染的是[0,1,2,3,4,5,6]和[8],这里面的数字或数组就是node节点对应的位置。那么根据我们这里提供的数字,可以找到对应的对象。在修改后的[8]处,我们可以看到,这就是我们的转换矩阵,而它所对应的子节点就是[0]号节点,继而渲染出所有对象并且执行了旋转。

这个地方有一点需要注意,如果把这个矩阵直接加到[0]节点也是可以的

{
      "children": [
         1,2,3,4,5,6,7
      ],
      "matrix": [0,0,1,0,0,1,0,0,-1,0,0,0,0,0,0,1]
    }

但是不建议这么做,因为会污染我们的场景。大家也可以看一个其他例子

四、参考资料

Blender Manual中文版: https://docs.blender.org/manual/zh-hans/dev/getting_started/index.html

Blender python API Documents: https://docs.blender.org/api/current/contents.html

obj文件格式详述: http://paulbourke.net/dataformats/obj/

论文:《3D Tiles 定义解析与生产规范设计》

likangning93

在此一并感谢!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zxzfcsu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值