【腾讯Bugly干货分享】WebVR如此近-three.js的WebVR示例解析

Threejs实现VR全景/模拟krpano异步加载/局部纹理刷新/图分层加载/瓦片加载 ​使用Threejs实现WebVR全景,模拟krpano异步加载、分层加载、瓦片加载、顺序加载,Threejs实现局部纹理刷新,Canvas纹理(CanvasTexture),从Canvas元素中创建纹理贴图。 ​ 阅读详情

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57c7ff1689a6c9121b1adb16

作者:苏晏烨

关于WebVR

最近VR的发展十分吸引人们的眼球,很多同学应该也心痒痒的想体验VR设备,然而现在的专业硬件价格还比较高,入手一个估计就要吃土了。但是,对于我们前端开发者来说,我们不仅可以简单地在手机上进行视觉上的VR体验,还可以立马上手进行Web端VR应用的开发!

WebVR是一个实验性的Javascript API,允许HMD(head-mounted displays)连接到web apps,同时能够接受这些设备的位置和动作信息。这让使用Javascript开发VR应用成为可能(当然已经有很多接口API让Javascript作为开发语言了,不过这并不影响我们为WebVR感到兴奋)。而让我们能够立马进行预览与体验,移动设备上的chrome已经支持了WebVR并使手机作为一个简易的HMD。手机可以把屏幕分成左右眼视觉并应用手机中的加速度计、陀螺仪等感应器,你需要做的或许就只是买一个cardboard。

不说了,我去下单了!

img cardborad纸盒,一顿食堂饭钱即可入手

前言

WebVR仍处于w3c的草案阶段,所以开发和体验都需要polyfill。

这篇解析基于 webvr-boilerplate ,这个示例的作者,任职google的 Boris Smus 同时也编写了 webvr-polyfill 。 three.js examples中也提供了关于VR的控制例子。这里主要通过对代码注释的方式来解读关键的文件。

示例的最终效果如下,打开Demo并把手机放进cardboard即可体验。你也可以在我的github对照有关的代码和注释。

Demo链接:http://soaanyip.github.io/webvr-boilerplate-cn/
我的github:https://github.com/SoAanyip/WebVR-Boilerplate-CN/

按照惯例,这篇解析默认你至少有three.js相关的基础知识。有兴趣也可以浏览一下我之前写的

《ThreeJS 轻松实现主视觉太阳系漫游》
https://zhuanlan.zhihu.com/p/20796329

这篇解析中three.js的版本为V76。文中如有各种错误请指出!

先从html开始

在示例中只用到了一个index.html。首先meta标签有几个值得注意的:

<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

这几个标签对web app开发的同学来说应该是十分熟悉了。其中 shrink-to-fit=no 是Safari的特性,禁止页面通过缩放去适应适口。

接下来在js引用的部分,引用了这几个资源:

//作者引入的一个promise polyfill;
<script src="node_modules/es6-promise/dist/es6-promise.js"></script>
//three.js核心库
<script src="node_modules/three/three.js"></script>
//从连接的VR设备中获得位置信息并应用在camera对象上,将在下文展开;
<script src="node_modules/three/examples/js/controls/VRControls.js"></script>
//处理立体视觉和绘制相关,将在下文展开;
<script src="node_modules/three/examples/js/effects/VREffect.js"></script>
//WebVR polyfill,下文简述调用的API option;
<script src="node_modules/webvr-polyfill/build/webvr-polyfill.js"></script>
// 界面按钮以及进入/退出VR模式的控制等。
<script src="build/webvr-manager.js"></script>

具体的整个项目文件,可以在这里查看有关的代码和注释。

VRControls.js - HMD状态感应

这个文件主要对HMD的状态信息进行获取并应用到camera上。例如在手机上显示的时候,手机的旋转倾斜等就会直接作用到camera上。

第一步是获取连接的VR设备,这一步是基本通过WebVR的API进行的:

//获取VR设备(作为信息输入源。如有多个则只取第一个)
function gotVRDevices( devices ) {
    for ( var i = 0; i < devices.length; i ++ ) {
        if ( ( 'VRDisplay' in window && devices[ i ] instanceof VRDisplay ) || ( 'PositionSensorVRDevice' in window && devices[ i ] instanceof PositionSensorVRDevice ) ) {
            vrInput = devices[ i ];
            break;  // We keep the first we encounter
        }
    }

    if ( !vrInput ) {
        if ( onError ) onError( 'VR input not available.' );
    }
}
//调用WebVR API获取VR设备
if ( navigator.getVRDisplays ) {
    navigator.getVRDisplays().then( gotVRDevices );
} else if ( navigator.getVRDevices ) {
    // Deprecated API.
    navigator.getVRDevices().then( gotVRDevices );
}

