threejs 详解(一)

1. 开始

1.1 开使用threejs写一个最简洁的demo

function init() {  // **写任何3d界面初始化必备的东西**

    **// renderer 渲染器**
    var renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById(“test-overview”)
    });

    renderer.setClearColor(0x000000);  // 设置场景舞台背景色

    **// scene  场景舞台,可以看做是最大的group,特殊的group**
    var scene = new THREE.Scene();

    **// camera 相机**
    var camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 1000);

    camera.position.set(0, 0, 5);

    scene.add(camera);

   ** // 创建一个模型, 模型(Mesh)=几何体(XXGeometry)+材质(XXMaterial),材质可以理解为游戏中的英雄皮肤,你可以切换皮肤,但模型的几何体不变**
    var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3),
            new THREE.MeshBasicMaterial({ // 基础材质,不受光照影响
                color: 0xff000 // 材质颜色
            }));

    scene.add(cube);  // 最后将模型添加到场景当中
    render(scene, camera)

}

funciton render(scene, camera){
    var myReq = requestAnimationFrame(render);   // 相当于setInterval 不停地render渲染, 渲染消耗性能,在不必要的情况下可以暂停渲染,暂停之后它就相当于静态图片
    renderer.render(scene, camera);
}

1.2 threejs常用对象或对象属性

1.2.1 Scene,Group

scene 是场景,group是组,他们都是装Mesh模型的箱子,容器, 不同的是:

scene一般只会有一个,他就好比树状结构的根节点,group可以有好几个,group还可以装group;
group 一般只会装模型, 而sence一般用来装group,mesh模型,camera相机, light灯光,就好比拍电影会用的道具,模型

var scene = new THREE.Scene()
var group = new THREE.Group()
  • 他俩常用api属性:

增加.add(xx), 删除.remove(xx), 清空所有clear(),

是否可见.visible = 布尔值

名称.name = ”xx” // 这个一般是group用,给组命个名,用到时好找,比如要给哪个组加特效


1.2.2 Light

没有光,就算模型加到场景当中也看不见,一片黑,但是呢,模型的亮度不一定是光越强越亮,这跟模型材质的反光度也有关

  • 常用的光的类型:
  1. 环境光
var light = new THREE.AmbientLight(光的颜色);   // 环境光一个就够了
light.intensity = 1  // 光的强度
sence.add(light)  // 记得加入场景
  1. 点光源
var light = new THREE.PointLight(颜色, 强度, 距离);   // 点光源可以多弄几个照射

环境光不需要调整位置他没有XXXHelper,点光源可以,将光添加到PointLightHelper就看到一个菱形物体了,你可以像调整模型一样调整光的位置

var pointLightHelper = new THREE.PointLightHelper(light, 10);
scene.add(pointLightHelper)
QQ图片20220129095114.png

1.2.3 Camera

就算有光,没有眼睛照样一片黑

  • 常用camera类型:
  1. 透视相机,就跟人眼看到的世界一样效果,远小近大
var width = document.getElementById("overview-container").clientWidth   // canvas的宽
var height= document.getElementById("overview-container").clientHeight  // canvas 的高
var camera = new THREE.PerspectiveCamera(45, width/height, 0.5, 6000)   // 参数基本不用调,详情看文档
20190918231348619.png
  1. 正交相机, 没有远小近大,看到都就是物体本身大小,使用场景比如选矿的俯视图
var k = width / height; //窗口宽高比
var s = 450; //三维场景显示范围控制系数,系数越大,显示的范围越大
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
20190918231213655.png
  • 常用属性:
    camera.lookAt(xx.poation) // 相机聚焦于那个点,即屏幕中心对准的三维空间中的哪个点

1.2.4 WebGLRender

有上述几个对象加到场景当中,我们就可以将模型渲染出来了,他就好比是一支笔开始画画了, 光告诉他颜色该怎么画,摄像机告诉他从哪个角度画

var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }) // 创建render
renderer.setSize(document.getElementById("overview-container").clientWidth, document.getElementById("overview-container").clientHeight)
renderer.setClearColor(0xeeeeee, 0.0)  

实例化后renderer会有一个domElement属性,值是一个canvas的dom元素,因为threejs就是靠canvas画出来的场景,把这个canvas塞到vue的div中就行了

let webglOutput = document.getElementById(“xx-container”);
webglOutput.appendChild(renderer.domElement)

然后定时器不停地调用,写一个render方法, 如果你只调用一次,那就是绘制了一张图片相当于,不停的用render方法,才会不停地绘制

