<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                譯自:[http://www.romancortes.com/blog/1k-rose/](http://www.romancortes.com/blog/1k-rose/) 轉載請標明作者和出處:[http://blog.csdn.net/hfahe](http://blog.csdn.net/hfahe) ![](https://box.kancloud.cn/2016-08-09_57a9a2ddad2ee.gif) ? ? ? 我曾參與[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); } } ~~~ ? ? ??下面是結果: ![](https://box.kancloud.cn/2016-08-09_57a9a2dded1ac.gif) 放大了4倍 ? ? ??現在,讓我們嘗試更密集的采樣間隔(更小的間隔=更密集的采樣): ![](https://box.kancloud.cn/2016-08-09_57a9a2de0da19.gif) ? ? ??正如你看到的一樣,因為你的樣例越來越密集,點越來越接近,當相鄰兩點的距離小于一個像素時,屏幕上區域已經被完全的充滿了(見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); } ~~~ ? ? ??效果如下: ![](https://box.kancloud.cn/2016-08-09_57a9a2de22d79.gif) ? ? ??正如我前面所說的一樣,有許多不同的方法來定義一個圓,其中一些并不需要拒絕抽樣。我將要展示一種方式,但只是作為一個提示,我不會在后面的文章中繼續采用: ~~~ 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 }; } ~~~ ![](https://box.kancloud.cn/2016-08-09_57a9a2de22d79.gif) ? ? ??(此方法比起之前的需要一個更密集的取樣來填充這個圓) ? ? ??好了,現在讓圓變形讓它看起來更像是一個花瓣: ~~~ 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; } } ~~~ ? ? ??效果如下: ![](https://box.kancloud.cn/2016-08-09_57a9a2de40e67.gif) ? ? ??好了,現在這看起來很像一個玫瑰花瓣的形狀。我建議你??應用一點變形。您可以使用任何可以想得到的數學函數,例如加,減,乘,除,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); } } } ~~~ ? ? ??效果如下: ![](https://box.kancloud.cn/2016-08-09_57a9a2de51c52.jpg) ? ? ??這里就是一個帶顏色的花瓣了! **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 }; } ~~~ ? ? ??現在,為了添加透視投影,第一步我們要定義一個相機: ![](https://box.kancloud.cn/2016-08-09_57a9a2de64013.gif) ? ? ??我將把相機放置在坐標(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); } } } ~~~ ? ? ??這將有如下的效果: ![](https://box.kancloud.cn/2016-08-09_57a9a2de79137.jpg) **Z-緩沖** ? ? ??Z-緩沖在計算機圖形學上是非常常見的技術,用于通過已經繪制過更遠的點來繪制離相機更近的點。它的工作原理是維持一個數組來記錄圖像上所有靠近z軸的點。 ![](https://box.kancloud.cn/2016-08-09_57a9a2de8b536.jpg) ? ? ??這是可視化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 }; } ~~~ ? ? ??效果如下: ![](https://box.kancloud.cn/2016-08-09_57a9a2de9bf64.jpg) **蒙特卡羅抽樣** ? ? ??我一直在這篇文章中使用基于間隔的采樣。它需要為每個曲面設置適當的時間間隔。如果間隔大,渲染的速度快,但是曲面上會出現未完全填充的洞。另一方面,如果時間間隔太小,渲染的時間會增長為一個驚人的數量。 ? ? ??所以,讓我們切換到蒙特卡羅抽樣: ~~~ 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這種要求簡單和文件大小的環境。 ? ? ??我真的希望能通過這篇文章激發讀者對計算機圖形學的興趣,并樂于嘗試所有不同的渲染方式。圖形學里包含一整個世界,在其中進行探索和發揮是相當美妙的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看