Martin_Jonathan

Martin_Jonathan 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

Martin_Jonathan 回答了问题 · 2019-11-04

解决前端跨域报错

npm install cors
var express = require('express')
var cors = require('cors')
var app = express()

app.use(cors())

这样总没问题了

关注 4 回答 3

Martin_Jonathan 提出了问题 · 2018-08-04

有后端加载3d模型生成图像的组件吗

后端有没有类似前端ThreeJS的组件,可以实现加载模型,加载贴图,并生成图像的

关注 1 回答 0

Martin_Jonathan 提出了问题 · 2018-08-04

白色布料纹理图片和深色图片合成,如何使布料颜色改变并保留纹理?

我使用正片叠底的方法,将两张图片进行合成,一张是白色的布料图片,一张是深色图片(以黑色为例)
正片叠底的原理是,将两个颜色的像素值相乘,然后除以255,所以所有颜色和黑色进行正片叠底,最后一定得到的是黑色,颜色越深,得到的结果图的纹理越浅

使用什么办法才能在保留白色布料图片的纹理的同时,将深色图片和白色布料图片合成呢?

像下图展示的这样

**图片描述**

关注 1 回答 1

Martin_Jonathan 评论了文章 · 2018-08-04

从3dMax导出供threeJS使用的带动作模型与加载(认真修改详尽版)

评论区发现的建议,最近没空测试,先贴这

clipboard.png

还有好多人说找不到插件的 https://pan.baidu.com/s/1Q5g0... 密码:b43e 。 应该是他们现在只是维护blender,只有这个的插件,不如改用blender?

在自己做的一个小玩意中,发现要从3dMax中导出js文件供给threeJS使用,真是太多坑了!所以打算详细记录一下方法,好像开发会3dMax的比较少,但是至少可以帮助开发与美工更好的沟通与交流。在文末,我会附上一个可加载的js模型,方便学习~

图片描述

导出文件类型选型

在《THREE.JS开发指南》2015年第一印第一版中提及的支持的模型主要有一下几种

clipboard.png

图中的确是一些常见格式,我们可以通过这个表格对常见格式做一些了解,但是我们从Three.js的官方实例的Loaders包中可以看到,发现他现在提供了各种加载器来支持文件加载,我们可以根据我们的需求来选择我们需要的加载方式

clipboard.png

与3dmax自带的导出选择相比较,可以导出.FBX,Collada,STL,OBJ和MTL等文件,然后使用对应的Loader进行加载。我们可以选择自己熟悉的工具,熟悉的格式进行模型的导出,但是也正是由于他的多样性,导致无从下手,在尝试了Blender导出动作模型,与DDS素材转换等等工具之后,最终决定采用,我较为熟悉3dMax为建模工具,来导出我最熟悉的js文件来完成模型的导出与加载

下面是我经过实验后对一些常见格式的记录与分析大致统计出的一些选型分析

clipboard.png

clipboard.png

如果不需要做那么多选型的话,直接导出js好了~各方面都比较合适。

如何导出动态模型的js文件

安装3dMax导出插件

clipboard.png

.OBJ导出来的仅仅是模型,.MTL导出来的仅仅是材质,那动作呢?js在Three.js中支持的动作导出,在3dmax,maya,blender中并没有直接导出js的方法,庆幸的是大牛已经给我们开发了越来越丰富的插件了。在utils中的export中包含了这些插件(文件目录.\three.js-master\three.js-master\utils\exporters\max),我们需要将这些插件直接复制到3dmax的plugin文件夹中,如果都是需要导出包含动作的,只需要安装ThreeJSAnimationExporter.ms这一个文件就够了,重启3dmax后打开就可以看到这个插件了。(如下图)

clipboard.png

如何获取一个可加载模型(以一个简单的圆柱体为例)

clipboard.pngclipboard.png

  1. 首先你的模型必须是一个整体,比如你的模型是熊猫吃竹子,如果你的熊猫和竹子要一块导出,那么你的熊猫和竹子必须是一个整体
  2. 贴图只能有一张,且贴图是png格式
  3. 给bone(骨骼)认真命名,js导出后你可能会发现骨骼有点偏移与错位,严格的命名有助于你在建模或者导出的时候进行修改,不过对开发来说这点会比较麻烦,这点也不是必须的。
  4. 模型的每一个点都必须赋予权重,如果发生破面行为,那么非常有可能是加了动作的模型却有的店没有权重
  5. 骨骼测试可用的有bone和CS骨骼,如果采用其他骨骼最好进行测试
  6. 导出的时候你的模型必须是一个Mesh,非常重要!这就意味着你不能导出一个可编辑多边形或者其他形式,在我们添加skin修改器的时候,先添加一个EditMesh修改器在我们的物体上,如果你已经是一个editable mesh,那这就不需要了。最好再使用Unwrap, UVW,中途出现warning点确定就好。

clipboard.png

  1. 导出的时候选择模型就好,不要选择骨骼,如果把骨骼一起选择导出会出现如下错误

    clipboard.png

  2. 添加一个XForm修改器于你的模型当你开始导出之前,他会重置你物件的位置,转向等等,将它变为初始那样,最小化在导出时可能遇到的问题

    clipboard.png

  3. 如果发生错误,修复完毕后需要重新启动3dmax才能正常使用导出插件
  4. 可以先验证模型导出的数据量和点格式的正确与否再进行加载,一般导出文件的贴图地址会与你的地址不同,需要打开导出文件(如下图)修改贴图对应地址

    clipboard.pngclipboard.png

  5. 加载过程中你有可能看不见模型,那么很有可能是你的模型过大或者过小,可以直接在代码中用scale函数进行调整测试
  6. 如果还有问题,建议在重复以上几点查看一遍

代码中如何加载动态模型

在程序中主要是这几个步骤
clipboard.png

下面是代码的部分节选,其实官方的教程例子写的很完整,之后把一个完整可运行的实例传在GIt上~

/**
 * @method animateModel
 * @description 
 *  一个创建带动画模型的方法
 * @param {object} object.g 几何体 object.m 材质  object.name 名字
 */
var zz;
var animateModel = function(config) {
    var geometry = config.g;
    var materials = config.m;
    for (var i = 0; i < materials.length; i++) {
        var m = materials[i];
        m.skinning = true
    }

    // 创建材质,由于材质是多面材质,由材质数组组成故要调用MultiMaterial方法来创建一个新的材质
    var material = new THREE.MultiMaterial(materials);
    //创建出一个骨骼带蒙皮的网格对象
    var mesh = new THREE.SkinnedMesh(geometry, material);
    //给这个网格模型增加他的属性
    mesh.name = config.name;
    // 初始化模型位置
    mesh.position.set(0, 0, 0);
    mesh.geometry.computeVertexNormals();

    //用你的网格模型去创建一个骨骼帮助器 
    var skeletonHelper = new THREE.SkeletonHelper(mesh)
    skeletonHelper.material.linewidth = 3;
    // 我们可以打开skeletonHelper,这样可以看到骨骼,便于调整
    skeletonHelper.visible = true;
    //将骨骼添加到场景
    scene.add(skeletonHelper);

    // AnimationMixer 动画混合器 理解为这个动画各方面的一个管理者吧
    var mixer = new THREE.AnimationMixer(mesh);
    // 骨骼动画的动作片段保存在geometry中 下面是读取第一个动画的方式,所以animationFirst是一个AnimationClip
    var firstAnimation = geometry.animations[0];
    // AnimationAction是动作的schedule,之所以叫schedule是因为他可以控制着动画开始 结束 停止 这些流程
    var action = mixer.clipAction(firstAnimation);

    // 接下来可以为这个动画配置一些细节了
    action.clampWhenFinished = false;
//  0会停止,这里设置为0默认停止,不停要注意其他的地方是否有设置这个值,值越大越快
    action.setEffectiveTimeScale(0);
    action.play();
    mesh.mixer = mixer;
    mesh.action = action;
    mesh.skeletonHelper = skeletonHelper;
    return mesh;
}