function render() {
        requestAnimationFrame(render)
        camera.updateProjectionMatrix();  // 摄像机也要不停地更新
        renderer.render(场景, 摄像机)
}

1.2.5 Controls

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 引文件

想要鼠标控制三维场景,那么就需要用到控制器, 控制器是我们一般只会用到OrbitControls,就是鼠标左键旋转场景,右键平移场景,滚轮放大缩小.

还有其他类型的控制器比如射击类游戏,他镜头的中心点始终就是你鼠标的位置;

  var controls = new OrbitControls(camera, renderer.domElement)
  • 常用api属性:
    controls.addEventListener("start", () => { // 监听鼠标触发的事件})
    controls.addEventListener("end", () => { // 监听鼠标触发结束的事件})
    controls.minDistance = 100 // 最近视距 // 滚轮放大的数值
    controls.maxDistance = 1500 //最远视距 // 滚轮缩小的数值
    controls.rotateSpeed = 0.7 // 左键旋转的速度
    controls.enableDamping = true // 是否开启阻尼效果, 展示单个模型可以用到,惯性漂移

1.2.6 XXGeometry

创建各种类型的几何体,虽然大部分模型都是ui给的,但是我们要加些效果的话,还是要手动写,这样性能会更好!

var geometry= new THREE.PlaneGeometry(1, 1) // 平面形状, 就是地板,只有一个面,要么是正方形,要么长方形, 常见就是作为场景的舞台地板,或者作为文字logo的面板

var geometry = new THREE.BoxGeometry(1,1,1) // 几何体,长方体,正方体

var geometry = new THREE.CylinderGeometry(1,1,1,1, 布尔值) // 圆柱体, 布尔值表示是否渲染上下两个面, 比如选矿中箭头旋转动画,表示机器在运转

var geometry = new THREE.EdgesGeometry(geometry) // 边框模型, 只渲染边, 比如鼠标移到某个物体上给他加个边框

var shape = new THREE.Shape();
var geometry = new THREE.ShapeGeometry() // 配合THREE.Shape绘制任意几边形,类似于钢笔工具, 比如选矿中每个区域地面标记的颜色

var geometry = new THREE.TubeGeometry() // 管道几何体,就是类似下水管道,比如炼钢钢水从管道中倒出;但是管道比较多的情况下,自己画管道太麻烦了,直接修改ui给的管道中的材质,也可以做流动动画,他会将管道分为几个部分,让他告诉你每个管道的.name就行了


1.2.7 XXMaterial

虽然threejs自带了很多类型的材质,但我们就用最基础的就好了,因为模型一般是ui给的,材质也在里面,我们自己也不会经常去画模型.

  var material= new THREE.MeshBasicMaterial()
  • 材质的属性:

我们做的更多的是修改材质的属性, 一下列举常用的一些属性:

new THREE.MeshBasicMaterial({
  color: “#fff”,  //材质的颜色,好比衣服的颜色
  transparent: true,  //配合opcity使用
  opacity: 1 // 0~1之间,设置材质透明度,即模型的透明度
  side: THREE.DoubleSide  // 决定渲染那个面,比如PlaneGeometry他有上下两个面,默认全部渲染,你也可以之渲染上面,或者只渲染下面,没有渲染的面从那个方向看,就是透明的
  metalness: // 金属度,0~1之间调整, 解决ui给的模型较暗问题
  map: texture  //纹理
})

1.2.8 TextureLoader

纹理是材质的很重要组成部分,如果没有纹理就好像你的衣服没有装饰,就是纯白色,纯黑色,很单调

加载纹理,纹理可是是图片,也可以是canavas或者视频都可以,这里以图片举例

  var textureLoader = new THREE.TextureLoader();
  var texture = textureLoader.load(path);  // 图片路径

texture 常用api属性:

// 设置阵列模式 RepeatWrapping, 纹理一般是一张图片,你需要铺满整个面,那么你就需要设置他以何种方式铺满,大部分只要按下面这两句复制就行了

texture.wrapS = THREE.RepeatWr<u>a</u>pping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.x = 2 // 纹理在X轴方向重复多少份,相同的还有y轴和z轴
texture.offset.x = 2 // 纹理在X轴方向挪动多少单位,你想让他对齐的位置是哪里,相同的还有y轴和z轴

texture.needsUpdate = true; 如果你的纹理是动态的,比如canvas动画,vedio视频,你需要在render里面持续设置, 因为render是在定时器中执行的,只设置一次是无效的!


1.2.9 Mesh

var mesh = new THREE.Mesh(几何体,材质) // 最常用的模型,你用loader加载ui给的模型就是mesh对象

  • 常用api属性:

缩放mesh.scale.set(x,y,z) ,旋转mesh.rotation.set(x,y,z),位置mesh.position.set(x,y,z) ,
mesh.clone() // 克隆模型,复制同一个引用,节约内存

遍历mesh,因为ui给的mesh可能是好几个mesh组成的整体,比如你要给这个模型透明,你需要遍历整个mesh的材质才能实现

mesh.traverse(function (child) {
  if (child.isMesh) {  // 如果是mesh类型,怎么怎么样
    ....
  }
})

1.2.10 GLTFLoader

加载ui给的模型文件,还有其他XXloader,我们基本不会用,就gltfLoader够了

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' //gltfloader需要单独引文件, 我上面写的THREE.xxx都是在three包里面,这个是单独分开的

gltfloader 用来加载.gltf格式的文件,这个文件就是ui给你的模型文件,如果不是这个格式让他转一下,或者你自己转一下

      var gltfLoader = new GLTFLoader()   // 这个一般情况下只要在new一次就够了
      gltfLoader.load(
        “模型文件路径”,
        (gltf) => {
              // 一般是gltf.sence,这个就是一个Mesh对象,加载完之后通过group.add(gltf.sence)添加到场景中
        },
      )

1.2.11 AnimationMixer

除了你自己可以做一些简单的缩放平移旋转动画, 但是比较复杂的动画是ui给你画的,需要你手动播放还是停止

定义AnimationMixer对象

var animate = new THREE.AnimationMixer(mesh模型) // 看业务,如果你复制的多个模型共用一个动画,那你就new一个,如果比如6个磁选机,可能某一个磁选机暂停了动画,但其他不暂停,那就要针对每个磁选机模型都要new一个AnimationMixer了

因为一个模型可能有多个动画片段,好比打游戏按Q是出拳,按W是踢腿,目前我遇到的都是一个模型就一个动画片段;所以直接获取第一个片段;如果多个片段就遍历

var clip = gltf.animations[0];
var animationAction = animate.clipAction( clip )

clipAction的常用的一些属性:

animationAction.clampWhenFinished = true; // 是否最后一帧暂停
animationAction.loop = THREE.LoopOnce; // 不循环播放
animationAction.time = 0; // 动画开始播放时间
clip.duration = 0; // 动画过程时间
animationAction.play(); // 动画开始执行,你可以把animationAction存起来,在合适的时机去play(),比如设备故障了你就不执行.stop()


1.3 在vue中使用threejs

  1. cnpm i three --save 安装依赖

  2. import * as THREE from 'three' // 引入threejs对象,你也可以按需引入,这里方便就全引入了

  3. 在template中定义一个dom, <div id="test-overview"></div>

  4. 在mounted中初始化, 这里可以看出created和mounted区别, 你需要等待vue的dom加载完成才能去画3d

mounted(){

执行1.1中的init方法

}

最简单的大致是这四个步骤.实际以这个为拓展;

下面看实际项目,虽然3d在项目之内,但写好一个3d文件代码量多,完全可以看做一个单独的项目;

我的项目结构一般这样:

├── overview                    
│   ├── components                 // 大屏的组件,比如两边侧边栏信息,3dcanvas
│         └── 3d                // 3d相关组件
│            └──XX3D.vue  // 承载threejs的容器,threejs生成的canvas插入到其中
│   └── images               //图片资源,比如纹理会用到的图片
│   └── utils               //我一般分为三个文件
│         └──model-animate.js   // 专门写动画
│         └──model-common.js  // 专门写初始化的一些操作,比如场景,相机,render渲染
│         └──model-loader.js  // 专门加载模型的,大部分是ui给的gltf或glb的模型,也有自己写的Mesh,比如文字
│         └──MoveUtil.js // 调试模型位置角度的工具logo,纹理等等
│   ├── Overview.vue    // 最大的父组件,路由引用的这个文件        

1.4 Vue中优化threejs加载

如果用最简单的方式写threejs有一堆问题,你需要考虑模型加载速度问题

1.4.1 使用keep-alive缓存

大部分情况下,我们最好将初始化后的threejs 场景缓存下来,不要每次都初始化一下,如果每次都初始化,如果内存清理不干净,vue单页面路由反复跳转会导致内存一直增长.

这里先使用keep-alive 将组件缓存,我是直接将Overview 缓存了,缓存了父组件子组件也是会被缓存的;

activated(){

执行1.1中的init方法

} // 使用activated钩子代替mounted钩子;

tip: 如果一个组件使用了activated钩子,并且这个组件在父组件是被异步引用的,那么这个组件第一次初始化是不会执行activeted钩子,异步引用组件好处父组件加载就是快那么一点点,同步相反;

1.4.2 模型预加载

如果你的页面一进来不是3d概览页面,那最好不过了,第一次与后续加载都会很快; 如果不是的话,并且你模型大小综合一两百兆第一次加载就会比较迟钝, 但是后续加载还是很快;

比如在main.js中 引入model-loader

import { prevLoadModels } from "@/views/overview/utils/model-loader";
const modelPathArr = [
  'static/three-models/管道.gltf',
  'static/three-models/地形.gltf',
  'static/three-models/碎矿.gltf',
...
]
modelPathArr.forEach(path => {   // 遍历模型文件路径,一个个后台偷偷加载,并存到内存中,这里我直接挂在到window对象上,因为他肯定是一直存在的;
  prevLoadModels(path)
})

1.4.3 模型按需加载

如果一个模型很大,就把ui拆分成好几个模型,就跟加载游戏场景一样,先把主要的场景加载完,一些小模型,细枝末节的后加载;

1.4.4 相同模型克隆

为了提高threejs 渲染效率,长得一摸一样的模型可以使用Mesh.clone的方法, 比如选矿的皮带

1.4.5 相同材质克隆(有缺点)

当你需要替换一个模型的材质时候,你不需要每次都new XXMaterial(), 这样可以优化渲染性能;但是不好的是同一个材质对象是引用类型,一个材质得变化会导致其他所有材质都变化;有时候我并不想这样; 比如选矿分成三个区域,选矿磨矿,当我的视角切换到选矿时候,我希望自己的区域内的皮带不透明另外两个区域透明(透明就是改变材质的.opacity属性), 但是改变了一个所有的都透明了(因为皮带Mesh.clone的时候材质都是用的同一份),这时候我应该new三个材质,分别替换每个区域皮带的材质,相对的这样性能就下降了些


1.5 更好的使用threejs,经验总结

1.5.1 善用Mesh模型的add方法

Mesh的add就跟group的add一样,只不过group是看不见容器,Mesh是看的见的, 如果A模型.add(B模型),那么这两个模型就可以是一个组,A模型放大,B模型会跟着放大,A模型移动,B模型会跟着移动,这样的好处就是就好比写vue组件,你将重复的代码抽成一个组件,这里不是为了方便引用而是方便调试,因为模型位置,大小经常会随业务变化而变化;

1.5.2 善用group和group.name

这比如选矿中有三个区,选矿,磨矿,尾矿,显而易见我应该给他们分成三个组,分别再设置组的name属性, 然后使用上面1.4.1的逻辑模型嵌套模型

总结就是 场景(sence) => 组(group) => 父模型(Mesh) => 子模型(Mesh)....

不仅是为了后面好写代码,而且你点开sence属性的时候逻辑也清晰明了;

1.5.3 修改模型的metalness和加多个灯光

初遇threejs 有一大堆格式的模型文件.fbx, .glb, .gltf, .... 我们就用gltf模型. 但是导入进入明明加了灯光,亮度也调的挺高的,可模型就是偏暗, 这时候要么你修改材质的metalness (金属度,我喜欢理解为反光程度) 0~1 之间调整; 我的loader方法里都是加了initEmissive方法的, 默认会设置为.1, 这时候大部分模型应该是亮的, 如果只有个别模型很暗,联系ui修改

mesh.traverse(function (child) {
 if (child.isMesh) {
     child.material.metalness= metalness;
 }
})

灯光的话一般加4个就够了,分别是三角形摆放3个灯, 然后正中心的上方放个灯,组成金字塔形状;

最后还有一个环境光,他是一个无处不在的光,他不是很强.加了更好

QQ图片20220129104834.png

1.5.4 理解摄像机和控制器的关系

常用的camera 对象就是 PerspectiveCamera (透视摄像机), 另外一个只在选矿中用了一下 OrthographicCamera(正投影摄像机,可以用俯视图扁平化查看,类似于流程图一样);

摄像机你也可以当做是一个模型,每new 一个Camera 就是创建一个眼睛, 在threejs中我们不需要两只眼睛,一个就够了; 初始化的时候你需为摄像机设置.postion.set(x,y,z); 同时你还需要设置一下.lookAt(xx.postion) 一般是场景的中心,当然你也可以看向别处;设置了眼睛看相的位置,那么屏幕中点就对准你看的那个位置

在写摄像机角度切换的动画时, 你会发现就算你设置了相机的postion,rotation的起始位置,终点位置,实际效果还是有偏差,这是因为你在调整摄像机时,摄像机的lookAt的点已经变了,你还需要调整control.target的起始位置和中心位置;,因为你在鼠标调整视角时,lookAt也跟着变了,变成了control的target坐标

1.5.5 判断模型有没有加载完成

虽然模型gltfLoader有加载的进度回调函数,但是没啥用,因为我们模型分开加载的,那么,你可以定义一个变量,每加载完成一个模型变量++,在定义一个模型总数的常量,如果到了总数就发送事件;

1.5.6 其他文件更好的调用render

现在的utils文件大致分为三个model_common, model_animate, model_loader, 只有common里面有定时器不断地render, 如果其他文件也要调用render ,建议各自在文件里写个方法,然后export他,然后common里面引入,放到render里面;这样三个文件尽量只做跟自己相关的事

1.5.7 vue文件跟js文件通信

  1. js文件中:
var vm;  // vue对象
export function getVue(that) {
  vm = that;
}

2.在Overview.vue 引入他,并把this,即当前对象传递过去即可;


1.6 threejs一些效果(具体业务具体发挥)

1.6.1 使用CSS2DObject 将html与3d场景结合

一般是用来做模型上面的标签,点击事件也好处理,直接是用vue那一套,如果是3d场景中做点击事件,点击某个模型触发xxx代码贼多!

import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; // 引入他

比如我创建一个html的dom元素

方法1: let domContainer = document.createElement(“div”) // 相当于html文件中直接写<div></div>,因为在js中,就需要函数式编程;

方法2: let domContainer = document.getElementById(“#xxx”) // 如果你已经在vue文件中创建了这个元素,并给他加个id=xxx,那么你可以直接获取到这个元素

以上简单的html你可以用方法1,复杂的推荐2;

let label = new CSS2DObject(domContainer); // label也有postion,rotation等属性

mesh.add(label) // 最后就跟添加模型一样添加label;

1.6.2 使用TWEENJS做动画

cnpm i @tweenjs/tween.js --save // 安装依赖

import TWEEN from"@tweenjs/tween.js" // 引入他

function xxx( mesh ){
        let startY = mesh.position.clone().y;  // 人生建议,先把起始位置通过clone的方式复制一份,不然某些情况下他会出现叠加态势的变化
        var tween = new TWEEN.Tween({   // 动画开始的数值,可以是postion,也可以是rotation,也可是scale,总之是数字的值都可以
            oGun_y: startY, // 氧枪y轴位置,
        });
        tween.to({  // 动画结束数值
            oGun_y: getRatioPosition(endY), // 氧枪y轴位置,
        }, 1000);
        tween.onUpdate(function (object) {   // 动画更新钩子,意思就是每次更新tween将下一次的运算结果覆盖上一个结果,实现一点点的更新,你用定时器也能实现类似简单的效果
            oGun_model.position.y = object.oGun_y;
        })
        tween.onComplete(function () { // 动画执行完成时候的钩子,你可以在一个动画结束后执行另一个动画

        })
        tween.easing(TWEEN.Easing.Quartic.Out);  // 设置动画执行的贝赛尔曲线
        tween.delay(1000)  // 动画延迟1秒执行
        tween.start();  // 动画执行
}

更多参考链接:

中文文档
http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/renderers/WebGLRenderer

threejs 官方demo,每个知识点都有
https://threejs.org/examples/#webgl_animation_keyframes // 网络有时候慢,需要翻墙


可应用与大多数项目的特效:

  1. 镜头切换效果

  2. 天空盒背景

  3. 模型阴影效果

  4. 鼠标判断是否选中取场景中的模型;

  5. 精灵图做模型标签

  6. Echart图表映射到3d场景中,并可以实时更新变化

  7. 模型外发光

  8. 模型沿自定义路径移动

  9. 粒子成像

  10. 物理引擎模拟碎石掉落

  11. Shader高级渲染

  12. 模型拆散自动组装

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影...
    吞风咽雪阅读 7,217评论 3 3
  • ThreeJS 里元素如下: 1.场景(Scene):是物体、光源等元素的容器, 2.相机(Camera):控制...
    _花阅读 946评论 0 1
  • Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影...
    了无_数据科学阅读 1,540评论 0 0
  • 马上就要毕业了,实习进入了一个GIS公司,任务要求使用Threejs写个渐变的发光半球小特效,我完成后决定写出这篇...
    gardenlike2阅读 15,783评论 1 3
  • 上个月底,在朋友圈看到一个号称“这可能是地球上最美的h5”的分享,点进入后发现这个h5还很别致,思考了一会,决定要...
    杨刚今晚请你吃饭阅读 3,216评论 0 5