然后是三个关于位置的参数:

// the Rift SDK returns the position in meters
// this scale factor allows the user to define how meters
// are converted to scene units.
//Rift SDK返回的位置信息是以米作为单位的。这里可以定义以几倍的缩放比例转换为three.js中的长度。
this.scale = 1;

// If true will use "standing space" coordinate system where y=0 is the
// floor and x=0, z=0 is the center of the room.
//表示使用者是否站立姿态。当为false时camra会在y=0的位置,而为true时会结合下面的模拟身高来决定camera的y值。
//在无法获取用户姿势信息的设备上,需要在调用时直接指定是站姿还是坐姿。
this.standing = false;

// Distance from the users eyes to the floor in meters. Used when
// standing=true but the VRDisplay doesn't provide stageParameters.
//当为站立姿态时,用户的眼睛(camera)的高度(跟如有硬件时返回的单位一致,为米)。这里会受scale的影响。如scale为2时,实际camera的高度就是3.2。
this.userHeight = 1.6;

通过WebVR API获取到用户的设备信息,并应用到camera上,是一个持续进行的过程。因此这部分的信息更新会在requestAnimationFrame中不断地调用。

//将在requestAnimationFrame中应用更新
this.update = function () {
    if ( vrInput ) {
        if ( vrInput.getPose ) {
            //方法返回传感器在某一时刻的信息(object)。例如包括时间戳、位置(x,y,z)、线速度、线加速度、角速度、角加速度、方向信息。
            var pose = vrInput.getPose();
            //orientation 方向
            if ( pose.orientation !== null ) {
                //quaternion  四元数
                //把设备的方向复制给camera
                object.quaternion.fromArray( pose.orientation );
            }
            //位置信息
            if ( pose.position !== null ) {
                //同样把设备的位置复制给camera
                object.position.fromArray( pose.position );
            } else {
                object.position.set( 0, 0, 0 );
            }

        } else {
            // Deprecated API.
            var state = vrInput.getState();
            if ( state.orientation !== null ) {
                object.quaternion.copy( state.orientation );
            }
            if ( state.position !== null ) {
                object.position.copy( state.position );
            } else {
                object.position.set( 0, 0, 0 );
            }
        }

        //TODO 此块会一直执行
        if ( this.standing ) {
            //如果硬件返回场景信息,则应用硬件返回的数据来进行站姿转换
            if ( vrInput.stageParameters ) {
                object.updateMatrix();
                //sittingToStandingTransform返回一个Matrix4,表示从坐姿到站姿的变换。
                standingMatrix.fromArray(vrInput.stageParameters.sittingToStandingTransform);
                //应用变换到camera。
                object.applyMatrix( standingMatrix );
            } else {
                //如果vrInput不提供y高度信息的话使用userHeight作为高度
                object.position.setY( object.position.y + this.userHeight );
            }

        }
        //使用上面定义的this.scale来缩放camera的位置。
        object.position.multiplyScalar( scope.scale );
    }
};

以上是vrcontrols的关键代码。

VREffect.js - 立体视觉

VREffect.js主要把屏幕显示切割为左右眼所视的屏幕,两个屏幕所显示的内容具有一定的差异,使得人的双目立体视觉可以把屏幕中的内容看得立体化。这个文件主要的流程如下图:

首先是对画布大小进行了设定。其中 renderer.setPixelRatio( 1 ); 是防止在retina等屏幕上出现图像变形等显示问题。

//初始化或者resize的时候进行。
this.setSize = function ( width, height ) {
    rendererSize = { width: width, height: height };

    //是否VR模式中
    if ( isPresenting ) {
        //getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
        var eyeParamsL = vrHMD.getEyeParameters( 'left' );
        //设备像素比
        //若设备像素比不为1时会出现显示问题。
        //https://github.com/mrdoob/three.js/pull/6248
        renderer.setPixelRatio( 1 );

        if ( isDeprecatedAPI ) {
            renderer.setSize( eyeParamsL.renderRect.width * 2, eyeParamsL.renderRect.height, false );

        } else {
            renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false );
        }

    } else {
        renderer.setPixelRatio( rendererPixelRatio );
        renderer.setSize( width, height );
    }
};