//加载模型数据
var loader = new THREE.JSONLoader();
loader.load("static/img/model/czz3.js", function(geometry, materials) {
    var config = { name: "zz", g: geometry, m: materials };
                zz = animateModel(config);
                zz.status = 1;
                zz.action.time = 0;
                zz.action.setEffectiveTimeScale(0.7);
                zz.rotation.set(-26, 0, 0);
                zz.scale.set(7, 7, 7)
                zz.visible = true;
                scene.add(zz);
                addAnimateModel.animate();
                console.log(TWEEN.Tween)
                var zz_tween = new TWEEN.Tween(jichan.position).to({ z: 20 }, 10000)
                zz_tween.repeat(Infinity);
                zz_tween.start()
});
//主要理解render和animate这两个函数,模型能载入,缺动不起来主要都是这个袁术
var clock = new THREE.Clock();
function render() {
    // if (animation) animation.update(delta);
    var r = Date.now() * 0.0005;
    var delta = clock.getDelta();
    zz.mixer.update(delta);
    zz.skeletonHelper.update();
    stats.update();
    renderer.render(scene, camera);
}
function animate() {
    render();
    requestAnimationFrame(animate);
    trackBallControl.update();
}

静态模型的导出与加载

静态模型的导出,对我而言用3dmax直接导出obj最简单了,用导出OBJ和MTL格式,如果只要选择导出不带贴图的模型,那么在Material这一栏的Create mat-library则不用勾选.

require('OBJLoader.js')
require('MTLLoader.js')

/**
 * 加载一个有贴图模型
 */
const createMtlObj = argv => {

    THREE.Loader.Handlers.add(/\.dds$/i, new THREE.DDSLoader());
    //创建材质加载器
    let mtlLoader = new THREE.MTLLoader();
    // 设置材质加载路径 (相对路径)
    mtlLoader.setPath(argv.mtlPath);

    mtlLoader.load(argv.mtlFileName, function(materials) {
        materials.preload();
        let objLoader = new THREE.OBJLoader();
        objLoader.setMaterials(materials);
        objLoader.setPath(argv.objPath);
        objLoader.load(argv.objFileName, function(object) {
                // 加载模型完成后的回调函数
                if (typeof argv.completeCallback === 'function') {
                    argv.completeCallback(object);
                }
            },

            // 加载中
            function(xhr) {
                if (xhr.lengthComputable) {
                    var percentComplete = xhr.loaded / xhr.total * 100;
                    console.log(Math.round(percentComplete, 2) + '% downloaded');
                }
            },
            function(error) { console.log('error:' + error) }
        );
    });

}

一个js动态模型文件——一头不吓人的蜘蛛

太长了,链接: https://pan.baidu.com/s/1boI98hL 密码: ethr

查看原文

Martin_Jonathan 评论了文章 · 2018-08-04

从3dMax导出供threeJS使用的带动作模型与加载(认真修改详尽版)

评论区发现的建议,最近没空测试,先贴这

clipboard.png

还有好多人说找不到插件的 https://pan.baidu.com/s/1Q5g0... 密码:b43e 。 应该是他们现在只是维护blender,只有这个的插件,不如改用blender?

在自己做的一个小玩意中,发现要从3dMax中导出js文件供给threeJS使用,真是太多坑了!所以打算详细记录一下方法,好像开发会3dMax的比较少,但是至少可以帮助开发与美工更好的沟通与交流。在文末,我会附上一个可加载的js模型,方便学习~

图片描述

导出文件类型选型

在《THREE.JS开发指南》2015年第一印第一版中提及的支持的模型主要有一下几种

clipboard.png

图中的确是一些常见格式,我们可以通过这个表格对常见格式做一些了解,但是我们从Three.js的官方实例的Loaders包中可以看到,发现他现在提供了各种加载器来支持文件加载,我们可以根据我们的需求来选择我们需要的加载方式

clipboard.png

与3dmax自带的导出选择相比较,可以导出.FBX,Collada,STL,OBJ和MTL等文件,然后使用对应的Loader进行加载。我们可以选择自己熟悉的工具,熟悉的格式进行模型的导出,但是也正是由于他的多样性,导致无从下手,在尝试了Blender导出动作模型,与DDS素材转换等等工具之后,最终决定采用,我较为熟悉3dMax为建模工具,来导出我最熟悉的js文件来完成模型的导出与加载

下面是我经过实验后对一些常见格式的记录与分析大致统计出的一些选型分析

clipboard.png

clipboard.png

如果不需要做那么多选型的话,直接导出js好了~各方面都比较合适。

如何导出动态模型的js文件

安装3dMax导出插件

clipboard.png

.OBJ导出来的仅仅是模型,.MTL导出来的仅仅是材质,那动作呢?js在Three.js中支持的动作导出,在3dmax,maya,blender中并没有直接导出js的方法,庆幸的是大牛已经给我们开发了越来越丰富的插件了。在utils中的export中包含了这些插件(文件目录.\three.js-master\three.js-master\utils\exporters\max),我们需要将这些插件直接复制到3dmax的plugin文件夹中,如果都是需要导出包含动作的,只需要安装ThreeJSAnimationExporter.ms这一个文件就够了,重启3dmax后打开就可以看到这个插件了。(如下图)

clipboard.png

如何获取一个可加载模型(以一个简单的圆柱体为例)

clipboard.pngclipboard.png

  1. 首先你的模型必须是一个整体,比如你的模型是熊猫吃竹子,如果你的熊猫和竹子要一块导出,那么你的熊猫和竹子必须是一个整体
  2. 贴图只能有一张,且贴图是png格式
  3. 给bone(骨骼)认真命名,js导出后你可能会发现骨骼有点偏移与错位,严格的命名有助于你在建模或者导出的时候进行修改,不过对开发来说这点会比较麻烦,这点也不是必须的。
  4. 模型的每一个点都必须赋予权重,如果发生破面行为,那么非常有可能是加了动作的模型却有的店没有权重
  5. 骨骼测试可用的有bone和CS骨骼,如果采用其他骨骼最好进行测试
  6. 导出的时候你的模型必须是一个Mesh,非常重要!这就意味着你不能导出一个可编辑多边形或者其他形式,在我们添加skin修改器的时候,先添加一个EditMesh修改器在我们的物体上,如果你已经是一个editable mesh,那这就不需要了。最好再使用Unwrap, UVW,中途出现warning点确定就好。

