HT for Web OBJ手册

索引


概述

HT for 3D Web预定义了多种三维模型,并可通过建模手册介绍的API方式构建更多样式的模型, 同时HT还提供了导入OBJ3D模型格式文件的功能。

导入OBJ格式功能需要引入ht-obj.js的插件扩展包,本手册的大部分例子由于需要读取OBJ文件,浏览器存在跨域安全的限制, 因此需要通过Web方式发布来阅读本手册,或者修改浏览器的参数,例如对于Chrome浏览器可通过增加 --allow-file-access-from-files的启动参数。

OBJ格式

OBJ 是一种3D模型文件格式,几乎所有主流3D建模工具, 如Blender3ds MaxMaya都支持OBJ格式的导出。

OBJ文件一般以.obj后缀名标示,描述的是模型顶点、面以及贴图坐标等几何模型相关信息;而模型的贴图图片以及颜色等材质信息, 则由另外的MTL材质文件描述,一般以.mtl后缀名标示。

OBJ 文件示例片段如下,v代表顶点信息,f代表面信息,usemtl代表以下面描述模型都将采用外部MTL文件描述的material3材质信息:

v 1.187283 0.016532 0.652852
v 1.187283 0.001827 1.045301
v 1.187283 0.155480 0.618752
v 1.187283 0.106104 1.046487
v 1.187283 0.330175 0.640612
v 1.187283 0.209969 1.085557
v 1.186590 1.499776 1.191882
usemtl material3
f 9918 9919 9920 9921
f 9919 9922 9923 9920
f 9922 9924 9925 9923
f 9924 9926 9927 9925
f 9926 9928 9929 9927
f 9928 9930 9931 9929

MTL文件示例片段如下,材质material3透明度d0.5kd代表diffuse颜色为[0.58 0.58 0.58],贴图路径为/SmokeAlarm.jpg

newmtl material3
    d 0.5
    Kd 0.58 0.58 0.588
    map_Kd /SmokeAlarm.jpg

解析OBJ

ht.Default.parseObj(objText, mtlText, params)函数用于解析objmtl文件, 解析后返回的map结构json对象中,每个材质名对应一个模型信息, 模型信息格式为建模手册介绍的HT自定义的模型格式标准。

param参数说明如下:

MTL格式还有诸多参数,目前HT仅支持dkdmap_kd这三个分别代表透明度、颜色和贴图的参数, 关于OBJMTL的格式标准可参考这里

map_Kd -o 0.1000 0.1200 0.0000 -s 45.0000 20.0000 0.0000 project/images/floor.jpg

如上所示的贴图参数map_Kd,其中-o相当于uv.offset的贴图偏移参数;-s相当于uv.scale的贴图倍数参数。 对于-o-s这两个属性HT只读取前面两个参数,忽略第三个参数。图片路径会自动增加prefix参数的前缀, 如果图片为相对路径,是相对于最终运行html页面的相对路径,也可以设置成注册到ht.Default.setImage(name, ...)中的图片名

如果通过ignoreNormal设置为true忽略法线向量,或导出的OBJ文件不包含法线向量信息时,HT会自动构建对应ns法线向量信息, 但为实现特殊的表面效果,或通过较少的顶点实现平滑的界面渲染效果时常需要指定每个顶点的法线向量, 可参考Phong shading进行理解, 概述章节例子可发现,ignoreNormal设置为true的模型表面较为突兀棱角分明,不如读取法线向量的模型平滑。

模型几何变换顺序分别为:mat -> s3 -> r3 -> t3 -> center -> cube。 一般设置s3r3t3就能满足大部分需求,如需更复杂的矩阵变换,可通过ht.Default.createMatrix函数构建出mat矩阵参数, 采用mat参数的情况常用于模型形状位置需要与数据模型值绑定的情况:

modelMap.pointer.mat = {
    func: function(data){
        var start = Math.PI * 0.736,
            range = Math.PI * 1.46,
            angle = start - range * data.a('value') / 100;
        return ht.Default.createMatrix([
            { t3: [0, -75, 0] },
            { r3: [Math.PI/4, 0, 0] },
            { r3: [0, 0, angle] },
            { r3: [-Math.PI/4, 0, 0] },
            { t3: [0, 75, 0]  }
        ]);
    }
};

以上代码意思是对pointer的表计指针模型,先通过t3: [0, -75, 0]沿着y轴向下移动75,使得指针旋转点位于坐标原点上, 之后进行r3: [Math.PI/4, 0, 0]沿着x轴旋转Math.PI/4的弧度,使得指针直立在xy平面上, 然后进行r3: [0, 0, angle]沿着z轴旋转angle值的角度,最后再通过r3: [-Math.PI/4, 0, 0]t3: [0, 75, 0] 将指针旋转和移动回原始位置,从而实现指针旋转角度与data.a('meter.value')的数据绑定。

通过ht.Default.parseObj解析后的map结构json对象中,每个材质名对应一个模型信息,如果cube参数为true, 则返回的每个模型信息上将具有rawS3的特殊参数,同时传入的param参数也会增加rawS3属性信息, 该值为进行单位立体化前所有模型组合的最大尺寸范围,因此每个模型中的rawS3值是一样的。

导入模型

要将OBJ解析后的模型信息绑定到图元,需先调用 建模手册模型注册 章节介绍的ht.Default.setShape3dModel(name, model)函数进行注册,之后图元只需将styleshape3d属性设置为注册的名称。