然后是关于全屏模式的设置,这里跟上面的设定差不远:

//显示设备进入全屏显示模式
function onFullscreenChange () {
    var wasPresenting = isPresenting;
    isPresenting = vrHMD !== undefined && ( vrHMD.isPresenting || ( isDeprecatedAPI && document[ fullscreenElement ] instanceof window.HTMLElement ) );
    if ( wasPresenting === isPresenting ) {
        return;
    }

    //如果此次事件是进入VR模式
    if ( isPresenting ) {
        rendererPixelRatio = renderer.getPixelRatio();
        rendererSize = renderer.getSize();

        //getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
        var eyeParamsL = vrHMD.getEyeParameters( 'left' );
        var eyeWidth, eyeHeight;

        if ( isDeprecatedAPI ) {
            eyeWidth = eyeParamsL.renderRect.width;
            eyeHeight = eyeParamsL.renderRect.height;
        } else {
            eyeWidth = eyeParamsL.renderWidth;
            eyeHeight = eyeParamsL.renderHeight;
        }
        renderer.setPixelRatio( 1 );
        renderer.setSize( eyeWidth * 2, eyeHeight, false );

    } else {
        renderer.setPixelRatio( rendererPixelRatio );
        renderer.setSize( rendererSize.width, rendererSize.height );
    }
}

接下来是对表示左右眼的camera的设定。两个camera也肯定是PerspectiveCamera:

var cameraL = new THREE.PerspectiveCamera();
//左camera显示layer 1层(即当某个元素只出现在layer 1时,只有cameraL可见。)
cameraL.layers.enable( 1 );

var cameraR = new THREE.PerspectiveCamera();
cameraR.layers.enable( 2 );

从WebVR API中获取关于某个眼睛所视的屏幕的信息:

//getEyeParameters包含了渲染某个眼睛所视的屏幕的信息,例如offset,FOV等
var eyeParamsL = vrHMD.getEyeParameters( 'left' );
var eyeParamsR = vrHMD.getEyeParameters( 'right' );

if ( ! isDeprecatedAPI ) {
    // represents the offset from the center point between the user's eyes to the center of the eye, measured in meters.
    //瞳距的偏移
    eyeTranslationL.fromArray( eyeParamsL.offset );
    eyeTranslationR.fromArray( eyeParamsR.offset );
    //represents a field of view defined by 4 different degree values describing the view from a center point.
    //获得左右眼的FOV
    eyeFOVL = eyeParamsL.fieldOfView;
    eyeFOVR = eyeParamsR.fieldOfView;

} else {
    eyeTranslationL.copy( eyeParamsL.eyeTranslation );
    eyeTranslationR.copy( eyeParamsR.eyeTranslation );
    eyeFOVL = eyeParamsL.recommendedFieldOfView;
    eyeFOVR = eyeParamsR.recommendedFieldOfView;
}

if ( Array.isArray( scene ) ) {
    console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' );
    scene = scene[ 0 ];
}

由于左右camera的视锥体还没确定,需要对获得的FOV信息进行计算来确定。在涉及透视投影矩阵的部分会比较复杂,所以这里不展开来说。如果有错误请指出:

cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far );
cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far );

//角度弧度的转换,然后进行后续的计算
function fovToProjection( fov, rightHanded, zNear, zFar ) {
    //角度转换为弧度  如30度转为1/6 PI
    var DEG2RAD = Math.PI / 180.0;

    var fovPort = {
        upTan: Math.tan( fov.upDegrees * DEG2RAD ),
        downTan: Math.tan( fov.downDegrees * DEG2RAD ),
        leftTan: Math.tan( fov.leftDegrees * DEG2RAD ),
        rightTan: Math.tan( fov.rightDegrees * DEG2RAD )
    };

    return fovPortToProjection( fovPort, rightHanded, zNear, zFar );
}