clipboard.png

  1. 导出的时候选择模型就好,不要选择骨骼,如果把骨骼一起选择导出会出现如下错误

    clipboard.png

  2. 添加一个XForm修改器于你的模型当你开始导出之前,他会重置你物件的位置,转向等等,将它变为初始那样,最小化在导出时可能遇到的问题

    clipboard.png

  3. 如果发生错误,修复完毕后需要重新启动3dmax才能正常使用导出插件
  4. 可以先验证模型导出的数据量和点格式的正确与否再进行加载,一般导出文件的贴图地址会与你的地址不同,需要打开导出文件(如下图)修改贴图对应地址

    clipboard.pngclipboard.png

  5. 加载过程中你有可能看不见模型,那么很有可能是你的模型过大或者过小,可以直接在代码中用scale函数进行调整测试
  6. 如果还有问题,建议在重复以上几点查看一遍

代码中如何加载动态模型

在程序中主要是这几个步骤
clipboard.png

下面是代码的部分节选,其实官方的教程例子写的很完整,之后把一个完整可运行的实例传在GIt上~

/**
 * @method animateModel
 * @description 
 *  一个创建带动画模型的方法
 * @param {object} object.g 几何体 object.m 材质  object.name 名字
 */
var zz;
var animateModel = function(config) {
    var geometry = config.g;
    var materials = config.m;
    for (var i = 0; i < materials.length; i++) {
        var m = materials[i];
        m.skinning = true
    }

    // 创建材质,由于材质是多面材质,由材质数组组成故要调用MultiMaterial方法来创建一个新的材质
    var material = new THREE.MultiMaterial(materials);
    //创建出一个骨骼带蒙皮的网格对象
    var mesh = new THREE.SkinnedMesh(geometry, material);
    //给这个网格模型增加他的属性
    mesh.name = config.name;
    // 初始化模型位置
    mesh.position.set(0, 0, 0);
    mesh.geometry.computeVertexNormals();

    //用你的网格模型去创建一个骨骼帮助器 
    var skeletonHelper = new THREE.SkeletonHelper(mesh)
    skeletonHelper.material.linewidth = 3;
    // 我们可以打开skeletonHelper,这样可以看到骨骼,便于调整
    skeletonHelper.visible = true;
    //将骨骼添加到场景
    scene.add(skeletonHelper);

    // AnimationMixer 动画混合器 理解为这个动画各方面的一个管理者吧
    var mixer = new THREE.AnimationMixer(mesh);
    // 骨骼动画的动作片段保存在geometry中 下面是读取第一个动画的方式,所以animationFirst是一个AnimationClip
    var firstAnimation = geometry.animations[0];
    // AnimationAction是动作的schedule,之所以叫schedule是因为他可以控制着动画开始 结束 停止 这些流程
    var action = mixer.clipAction(firstAnimation);

    // 接下来可以为这个动画配置一些细节了
    action.clampWhenFinished = false;
//  0会停止,这里设置为0默认停止,不停要注意其他的地方是否有设置这个值,值越大越快
    action.setEffectiveTimeScale(0);
    action.play();
    mesh.mixer = mixer;
    mesh.action = action;
    mesh.skeletonHelper = skeletonHelper;
    return mesh;
}

//加载模型数据
var loader = new THREE.JSONLoader();
loader.load("static/img/model/czz3.js", function(geometry, materials) {
    var config = { name: "zz", g: geometry, m: materials };
                zz = animateModel(config);
                zz.status = 1;
                zz.action.time = 0;
                zz.action.setEffectiveTimeScale(0.7);
                zz.rotation.set(-26, 0, 0);
                zz.scale.set(7, 7, 7)
                zz.visible = true;
                scene.add(zz);
                addAnimateModel.animate();
                console.log(TWEEN.Tween)
                var zz_tween = new TWEEN.Tween(jichan.position).to({ z: 20 }, 10000)
                zz_tween.repeat(Infinity);
                zz_tween.start()
});
//主要理解render和animate这两个函数,模型能载入,缺动不起来主要都是这个袁术
var clock = new THREE.Clock();
function render() {
    // if (animation) animation.update(delta);
    var r = Date.now() * 0.0005;
    var delta = clock.getDelta();
    zz.mixer.update(delta);
    zz.skeletonHelper.update();
    stats.update();
    renderer.render(scene, camera);
}
function animate() {
    render();
    requestAnimationFrame(animate);
    trackBallControl.update();
}

静态模型的导出与加载

静态模型的导出,对我而言用3dmax直接导出obj最简单了,用导出OBJ和MTL格式,如果只要选择导出不带贴图的模型,那么在Material这一栏的Create mat-library则不用勾选.

require('OBJLoader.js')
require('MTLLoader.js')

/**
 * 加载一个有贴图模型
 */
const createMtlObj = argv => {

    THREE.Loader.Handlers.add(/\.dds$/i, new THREE.DDSLoader());
    //创建材质加载器
    let mtlLoader = new THREE.MTLLoader();
    // 设置材质加载路径 (相对路径)
    mtlLoader.setPath(argv.mtlPath);

    mtlLoader.load(argv.mtlFileName, function(materials) {
        materials.preload();
        let objLoader = new THREE.OBJLoader();
        objLoader.setMaterials(materials);
        objLoader.setPath(argv.objPath);
        objLoader.load(argv.objFileName, function(object) {
                // 加载模型完成后的回调函数
                if (typeof argv.completeCallback === 'function') {
                    argv.completeCallback(object);
                }
            },

            // 加载中
            function(xhr) {
                if (xhr.lengthComputable) {
                    var percentComplete = xhr.loaded / xhr.total * 100;
                    console.log(Math.round(percentComplete, 2) + '% downloaded');
                }
            },
            function(error) { console.log('error:' + error) }
        );
    });

}

一个js动态模型文件——一头不吓人的蜘蛛

太长了,链接: https://pan.baidu.com/s/1boI98hL 密码: ethr

查看原文

Martin_Jonathan 回答了问题 · 2018-08-04

解决Three.js较复杂的贴图的UV映射有较成熟的获取方案吗?

在学习ThreeJS的过程中,常常会有这样的疑惑,为什么做一个模型这么复杂,复杂的UV映射完全无从下手,Demo里那些看起来很厉害的模型是怎么做出来的
其实大多数的模型都是使用桌面软件制作出来的,包括建模、贴图、动画等,使用ThreeJS也可以完成部分作品,但是费事费力,在我看来不能作为成熟方案用作商业用途


