通过上一篇文章我的three.js学习记录(一)基本上是入门了three.js,但是这不够3D,这次我希望能把之前做的demo弄出来,然后通过例子来分析操作步骤。
1. 示例
上图是之前做的一个demo,有点丑,希望不要介意。
这个主要是外面一层包裹着天空盒, 然后里面是一个由开顶的立方体做成的房子(暂且理解为房子)以及里面的家具构成,其中包括可以播放视频的电视,一个可以照的镜子,导入的沙发模型等
2. 操作步骤
2.1 准备工作
首先,我们需要上一篇文章的基础,这里不再赘述,我们直接进入主题,首先需要初始化我们所需要的环境,代码如下:
2.1.1 加入html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>day0613</title>
<style>
body {
background-color: #000;
color: #fff;
margin: 0;
overflow: hidden;
}
</style>
<script src="js/three.js"></script>
<script src="js/stats.min.js"></script>
<script src="js/dat.gui.js"></script>
<script src="js/Detector.js"></script>
<script src="js/DDSLoader.js"></script>
<script src="js/OBJLoader.js"></script>
<script src="js/MTLLoader.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/day0613.js"></script>
<script src="js/Mirror.js"></script>
</head>
<body>
<!--用于加载视频资源-->
<video id="video" src="video/sintel.ogv" style="display: none; left: 15px; top: 75px;"></video>
<div id="webgl" style="position: absolute; left: 0; top: 0;"></div>
<script type="text/javascript">
threeStart();
</script>
</body>
</html>
我们需要加入<video>
加载我们的视频,这是在我们的模型中播放的纹理资源,然后div #webgl
是用于嵌入WebGL的渲染器的,接下来调用threeStart()
这个函数来进入我们3D的世界
2.1.2 初始化变量
var WALL_POSITION_Y = 150;
var WALL_HEIGHT = 355;
var WALL_WIDTH = 10;
var WALL_LENGTH = 800;
var TV_WIDTH = WALL_LENGTH / 4;
var TV_HEIGHT = WALL_HEIGHT / 3;
var TV_THICKNESS = 5;
var SKYBOX_HEIGHT = 0;
var MIRROR_WIDTH = 70;
var MIRROR_HEIGHT = 150;
var camera, scene, renderer, container;
var material, controls, stats, video, texture, object;
var verticalMirrorMesh;
var mirror;
以上的变量我没有注释,如果不知道可以查下单词,记得我写的时候是查字典写的,翻译应该没错:-)
2.1.3 threeStart()
在这里,因为涉及的东西不是能够一下子就能够说出来,所以先来一个总体的概览可以使得自己在心里有一定的印象,知道自己应该做些什么
var threeStart = function () {
//判断浏览器是否支持webGL
if (!Detector.webgl) Detector.addGetWebGLMessage();
//窗口尺寸改变事件
window.addEventListener('resize', onWindowResize, false);
/**
* 窗口监听,用于改变窗口时能够实时保持窗口的比例
*/
function onWindowResize() {
//重新设置相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
//更新相机的投影矩阵,这里没有理解什么意思,我把它理解为更新相机里面的各种参数
camera.updateProjectionMatrix();
//重新设置
renderer.setSize(window.innerWidth, window.innerHeight);
}
initRenderer();
initScene();
initCamera();
//加入灯光
initLight();
//这里是绘制TV、墙壁、镜子、地板
paint();
createSky();
//导入模型
initObj();
//回调重复渲染
arimate();
};
这里我前面的加入相机、渲染器、场景的部分先不说,我们可以看到的是需要在原有我的three.js学习记录(一)基础上加入灯光、导入模型、绘制包含纹理的形状、渲染等
2.1.4 加入灯光
/**
* 初始化灯光
*/
function initLight() {
//环境光,没有方向,任意方向的发射光线
var ambient = new THREE.AmbientLight();
//环境光强度
ambient.intensity = .8;
//场景加入环境光
scene.add(ambient);
//方向光,颜色十六进制表示 0xffeedd
var directionalLight = new THREE.DirectionalLight(0xffeedd);
//设置方向光的来源点
directionalLight.position.set(0, 10, 10).normalize();
scene.add(directionalLight);
}
2.1.5 回调渲染
这里就是当我们改变了视角之后这是一个连贯的动画,需要我们的计算机重复的渲染场景才行,所以需要一个函数去重复的调用才行
/**
* 回调函数,重画整个场景
*/
function arimate() {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
//设置我们的纹理需要更新 这里的纹理就是视频纹理
//其实更新的就是将视频播放的画面重新截取到纹理,一帧一帧我们看起来就是动画
if (texture) texture.needsUpdate = true;
video.play();
}
//镜子也需要渲染,它相当于摄像机从这个3d世界看到的渲染到平面(镜子)
mirror.renderWithMirror(new THREE.Mirror(renderer, camera));
//渲染
renderer.render(scene, camera);
//fps状态更新
stats.update();
//重新调用arimate
requestAnimationFrame(arimate);
}
2.2 天空盒
我先不按照threeStart()
函数的调用顺序展开说明,这里我先来一个简单的创建天空盒createSky()
/**
* 创建天空盒
*/
function createSky() {
//这部分是给出图片的位置及图片名
var imagePrefix = "img/sky/dawnmountain-";
var directions = ["xpos", "xneg", "ypos", "yneg", "zpos", "zneg"];
var imageSuffix = ".png";
//创建一个立方体并且设置大小
var skyGeometry = new THREE.CubeGeometry( 5000, 5000, 5000 );
//这里是用于天空盒六个面储存多个材质的数组
var materialArray = [];
//循环将图片加载出来变成纹理之后将这个物体加入数组中
for (var i = 0; i < 6; i++)
materialArray.push( new THREE.MeshBasicMaterial({
//这里imagePrefix + directions[i] + imageSuffix 就是将图片路径弄出来
map: THREE.ImageUtils.loadTexture( imagePrefix + directions[i] + imageSuffix ),
side: THREE.BackSide //因为这里我们的场景是在天空盒的里面,所以这里设置为背面应用该材质
}));
//MultiMaterial可以将MeshBasicMaterial多个加载然后直接通过Mesh生成物体
var skyMaterial = new THREE.MultiMaterial( materialArray );
//加入形状skyGeometry和材质MultiMaterial
var sky = new THREE.Mesh( skyGeometry, skyMaterial );
//设置天空盒的高度
sky.position.y = SKYBOX_HEIGHT;
//场景当中加入天空盒
scene.add( sky );
}
虽然上面实现的东西不复杂,但是以上短短的代码需要理解确实不是非常容易,这里有涉及到了图片纹理的问题(有图形学的基础会好点吧,但是我上课的忘了差不多),可能我并不能真正意义的去理解,我现在只是需要快速的入门,能够简单的使用three.js就行了
2.3 绘制图形
接下来我们来绘制我们的‘房子’,总体的代码如下:
/**
* 绘制场景
*/
function paint() {
paintFloor();
paintMirror();
paintWall();
paintTV();
}
2.3.1 绘制地板
function paintFloor() {
var loader = new THREE.TextureLoader;
loader.load('img/floor.jpg', function (texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping; //这里设置x和y超过了图片的像素之后进行的是重复绘制图片操作
texture.repeat = new THREE.Vector2(5, 5); //设置图片重复绘制的密度这里是5*5
//设置材质是双面应用该图片材质
var floorMaterial = new THREE.MeshBasicMaterial({map: texture, side: THREE.DoubleSide});
//地板使用PlaneGeometry生成平面
var floorGeometry = new THREE.PlaneGeometry(WALL_LENGTH, WALL_LENGTH);
//生成地板的模型
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
//设置地板的位置
floor.position.y = -27;
floor.rotation.x = Math.PI / 2;
scene.add(floor);//场景加载该地板
});
}
2.3.2 绘制镜子
我们这个镜子是由两个部分组成的,一个是后面的一个盒子,一个就是镜面,盒子是为了看起来凸显一点,如果没有镜面镜子将会是这样的
加入镜片之后效果就不一样了
以下是代码的实现
function paintMirror() {
//这里是背面的盒子
var Geometry = new THREE.BoxGeometry(MIRROR_WIDTH + 3, MIRROR_HEIGHT + 3, 5);
var Material = new THREE.MeshBasicMaterial({color:0x000, side: THREE.DoubleSide});
var Mesh = new THREE.Mesh(Geometry, Material);
//确定位置
Mesh.position.y = 50;
Mesh.position.z = -220;
Mesh.position.x = -395;
Mesh.rotateY(Math.PI / 2);
scene.add(Mesh);
//three.js有一个Mirror.js用于生成镜子的,这是我在官方的示例看的
//这里有些代码不是很理解,都是直接使用官方给出的,不过按照我的理解
//主要是给它一个渲染器和照相机,它将3d世界看到的重新渲染一遍,但是需要不断渲染,所以在回调函数中需要更新
mirror = new THREE.Mirror( renderer, camera);
verticalMirrorMesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(MIRROR_WIDTH, MIRROR_HEIGHT), mirror.material );
verticalMirrorMesh.add( mirror );
}
2.3.3 绘制墙壁
function paintWall() {
//图片加载器,加载成纹理
var loader = new THREE.TextureLoader;
var wallOutside = loader.load('img/wall-outside.jpg');
var wallInside = loader.load('img/wall-inside.jpg');
//设置纹理的过滤方式
wallInside.wrapT = wallInside.wrapS = THREE.RepeatWrapping;
wallInside.repeat = new THREE.Vector2(5, 5);
//材质
var materials = [];
materials.push(new THREE.MeshBasicMaterial()); //默认的材质,没有纹理
materials.push(new THREE.MeshBasicMaterial());
materials.push(new THREE.MeshBasicMaterial());
materials.push(new THREE.MeshBasicMaterial());
//这里设置了两种不同图片生成的纹理,墙外的和墙内的
materials.push(new THREE.MeshBasicMaterial({map: wallOutside, side: THREE.DoubleSide}));
materials.push(new THREE.MeshBasicMaterial({map: wallInside, side: THREE.DoubleSide}));
//这6个基础材质的数组作为参数传递给MeshFaceMaterial
var faceMaterial = new THREE.MultiMaterial(materials);
//创建其中一面墙,然后其他的由此面墙生成
var Geometry = new THREE.BoxGeometry(WALL_LENGTH, WALL_HEIGHT, WALL_WIDTH);
//设置墙的位置参数
var wallFront = new THREE.Mesh(Geometry, faceMaterial);
wallFront.position.y = WALL_POSITION_Y;
//通过clone函数可以得到一个全新的面,然后通过位置的改变作为其他墙面
var wallLeft = wallFront.clone();
wallLeft.rotation.y = 3 * Math.PI / 2;
wallLeft.position.x = -WALL_LENGTH / 2;
wallLeft.width = WALL_LENGTH + 100;
var wallRight = wallFront.clone();
wallRight.rotation.y = Math.PI / 2;
wallRight.position.x = WALL_LENGTH / 2;
var wallBack = wallFront.clone();
wallBack.rotation.y = Math.PI;
wallBack.position.z = -WALL_LENGTH / 2;
wallFront.position.z = WALL_LENGTH / 2;
scene.add(wallFront);
scene.add(wallLeft);
scene.add(wallRight);
scene.add(wallBack);
}
2.3.4 绘制电视
这里跟镜子的实现是一样的,背面一个盒子(加了纹理),然后加一个面用于贴入视频纹理的,代码如下:
function paintTV() {
//获取我们的视频的元素
video = document.getElementById('video');
//将我们的视频加载为纹理(播放时的每一帧都可以将它看作图片)
texture = new THREE.Texture(video);
//这里是屏幕
var tvScreenGeometry = new THREE.PlaneGeometry(TV_WIDTH - 2, TV_HEIGHT - 4);
//将视频纹理加入
var tvScreenMaterial = new THREE.MeshBasicMaterial({map: texture, side: THREE.DoubleSide});
var tvScreen = new THREE.Mesh(tvScreenGeometry, tvScreenMaterial);
tvScreen.position.y = WALL_HEIGHT / 3 + 1;
tvScreen.position.z = -WALL_LENGTH / 2 + 8;
//这里是屏幕后面的盒子
var loader = new THREE.TextureLoader();
var tvGeometry = new THREE.BoxGeometry(TV_WIDTH, TV_HEIGHT, TV_THICKNESS);
var tvMaterial = new THREE.MeshBasicMaterial({map: loader.load('img/tv.jpg')});
var tv = new THREE.Mesh(tvGeometry, tvMaterial);
tv.position.y = WALL_HEIGHT / 3;
tv.position.z = -WALL_LENGTH / 2 + TV_THICKNESS;
scene.add(tvScreen);
scene.add(tv);
}
2.4 加载模型
/**
* 初始化模型
*/
function initObj() {
//这里我们是用max导出的obj模型包含材质
//这里是直接官网的例子
// THREE.Loader.Handlers.add(/\.dds$/i, new THREE.DDSLoader());
//材质加载器
var mtlLoader = new THREE.MTLLoader();
//设置路径
mtlLoader.setPath('./model/');
//导入材质
mtlLoader.load('room.mtl', function (materials) {
//材质导入调用这个回调函数(钩子函数)
// materials.preload();
//obj模型加载器
var objLoader = new THREE.OBJLoader();
//设置将传入材质参数
objLoader.setMaterials(materials);
//设置路径
objLoader.setPath('./model/');
//导入模型时可以加入执行信息和错误信息的函数,这里我没有加入
//onProgress, onError
//导入模型
objLoader.load('room.obj', function (object) {
//将导入的模型旋转到合适的位置
object.rotation.y = Math.PI;
scene.add(object);
});
});
}
2.5 其他
这里除了以上的东西还有其他的小组件,如fps监听器(我截屏时左下角的东西),还有我们的控制器,控制器其实就是通过改变我们的模型位置或者是照相机位置达到的移动效果等等这些里面所用到的我没有在这里拿出来,如果需要的可以将代码拿回去研究
3. 总结
因为我之前是需要快速入门three.js,所以没有好好的系统地学习,所以我觉得我并没有掌握好这项技能,而且在上课(图形学,OpenGL)也没有利用好机会努力地学,图形学基础有点差,所以我如果要接触这类相关的还是需要努力将图形学弄好,并且理解three.js的东西
基础部分推荐
http://www.hewebgl.com/article/articledir/1
https://www.zhihu.com/question/36367846?from=profile_question_card
http://blog.csdn.net/doupi520/article/category/6645411
除了以上还有上篇我的three.js学习记录(一)推荐的
以上的代码已经上传Github