//根据从设备获得的FOV以及相机设定的near、far来生成透视投影矩阵
function fovPortToProjection( fov, rightHanded, zNear, zFar ) {

    //使用右手坐标
    rightHanded = rightHanded === undefined ? true : rightHanded;
    zNear = zNear === undefined ? 0.01 : zNear;
    zFar = zFar === undefined ? 10000.0 : zFar;

    var handednessScale = rightHanded ? - 1.0 : 1.0;

    // start with an identity matrix

    var mobj = new THREE.Matrix4();
    var m = mobj.elements;

    // and with scale/offset info for normalized device coords
    var scaleAndOffset = fovToNDCScaleOffset( fov );

    //建立透视投影矩阵

    // X result, map clip edges to [-w,+w]
    m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ];
    m[ 0 * 4 + 1 ] = 0.0;
    m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale;
    m[ 0 * 4 + 3 ] = 0.0;

    // Y result, map clip edges to [-w,+w]
    // Y offset is negated because this proj matrix transforms from world coords with Y=up,
    // but the NDC scaling has Y=down (thanks D3D?)
    //NDC(归一化设备坐标系)是左手坐标系
    m[ 1 * 4 + 0 ] = 0.0;
    m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ];
    m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale;
    m[ 1 * 4 + 3 ] = 0.0;

    // Z result (up to the app)
    m[ 2 * 4 + 0 ] = 0.0;
    m[ 2 * 4 + 1 ] = 0.0;
    m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale;
    m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar );

    // W result (= Z in)
    m[ 3 * 4 + 0 ] = 0.0;
    m[ 3 * 4 + 1 ] = 0.0;
    m[ 3 * 4 + 2 ] = handednessScale;
    m[ 3 * 4 + 3 ] = 0.0;

    //转置矩阵,因为mobj.elements是column-major的
    mobj.transpose();

    return mobj;
}

//计算线性插值信息
function fovToNDCScaleOffset( fov ) {

    var pxscale = 2.0 / ( fov.leftTan + fov.rightTan );
    var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5;
    var pyscale = 2.0 / ( fov.upTan + fov.downTan );
    var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5;
    return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] };
}

之后是确定左右camera的位置和方向。由于左右眼(左右camera)肯定是在头部(主camera,位置和方向由HMD返回的信息确定)上的,在我们获得把眼睛从头部飞出去的超能力之前,左右camera的位置和方向都是根据主camera来设定的。

//使主camera的位移、旋转、缩放变换分解,作用到左camra 右camera上。
camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale );
camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale );

var scale = this.scale;
//左右眼camera根据瞳距进行位移。
cameraL.translateOnAxis( eyeTranslationL, scale );
cameraR.translateOnAxis( eyeTranslationR, scale );

最后便是对两个区域进行渲染。

// 渲染左眼视觉
renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
renderer.render( scene, cameraL );

// 渲染右眼视觉
renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
renderer.render( scene, cameraR );

VREffect文件的关键点差不多是上述这些。

webvr-polyfill.js - 让现在使用WebVR成为可能

webvr-polyfill.js 根据WebVR API的草案来实现了一套polyfill。例如根据所处环境是pc还是手机来确定使用的是 CardboardVRDisplay 还是 MouseKeyboardVRDisplay ,在手机环境下的话使用 Device API 来处理手机旋转、方向等参数的获取。此外作者还顺便做了几个提示图标和画面来优化体验。在这里我们来看一下其API参数:

WebVRConfig = {
  /**
   * webvr-polyfill configuration
   */

  // Flag to disabled the UI in VR Mode.
  //是否禁用VR模式的UI。
  CARDBOARD_UI_DISABLED: false, // Default: false

  // Forces availability of VR mode.
  //是否强制使VR模式可用。
  //FORCE_ENABLE_VR: true, // Default: false.

  // Complementary filter coefficient. 0 for accelerometer, 1 for gyro.
  //互补滤波系数。加速度计在静止的时候是很准的,但运动时的角度噪声很大,陀螺仪反之。
  //互补滤波器徘徊在信任陀螺仪和加速度计的边界。首先选择一个时间常数,然后用它来计算滤波器系数。
  //例如陀螺仪的漂移是每秒2度,则可能需要一个少于一秒的时间常数去保证在每一个方向上的漂移不会超过2度。
  //但是当时间常数越低,越多加速度计的噪声将允许通过。所以这是一个权衡的内容。
  //K_FILTER: 0.98, // Default: 0.98.

  // Flag to disable the instructions to rotate your device.
  //是否禁用旋转设备的提示(横放手机以进入全屏)。
  ROTATE_INSTRUCTIONS_DISABLED: false, // Default: false

  // How far into the future to predict during fast motion.
  //由于有给定的方向以及陀螺仪信息,选择允许预测多长时间之内的设备方向,在设备快速移动的情况下可以让渲染比较流畅。
  //PREDICTION_TIME_S: 0.040, // Default: 0.040 (in seconds).

  // Flag to disable touch panner. In case you have your own touch controls、
  //是否禁用提供的触摸控制,当你有自己的触摸控制方式时可以禁用
  //TOUCH_PANNER_DISABLED: true, // Default: false.

  // To disable keyboard and mouse controls, if you want to use your own
  // implementation.
  //是否禁用pc下的鼠标、键盘控制。同上。
  //MOUSE_KEYBOARD_CONTROLS_DISABLED: true, // Default: false.

  // Enable yaw panning only, disabling roll and pitch. This can be useful for
  // panoramas with nothing interesting above or below.
  // 仅关心左右角度变化,忽略上下和倾斜等。
  // YAW_ONLY: true, // Default: false.

  // Prevent the polyfill from initializing immediately. Requires the app
  // to call InitializeWebVRPolyfill() before it can be used.
  //是否阻止组件直接进行初始化构建。如果为true则需要自己调用InitializeWebVRPolyfill()。
  //DEFER_INITIALIZATION: true, // Default: false.

  // Enable the deprecated version of the API (navigator.getVRDevices).
  //允许使用过时版本的API。
  //ENABLE_DEPRECATED_API: true, // Default: false.

  // Scales the recommended buffer size reported by WebVR, which can improve
  // performance. Making this very small can lower the effective resolution of
  // your scene.
  //在VR显示模式下对WebVR推荐的屏幕比例进行缩放。在IOS下如果不为0.5会出现显示问题,查看
  //https://github.com/borismus/webvr-polyfill/pull/106
  BUFFER_SCALE: 0.5, // default: 1.0

  // Allow VRDisplay.submitFrame to change gl bindings, which is more
  // efficient if the application code will re-bind it's resources on the
  // next frame anyway.
  // Dirty bindings include: gl.FRAMEBUFFER_BINDING, gl.CURRENT_PROGRAM,
  // gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING,
  // and gl.TEXTURE_BINDING_2D for texture unit 0
  // Warning: enabling this might lead to rendering issues.
  //允许 VRDisplay.submitFrame使用脏矩形渲染。但是开启此特性可能会出现渲染问题。
  //DIRTY_SUBMIT_FRAME_BINDINGS: true // default: false
};

其config主要是对一些用户可选项进行设定。在文件内部,更多的是对 Device API 的应用等等。

现在就开始编写WebVR应用吧!

在示例的最后是一个显示简单的旋转立方体的demo。此处可以帮助我们学习怎么创建一个WebVR应用。

首先是建立好scene、renderer、camera的三要素:

// Setup three.js WebGL renderer. Note: Antialiasing is a big performance hit.
// Only enable it if you actually need to.
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);

// Append the canvas element created by the renderer to document body element.
document.body.appendChild(renderer.domElement);

// Create a three.js scene.
var scene = new THREE.Scene();

// Create a three.js camera.
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);

对上面解析过的controls、effect进行调用:

// Apply VR headset positional data to camera.
var controls = new THREE.VRControls(camera);
//站立姿态
controls.standing = true;

// Apply VR stereo rendering to renderer.
var effect = new THREE.VREffect(renderer);
effect.setSize(window.innerWidth, window.innerHeight);

// Create a VR manager helper to enter and exit VR mode.
//按钮和全屏模式管理
var params = {
  hideButton: false, // Default: false.
  isUndistorted: false // Default: false.
};
var manager = new WebVRManager(renderer, effect, params);

在场景中,添加一个网格显示的空间,在空间内加入一个小立方体:

// Add a repeating grid as a skybox.
var boxSize = 5;
var loader = new THREE.TextureLoader();
loader.load('img/box.png', onTextureLoaded);

function onTextureLoaded(texture) {
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(boxSize, boxSize);

  var geometry = new THREE.BoxGeometry(boxSize, boxSize, boxSize);
  var material = new THREE.MeshBasicMaterial({
    map: texture,
    color: 0x01BE00,
    side: THREE.BackSide
  });

  // Align the skybox to the floor (which is at y=0).
  skybox = new THREE.Mesh(geometry, material);
  skybox.position.y = boxSize/2;
  scene.add(skybox);

  // For high end VR devices like Vive and Oculus, take into account the stage
  // parameters provided.
  //在高端的设备上,要考虑到设备提供的场景信息的更新。
  setupStage();
}