使用3D max建模(各类建模软件都可以),制作UV展开,导出带贴图信息的obj格式文件(以obj为例)
使用ThreeJS加载即可,在这里ThreeJS的作用仅类似于视频播放器,仅完成模型加载渲染的工作,建模贴图等工作交给3D max等桌面软件即可

    var manager = new THREE.LoadingManager();
    manager.onProgress = function ( item, loaded, total ) {
        console.log( item, loaded, total );
    };
    manager.onLoad = function ( item, loaded, total ) {
        console.log( 'load finish' );
    };
    // 加载材质
    var textureLoader = new THREE.TextureLoader( manager );
    var texture = textureLoader.load( '/obj/caizhi.png' );
    
    var onProgress = function ( xhr ) {
        if ( xhr.lengthComputable ) {
            var percentComplete = xhr.loaded / xhr.total * 100;
            console.log( Math.round(percentComplete, 2) + '% downloaded' );
        }
    };

    var onError = function ( xhr ) {
    };
    // 加载模型
    var loader = new THREE.OBJLoader( manager );
    loader.load( '/obj/C-BOX.obj', function ( obj ) {

        obj.traverse( function ( child ) {
            if ( child instanceof THREE.Mesh ) {
                // 为模型赋予材质
                child.material.map = texture;
            }
        } );
        // 模型加载完后向场景中添加模型
        scene.add( obj );
    }, onProgress, onError );

关注 2 回答 2

Martin_Jonathan 收藏了文章 · 2018-07-30

中小型研发团队对于架构的选择与思考

如果你正好处在中小型研发团队……

中小型研发团队很多,而社区在中小型研发团队架构实践方面的探讨却很少。中小型研发团队特别是 50 至 200 人的研发团队,在早期的业务探索阶段,更多关注业务逻辑,快速迭代以验证商业模式,很少去关注技术架构。

这时如果继续按照原有的架构及研发模式,会出现大量的问题,再也无法玩下去了。能不能有一套可直接落地、基于开源、成本低,可快速搭建的中间件及架构升级方案呢?

在接下来的一段时间里,我会陆续推出此系列文章。

本系列文章涉及内容清单如下(并不按这顺序发布),其中有感兴趣的,欢迎关注:

  • 开篇
  • 缓存 Redis
  • 消息队列 RabbitMQ
  • 集中式日志 ELK
  • 任务调度 Job
  • 应用监控 Metrics
  • 微服务框架 MSA
  • 搜索利器 Solr
  • 分布式协调器 ZooKeeper
  • 小工具:Dapper.NET/EmitMapper/AutoMapper/Autofac/NuGet
  • 发布工具 Jenkins
  • 总体架构设计
  • 单个项目架构设计
  • 统一应用分层
  • 调试工具 WinDbg
  • 单点登录
  • 企业支付网关
  • 结篇

文章中部分 Demo 采用 C# 语言, 但到了框架或架构层面,与语言本身没有太多直接的关系。如 RabbitMQ、Job、Redis 和集中式日志,它们服务端的部署是一样的,只是客户端语言版本稍有不同。

所有 Demo 都可直接运行,服务地址及管理后台也可直接访问。 因为部署在公有云,牵涉到成本费用的问题,我计划持续到明年 3 月底。

这些小小的基础工作,希望能够帮到中小型研发团队,解决大家项目中遇到的实际问题。愿与你一起成长,你的分享和点赞是我此次付出的动力,谢谢!

整个系列文章分为三个部分,包括 框架篇 、 架构篇 和 公共应用篇 。

框架篇 即中间件或工具的使用,如缓存、消息队列、集中式日志、度量、微服务框架等,工欲善其事,必先利其器。
架构篇 主要是设计思想的提升,有企业总体架构、单个项目架构设计、统一应用分层等。
公共应用篇 是业务与技术的结合,有单点登录和企业支付网关。
以下是篇章的具体介绍:

框架篇——工欲善其事,必先利其器

如果说运维是地基,那么框架就是承重墙。农村建住房是一块砖一块砖地往上垒,而城市建大 House 则是先打地基,再建承重墙,最后才是垒砖,所以中间件的搭建和引进是建设高可用、高性能、易扩展可伸缩的大中型系统的前提。

框架篇中的每篇主要由四部分组成: 它是什么 、 工作原理 、 使用场景 和 可直接调试的 Demo。其中 Demo 及中间件历经两家公司四年时间的考验,涉及几百个应用,100 多个库 1 万多张表,日订单从几万张到十几万,年 GMV 从几十亿到几百亿。

所有中间件及工具都是基于开源,早期我们也有部分自主研发如集中式日志和度量框架。后期在第二家公司时为了快速地搭建,降低成本,易于维护和扩展,全部改为开源。这样不仅利于个人的学习成长、知识重用和职业生涯,也利于团队的组建和人才的引进。

集中式缓存 Redis

缓存是计算机的难题之一,分布式缓存亦是如此。Redis 看起来非常简单,但它影响着系统的效率、性能、数据一致性。

用好它不容易,涉及到的问题包括:缓存时长(复杂多维度的计算)、缓存失效处理(主动更新)、缓存键(Hash 和方便人工干预)、缓存内容及数据结构的选择、缓存雪崩的处理、缓存穿透的处理等。

Redis 除了缓存的功能,还有其它功能如 Lua 计算能力、Limit 与 Session 时间窗口、分布式锁等。

消息队列 RabbitMQ

消息队列好比葛洲坝,有大量数据的堆积能力,然后再可靠地进行异步输出。它是 EDA 事件驱动架构的核心,也是 CQRS 同步数据的关键。为什么选择 RabbitMQ 而没有选择 Kafka,因为业务系统有对消息的高可靠性要求,以及对复杂功能如消息确认 Ack 的要求。

集中式日志 ELK

日志主要分为 系统日志 和 应用日志 两类。试想一下,你该如何在一个具有几百台服务器的集群中定位到问题?如何追踪每天产生的几 G 甚至几 T 的数据?集中式日志就是此类问题的解决方案。

早期我们使用自主研发的 Log4Net+MongoDB 来收集和检索日志信息,但随着数据量的增加,查询速度却变得越来越慢。后期改为开源的 ELK,虽然易用性有所下降,但它支持海量数据以及与编程语言无关的特征。下面是 ELK 的架构图。

clipboard.png

任务调度 Job

任务调度 Job 如同数据库作业或 Windows 计划任务,是分布式系统中异步和批处理的关键。我们的 Job 分为 WinJob 和 HttpJob:WinJob 是操作系统级别的定时任务,使用开源的框架 Quartz.NET 实现;而 HttpJob 则是自主研发实现,采用 URL 方式可定时调用微服务。

HttpJob 借助集群巧妙地解决了 WinJob 的单点和发布问题,并集中管理所有的调度规则,调度规则有简单规则和 Cron 表达式。HttpJob 它简单易用,但间隔时间不能低于 1 分钟,毕竟通过 URL 方式来调度并不高效。下图是 HttpJob 的管理后台。

clipboard.png

应用监控 Metrics

“没有度量就没有提升”,度量是改进优化的基础,是做好一个系统的前置条件。Zabbix 一般用于系统级别的监控,Metrics 则用于业务应用级别的监控。

业务应用是个黑盒子,通过数据埋点来收集应用的实时状态,然后展示在大屏或看板上。它是报警系统和数字化管理的基础,还可以结合集中式日志来快速定位和查找问题。我们的业务监控系统使用Metrics.NET+InfluxDB+Grafana。

clipboard.png

微服务框架 MSA

