通過上一篇文章 "我的three.js學習記錄(一)" 基本上是入門了three.js,但是這不夠3D,這次我希望能把之前做的demo弄出來,然後通過例子來分析操作步驟。 1. 示例 上圖是之前做的一個demo,有點醜,希望不要介意。 這個主要是外面一層包裹著 "天空盒" , 然後裡面是一個由開頂的 ...
通過上一篇文章我的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