摘要:光照我們能看到物體,是因為光照射在物體上然后反射到我們的眼睛當中。這篇文章也是想通過這個簡單的光照計算來引出,后面的文章我會用來重新實現(xiàn)這個效果。渲染的光照效果關(guān)于我的博客這篇文章到這里就結(jié)束了。
光照
我們能看到物體,是因為光照射在物體上然后反射到我們的眼睛當中。其中的影響因素非常多:觀察者的位置、光源的位置、光的顏色、物體表面的顏色、材質(zhì)和粗糙程度等等。以后我們將會詳細探究如何模擬物體的材質(zhì),在這篇文章中我們只討論光源。
平行光源太陽的尺度相對地球來說非常大,所以可以認為從太陽照射來的光線都是平行的,即太陽是一個平行光源。
模擬平行光源的光照非常簡單,當光垂直照射到平面上,即光線方向和平面呈90度角時,這時光照是最強的。如果照射的角度不斷變大(或者說光線和平面的夾角不斷變小),光照也會隨之變?nèi)?,當光線方向完全和平面平行時,這時沒有光能照射到平面上,光強變成了0。
可以總結(jié)出,平行光的光照情況和兩個方向有關(guān):光線的方向和受光照平面的朝向。
我們用一個垂直于平面的向量去描述平面的朝向,在圖形學中,一般把這個向量稱為“法向量”。
我們可以用向量的“點乘”運算來計算光強變化。
點乘也叫數(shù)量積,是接受在實數(shù)R上的兩個向量并返回一個實數(shù)值標量的二元運算。點乘運算規(guī)則非常簡單,將兩個向量對應(yīng)坐標的乘積求和就行了。
這里我們計算的是三維向量,我們用數(shù)組來表示向量,寫一個簡單的方法來計算點乘:
/** * 點乘運算 * @param {Array} v1 向量v1 * @param {Array } v2 向量v2 * @return {number} 點乘結(jié)果 */ function dot( v1, v2 ) { return v1[ 0 ] * v2[ 0 ] + v1[ 1 ] * v2[ 1 ] + v1[ 2 ] * v2[ 2 ]; }
還有幾個重要的向量運算我們也會用到,在這里我們提前定義好,為減小篇幅,這里省略掉具體實現(xiàn),代碼可以看最后的實例源碼。
/** * 將向量轉(zhuǎn)為單位向量 * @param {Array} v * @return {Array } 單位向量 */ function normalize( v ) { /* ... */ } /** * 兩向量相減 * @param {Array } v1 * @param {Array } v2 * @return {Array } */ function sub( v1, v2 ) { /* ... */ } /** * 計算一個向量的反方向向量 * @param {Array } v * @return {Array } */ function negate( v ) { /* ... */ }
我們假設(shè)頁面的左上角為原點O,右方向為x軸正方向,下方向為y軸正方向,垂直屏幕向外的方向為z軸正方向。我們可以這樣定義一個寬高都為500的平面:
var plane = { center: [ 250, 250, 0 ], // 平面中心點坐標 width: 500, // 寬 height: 500, // 高 normal: [ 0, 0, 1 ], // 朝向,即法向量 color: { r: 255, g: 0, b: 0 } // 顏色為紅色 }
對于平行光,只需要關(guān)心它的方向和顏色,我們可以這樣來定義一個平行光源:
var directionalLight = { direction: [ 0, 0, -1 ], // 從屏幕外垂直照向屏幕 color: { r: 255, g: 255, b: 255 } // 顏色為純白色 }
平行光的光線都是平行的,所以它照射到平面上各個位置的效果都是一樣的,換言之,整個平面都應(yīng)該是同一個顏色。
根據(jù)上面的規(guī)則(光強等于光線反方向向量點乘平面法向量),我們可以計算出這個顏色:
// ... var reverseLightDirection = negate( directionalLight.direction ); // 計算平行光的反方向向量 var intensity = dot( reverseLightDirection, plane.normal ); // 計算兩向量點乘 // 計算有光照時的顏色 var color = { r: intensity * plane.color.r + intensity * directionalLight.r, g: intensity * plane.color.g + intensity * directionalLight.g, b: intensity * plane.color.b + intensity * directionalLight.g, } var canvas = document.getElementById( "canvas" ); var ctx = canvas.getElementById( "2d" ); ctx.rect( plane.center[ 0 ], plane.center[ 1 ], plane.width, plane.height ); ctx.fillStyle = "rgb(" + color.r + "," + color.g + "," + color.b ")"; ctx.fill();
我寫了一個示例,可以調(diào)整光線方向來觀察不同方向下的光照效果。
在線運行示例
在日常生活中,點光源更加常見,白熾燈、臺燈等都可以認為是點光源。
首先,我們先定義一個點光源,對于一個點光源來說,我們只需要關(guān)心它的位置和顏色:
var pointLight = { position: [ 250, 250, 100 ], // 光源位于平面中心上方100處 color: { r: 255, g: 255, b: 255 } // 顏色為純白色 }
光強的計算規(guī)則仍然不變:光強等于光線反方向向量點乘平面法向量。但是點光源的光是從一個點發(fā)射出來,它們照射到平面上時,所有光線的方向都不一樣。所以,我們必須挨個計算平面上所有像素的光強。
這里需要用到canvas提供的putImageData,這個方法可以直接填入一個區(qū)域的像素顏色值來繪圖。代碼如下:
// ... var imageData = ctx.createImageData( 500, 500 ); // 創(chuàng)建一個ImageData,用來保存像素數(shù)據(jù) for ( var x = 0; x < imageData.width; x++ ) { for ( var y = 0; y < imageData.height; y++ ) { var index = y * imageData.width + x; // 當前計算的像素點的索引 var point = [ x, y, 0 ]; var normal = [ 0, 0, 1 ]; var reverseLightDirection = normalize( sub( pointLight.position, point ) ); // 光線方向的反方向向量 var light = dot( reverseLightDirection, normal ); imageData.data[ index * 4 ] = pointLight.color.r * intensity + plane.color.r * intensity; imageData.data[ index * 4 + 1 ] = pointLight.color.g * intensity + plane.color.g * intensity; imageData.data[ index * 4 + 2 ] = pointLight.color.b * intensity + plane.color.b * intensity; imageData.data[ index * 4 + 3 ] = 255; } } ctx.putImageData( imageData, 100, 100 );
這樣就可以看到結(jié)果了:
我寫了一個更復雜一點的例子,可以通過鼠標去移動光源,滑動滾輪來改變光源高度:
在線運行示例
動態(tài)圖看起來有很多圈圈,實際上并沒有,可以自己玩一下
WebGL的優(yōu)勢對于一個500*500的平面,我們?nèi)ビ嬎闼邳c光源光照下的顏色,需要挨個計算平面上所有點,需要循環(huán)500*500=250000次,這其實是非常低效的。并且在做復雜場景的渲染時,不會只有一個光源,而且還會有投影等計算,計算量將會非常大。
從更底層的角度來說,這是因為每次計算都是由CPU完成的,而CPU只能串行計算,它只能完成一個計算以后才能開始下一次計算,所以非常緩慢。
這種復雜的渲染其實更適合用WebGL來做,因為每一次計算其實前后無關(guān),WebGL可以利用GPU的并行計算能力,同時去計算所有點的光照強度。一個500*500的平面,理論上只需要花一次計算的時間,這個提升是非常大的。
這篇文章也是想通過這個簡單的光照計算來引出WebGL,后面的文章我會用WebGL來重新實現(xiàn)這個效果。
WebGL渲染的光照效果
這篇文章到這里就結(jié)束了。
我計劃寫一系列關(guān)于前端圖形渲染的文章,將會涵蓋常用的前端圖形繪制技術(shù):canvas、svg和WebGL。希望通過這一系列文章能讓讀者對前端的各種圖形繪制接口以及圖像處理、圖形學的基礎(chǔ)知識有所了解。希望在分享的同時,也能鞏固和復習自己所學知識,和大家共同進步。
系列博客地址:https://github.com/hujiulong/...
如果能幫助到你,歡迎star,這樣也能及時追蹤博客的更新。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90764.html
摘要:對于自身不能發(fā)光的物體,需要給場景添加光源從而達到可視的效果。中渲染陰影的開銷比較大,所以默認物體是沒有陰影的,需要單獨開啟。主要用于檢測動畫運行時的幀數(shù),可以顯示表示每秒多少幀和每幀多少毫秒,越大越好,但太大會影響性能,一般為左右。 一、WebGL 與 three.js WebGL(Web Graphics Library)是一種3D繪圖協(xié)議,它允許把JavaScript和OpenG...
摘要:次時代傳統(tǒng)的方式就是創(chuàng)建次時代模型,對應(yīng)中的材質(zhì)是高光網(wǎng)格材質(zhì)對象,通常貼圖文件包含顏色貼圖法線貼圖和高光貼圖。 產(chǎn)品在線展示案例預覽 玉鐲在線預覽:http://www.yanhuangxueyuan.co... 汽車在線預覽:http://www.yanhuangxueyuan.co... Web3D技術(shù)歷史 可通過插件或WebGL技術(shù)實現(xiàn)Web3D,在線網(wǎng)頁上預覽操作三維...
摘要:上帝覺得缺少了些生氣,便用泥巴捏了一個小人兒,不叫亞當,她叫小芳。接下來預先恭喜你,你可以成為這網(wǎng)頁世界的一個小上帝。使用可以向場景中發(fā)射光線。在下述案例中,從攝像機的位置向場景中鼠標的點擊位置發(fā)射光線。 先得擺出幾個關(guān)鍵詞:場景、燈光、模型、材質(zhì)、貼圖與紋理、相機、渲染器。然后我開始裝模作樣地解釋: 上帝說,要有場景!于是就有了場景,場景去納這萬事萬物。 上帝說,要有光!于是就有了光...
閱讀 2993·2023-04-25 17:22
閱讀 1559·2019-08-30 15:54
閱讀 1289·2019-08-30 15:53
閱讀 1809·2019-08-30 15:43
閱讀 3068·2019-08-29 12:29
閱讀 1248·2019-08-26 11:37
閱讀 3284·2019-08-23 18:02
閱讀 1621·2019-08-23 14:15