WebGL and Threejs: Lightig
什么是webgl和threejs?
webgl是一個在瀏覽器中用來把3D圖形渲染到屏幕上的javascript API。使用webgl的api直接來編程可能是復(fù)雜和雜亂的,但是幸運的是我們有一些庫來簡化這點。Three.js就是其中一個庫。
Threejs
Threejs是一個輕量的3D庫,隱藏了很多WebGL的復(fù)雜性,使得在開始在網(wǎng)絡(luò)3D編程上變得非常簡單。threejs可以在github或者thereejs的網(wǎng)站上下載,在那里你還可以發(fā)現(xiàn)文檔和示例的鏈接。
在我的第一個教程中,我向你展示了如何構(gòu)建一個基本的Threejs應(yīng)用程序。在那個教程中,我談到了設(shè)置舞臺,構(gòu)建場景和一個簡單的立方體動畫。在我的第二個教程中,我解釋了紋理貼圖,并展示了使用Threejs紋理對象的三種不同方法。
在本教程中,我將討論Threejs的燈光。我將解釋以下五種類型的燈以及如何使用每種燈:定向燈(directional lights),環(huán)境燈(ambient lights),點燈(point lights),聚光燈(spot lights)和半球燈(hemisphere lights)。
查看此示例,你需要一個兼容webgl的瀏覽器,如果你使用的是最新版本的任何主流瀏覽器,你應(yīng)該可以查看它。如果你使用的是老的ie版本或者在手機設(shè)備上查看,你就沒那么幸運了。你可以點此查看本教程的示例,也可以在這里找到源代碼。
燈光的藝術(shù)
大多數(shù)人在做3D游戲或者場景時,對于燈光并不會真的想得太多---畢竟,每個人都知道困難的部分是找到完美的模型和紋理,然后你花幾個小時把他們放到一個完美的場景中。一旦你完成,你需要添加一個光源,并驚嘆你的新創(chuàng)作。除了,當(dāng)你看著你的新場景,你不能不覺得有些東西丟失了。這是因為,在我們?nèi)粘I钪泻苌傩蕾p燈光對我們正在看的色調(diào),顏色,心情和氛圍有多少貢獻。照明可以通過調(diào)整照明將您的場景從幸福的安全場景轉(zhuǎn)變?yōu)楹诎岛筒辉數(shù)膱鼍啊?/p>
本教程將介紹threejs中不同類型的燈光,并解釋如何在技術(shù)上使用每個燈光。這不是一個良好的藝術(shù)照明技術(shù)的教程,因為,說實話,我是一個可怕的藝術(shù)家。
入門
在本教程中,我們將會以一個簡單的HTML文件,類似于我在上一個教程中使用的:
<!DOCTYPE HTML>
<html>
<head>
<title>WebGL/Three.js Light Tutorial</title>
<style>
body {
background-color:#cccccc;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="js/three.min.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/three-light-tut.js"></script>
</body>
</html>
這個HTML假設(shè)你已經(jīng)下載了壓縮版的three.js庫且保存到來js目錄下,假設(shè)你已經(jīng)拷貝了OrbitControls.js到相同目錄下了。OrbitControls.js可以在threejs的包里的examples\js\controls
目錄下找到。OrbitControls 允許通過點擊左鍵拖拽來旋轉(zhuǎn)場景、點擊右鍵拖拽來平移場景、以及通過鼠標(biāo)滾輪來縮放。我也創(chuàng)建了我的js three-light-tut.js
,且把它保存在js文件夾中。以下是我們應(yīng)用程序的起始代碼:
var camera;
var scene;
var renderer;
var controls;
init();
animate();
function init() {
// Create a scene
scene = new THREE.Scene();
// Add the camera
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 100, 250);
// Add scene elements
addSceneElements();
// Add lights
addLights();
// Create the WebGL Renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
// Append the renderer to the body
document.body.appendChild( renderer.domElement );
// Add a resize event listener
window.addEventListener( 'resize', onWindowResize, false );
// Add the orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(0, 100, 0);
}
function addLights() {
var dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(100, 100, 50);
scene.add(dirLight);
}
function addSceneElements() {
// Create a cube used to build the floor and walls
var cube = new THREE.CubeGeometry( 200, 1, 200);
// create different materials
var floorMat = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/wood-floor.jpg') } );
var wallMat = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/bricks.jpg') } );
var redMat = new THREE.MeshPhongMaterial( { color: 0xff3300, specular: 0x555555, shininess: 30 } );
var purpleMat = new THREE.MeshPhongMaterial( { color: 0x6F6CC5, specular: 0x555555, shininess: 30 } );
// Floor
var floor = new THREE.Mesh(cube, floorMat );
scene.add( floor );
// Back wall
var backWall = new THREE.Mesh(cube, wallMat );
backWall.rotation.x = Math.PI/180 * 90;
backWall.position.set(0,100,-100);
scene.add( backWall );
// Left wall
var leftWall = new THREE.Mesh(cube, wallMat );
leftWall.rotation.x = Math.PI/180 * 90;
leftWall.rotation.z = Math.PI/180 * 90;
leftWall.position.set(-100,100,0);
scene.add( leftWall );
// Right wall
var rightWall = new THREE.Mesh(cube, wallMat );
rightWall.rotation.x = Math.PI/180 * 90;
rightWall.rotation.z = Math.PI/180 * 90;
rightWall.position.set(100,100,0);
scene.add( rightWall );
// Sphere
var sphere = new THREE.Mesh(new THREE.SphereGeometry(20, 70, 20), redMat);
sphere.position.set(-25, 100, -20);
scene.add(sphere);
// Knot thingy
var knot = new THREE.Mesh(new THREE.TorusKnotGeometry( 40, 3, 100, 16 ), purpleMat);
knot.position.set(0, 60, 30);
scene.add(knot);
}
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
controls.update();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
如果你現(xiàn)在運行這個示例,你將會看到一個如下所示的簡單場景,它包含了木地板,三面墻,一個紅色球,和一個紫色圓環(huán)結(jié)。
我們將主要關(guān)注addLights()
這行代碼。事實上,如果你現(xiàn)在看看這個函數(shù),可以看到我們?yōu)閳鼍岸x了一個光:定向光(directional light)。
directional light 定向光
directional light 是創(chuàng)建戶外場景時常用的照明形式,但可用在任何場景上。定向光類似于太陽的光。它是以惡搞非常遙遠的光,在一個方向閃耀。就像太陽一樣,因為它是非常遠的,所有的光線互相平行。此外,這個光的行為好像是無限遠,所以光的位置是沒有關(guān)系的,只有光的角度有影響。這里是我們光線的代碼:
var dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(100, 100, 50);
scene.add(dirLight);
在第一行中,我們用兩個參數(shù)來創(chuàng)建light:光線的顏色和光的強度或亮度。你可以隨意調(diào)整它們,看看如何影響場景。第二行代碼是設(shè)置光源的位置,最后第三行,將光源添加到我們的場景中。
既然之前說到位置沒有關(guān)系,為什么我們要設(shè)置光的位置呢?原因是因為在threejs中,光的方向不是由光的旋轉(zhuǎn)確定的,而是通過從其當(dāng)前位置和其目標(biāo)的位置計算角度。由于我沒有指定目標(biāo)位置,默認(rèn)的目標(biāo)位子是0,0,0原點。我可以通過不移動光源的位置和為我們的光源指定一個目標(biāo)位置(-100, -100, -50)來達到一樣的效果。
Ambient Light 環(huán)境光
讓我們通過在addLights()
方法的結(jié)尾添加兩行代碼開始
var ambLight = new THREE.AmbientLight(0x404040);
scene.add(ambLight);
環(huán)境光只有一個屬性:顏色。第一行代碼創(chuàng)建了一個柔和灰色的環(huán)境光。如果你刷新你的瀏覽器,你會看到添加這個燈引起的變化,場景中的所有的物體都添加了平坦的灰色陰影。事實上,它看起來很不好,那是因為環(huán)境光并不是真正的光。它只是我之前說的,給整個場景添加一些顏色的平陰影。我曾經(jīng)給過一個使用環(huán)境光的提示:
- 完整的移除它。
- 添加所有其他燈光。
- 如果場景的某些部分沒有被任何主燈照亮,那么只需添加足夠的環(huán)境光,使這些黑暗的角落可見。
- 如果仍然看起來不好,請回到第一步。
我們加上環(huán)境光就像這樣了:
point Lights 點光源
點光源是像燈泡一樣工作的燈光:你把它放置在一個位置,它們會照向各個方向,點亮任何在他們范圍的區(qū)域。
讓我們刪除所有現(xiàn)有的燈光,用以下內(nèi)容替換掉addLights()
方法中的內(nèi)容:
var bluePoint = new THREE.PointLight(0x0033ff, 3, 150);
bluePoint.position.set( 70, 5, 70 );
scene.add(bluePoint);
scene.add(new THREE.PointLightHelper(bluePoint, 3));
var greenPoint = new THREE.PointLight(0x33ff00, 1, 150);
greenPoint.position.set( -70, 5, 70 );
scene.add(greenPoint);
scene.add(new THREE.PointLightHelper(greenPoint, 3));
我們創(chuàng)建了2個點光源,一個藍色一個綠色。點光源的構(gòu)造函數(shù)需要3個屬性:光的顏色、強度、和強度下降到0的距離(光的范圍)。如果設(shè)置距離為0,那么距離是無限的。在前三行中,我們創(chuàng)建了一個點光源,設(shè)置其位置并且將它加入到場景中。第四行添加一個PointLightHelper,對于向場景添加光,PointLightHelper并不是必須的。它是一個幫助類,可以在創(chuàng)建場景的同時在光的位置放置一個wireframed球體,使其更容易可視化。
在刪除現(xiàn)有光源并且添加綠色和藍色點光源后,我們的場景如下所示:
spot Light 射燈
接下來我們添加一個射燈到場景中。聚光燈正如它的名字那樣:從一個方向上的給定點照射的光形成圓錐形狀。
將以下代碼添加到addLights()
方法的結(jié)尾部分:
var spotLight = new THREE.SpotLight(0xffffff, 1, 200, 20, 10);
spotLight.position.set( 0, 150, 0 );
var spotTarget = new THREE.Object3D();
spotTarget.position.set(0, 0, 0);
spotLight.target = spotTarget;
scene.add(spotLight);
scene.add(new THREE.PointLightHelper(spotLight, 1));
正如你第一行看到的,我們?yōu)閟potlight的構(gòu)造器指定了5個參數(shù):顏色,強度,距離,角度和指數(shù)。之前我們已經(jīng)使用過顏色,強度和距離,這里新增了2個:角度和指數(shù)。
- 角度是錐形將采取的角度,或者點將是多寬。
- 指數(shù)是指光從目標(biāo)方向落到0的速度。數(shù)字越高,光線越暗。
在第二行,我們給light設(shè)置了位置,在接下來的第三行,我們創(chuàng)建了一個Object3D,設(shè)置它的位置為0,0,0。然后將這個對象設(shè)置為spotlight的目標(biāo)。就像定向光,聚光燈所面對的方向不是由光的旋轉(zhuǎn)決定的,而是通過找到光及其目標(biāo)的角度來計算。在這種情況下,我們設(shè)置目標(biāo)為一個通用的Object3D類實例。在threejs中Object3D是所以3D object的父類。所以我們可以很容易地將我們的聚光燈的目標(biāo)設(shè)置成任意場景元素,比如球體。那樣,如果我們對球體進行動畫處理,聚光燈會自動跟蹤球體。事實上,我們將添加一點動畫到我們的聚光燈,你可以刷新瀏覽器看到如下:
Hemisphere Light 半球光
如你看到的,我們的場景還是比較暗,特別是房間后面的角落。我們可以添加一個低強度的定向光,或少量的環(huán)境光照亮這些區(qū)域,但接下來我要添加另一種稱為半球光的光。半球光與環(huán)境光類似,因為它沒有位置或方向。
添加以下代碼到addLights()
函數(shù)的結(jié)尾:
var hemLight = new THREE.HemisphereLight(0xffe5bb, 0xFFBF00, .1);
scene.add(hemLight);
如你看到的,半球光的構(gòu)造器有3個屬性,前兩個是顏色,第三個是強度。為什么有2個顏色呢?半球照明是一種添加一些環(huán)境照明的方式,但有一點更現(xiàn)實主義。第一種顏色代表來自我們模型上方的光的顏色,像太陽或者天花板燈。第二種顏色是來自我們模型下方的光的顏色,表示地面反射的太陽的顏色或反射離開地板的天花板燈的顏色。這兩種顏色的梯度應(yīng)用于我們場景中的模型。就像其他燈,我們可以指定這種光的強度。它為我們提供了比環(huán)境光更多的微調(diào)控制。
刷新瀏覽器,可以看到如下場景:
區(qū)別很小,但是你應(yīng)該注意到,你現(xiàn)在可以看到場景的后角了。
threejs為你的場景提供了廣泛的選擇,每一種燈源都包含大量的選項,如顏色,距離和強度等。當(dāng)彼此結(jié)合使用時,照明場景的可能性幾乎是無限的。
在結(jié)束本教程之前,我想做最后一個調(diào)整,使我們的場景更加動態(tài)。我要動畫的聚光燈,以便它模擬一個吊燈從電線上來回擺動的天花板燈。
為此,我們首先需要將spotLight變量聲明為全局變量,這樣我們可以在animate()
函數(shù)中使用它,我們再聲明一個計數(shù)器的變量。
添加以下代碼到應(yīng)用的最上面:
var spotLight;
var counter = 0;
把addLight()
函數(shù)帶代碼由下面:
var spotLight = new THREE.SpotLight(0xffffff, 1, 200, 20, 10);
改為:
spotLight = new THREE.SpotLight(0xffffff, 1, 200, 20, 10);
再添加以下代碼到animate()
函數(shù)中:
counter += .1;
spotLight.target.position.x = Math.sin(counter) * 100;
我們在這里所做的是更新聚光燈所指向的X位置。我使用正弦波計算位置,所以我們得到一個很好的平滑擺動效果。
刷新你的瀏覽器,應(yīng)該看到聚光燈來回擺動。
結(jié)論
Threejs提供了許多不同的光源可以在場景里使用,每一個光源提供了一些不同的選項來控制光源的效果。當(dāng)組合使用時,你有無盡的選擇來增強你的3D場景。在使用燈源方面沒有正確或錯誤,只是玩弄他們得到你想要的效果。祝你好運和快樂使用燈光!
原文鏈接:https://solutiondesign.com/blog/-/blogs/webgl-and-three-js-lighting/