// Create 3D objects.
var geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
var material = new THREE.MeshNormalMaterial();
var cube = new THREE.Mesh(geometry, material);

// Position cube mesh to be right in front of you.
cube.position.set(0, controls.userHeight, -1);

scene.add(cube);

最后便是设置requestAnimationFrame的更新。在animate的函数中,不但要考虑立方体的旋转问题,更重要的是要不断地获取HMD返回的信息以及对camera进行更新。

// Request animation frame loop function
var lastRender = 0;
function animate(timestamp) {
  var delta = Math.min(timestamp - lastRender, 500);
  lastRender = timestamp;

  //立方体的旋转
  cube.rotation.y += delta * 0.0006;

  // Update VR headset position and apply to camera.
  //更新获取HMD的信息
  controls.update();

  // Render the scene through the manager.
  //进行camera更新和场景绘制
  manager.render(scene, camera, timestamp);

  requestAnimationFrame(animate);
}

总结

以上便是此示例的各个文件的解析。我相信VR的形式除了在游戏上的应用的前景,在其他方面也有其值得探索的可行性。所以让我们一起来开始WebVR之旅吧!

参考内容

webvr.info

WebVR_API

http://threejs.org/docs/

https://github.com/borismus/webvr-polyfill

https://github.com/borismus/webvr-boilerplate

https://w3c.github.io/webvr/

Eye FOV

http://blog.youkuaiyun.com/popy007/article/category/640562

http://blog.youkuaiyun.com/iispring/article/details/27970937

http://www.idom.me/articles/841.html

更多精彩内容欢迎关注bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!

Threejs实现WebVR全景小行星入场动画/鱼眼效果/球形展开效果 使用Threejs实现WebVR全景,VR全景小行星入场动画、鱼眼效果、由远到近效果、球形展开动画效果。初始化场景和设置相机角度、位置。像机视锥体垂直视野角度fov,和相机的坐标位置。轨道控制器OrbitControls.js。实现动画效果,鼠标拖动查看使用。这种效果其实很简单,通过修改。 阅读详情

相关推荐

使用threejs实现VR看房效果,仿贝壳找房VR看房

一、效果演示 最近看到贝壳找房里的VR看房,感觉挺有意思;就试着仿照实现了一下。在这里做一个学习记录。 二、代码 <script> // 记录当前是在哪个房间 let currentHome = 'home1'; // 纹理加载器 let loader = new THREE.TextureLoader(); // 加载所需要的纹理图片 let texture1 = loader.load('./img/home1_left.jpg')

weixin_40856652的博客 7073

three.js-打造VR看房 快速掌握3D开发

通过使用three.js开发VR看房,以高仿真的实战授课模式,带你深入学习three.js框架、webgl的核心概念、让你了解VR看房是如何开发的,掌握这一知识后可以做VR全景、室内漫游等项目。

webvr-boilerplate:适用于所有VR头显的基于Web的VR体验的起点

WebVR样板 基于的跨平台基于Web的VR体验的起点。 如果未实现则该项目严重依赖提供VR支持。 它还使用项目渲染UI,以进入VR和魔术窗口模式。 观看这里的。 入门 最简单的开始方法是派生或克隆此存储库。 还可以通过npm获得样板。 易于安装: npm install webvr-boilerplate

threejs中解决画质不清晰,模糊

threejs中解决画质不清晰,模糊

learn_12的博客 5856

【深度学习】肺结节分割项目实战一:处理数据集