微服务是细粒度业务行为的重用,需要与业务能力及业务阶段相匹配。微服务框架是实现微服务及分布式架构的关键组件,我们的微服务框架是基于开源 ServiceStack 来实现。

它简单易用、性能好,文档自动生成、方便调试测试,调试工具 Swagger UI、自动化接口测试工具 SoapUI。微服务的接口开放采用我们自主研发的微服务网关,通过治理后台简单的配置即可。网关以 NIO、IOCP 的方式实现高并发,主要功能有鉴权、超时、限流、熔断、监控等,下图是 Swagger UI 调试工具。

clipboard.png

搜索利器 Solr

分库分表后的关联查询,大段文本的模糊查询,这些要如何实现呢?显然传统的数据库没有很好的解决办法,这时可以借助专业的检索工具。

全文检索工具 Solr 不仅简单易用性能好,而且支持海量数据高并发,只需实现系统两边数据的准实时或定时同步即可。下图是 Solr 的工作原理。

clipboard.png

更多工具

分布式协调器 ZooKeeper
ZK 工作原理、配置中心、Master 选举、Demo,一篇足以。
ORM 框架
Dapper.NET 语法简单、运行速度快,与数据库无关,SQL 自主编写可控,是一款适合于互联网系统的数据库访问工具。
对象映射工具 EmitMapper 和 AutoMapper
EmitMapper 性能较高,AutoMapper 易用性较好。
IoC 框架
控制反转 IoC 轻量级框架 Autofac。
DLL 包管理
公司内部 DLL 包管理工具 NuGet,可解决 DLL 集中存储、更新、引用、依赖问题。
发布工具 Jenkins
一键编译、发布、自动化测试、一键回滚,高效便捷故障低。

架构篇——思想提升

会使用以上框架并不一定能成为优秀的架构师,但一位优秀架构师一定会使用框架。架构师除了会使用工具外,还需要设计思想的提升和性能调优技能。

此篇以真实项目为背景,思想方法追求简单有效,主要内容包括 企业总体架构 、 单个项目架构设计 、 统一应用分层、 调试工具 WinDbg。

说到这里顺便给大家推荐一个架构方面的交流学习群:650385180,里面会分享一些资深架构师整理的文档资料和录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。

企业总体架构

当我们有了几百个上千个应用后,不仅仅需要单个项目的架构设计,还需要企业总体架构做顶层思考和指导。大公司与小商贩的商业思维是一样的,但大公司比较难看到商业全貌和本质。而小公司又缺乏客户流量和中间件的应用场景,中型公司则兼而有之,所以企业总体架构也相对好落地。

企业总体架构需要在 技术 、 业务 、 管理 之间游刃有余地切换,它包括业务架构、应用架构、数据架构和技术架构。附档是一份脱敏感信息后的真实案例,有参考 TOGAF 标准。但内容以解决公司系统的架构问题为导向、以时间为主线,包括企业商务模型、架构现状、架构规划和架构实施。

单个项目架构设计

单个项目的架构设计如同施工图纸,能直接指导工程代码的实施。上一环是功能需求,下一环是代码实施,这是架构设计的价值所在。从功能需求到用例,到用例活动图,到领域图、架构分层,到核心代码,它们之间环环相扣。

做不好领域图可能源自没有做好用例活动图,因为用例活动图是领域图的上一环。关注职责、边界、应用关系、存储、部署是架构设计的核心,下图是具体案例参考。

clipboard.png

统一应用分层

给应用分层这件事情很简单,但是让一家公司的几百个应用采用统一的分层结构,这可不是件简单的事情。它要做到可大可小、简单易用、支持多种场景,我们使用 IPO 方式:I 表示 Input、O 表示 Output、P 表示 Process,一进一出一处理。应用系统的本质就是机器,是处理设备,也是一进一出一处理,IPO 方式相对于 DDD 而言更为简单实用。

clipboard.png

调试工具 WinDbg

生产环境偶尔会出现一些异常问题,而 WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDbg 如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump 文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。

主要介绍调试工具 WinDbg 和抓包工具 ProcDump 的使用,并分享一个真实的案例。N 年前不知谁写的代码,导致每一两个月偶尔出现 CPU 飙高的现象。

我们先使用 ProcDump 在生产环境中抓取异常进程的 Dump 文件,然后在不了解代码的情况下通过 WinDbg 命令进行分析,最终定位到有问题的那行代码。

clipboard.png

公共应用篇

先工具再框架,然后架构设计,最后深入公共应用。公共应用因为与业务系统结合紧密,但又具有一定的独立性,所以一般自主开发,不使用开源也不方便开源。公共应用主要包括单点登录、企业支付网关、CTI 通讯网关(短信邮件微信),此次分享单点登录和企业支付网关。

单点登录

应用拆分后总要合在一起,拆分是应用实施层面的拆分,合成是用户层面的合成,而合成必须解决认证和导航问题。单点登录 SSO 即只需要登录一次,便可到处访问,它是建立在用户系统、权限系统、认证系统和企业门户的基础上。我们的凭证数据 Token 使用 JWT 标准,以解决不同语言、不同客户端、跨 WebAPI 的安全问题。

企业支付网关

企业支付网关集中和封装了公司的各大支付,例如支付宝、财付通、微信、预付款等。它统一了业务系统调用各支付接口的方式,简化了业务系统与支付系统的交互。

它将各种支付接口统一为支付、代扣、分润、退款、退分润、补差、转账、冻结、解冻、预付款等,调用时只需选择支付类型即可。企业支付网关将各大支付系统进行集中的设计、研发、部署、监控、维护,提供统一的加解密、序列化、日志记录,安全隔离。

查看原文

Martin_Jonathan 赞了文章 · 2018-07-30

中小型研发团队对于架构的选择与思考

如果你正好处在中小型研发团队……

中小型研发团队很多,而社区在中小型研发团队架构实践方面的探讨却很少。中小型研发团队特别是 50 至 200 人的研发团队,在早期的业务探索阶段,更多关注业务逻辑,快速迭代以验证商业模式,很少去关注技术架构。

这时如果继续按照原有的架构及研发模式,会出现大量的问题,再也无法玩下去了。能不能有一套可直接落地、基于开源、成本低,可快速搭建的中间件及架构升级方案呢?

在接下来的一段时间里,我会陆续推出此系列文章。

本系列文章涉及内容清单如下(并不按这顺序发布),其中有感兴趣的,欢迎关注:

  • 开篇
  • 缓存 Redis
  • 消息队列 RabbitMQ
  • 集中式日志 ELK
  • 任务调度 Job
  • 应用监控 Metrics
  • 微服务框架 MSA
  • 搜索利器 Solr
  • 分布式协调器 ZooKeeper
  • 小工具:Dapper.NET/EmitMapper/AutoMapper/Autofac/NuGet
  • 发布工具 Jenkins
  • 总体架构设计
  • 单个项目架构设计
  • 统一应用分层
  • 调试工具 WinDbg
  • 单点登录
  • 企业支付网关
  • 结篇