以上例子构建了两辆摩托车模型,他们都读取至相同的OBJ文件信息,名称为Separate Scooter的摩托车,是有由一堆的Node, 相互host吸附成环状,当用户拖动旋转操作时感觉像一个整体,这种方式下每个部分可独立选中,染色和隐藏等操作。

如果需要整个摩托车模型上仅对应一个Node图元,则可采用例子中标注为One Node的摩托车方式,将所有材质对应的模型融合成一个array数组, 通过ht.Default.setShape3dModel('scooter', array)进行注册,这样scooter名称的模型将具有完整的OBJ模型信息, 参见建模手册的模型组合章节。

for(var name in modelMap){
    var model = modelMap[name];
    var shape3d = 'scooter:' + name;

    ht.Default.setShape3dModel(shape3d, model);
    array.push(model);

    var node = new ht.Node();
    node.s({
        'shape3d': shape3d
    });
    node.setHost(lastNode);
    ...
}

ht.Default.setShape3dModel('scooter', array);
var node = new ht.Node();
node.s('shape3d', 'scooter');

Nodes3大小参数会影响模型的最终呈现效果,最终呈现的模型大小将由导入的OBJ几何模型大小乘以对应的Nodes3大小, 也就是说如果导入的OBJ模型的rawS3大小为[10, 20, 30]的尺寸,则如果Nodes3[10, 5, 3], 则最终呈现于界面的大小为[10*10, 20*5, 30*3]

如果模型的大小不像受Nodes3的影响,可通过设置shape3d.scaleablefalse,该值默认为true。 因此如果模型大小受s3影响的情况下,一般导入模型时设置cube参数为true,将导入的模型缩放到[1,1,1]单元立方体内, 然后将对应的Node图元的s3参数设置成解析后模型的rawS3参数,该参数代表OBJ模型在缩放到单元立方体之前的大小。

AJAX加载

上例加载OBJ文件采用AJAX的方式,通过构建两个XMLHttpRequest对象, 通过AJAX方式分别获取objmtl文件,在数据onload之后再进行解析处理。

load('obj/scooter.mtl', 'obj/scooter.obj');

function load(mtlUrl, objUrl){
    var xhr1 = new XMLHttpRequest();
    xhr1.onload = function(e){
        var mtlText = e.target.responseText;
        var xhr2 = new XMLHttpRequest();
        xhr2.onload = function(e){
            var objText = e.target.responseText;
            parse(mtlText, objText);
        };
        xhr2.open('GET', objUrl, true);
        xhr2.send(null);
    };
    xhr1.open('GET', mtlUrl, true);
    xhr1.send(null);
}

loadObj函数

以下例子也实现了同样的功能,但采用了HT提供的ht.Default.loadObj更为便捷的函数。

ht.Default.loadObj(objUrl, mtlUrl, params)

通过JSON加载

除了上述的加载方式,HT还提供了更简便的加载方式,通过一个JSON对象来加载

json属性说明如下

除了以上基本属性,还可以增加其它与ht.Default.parseObj(objText, mtlText, params)params的参数,如增加s3大小变化参数,可参考解析OBJ

var dm = new ht.DataModel(),
    g3d = window.g3d = new ht.graph3d.Graph3dView(dm);
g3d.setGridVisible(true);
g3d.addToDOM();

// 通过 json 文件加载
var node = new ht.Node();
node.setAnchor3d([0.5, 0, 0.5]);
node.p3(-200, 0, 0);
node.s('shape3d', 'models/equipment.json');
dm.add(node);

// 通过 json 对象加载
var model = {
    "modelType": "obj",
    "obj": "obj/equipment.obj",
    "mtl": "obj/equipment.mtl",
    "prefix": "obj/"
};
var node2 = new ht.Node();
node2.setAnchor3d([0.5, 0, 0.5]);
node2.p3(200, 0, 0);
node2.s('shape3d', model);
dm.add(node2);

字符串嵌入

序列化手册例子获取OBJMTL文本内容的方式较特殊, 将模型信息内容存放到function的注解中,将function转换成字符串,然后裁剪掉头尾取中间实际模型内容部分, 传统的方式一般将.obj.mtl存放在服务端,客户端通过AJAX的方式分别加载文件内容进行解析, 采用截取注解字符串的方式,可避免跨域安全访问限制问题,浏览器直接本地打开即可运行。

var scooter_mtl = getRawText(function(){/*

newmtl Black
Ns 190.196078
Ka 0.000000 0.000000 0.000000
Kd 0.000000 0.000000 0.000000
Ks 0.100000 0.100000 0.100000
Ni 1.000000
d 1.000000
illum 2
...

*/});

function getRawText(obj){
    var text = String(obj);
    return text.substring(14, text.length-3);
}

门窗应用

OBJ导入模型的另外一种应用就是与CSGNode的结合, 特别是构建门窗等应用场景时,可用CSGNode进行挖空, 将图元styleshape3d属性设置为OBJ导入注册的模型,使得图元显示为更逼真的OBJ门窗建模效果。

结合DoorWindow类型可实现开门开窗效果, 以下示例展示了OBJ导入模型与CSGNodeDoorWindow两种类型结合的门窗应用场景:


欢迎交流 service@hightopo.com