THREE.JS之文字物件
在很多應用中,不光是有影象影象物件,有時還要給場景新增必要的文字說明等等。比圖說遊戲中的積分,生命值等等,或者使用文字做裝飾等等。在three.js中,使用TextGeometry這個類來建立文字。今天要實現的效果如下圖:
首先建立檔案index.html
<!DOCTYPE html>
<html>
<head>
<title>立體字</title>
<meta charset="utf-8">
<style type="text/css" >
*{
padding:0px;margin: 0px
}
#container{
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src="./build/three.js"></script>
<script type="text/javascript" src="./js/controls/OrbitControls.js"></script>
<script type="text/javascript">
//TODO
</script>
</body>
</html>
在上面程式碼中首先引入three.js[官網]檔案和OrbitControls.js檔案,其中OrbitControls是一個攝像機控制器,用來進行場景漫遊。使用控制器來控制視角,更能體現3D的立體效果。
完整的例子步驟如下:
1、建立並初始化渲染器(Renderer);
2、建立並初始化場景(Scene);
3、建立並初始化攝像機(PerspectiveCamera/透視相機),將相機新增到場景中;
4、建立並初始化光源(PointLight/點光源),並將光源新增到場景中;
5、匯入字型庫,使用字型建立文字形狀(TextGeometry),並新增到場景中;
6、建立控制器並初始化控制器(OrbitControls);
7、渲染;
首先建立全域性變數,以便共享:
var renderer,scene,camera,controls;
1、建立並初始化渲染器(Renderer);
function initRenderer(){
renderer = new THREE.WebGLRenderer( { antialias: true } );
var width = document.getElementById("container").clientWidth;
var height = document.getElementById("container").clientHeight;
renderer.setSize(width,height);
renderer.setClearColor(0xffffff);
document.getElementById("container").appendChild(renderer.domElement);
}
建立一個WebGLRenderer渲染器,WebGLRenderer使用WebGL來繪製3D圖形。three.js一共有兩種渲染器,另外一個是CanvasRenderer,使用 Canvas 2D Context API來繪製3D圖形。對於開發者來說不管使用哪個渲染器,他們的差別對於我們來說是透明的,也就是說無論使用哪個渲染器,寫的程式碼都是一樣的。但是經過我的測試,發現CanvasRenderer的渲染效果明顯次於WebGLRenderer,在場景漫遊的過程重出現了很嚴重的跳幀。而且CanvasRenderer已經被移除了標準庫three.js,如果使用需要使用,必須引入檔案“/examples/js/renderers/CanvasRenderer.js”。
.setClearColor() 設定清除色,也就是背景色;
renderer.domElement 就是渲染器中的canvas物件,將canvas物件新增到頁面中才能看到渲染效果。
2、建立並初始化場景(Scene);
function initScene(){
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, 0, 3000 );
}
給場景新增煙霧效果;
new THREE.Fog( color,near,far),其中:
color:十六進位制,煙霧的顏色值;
near:煙霧的範圍,沿Z軸的近端;
far:煙霧的範圍,沿Z軸的遠端;
3、建立並初始化攝像機(PerspectiveCamera/透視相機),將相機新增到場景中;
function initCamera(){
var width = document.getElementById("container").clientWidth;
var height = document.getElementById("container").clientHeight;
camera = new THREE.PerspectiveCamera(30, width/height,1, 10000);
camera.position.y=800/Math.tan(Math.PI/2.5);
camera.position.z=800;
}
PerspectiveCamera( fov, aspect, near, far ):
fov:視角,視角越大視野越大,但是看到的物體越小;
aspect:縱橫比,一般設定為canvas的width/height,否則場景中的物體會比例失調;
near:與相機的距離近端,當物體與相機的距離小於該值時不渲染;
far:與相機的距離近遠端,當物體與相機的距離大於該值時不渲染;
相機的預設位置是放在圓點,重置器position的x/y/z來調整相機的位置;
相機的焦點預設是原點,可以使用camera.lookAt(THREE.vector3())來改變焦點;
4、建立並初始化光源(PointLight/點光源),並將光源新增到場景中;
function initLight(){
var pointLight = new THREE.PointLight( 0xffffff, 1);
pointLight.position.set( 0, 100, 100 );
scene.add( pointLight );
var pointLight = new THREE.PointLight( 0xffffff, 1);
pointLight.position.set( 0, 100, -100 );
scene.add( pointLight );
}
three.js中的光源有5種:
DirectinalLight:直線光;
PointLignt:點光源;
SpotLight:聚光燈;
AmbientLight:環境光;
HemisphereLight:半球光(天光);
THREE.PointLight(color, intensity):
color:光的顏色;
intensity:光強;
5、匯入字型庫,使用字型建立文字形狀(TextGeometry),並新增到場景中;
function initText(){
var loader = new THREE.FontLoader();
loader.load('../examples/fonts/optimer_bold.typeface.json',function(response){
var font = response;
var textGeometry = new THREE.TextGeometry("three.js",{
"font" : font,
"size" : 70,
"height" : 20,
"bevelEnabled" : true,
"bevelSize": 2
})
text = new THREE.Mesh(textGeometry,new THREE.MultiMaterial( [
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ),
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } )
] ))
textGeometry.computeBoundingBox();
var centerOffset = -0.5 * (textGeometry.boundingBox.max.x-textGeometry.boundingBox.min.x);
text.position.x = centerOffset;
text.position.y = 30;
var mirror = new THREE.Mesh(textGeometry,new THREE.MultiMaterial( [
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ),
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } )
] ))
mirror.rotation.x = Math.PI;
mirror.position.x = centerOffset;
mirror.position.z = 20;
mirror.position.y = -30;
scene.add(text);
scene.add(mirror);
var plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry( 10000, 10000 ),
new THREE.MeshBasicMaterial( { color: 0xfffffff, opacity: 0.5, transparent: true } )
);
plane.rotation.x = -Math.PI/2;
plane.position.y = 0;
scene.add(plane);
})
}
var loader = new THREE.FontLoader()
建立字型載入器;
loader.load(src, callback)
載入字型庫,載入成功後將字型庫傳給回撥函式;
var textGeometry = new THREE.TextGeometry("three.js",{
"font" : font,
"size" : 70,
"height" : 20,
"bevelEnabled" : true,
"bevelSize": 2
})
建立字型幾何圖形,其中TextGeometry的第一個引數是需要繪製的文字,第二個引數是一個json物件,設定如何繪製文字,具體意義為:
font: 使用的字型庫;
size:繪製字型的大小;
height:繪製文字的厚度;
bevelEnabled:是否允許稜角平滑過渡;
bevelSize:稜角平滑過渡的尺寸;
注意:bevelSize通常要比size和height小一個量級,過渡帶就會很飽滿甚至比文字主體本身還要大
在3D世界裡所有的物體都是由網格構成的,在three.js中,繪製圖形的步驟是:先使用geometry定義圖形的幾何形狀,然後使用幾何形狀和材質構建網格;
text = new THREE.Mesh(textGeometry,new THREE.MultiMaterial( [
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ),
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } )
] ))
這裡使用材質陣列來對網格進行修飾,材質陣列的第一項修飾文字正面和背面,第二項修飾文字的側面即頂部和底部。使用的兩個材質都是MeshPhongMaterial,這種材質的特點是能夠像塑料一樣反光。
可以認為文字物件是放在一個剛好容納其內容的立方體中,而預設情況下,這個立方體的(底,背,左)的點與原點重合,然後沿x軸向右延伸。這樣的話,文字就不會顯示在視野中央。這樣的話,就要計算出包含文字外部的立方體,然後使用立方體的頂點位置,重新計算出其起始位置,重置位置。
textGeometry.computeBoundingBox();
var centerOffset = -0.5 * (textGeometry.boundingBox.max.x-textGeometry.boundingBox.min.x);
text.position.x = centerOffset;
text.position.y = 30;
使用同樣的方法建立一個倒影,最後不要忘記把物件新增到場景中。
scene.add(text);
scene.add(mirror);
為了使倒影的效果更逼真,給場景新增一個半透明的平面,把文字真身和倒影分開。
var plane = new THREE.Mesh(
new THREE.PlaneGeometry( 10000, 10000 ),
new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true } )
);
plane.rotation.x = -Math.PI/2;
plane.position.y = 0;
scene.add(plane);
THREE.PlaneGeometry( 10000, 10000 )建立一個10000x10000的平面,transparent: true設定該平面透明, opacity: 0.5透明度是0.5。此時平面是與x0y平面重合,也就是和螢幕平面重合,然後旋轉平面到xoz平面上。
6、建立控制器並初始化控制器(OrbitControls);
function initControls(){
controls = new THREE.OrbitControls(camera);
controls.enableZoom = true;
controls.minPolarAngle = Math.PI/2.5;
controls.maxPolarAngle = Math.PI/2.5;
}
controls.enableZoom = true; //支援縮放;
controls.minPolarAngle = Math.PI/2.5; //限制豎直方向上最小旋轉角度 y軸正向為0度
controls.maxPolarAngle = Math.PI/2.5; //限制豎直方向上最大旋轉角度 y軸正向為0度
7、渲染;
function render(){
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function start(){
initRenderer();
initScene();
initCamera();
initControls();
initLight();
initText();
render();
}
start();
到這裡,文章開頭的效果已經出來了,然後補充一下requestAnimationFrame()這個方法,requestAnimationFrame是為了做動畫渲染而誕生的,其作用就是每個一段時間呼叫一下傳入的回撥函式,那麼他與setTimeout()有什麼區別呢?
大部分瀏覽器的重新整理週期是16.7ms,因為1000/60=16.7;但是也不盡然,如果我們使用setTimeout()來呼叫渲染函式,而我們設定的重新整理時間間隔與瀏覽器的重新整理週期不一致的話,就會導致畫面延時重新整理。比如說,瀏覽器的重新整理週期是16.7ms,而我們設定的時間是10ms,那麼程式就要求瀏覽器超負荷地去重新整理,但是事實上瀏覽器並不會這麼做,而是等待到16.7ms才重新整理,導致卡幀。而requestAnimationFrame()則不需要我們設定時間,他的重新整理週期是和瀏覽器的重新整理週期同步。而且,當網頁被掛起之後,requestAnimationFrame將不再重新整理,比setTimeout節省資源。
完整程式碼
<!DOCTYPE html>
<html>
<head>
<title>立體字</title>
<meta charset="utf-8">
<style type="text/css">
*{
padding:0px;margin: 0px
}
#container{
width: 100%;
height: 100vh;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src="../build/three.js"></script>
<script type="text/javascript" src="../examples/js/renderers/CanvasRenderer.js"></script>
<script type="text/javascript" src="../examples/js/renderers/Projector.js"></script>
<script type="text/javascript" src="../examples/js/controls/OrbitControls.js"></script>
<script type="text/javascript" src="./Stats.js"></script>
<script type="text/javascript">
var renderer,scene,camera,controls;
function initRenderer(){
renderer = new THREE.WebGLRenderer( { antialias: true } );
var width = document.getElementById("container").clientWidth;
var height = document.getElementById("container").clientHeight;
renderer.setSize(width,height);
renderer.setClearColor(0xffffff);
document.getElementById("container").appendChild(renderer.domElement);
stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// 將stats的介面對應左上角
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild( stats.domElement);
}
function initScene(){
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, 0, 5000 );
}
function initCamera(){
var width = document.getElementById("container").clientWidth;
var height = document.getElementById("container").clientHeight;
camera = new THREE.PerspectiveCamera(30, width/height,1, 10000);
camera.position.y=800/Math.tan(Math.PI/2.5);
camera.position.z=800;
}
function initControls(){
controls = new THREE.OrbitControls(camera);
controls.enableZoom = true;
controls.minPolarAngle = Math.PI/2.5;
controls.maxPolarAngle = Math.PI/2.5;
controls.autoRotate = false;
}
function initLight(){
var pointLight = new THREE.PointLight( 0xffffff, 1);
pointLight.position.set( 0, 100, 100 );
scene.add( pointLight );
var pointLight = new THREE.PointLight( 0xffffff, 1);
pointLight.position.set( 0, 100, -100 );
scene.add( pointLight );
}
function initText(){
var loader = new THREE.FontLoader();
loader.load('../examples/fonts/optimer_bold.typeface.json',function(response){
font = response;
var textGeometry = new THREE.TextGeometry("three.js",{
"font" : font,
"size" : 70,
"height" : 20,
"bevelEnabled" : true,
//"curveSegments" : 1,
bevelSize: 2,
// material: 0,
// extrudeMaterial: 1
})
text = new THREE.Mesh(textGeometry,new THREE.MultiMaterial( [
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ), // front
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } ) // side
] ))
textGeometry.computeBoundingBox();
//textGeometry.computeVertexNormals();
var centerOffset = -0.5 * (textGeometry.boundingBox.max.x-textGeometry.boundingBox.min.x);
text.position.x = centerOffset;
text.position.y = 30;
var mirror = new THREE.Mesh(textGeometry,new THREE.MultiMaterial( [
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ), // front
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } ) // side
] ))
mirror.rotation.x = Math.PI;
mirror.position.x = centerOffset;
mirror.position.z = 20;
mirror.position.y = -30;
scene.add(text);
scene.add(mirror);
var plane = new THREE.Mesh(
new THREE.PlaneGeometry( 10000, 10000 ),
new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true } )
);
plane.rotation.x = -Math.PI/2;
plane.position.y = 0;
scene.add(plane);
})
}
function render(){
renderer.render(scene, camera);
stats.update();
requestAnimationFrame(render);
}
function start(){
initRenderer();
initScene();
initCamera();
initControls();
initLight();
initText();
render();
}
start();
</script>
</body>
</html>