文章中部分 Demo 采用 C# 语言, 但到了框架或架构层面,与语言本身没有太多直接的关系。如 RabbitMQ、Job、Redis 和集中式日志,它们服务端的部署是一样的,只是客户端语言版本稍有不同。

所有 Demo 都可直接运行,服务地址及管理后台也可直接访问。 因为部署在公有云,牵涉到成本费用的问题,我计划持续到明年 3 月底。

这些小小的基础工作,希望能够帮到中小型研发团队,解决大家项目中遇到的实际问题。愿与你一起成长,你的分享和点赞是我此次付出的动力,谢谢!

整个系列文章分为三个部分,包括 框架篇 、 架构篇 和 公共应用篇 。

框架篇 即中间件或工具的使用,如缓存、消息队列、集中式日志、度量、微服务框架等,工欲善其事,必先利其器。
架构篇 主要是设计思想的提升,有企业总体架构、单个项目架构设计、统一应用分层等。
公共应用篇 是业务与技术的结合,有单点登录和企业支付网关。
以下是篇章的具体介绍:

框架篇——工欲善其事,必先利其器

如果说运维是地基,那么框架就是承重墙。农村建住房是一块砖一块砖地往上垒,而城市建大 House 则是先打地基,再建承重墙,最后才是垒砖,所以中间件的搭建和引进是建设高可用、高性能、易扩展可伸缩的大中型系统的前提。

框架篇中的每篇主要由四部分组成: 它是什么 、 工作原理 、 使用场景 和 可直接调试的 Demo。其中 Demo 及中间件历经两家公司四年时间的考验,涉及几百个应用,100 多个库 1 万多张表,日订单从几万张到十几万,年 GMV 从几十亿到几百亿。

所有中间件及工具都是基于开源,早期我们也有部分自主研发如集中式日志和度量框架。后期在第二家公司时为了快速地搭建,降低成本,易于维护和扩展,全部改为开源。这样不仅利于个人的学习成长、知识重用和职业生涯,也利于团队的组建和人才的引进。

集中式缓存 Redis

缓存是计算机的难题之一,分布式缓存亦是如此。Redis 看起来非常简单,但它影响着系统的效率、性能、数据一致性。

用好它不容易,涉及到的问题包括:缓存时长(复杂多维度的计算)、缓存失效处理(主动更新)、缓存键(Hash 和方便人工干预)、缓存内容及数据结构的选择、缓存雪崩的处理、缓存穿透的处理等。

Redis 除了缓存的功能,还有其它功能如 Lua 计算能力、Limit 与 Session 时间窗口、分布式锁等。

消息队列 RabbitMQ

消息队列好比葛洲坝,有大量数据的堆积能力,然后再可靠地进行异步输出。它是 EDA 事件驱动架构的核心,也是 CQRS 同步数据的关键。为什么选择 RabbitMQ 而没有选择 Kafka,因为业务系统有对消息的高可靠性要求,以及对复杂功能如消息确认 Ack 的要求。

集中式日志 ELK

日志主要分为 系统日志 和 应用日志 两类。试想一下,你该如何在一个具有几百台服务器的集群中定位到问题?如何追踪每天产生的几 G 甚至几 T 的数据?集中式日志就是此类问题的解决方案。

早期我们使用自主研发的 Log4Net+MongoDB 来收集和检索日志信息,但随着数据量的增加,查询速度却变得越来越慢。后期改为开源的 ELK,虽然易用性有所下降,但它支持海量数据以及与编程语言无关的特征。下面是 ELK 的架构图。

clipboard.png

任务调度 Job

任务调度 Job 如同数据库作业或 Windows 计划任务,是分布式系统中异步和批处理的关键。我们的 Job 分为 WinJob 和 HttpJob:WinJob 是操作系统级别的定时任务,使用开源的框架 Quartz.NET 实现;而 HttpJob 则是自主研发实现,采用 URL 方式可定时调用微服务。

HttpJob 借助集群巧妙地解决了 WinJob 的单点和发布问题,并集中管理所有的调度规则,调度规则有简单规则和 Cron 表达式。HttpJob 它简单易用,但间隔时间不能低于 1 分钟,毕竟通过 URL 方式来调度并不高效。下图是 HttpJob 的管理后台。

clipboard.png

应用监控 Metrics

“没有度量就没有提升”,度量是改进优化的基础,是做好一个系统的前置条件。Zabbix 一般用于系统级别的监控,Metrics 则用于业务应用级别的监控。

业务应用是个黑盒子,通过数据埋点来收集应用的实时状态,然后展示在大屏或看板上。它是报警系统和数字化管理的基础,还可以结合集中式日志来快速定位和查找问题。我们的业务监控系统使用Metrics.NET+InfluxDB+Grafana。

clipboard.png

微服务框架 MSA

微服务是细粒度业务行为的重用,需要与业务能力及业务阶段相匹配。微服务框架是实现微服务及分布式架构的关键组件,我们的微服务框架是基于开源 ServiceStack 来实现。

它简单易用、性能好,文档自动生成、方便调试测试,调试工具 Swagger UI、自动化接口测试工具 SoapUI。微服务的接口开放采用我们自主研发的微服务网关,通过治理后台简单的配置即可。网关以 NIO、IOCP 的方式实现高并发,主要功能有鉴权、超时、限流、熔断、监控等,下图是 Swagger UI 调试工具。

clipboard.png

搜索利器 Solr

分库分表后的关联查询,大段文本的模糊查询,这些要如何实现呢?显然传统的数据库没有很好的解决办法,这时可以借助专业的检索工具。

全文检索工具 Solr 不仅简单易用性能好,而且支持海量数据高并发,只需实现系统两边数据的准实时或定时同步即可。下图是 Solr 的工作原理。

clipboard.png

更多工具

分布式协调器 ZooKeeper
ZK 工作原理、配置中心、Master 选举、Demo,一篇足以。
ORM 框架
Dapper.NET 语法简单、运行速度快,与数据库无关,SQL 自主编写可控,是一款适合于互联网系统的数据库访问工具。
对象映射工具 EmitMapper 和 AutoMapper
EmitMapper 性能较高,AutoMapper 易用性较好。
IoC 框架
控制反转 IoC 轻量级框架 Autofac。
DLL 包管理
公司内部 DLL 包管理工具 NuGet,可解决 DLL 集中存储、更新、引用、依赖问题。
发布工具 Jenkins
一键编译、发布、自动化测试、一键回滚,高效便捷故障低。

架构篇——思想提升

会使用以上框架并不一定能成为优秀的架构师,但一位优秀架构师一定会使用框架。架构师除了会使用工具外,还需要设计思想的提升和性能调优技能。

此篇以真实项目为背景,思想方法追求简单有效,主要内容包括 企业总体架构 、 单个项目架构设计 、 统一应用分层、 调试工具 WinDbg。

说到这里顺便给大家推荐一个架构方面的交流学习群:650385180,里面会分享一些资深架构师整理的文档资料和录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。

企业总体架构

当我们有了几百个上千个应用后,不仅仅需要单个项目的架构设计,还需要企业总体架构做顶层思考和指导。大公司与小商贩的商业思维是一样的,但大公司比较难看到商业全貌和本质。而小公司又缺乏客户流量和中间件的应用场景,中型公司则兼而有之,所以企业总体架构也相对好落地。