一、LIDC-IDRI数据集简介 官方网站 肺图像数据库协会的图像收集(LIDC-IDRI)包括诊断和带有病变注释标记的肺癌筛查胸部CT。这是一个网络公开的国际资源,用于肺癌检测和诊断的计算机辅助诊断(CAD)方法的开发、培训和评估。 数据集包含1018个病例,每个病例包括来自临床胸部CT扫描的图像和一个相关的XML文件,该文件记录了由四名经验丰富的胸部放射科医生进行的两阶段图像注释过程的结果。在最初的盲读阶段,每个放射科医生独立审查每个CT扫描,并标记出属于三种类型之一的病变(“结节>= 3mm”,

weixin_42182906的博客 1万+

量子密钥分发(QKD)原理、工程实现与实战部署全解析

密钥分发是信息安全的核心基础,它解决了通信双方如何安全共享秘密密钥的难题。传统方法依赖于数学问题的计算复杂性,而量子密钥分发(QKD)则基于量子力学原理,为密钥分发提供了信息论安全的物理保障。其核心价值在于,利用量子不可克隆定理和测量坍缩原理,使得任何窃听行为都会被合法通信方检测到,从而在理论上实现无条件安全的密钥协商。这项技术特别适用于对安全性要求极高的场景,如金融数据同步、政务专网和电力系统保护。本文聚焦于量子保密通信,深入剖析了以BB84协议为代表的QKD工作原理,并详细拆解了包含单光子探测、偏振编码

weixin_29324401的博客 346

three.js入门篇8 之 实现VR看房

three.js入门篇8 之 实现VR看房 立方体与球体

weixin_43845137的博客 2624

【Unity人物动画】SALSA With RandomEyes (语音生成嘴型/人物说话) 使用

SALSA使用探索 之前做项目时想做人物说话的嘴部动作,因为我们的语音是AI合成的,语音片段很多,如果能根据语音生成嘴部动画,那将极大便利我们的工作。后面是找到了SALSA的这款插件,并摸索出使用方法。 1 插件介绍 官方网站:https://crazyminnowstudio.com/ 一个能做2D纸片人和3D人物模型嘴部和眼睛动画的插件,由两人组成的独立工作室(疯狂米诺工作室)开发。 插件全名为SALSA With RandomEyes,2014年上架Asset Store,目前是已下架,替换为新版本

里约热内驴的博客 1万+

Three.JS VR看房

vr看房

yuzheh521的博客 1349

vscode使用continue插件连接LM studio的模型

本文详细介绍了如何使用VSCode的Continue插件连接LM Studio模型。主要步骤包括:在LM Studio中加载模型并设置相关参数,启动API服务,修改Continue配置文件以集成LM Studio模型,并进行测试以验证连接是否成功。通过这些步骤,用户可以将LM Studio中的预训练模型与VSCode的Continue插件无缝对接,实现高效的自然语言处理和编程辅助功能。

weixin_43590837的博客 8630

改进的主动轮廓模型实现图像分割

主动轮廓模型的优点在于可以自适应地调整轮廓形状和位置,适用于不同形状和大小的目标分割。改进的主动轮廓模型是一种有效的图像分割方法,能够适应不同场景下的图像分割需求。改进的主动轮廓模型涉及到许多参数,如模型参数、分割参数、权值系数等,这些参数的调整也会直接影响到分割结果。本文将介绍一个改进的主动轮廓模型,该模型可以实现对图像进行有效的分割。改进的主动轮廓模型是在传统的主动轮廓模型基础上进行的改进。和传统的模型相比,改进的主动轮廓模型加入了外部力和内部力,能够更好地适应复杂场景下的图像分割。

qq_39605374的博客 295

Threejs实现3D场景浏览器内存消耗过高导致浏览器卡顿崩溃刷新等问题解决办法以及3D场景在手机浏览器中画质不高的原因

不同的移动设备和浏览器对WebGL的支持程度可能不同,这可能会导致3D场景在不同设备上显示效果不同。相比于桌面设备,移动设备的性能普遍较低,可能无法支持高质量的3D场景渲染。如果场景结构不合理,例如过多的对象或层级嵌套太深等,则会导致性能问题。不合理的代码编写也会导致性能问题,例如频繁的创建和销毁对象、过多的计算和事件监听等。如果场景中有过多的光照和阴影效果、反射和折射等效果,则会导致渲染速度变慢。在一些低端设备上,三维场景的渲染可能受到硬件性能的限制,可以尝试使用更低的画质设置或减少场景中的对象数量。

左本Web3D 7180

15、SQL Server查询优化与CPU负载管理

本文深入探讨了SQL Server查询优化与CPU负载管理的关键技术,包括强制连接类型的使用、键查找问题的解决、索引设计的最佳实践、高CPU负载的排查与优化,以及并行性的利弊与调整策略。通过优化查询逻辑、合理设计索引、监控与管理CPU资源,可以显著提升SQL Server的性能和稳定性。适用于数据库开发人员和DBA进行性能调优参考。

pz8901234的博客 78

Threejs + vue 学习- VR 看房

threejs + vue 实现vr 看房

weixin_41734373的博客 1503

【史上最全面esp32教程】网络篇和tcp/ip的使用

ESP32史上最全面wifi和tcp/ip连接及示例程序

m0_62599305的博客 9522

数据库系统概论第五版课后习题答案第十二章

对元组的插入、删除、修改;语义检查的具体做法是,DBMS读取数据字典,检查是否存在与该语句中的表、视图或索引等要创建的对象相同的对象名,检查该用户是否具有创建数据库对象的权限。答:操作系统提供的封锁机制和DBMS的封锁机制在封锁对象、封锁对象的状态、封锁的粒度、及封锁的类型上存在很大的差别,操作系统的封锁机制不能直接应用在DBMS中,DBMS必须重新设计,来满足复杂的封锁需求。数据库的建立和维护包括数据库的初始建立、数据的转换、数据库的转储和恢复、数据库的重组织和重构造以及性能监测分析等功能。

wenling54321的博客 694

Three.js如何创建VR应用程序

Three.js是一款基于WebGL的JavaScript 3D引擎,提供了大量的3D图形渲染功能和工具,可以用于开发VR应用程序。💕 💕积跬步以至千里,致敬每个爱学习的你。喜欢的话请三连,有问题请私信或者加微信。个人简介:专注Web3D使用ThreeJS实现3D效果技巧和学习案例。2.创建WebGL渲染器和场景。,更多案例预览请点击==》3.创建VR设备管理器。4.创建摄像机和控制器。

左本Web3D 929

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

手机拍摄全景图并且使用Threejs实现VR全景,超简单WebVR

VR全景效果实现有好几种方式,比如720云,四方环视...在这些平台上可以注册账号上传全景素材制作自己的全景作品,还有一些工具软件如krpano、utovr...,krpano工具软件门槛较高需要根据教程学习开发,也可以使用ThreeJs实现全景效果,本文将主要介绍如何使用ThreeJs进行VR全景开发。使用ThreeJs制作VR全景其实很简单,拍摄全景图和网页开发。

左本Web3D 5876

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

THREE.JS实现看房自由(VR看房)

目前随着`元宇宙`概念的爆火,`THREE`技术已经深入到了物联网、VR、游戏、数据可视化等多个平台,今天我们主要基于`THREE`实现一个三维的`VR`看房小项目,THREE基于`WebGL`的三维引擎,目前是国内资料最多、使用最广泛的`三维引擎`,可以制作一些`3D`可视化项目

知心宝贝的博客 1万+

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

three.js学习之vR展厅

注意:video文件超过100M, git push 不上去,我没加git扩展。所以clone下来项目后需要你自己加上一个video。清空 main.js 和 style.css 内容。新建如下untils/init.js文件填入内容。index.html 中只留下核心代码。main.js - 创建立方缓冲几何体。安装项目需要的所有依赖,并启动项目浏览。二、展厅-第一个页面内容展示。一、初始化 vite 项目。选择 Vanilla 模版。四、展厅 第三个页面。五、展厅-第四个页面。六、展厅-第五个页面。

weixin_43909743的博客 2205

three.js实现VR看房自由

万字详解-three.js实现VR看房自由

嘿嘿,大部分都是前端三维开发的时候遇到的问题,顺手解决了,如果你也遇到了和我一样的问题,希望我的文章可以帮到你 3078

3步跑通 drawio-desktop:免费离线流程图绘制与 Visio 文件转换实战指南

drawio-desktop 是 draw.io 的官方桌面版,基于 Electron 构建,采用 Apache 2.0 许可,学习和工作中使用都不用担心授权费用。它给你三样东西:不依赖浏览器的完整绘图界面、离线运行的环境、以及能把 Visio 文件批量转换的命令行。 [![drawio-desktop 主界面:左侧形状库、中央绘图画布、右侧属性面板](https://raw.gitcode.c

gitblog_01109的博客 564
上一篇: 【腾讯Bugly干货分享】动态链接库加载原理及HotFix方案介绍
下一篇: 【腾讯Bugly干货分享】Android动态布局入门及NinePatchChunk解密
腾讯Bugly
博客等级 码龄12年 1875粉丝 201原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值