threejs Light

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é)。

Lighting1.jpg

我們將主要關(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)境光的提示:

  1. 完整的移除它。
  2. 添加所有其他燈光。
  3. 如果場景的某些部分沒有被任何主燈照亮,那么只需添加足夠的環(huán)境光,使這些黑暗的角落可見。
  4. 如果仍然看起來不好,請回到第一步。

我們加上環(huán)境光就像這樣了:

Lighting2.jpg

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)有光源并且添加綠色和藍色點光源后,我們的場景如下所示:

Lighting3.jpg

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ù)。

  1. 角度是錐形將采取的角度,或者點將是多寬。
  2. 指數(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è)置成任意場景元素,比如球體。那樣,如果我們對球體進行動畫處理,聚光燈會自動跟蹤球體。事實上,我們將添加一點動畫到我們的聚光燈,你可以刷新瀏覽器看到如下:

Lighting4.jpg

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)控制。

刷新瀏覽器,可以看到如下場景:

Lighting5.jpg

區(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內(nèi)容