企业总体架构需要在 技术 、 业务 、 管理 之间游刃有余地切换,它包括业务架构、应用架构、数据架构和技术架构。附档是一份脱敏感信息后的真实案例,有参考 TOGAF 标准。但内容以解决公司系统的架构问题为导向、以时间为主线,包括企业商务模型、架构现状、架构规划和架构实施。

单个项目架构设计

单个项目的架构设计如同施工图纸,能直接指导工程代码的实施。上一环是功能需求,下一环是代码实施,这是架构设计的价值所在。从功能需求到用例,到用例活动图,到领域图、架构分层,到核心代码,它们之间环环相扣。

做不好领域图可能源自没有做好用例活动图,因为用例活动图是领域图的上一环。关注职责、边界、应用关系、存储、部署是架构设计的核心,下图是具体案例参考。

clipboard.png

统一应用分层

给应用分层这件事情很简单,但是让一家公司的几百个应用采用统一的分层结构,这可不是件简单的事情。它要做到可大可小、简单易用、支持多种场景,我们使用 IPO 方式:I 表示 Input、O 表示 Output、P 表示 Process,一进一出一处理。应用系统的本质就是机器,是处理设备,也是一进一出一处理,IPO 方式相对于 DDD 而言更为简单实用。

clipboard.png

调试工具 WinDbg

生产环境偶尔会出现一些异常问题,而 WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDbg 如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump 文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。

主要介绍调试工具 WinDbg 和抓包工具 ProcDump 的使用,并分享一个真实的案例。N 年前不知谁写的代码,导致每一两个月偶尔出现 CPU 飙高的现象。

我们先使用 ProcDump 在生产环境中抓取异常进程的 Dump 文件,然后在不了解代码的情况下通过 WinDbg 命令进行分析,最终定位到有问题的那行代码。

clipboard.png

公共应用篇

先工具再框架,然后架构设计,最后深入公共应用。公共应用因为与业务系统结合紧密,但又具有一定的独立性,所以一般自主开发,不使用开源也不方便开源。公共应用主要包括单点登录、企业支付网关、CTI 通讯网关(短信邮件微信),此次分享单点登录和企业支付网关。

单点登录

应用拆分后总要合在一起,拆分是应用实施层面的拆分,合成是用户层面的合成,而合成必须解决认证和导航问题。单点登录 SSO 即只需要登录一次,便可到处访问,它是建立在用户系统、权限系统、认证系统和企业门户的基础上。我们的凭证数据 Token 使用 JWT 标准,以解决不同语言、不同客户端、跨 WebAPI 的安全问题。

企业支付网关

企业支付网关集中和封装了公司的各大支付,例如支付宝、财付通、微信、预付款等。它统一了业务系统调用各支付接口的方式,简化了业务系统与支付系统的交互。

它将各种支付接口统一为支付、代扣、分润、退款、退分润、补差、转账、冻结、解冻、预付款等,调用时只需选择支付类型即可。企业支付网关将各大支付系统进行集中的设计、研发、部署、监控、维护,提供统一的加解密、序列化、日志记录,安全隔离。

查看原文

赞 66 收藏 55 评论 0

Martin_Jonathan 评论了文章 · 2018-07-28

现在做 Web 全景合适吗?

Web 全景在以前带宽有限的条件下常常用来作为街景和 360° 全景图片的查看。它可以给用户一种 self-immersive 的体验,通过简单的操作,自由的查看周围的物体。随着一些运营商推出大王卡等免流服务,以及 4G 环境的普及,大流量的应用也逐渐得到推广。比如,我们是否可以将静态低流量的全景图片,变为动态直播的全景视频呢?在一定网速带宽下,是可以实现的。后面,我们来了解一下,如何在 Web 端实现全景视频。先看一下实例 gif:

gif

tl;dr;

  • 使用 three.js 实现全景技术
  • UV 映射原理简介
  • 3D 坐标原理和移动控制
  • Web 陀螺仪简介
  • iv-panorama 简单库介绍

基于 Three.js

全景视频是基于 3D 空间,而在 Web 中,能够非常方便触摸到 3D 空间的技术,就是 WebGL。为了简化,这里就直接采用 Three.js 库。具体的工作原理就是将正在播放的 video 元素,映射到纹理(texture) 空间中,通过 UV 映射,直接贴到一个球面上。精简代码为:

let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100); 
// 添加相机


camera.target = new THREE.Vector3(0, 0, 0); 
// 设置相机的观察位置,通常在球心

scene = new THREE.Scene();
let  geometry = new THREE.SphereBufferGeometry(400, 60, 60);
// 在贴图的时候,让像素点朝内(非常重要)
geometry.scale(-1, 1, 1);

// 传入视频 VideoEle 进行绘制
var texture = new THREE.VideoTexture(videoElement);

var material = new THREE.MeshBasicMaterial({ map: texture });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);


renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio); // canvas 的比例
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);

具体的过程差不多就是上面的代码。上面代码中有两块需要注意一下,一个是 相机的视野范围值,一个是几何球体的相关参数设置。

相机视野范围

具体代码为:

let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100); 

这里主要利用透视类型的相机,模拟人眼的效果。设置合适的视野效果,这里的范围还需要根据球体的直径来决定,通常为 2*radius + 100,反正只要比球体直径大就行。

几何球体的参数设置

let  geometry = new THREE.SphereBufferGeometry(400, 60, 60);
// 在贴图的时候,让像素点朝内(非常重要)
geometry.scale(-1, 1, 1);

上面其实有两个部分需要讲解一下

  • 球体参数设置里面有三个属性值比较重要,该 API 格式为:SphereBufferGeometry(radius, widthSegments, heightSegments,...)

    • raidus: 设置球体的半径,半径越大,视频在 canvas 上绘制的内容也会被放大,该设置值合适就行。
    • width/height Segments: 切片数,主要用来控制球体在宽高两个维度上最多细分为多少个三角切片数量,越高纹理拼接的边角越清晰。不过,并不是无限制高的,高的同时性能损耗也是有的。
  • 在几何绘制时,通过坐标变换使 X 轴的像素点朝内,让用户看起来不会存在 凸出放大的效果。具体代码为:geometry.scale(-1, 1, 1)

UV 映射

上面只是简单介绍了一下代码,如果仅仅只是为了应用,那么这也就足够了。但是,如果后面遇到优化的问题,不知道更底层的或者更细节内容的话,就感觉很尴尬。在全景视频中,有两个非常重要的点:

  • UV 映射
  • 3D 移动

这里,我们主要探索一下 UV 映射的细节。UV 映射主要目的就是将 2D 图片映射到三维物体上,最经典的解释就是:

盒子是一个三维物体,正如同加到场景中的一个曲面网络("mesh")方块.
如果沿着边缝或折痕剪开盒子,可以把盒子摊开在一个桌面上.当我们从上往下俯视桌子时,我们可以认为U是左右方向,V是上下方向.盒子上的图片就在一个二维坐标中.我们使用U V代表"纹理坐标系"来代替通常在三维空间使用的 X Y.
在盒子重新被组装时,纸板上的特定的UV坐标被对应到盒子的一个空间(X Y Z)位置.这就是将2D图像包裹在3D物体上时计算机所做的.

