譯自:[http://www.romancortes.com/blog/1k-rose/](http://www.romancortes.com/blog/1k-rose/)
轉載請標明作者和出處:[http://blog.csdn.net/hfahe](http://blog.csdn.net/hfahe)

? ? ? 我曾參與[js1k](http://js1k.com/2012-love/)愛情主題的第四次活動(譯者注:關于有趣的js1k,可以看看我上一篇博文《[JS1k比賽與3D玫瑰](http://blog.csdn.net/hfahe/article/details/7249682)》)。我所提交的是一個靜態圖像,由程序生成的三維玫瑰。你可以在[這里](http://js1k.com/2012-love/demo/1022)看到它。
? ? ??它是通過顯式分段三維曲面的蒙特卡洛采樣所實現的。我要在這篇文章中嘗試解釋所有內容。
**關于蒙特卡羅方法的簡短說明**
? ? ??蒙特卡羅方法是令人難以置信的強大工具。我一直在使用它們來實現很多功能優化和采樣的問題。相比起設計和編寫算法,如果你有更多CPU時間的話,它們幾乎可以像變魔術一樣。在這個關于玫瑰的案例里,它對于代碼大小的優化非常有用。
? ? ??如果你對于蒙特卡羅方法了解不多,你可以讀讀[Wikipedia上一篇不錯的相關文章](http://en.wikipedia.org/wiki/Monte_carlo_method)。
**顯式的曲面和采樣/繪圖**
? ? ??我使用多個顯式定義的曲面來定義玫瑰的形狀。我總共使用了31個面:24片花瓣,4片萼片(花瓣周圍的薄葉),2片葉子以及玫瑰枝。
? ? ??那么它們是如何表示這些顯式曲面的呢?這非常容易,我將會提供一個二維的例子:
? ? ??首先,我定義了顯式曲面的函數:
~~~
function surface(a, b) { // 我使用0到1之間的a和b作為參數
return {
x: a*50,
y: b*50
};
// 曲面將會是50*50大小的一個正方形
}
~~~
? ? ??然后下面是繪制它的代碼:
~~~
var canvas = document.body.appendChild(document.createElement("canvas")),
context = canvas.getContext("2d"),
a, b, position;
// 現在我將要為a和b參數采用0.1間隔來進行曲面采樣
for (a = 0; a < 1; a += .1) {
for (b = 0; b < 1; b += .1) {
position = surface(a, b);
context.fillRect(position.x, position.y, 1, 1);
}
}
~~~
? ? ??下面是結果:

放大了4倍
? ? ??現在,讓我們嘗試更密集的采樣間隔(更小的間隔=更密集的采樣):

? ? ??正如你看到的一樣,因為你的樣例越來越密集,點越來越接近,當相鄰兩點的距離小于一個像素時,屏幕上區域已經被完全的充滿了(見0.01的圖)。之后使它更密集并不會造成太大的視覺差別,因為你只是在已經填滿的區域上繼續繪制(比較0.01和0.001的結果)。
? ? ??OK,現在讓我們重新定義曲面函數來畫一個圓。?有多種方法可以做到這一點,但我會用這個公式:(x-x0)^2 + (y-y0)^2 <radius^2,其中(x0, y0)?是圓心:
~~~
function surface(a, b) {
var x = a * 100,
y = b * 100,
radius = 50,
x0 = 50,
y0 = 50;
if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {
// 圓內
return {
x: x,
y: y
};
} else {
// 圓外
return null;
}
}
~~~
? ? ??因為不允許圓外有點,我應該添加在抽樣時添加條件:
~~~
if (position = surface(a, b)) {
context.fillRect(position.x, position.y, 1, 1);
}
~~~
? ? ??效果如下:

? ? ??正如我前面所說的一樣,有許多不同的方法來定義一個圓,其中一些并不需要拒絕抽樣。我將要展示一種方式,但只是作為一個提示,我不會在后面的文章中繼續采用:
~~~
function surface(a, b) {
var angle = a * Math.PI * 2,
radius = 50,
x0 = 50,
y0 = 50;
return {
x: Math.cos(angle) * radius * b + x0,
y: Math.sin(angle) * radius * b + y0
};
}
~~~

? ? ??(此方法比起之前的需要一個更密集的取樣來填充這個圓)
? ? ??好了,現在讓圓變形讓它看起來更像是一個花瓣:
~~~
function surface(a, b) {
var x = a * 100,
y = b * 100,
radius = 50,
x0 = 50,
y0 = 50;
if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {
return {
x: x,
y: y * (1 + b) / 2 // 變形
};
} else {
return null;
}
}
~~~
? ? ??效果如下:

? ? ??好了,現在這看起來很像一個玫瑰花瓣的形狀。我建議你??應用一點變形。您可以使用任何可以想得到的數學函數,例如加,減,乘,除,SIN,COS,開方......等等。只需要試驗修改一點函數,就會出現很多形狀(一些會更有趣,一些則不)。
? ? ??現在我想給它添加一些顏色,所以我要添加曲面顏色數據:
~~~
function surface(a, b) {
var x = a * 100,
y = b * 100,
radius = 50,
x0 = 50,
y0 = 50;
if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {
return {
x: x,
y: y * (1 + b) / 2,
r: 100 + Math.floor((1 - b) * 155), // 這將添加一個漸變
g: 50,
b: 50
};
} else {
return null;
}
}
for (a = 0; a < 1; a += .01) {
for (b = 0; b < 1; b += .001) {
if (point = surface(a, b)) {
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(point.x, point.y, 1, 1);
}
}
}
~~~
? ? ??效果如下:

? ? ??這里就是一個帶顏色的花瓣了!
**3D曲面和透視投影**
? ? ??定義三維曲面很簡單:只需要為曲面函數添加一個Z屬性。作為一個示例,我將要定義一個管道/圓柱體:
~~~
function surface(a, b) {
var angle = a * Math.PI * 2,
radius = 100,
length = 400;
return {
x: Math.cos(angle) * radius,
y: Math.sin(angle) * radius,
z: b * length - length / 2, // 通過剪掉lenght/2,我把管道的中心放在(0,0,0)
r: 0,
g: Math.floor(b * 255),
b: 0
};
}
~~~
? ? ??現在,為了添加透視投影,第一步我們要定義一個相機:

? ? ??我將把相機放置在坐標(0,0,cameraZ)上并把從相機到畫布的距離稱為“透視”。我會考慮到畫布是在X / Y平面,以(0,0,cameraZ +透視)為中心。現在每個采樣點將會被投影到畫布上:
~~~
var pX, pY, // 設計畫布x和y坐標
perspective = 350,
halfHeight = canvas.height / 2,
halfWidth = canvas.width / 2,
cameraZ = -700;
for (a = 0; a < 1; a += .001) {
for (b = 0; b < 1; b += .01) {
if (point = surface(a, b)) {
pX = (point.x * perspective) / (point.z - cameraZ) + halfWidth;
pY = (point.y * perspective) / (point.z - cameraZ) + halfHeight;
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(pX, pY, 1, 1);
}
}
}
~~~
? ? ??這將有如下的效果:

**Z-緩沖**
? ? ??Z-緩沖在計算機圖形學上是非常常見的技術,用于通過已經繪制過更遠的點來繪制離相機更近的點。它的工作原理是維持一個數組來記錄圖像上所有靠近z軸的點。

? ? ??這是可視化Z-緩沖的玫瑰,黑色表示遠離相機,白色表示靠近相機。
? ? ??實現如下:
~~~
var zBuffer = [],
zBufferIndex;
for (a = 0; a < 1; a += .001) {
for (b = 0; b < 1; b += .01) {
if (point = surface(a, b)) {
pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);
pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);
zBufferIndex = pY * canvas.width + pX;
if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {
zBuffer[zBufferIndex] = point.z;
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(pX, pY, 1, 1);
}
}
}
}
~~~
**旋轉圓柱體**
? ? ??你可以使用任何矢量旋轉的方法。在這個例子里,我使用[歐拉旋轉](http://en.wikipedia.org/wiki/Euler_angles)。讓我們來實現繞Y軸旋轉:
~~~
function surface(a, b) {
var angle = a * Math.PI * 2,
radius = 100,
length = 400,
x = Math.cos(angle) * radius,
y = Math.sin(angle) * radius,
z = b * length - length / 2,
yAxisRotationAngle = -.4, // in radians!
rotatedX = x * Math.cos(yAxisRotationAngle) + z * Math.sin(yAxisRotationAngle),
rotatedZ = x * -Math.sin(yAxisRotationAngle) + z * Math.cos(yAxisRotationAngle);
return {
x: rotatedX,
y: y,
z: rotatedZ,
r: 0,
g: Math.floor(b * 255),
b: 0
};
}
~~~
? ? ??效果如下:

**蒙特卡羅抽樣**
? ? ??我一直在這篇文章中使用基于間隔的采樣。它需要為每個曲面設置適當的時間間隔。如果間隔大,渲染的速度快,但是曲面上會出現未完全填充的洞。另一方面,如果時間間隔太小,渲染的時間會增長為一個驚人的數量。
? ? ??所以,讓我們切換到蒙特卡羅抽樣:
~~~
var i;
window.setInterval(function () {
for (i = 0; i < 10000; i++) {
if (point = surface(Math.random(), Math.random())) {
pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);
pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);
zBufferIndex = pY * canvas.width + pX;
if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {
zBuffer[zBufferIndex] = point.z;
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(pX, pY, 1, 1);
}
}
}
}, 0);
~~~
? ? ??現在A和B參數被設置為2個隨機值。通過足夠多點的采樣,我們填充了曲面。感謝采樣間隔,我可以每次繪制10000個點,然后讓屏幕刷新。
? ? ??另外需要說明的是,只有隨機數生成器足夠好才能保證曲面的充分填充。在一些瀏覽器中,Math.random通過[線性同余生成器](http://en.wikipedia.org/wiki/Linear_congruential_generator)來實現?,這可能會導致曲面產生一些問題。如果你需要一個好的PRNG采樣,可以使用一些高質量的方式例如Mersenne Twister(它有JS的實現),或者使用一些瀏覽器提供的密碼隨機生成器。使用[低差異序列](http://en.wikipedia.org/wiki/Low-discrepancy_sequence)也是非常可取的。
**最后的事項**
? ? ??要完成這個玫瑰,玫瑰的每個部分,每一個曲面,都會在同一時間統一進行渲染。我為選擇玫瑰一部分的函數添加另外一個參數來返回一個點。從數學來說這是一個分段函數,其中每一塊各代表了玫瑰的一部分。對花瓣來說,我用旋轉和伸展/變形來創建所有的花瓣。所有的一切都是通過文章中所介紹的概念來綜合實現的。
? ? ??雖然顯式曲面抽樣是一個相當有名,和3D圖形學最古老的方法之一,但是像我這樣藝術的使用分段/蒙特卡洛/Z-緩沖已經相當少見了。不算非常有創意,在現實生活場景中可能也不算有用,但它非常適合js1k這種要求簡單和文件大小的環境。
? ? ??我真的希望能通過這篇文章激發讀者對計算機圖形學的興趣,并樂于嘗試所有不同的渲染方式。圖形學里包含一整個世界,在其中進行探索和發揮是相當美妙的。
- 前言
- AutoPager的簡單實現
- 利用CSS3特性巧妙實現漂亮的DIV箭頭
- IE9在Win7下任務欄新特性簡介
- 瀏覽器九宮格的簡單實現
- Raphael js庫簡介
- 使用CSS3構建Ajax加載動畫
- 用CSS3創建動畫價格表
- 用CSS3實現瀏覽器的縮放功能
- 用純CSS3實現QQ LOGO
- 用CSS3創建旋轉載入器
- 使用Javascript開發移動應用程序
- 用HTML5創建超酷圖像灰度漸變效果
- 使用CSS3創建文字顏色漸變(CSS3 Text Gradient)
- 僅用CSS創建立體旋轉幻燈片
- 如何創建跨瀏覽器的HTML5表單
- 用CSS3實現動畫進度條
- HTML5 Guitar Tab Player
- 奇妙的HTML5 Canvas動畫實例
- 談HTML5和CSS3的國際化支持
- 實現跨瀏覽器的HTML5占位符
- 前端開發必備工具:WhatFont Bookmarklet-方便的查詢網頁上的字體
- 使用HTML5和CSS3來創建幻燈片
- HTML5之美
- 如何使用HTML5創建在線精美簡歷
- 以小見大、由淺入深-談如何面試Javascript工程師
- 快速入門:HTML5強大的Details元素
- 用CSS3實現圖像風格
- HTML5視頻字幕與WebVTT
- 用純CSS3實現Path華麗動畫
- 用3個步驟實現響應式網頁設計
- 遇見CSS3濾鏡
- 關于CSS3濾鏡的碎念
- 用純CSS3繪制萌系漫畫人物動態頭像
- CSS3新的鼠標樣式介紹
- 用HTML5獻上愛的3D玫瑰
- 對HTML5 Device API相關規范的解惑
- 如何使用HTML5實現拍照上傳應用
- 2012第一季度國外HTML5移動開發趨勢
- HTML5新特性:范圍樣式
- 百度開發者大會-《用HTML5新特性開發移動App》PPT分享
- Chrome 19對于HTML5最新支持的動態:電池狀態API,全屏API,震動API,語音API
- 遇見Javascript類型數組(Typed Array)
- 用HTML5 Audio API開發游戲音樂
- 用HTML5實現人臉識別
- 用Javascript實現人臉美容
- Chrome 20對于HTML5最新支持的動態:顏色輸入,網絡信息API,CSS著色器
- 用HTML5實現手機搖一搖的功能
- 用HTML5實現iPad應用無限平滑滾動
- 用非響應式設計構建跨端Web App
- 了解SVG
- HTML5圖像適配介紹
- HTML5安全:內容安全策略(CSP)簡介
- HTML5安全:CORS(跨域資源共享)簡介
- 用CSS3 Region和3D變換實現書籍翻頁效果
- 談談移動App的思維誤區
- Chrome新特性:文件夾拖拽支持
- 《關注HTML5安全》
- HTML5安全風險詳析之一:CORS攻擊
- HTML5安全風險詳析之二:Web Storage攻擊
- HTML5圖像適配最新進展:響應式圖片規范草案
- HTML5移動Web App相關標準狀態及路線圖
- HTML5安全風險詳析之三:WebSQL攻擊
- Chrome引入WebRTC支持視頻聊天App
- HTML5安全風險詳析之四:Web Worker攻擊
- HTML5安全風險詳析之五:劫持攻擊
- HTML5安全風險詳析之六:API攻擊
- HTML5安全攻防詳析之七:新標簽攻擊
- 在iOS Safari中播放離線音頻
- 使用WebRTC實現遠程屏幕共享
- Firefox、Android、iOS遇見WebRTC
- HTML5光線傳感器簡介
- HTML5安全攻防詳析之八:Web Socket攻擊
- HTML5安全攻防詳析之完結篇:HTML5對安全的改進
- 激動人心!在網頁上通過語音輸入文字 - HTML5 Web Speech API介紹
- Web滾動性能優化實戰
- 用CSS3設計響應式導航菜單
- 用HTML5構建高性能視差網站
- 漫談@supports與CSS3條件規則
- HTML5下載屬性簡介
- 如何開發優秀的HTML5游戲?-迪斯尼《尋找奧茲之路》游戲技術詳解(一)
- 如何開發優秀的HTML5游戲?-迪斯尼《尋找奧茲之路》游戲技術詳解(二)
- 趨勢:Chrome為打包應用提供強大新特性
- 從HTML5移動應用現狀談發展趨勢
- 基于HTML5的Web跨設備超聲波通信方案