碼農(nóng)干貨系列【9】--javascript光線追蹤基礎(chǔ)
2013-03-12 16:31 【當(dāng)耐特】 閱讀(3716) 評(píng)論(4) 收藏 舉報(bào)簡(jiǎn)介
光線追蹤(ray tracing)(也叫raytracing或者光束投射法)是一個(gè)在二維(2D)屏幕上呈現(xiàn)三維(3D)圖像的方法。為了嘗試光線追蹤算法,并且盡可能得保證javascript代碼精煉,我做了一些嘗試。
射線與球體相交檢測(cè)
最開(kāi)始嘗試了射線與球體的相交檢測(cè)(不計(jì)算交點(diǎn)),只判斷相交還是未相交。代碼如下所示:
var Vector3 = function (x, y, z) { this.x = x; this.y = y; this.z = z; }; Vector3.prototype ={ dot: function (v) { return this.x * v.x + this.y * v.y + this.z * v.z; }, sub: function (v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); }, normalize: function () { return this.divideScalar(this.length()); }, divideScalar: function (s) { return new Vector3(this.x/s, this.y/s, this.z/s); }, length: function () { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); }, sqrDistanceToline:function(a,b){ var ab = b.sub(a), ac = this.sub(a), bc = this.sub(b); var e = ac.dot(ab.normalize()); var f = ac.length(); return f * f - e * e; } }
其中sqrDistanceToline為計(jì)算點(diǎn)到直線之間的距離的平方(開(kāi)跟號(hào)性能損耗大)。使用如下:
ball.p.sqrDistanceToline(v, camera.p) < sqrBallR
其中v是屏幕上的點(diǎn)的坐標(biāo),camera.p為視點(diǎn)的坐標(biāo),ball.p為球體中心坐標(biāo),sqrBallR為球體半徑的平方。當(dāng)上面返回true時(shí),判定為相交;反之亦然。
相交測(cè)試
for (var y = 0; y < canvas.height; y++) { for (var x = 0; x < canvas.width; x++) { var v = new Vector3(-canvas.width / 2 + x, canvas.height - y, 0); var cv = new Vector3(camera.p.y * v.x / (camera.p.y - v.y), 0, camera.p.z * v.y / (v.y - camera.p.y)); if (cv.z > -planeLength && cv.z < 0) { if (ball.p.sqrDistanceToline(v, camera.p) < sqrBallR) { pixels[i] = pixels[i + 1] = pixels[i + 2] = 111; } else { pixels[i] = pixels[i + 1] = pixels[i + 2] = (Math.ceil(cv.x / sideLength) + Math.ceil(cv.z / sideLength)) % 2 === 0 ? 148 : 0; } pixels[i + 3] = 255 * (planeLength - Math.abs(cv.z)) / planeLength; } i += 4; } }
由于沒(méi)有獲取交點(diǎn)坐標(biāo),無(wú)法計(jì)算視點(diǎn)到球體上點(diǎn)的距離,所以無(wú)法進(jìn)行球體深度渲染。所以得到了以下的圖像:
獲取交點(diǎn)
所以現(xiàn)在目的很明確,不僅要判定相交不相交,還需要找到交點(diǎn)的坐標(biāo)。當(dāng)然,上面的方法不是一無(wú)是處,可以進(jìn)行一些初步坐標(biāo)的篩選(如果性能好于找交點(diǎn)計(jì)算一個(gè)數(shù)量級(jí)的話,這個(gè)待測(cè)試)。那么怎么獲取射線與球體的交點(diǎn)呢?該點(diǎn)要滿足以下兩個(gè)條件:
1.交點(diǎn)在在光線上
x=S+dt2.交點(diǎn)在球上
|x-C|=r
C 表示球心,r 表示半徑,光線起點(diǎn)是 S,方向是 d(單位向量),交點(diǎn) x。
所有小球的代碼如下所示:
var Ball = function (p, r) { this.p = p; this.r = r; this.sqrR = this.r * this.r; } Ball.prototype = { intersect: function (p1, p2) { var v = p1.sub(this.p); var a0 = v.sqrLength() - this.sqrR; var np = p2.sub(p1).normalize(); var dotV = np.dot(v); if (dotV <= 0) { var discr = dotV * dotV - a0; if (discr >= 0) { return p1.add(np.multiplyScalar(-dotV - Math.sqrt(discr))); } } return null; } }
拿到了交點(diǎn)坐標(biāo),現(xiàn)在可以做深度渲染:
var result = ball.intersect(camera.p, screenP); if (result) { (pixels[i] = pixels[i + 1] = pixels[i + 2] = ((result.z - ball.p.z) / ball.r) * 255) pixels[i + 3] = 255; }
在線演示
修改里面的參數(shù)試一試!
參考文獻(xiàn)
用JavaScript玩轉(zhuǎn)計(jì)算機(jī)圖形學(xué)(一)光線追蹤入門
光線跟蹤 - 維基百科,自由的百科全書(shū)
Have Fun!

浙公網(wǎng)安備 33010602011771號(hào)