image.png-544.6kB from 浙江研报

这里,我们通过代码来细致讲解一下。我们需要完成一个贴图,将如下的 sprite,贴到一个正方体上。

sprite

from iefreer

cube

这里,我们先将图片加载到纹理空间:

var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/texture-atlas.jpg') } );

那么,现在我们有一个如下的纹理空间区域:

此处输入图片的描述

这块内容,就实际涉及到 WebGL 的知识,纹理空间和物理空间并不是在一块,WebGL 中的 GLSL 语法,就是将纹理内容通过相关规则,映射到指定的三角形区域的表面。

这里需要注意的是,纹理空间并不存在所谓的最小三角区域,这里适应的只是在物理空间中划分的三角区域。为了简单起见,我们设置的 boxGeometry 只使用单位为 1 的 Segments,减少需要划分的三角形数量。

这样,就存在 12 块需要贴的三角区域。这里,我们就需要利用 Vector2 来手动划分一下纹理空间的区域,实际在映射的时候,就是按顺序,将物理空间的定点 和 纹理空间的定点一一映射,这样就实现了将纹理和物理空间联系到一起的步骤。

因为,Three.js 中 geometry.faceVertexUvs 在划分物理空间时,定义的面分解三角形的顺序 是 根据逆时针方向,按序号划分,如下图所示:

此处输入图片的描述

根据上图的定义,我们可以得到每个几何物体的面映射到纹理空间的坐标值可以分为:

left-bottom = [0,1,3]
right-top = [1,2,3]

所以,我们需要定义一下纹理坐标值:

face1_left = [new THREE.Vector2(0, 0),new THREE.Vector2(.5, 0),new THREE.Vector2(0, .333)]
face1_right = [new THREE.Vector2(.5, 0),new THREE.Vector2(.5, .333),new THREE.Vector2(0, .333)]

//... 剩下 10 个面

定点 UV 映射 API 具体格式为:

geometry.faceVertexUvs[ 0 ][ faceIndex ][ vertexIndex ]

则定义具体面的映射为:

geometry.faceVertexUvs[0][0] = face1_left;
geometry.faceVertexUvs[0][0] = face1_right;
//...剩下 10 个面

如果,你写过原生的 WebGL 代码,对于理解 UV 映射原理应该很容易了。

3D 移动原理

这里需要注意的是 Web 全景不是 WebVR。全景没有 VR 那种沉浸式体验,单单只涉及三个维度上的旋转而没有移动距离这个说法。

上面的描述中,提到了三维,旋转角度 这两个概念,很容易让我们想到《高中数学》学到的一个坐标系--球坐标系(这里默认都是右手坐标系)。

球坐标

  • φ 是和 z 轴正方向 <=180°的夹角
  • ∂ 是和 x 轴正方向 <=180°的夹角
  • p 是空间点距离原点的直线距离

计算公式为:

formula

现在,如果应用到 Web 全景,我们可以知道几个已知条件:

  • p:定义的球体(SphereBufferGeometry)的半径大小
  • ∆φ:用户在 y 轴上移动的距离
  • ∆∂:用户在 x 轴上移动的距离

p 这个是不变的,而 ∆φ 和 ∆∂ 则是根据用户输入来决定的大小值。这里,就需要一个算法来统一协定。该算法控制的主要内容就是:

用户的手指在 x/y 平面轴上的 ∆x/∆y 通过一定的比例换算成为 ∆φ/∆∂

如果考虑到陀螺仪就是:

用户的手指在 x/y 平面轴上的 ∆x/∆y 通过一定的比例换算成为 ∆φ/∆∂,用户在 x/y 轴上旋转的角度值 ∆φ'/∆∂',分别和视角角度进行合并,算出结果。

为了更宽泛的兼容性,我们这里根据第二种算法的描述来进行讲解。上面 ∆φ/∆∂ 的变动主要映射的是我们视野范围的变化。

gif

在 Threejs 中,就是用来控制相机的视野范围。那我们如何在 ThreeJS 控制视野范围呢?下面是最简代码:

phi = THREE.Math.degToRad(90 - lat);
theta = THREE.Math.degToRad(-lon);
camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
camera.position.y = distance * Math.cos(phi);
camera.position.z = distance * Math.sin(phi) * Math.sin(theta);

这里主要模拟地球坐标:

  • lat 代表维度(latitude): 用户上下滑动改变的值,或者手机上下旋转
  • lon 代表经度(lontitude): 用户左右滑动改变的值,或者手机左右旋转

具体内容为:

image.png-17.9kB

在通常实践当中,改变全景视角的维度有两种,一种直接通过手滑,一种则根据陀螺仪旋转。

简单来说,就是监听 touchorientation 事件,根据触发信息来手动改变 lat/lon 的值。不过,这里有一个注意事项:

latitude 方向上最多只能达到 (-90,90),否则会造成屏幕翻转的效果,这种体验非常不好。

我们分别通过代码来实践一下。

添加 touch 控制

Touch 相关的事件在 Web 中,其实可以讲到你崩溃为止,比如,用户用几个手指触摸屏幕?用户具体在屏幕上的手势是什么(swipezoom)?

这里,我们简单起见,只针对一个手指滑动的距离来作为 相机 视角移动的数据。具体代码为:

// 为了给自己博客拉量,完整版可以去我的博客查看:
https://www.villainhr.com

iv-panorama 简介

iv-panorama 是 IVWEB 团队,针对于全景直播这个热点专门开发的一个播放器。现在 Web 对 VR 支持度也不是特别友好,但是,对于全景视频来说,在机器换代更新的前提下,全景在性能方面的瓶颈慢慢消失了。其主要特性为:

  • 依赖于 Three.js,需要预先挂载到 window 对象上
  • 灵活配置,内置支持陀螺仪和 touch 控制。
  • 支持灵敏度参数的动态调整
  • 使用 ES6 语法
  • 兼容 React,jQuery(简单凑数的)

项目地址为:iv-panorama。该项目使用非常简单,有两种全景模式,一个是 图片,一个是视频:

import VRPlayer from 'iv-panorama';

new VRPlayer({
        player: {
            url: '/test/003.mp4'
        },
        container:document.getElementById('container')
    });
    
// image

let panorama = new VRPlayer({
    image: {
            url: './banner.png'
        },
        container:document.getElementById('container')
    });

全景资源都已经放在 github 仓库了,有兴趣的可以实践观察一下。

查看原文

Martin_Jonathan 提出了问题 · 2018-07-28

ThreeJS里参数的单位和3dmax、blender这些软件里的单位是一样的吗?

就比如 3dmax 里 将 camera 的 position 设置成0, 0, 24,使用ThreeJS加载模型将 camera 的 position 也设置成 0, 0, 24,得到的图像和 3dmax 的图像是一样的吗?

关注 1 回答 0

认证与成就

  • 获得 1 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-06
个人主页被 150 人浏览