<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 第十八課: Billboard和粒子 # 第十八課:Billbard和粒子 公告板是3D世界中的2D元素。它既不是最頂層的2D菜單,也不是可以隨意轉動的3D平面,而是介于兩者之間的一種元素,比如游戲中的血條。 公告板的獨特之處在于:它位于某個特定位置,朝向是自動計算的,這樣它就能始終面向相機(觀察者)。 ## 方案1:2D法 2D法十分簡單。只需計算出點在屏幕空間的坐標,然后在該處顯示2D文本(參見第十一課)即可。 ``` <pre class="calibre16">``` <span class="token2">// Everything here is explained in Tutorial 3 ! There's nothing new.</span> glm<span class="token1">:</span><span class="token1">:</span>vec4 <span class="token3">BillboardPos_worldspace</span><span class="token1">(</span>x<span class="token1">,</span>y<span class="token1">,</span>z<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token1">)</span><span class="token1">;</span> glm<span class="token1">:</span><span class="token1">:</span>vec4 BillboardPos_screenspace <span class="token">=</span> ProjectionMatrix <span class="token">*</span> ViewMatrix <span class="token">*</span> BillboardPos_worldspace<span class="token1">;</span> BillboardPos_screenspace <span class="token">/</span><span class="token">=</span> BillboardPos_screenspace<span class="token1">.</span>w<span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>BillboardPos_screenspace<span class="token1">.</span>z <span class="token"><</span> <span class="token6">0.0</span>f<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Object is behind the camera, don't display it.</span> <span class="token1">}</span> ``` ``` 就這么搞定了! 2D法優點是簡單易行,無論點與相機距離遠近,公告板始終保持大小不變。但此法總是把文本顯示在最頂層,有可能會遮擋其他物體,影響渲染效果。 ## 方案2:3D法 與2D法相比,3D法常常效果更好,也沒復雜多少。我們的目的就是無論相機如何移動,都要讓公告板網格正對著相機: ![](https://box.kancloud.cn/2015-11-02_5636f30b3cb20.gif) 可將此視為模型矩陣的構造問題之簡化版。基本思路是將公告板的各角落置于 (存疑待查)The idea is that each corner of the billboard is at the center position, displaced by the camera’s up and right vectors : ![](https://box.kancloud.cn/2015-11-02_5636f30b719ca.png) 當然,我們僅僅知道世界空間中的公告板中心位置,因此還需要相機在世界空間中的up/right向量。 在相機空間,相機的up向量為(0,1,0)。要把up向量變換到世界空間,只需乘以觀察矩陣的逆矩陣(由相機空間變換至世界空間的矩陣)。 用數學公式表示即: CameraRight\_worldspace = {ViewMatrix\[0\]\[0\], ViewMatrix\[1\]\[0\], ViewMatrix\[2\]\[0\]}CameraUp\_worldspace = {ViewMatrix\[0\]\[1\], ViewMatrix\[1\]\[1\], ViewMatrix\[2\]\[1\]} 接下來,頂點坐標的計算就很簡單了: ``` <pre class="calibre16">``` vec3 vertexPosition_worldspace <span class="token">=</span> particleCenter_wordspace <span class="token">+</span> CameraRight_worldspace <span class="token">*</span> squareVertices<span class="token1">.</span>x <span class="token">*</span> BillboardSize<span class="token1">.</span>x <span class="token">+</span> CameraUp_worldspace <span class="token">*</span> squareVertices<span class="token1">.</span>y <span class="token">*</span> BillboardSize<span class="token1">.</span>y<span class="token1">;</span> ``` ``` - `particleCenter_worldspace`顧名思義即公告板的中心位置,以vec3類型的uniform變量表示。 - `squareVertices`是原始的網格。左頂點的`squareVertices.x`為-0.5(存疑待查),which are thus moved towars the left of the camera (because of the \*CameraRight\_worldspace) - `BillboardSize`是公告板大小,以世界單位為單位,uniform變量。 效果如下。怎么樣,是不是很簡單? ![](https://box.kancloud.cn/2015-11-02_5636f30b81ed3.gif) 為了保證內容完整性,這里給出`squareVertices`的數據: ``` <pre class="calibre16">``` <span class="token2">// The VBO containing the 4 vertices of the particles.</span> static const GLfloat g_vertex_buffer_data<span class="token1">[</span><span class="token1">]</span> <span class="token">=</span> <span class="token1">{</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token1">}</span><span class="token1">;</span> ``` ``` ## 方案3:固定大小3D法 正如上面所看到的,公告板大小隨著相機與之的距離變化。有些情況下的確需要這樣的效果,但血條這類公告板則需要保持大小不變。 ``` <pre class="calibre16">``` vertexPosition_worldspace <span class="token">=</span> particleCenter_wordspace<span class="token1">;</span> <span class="token2">// Get the screen-space position of the particle's center</span> gl_Position <span class="token">=</span> VP <span class="token">*</span> <span class="token3">vec4</span><span class="token1">(</span>vertexPosition_worldspace<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Here we have to do the perspective division ourselves.</span> gl_Position <span class="token">/</span><span class="token">=</span> gl_Position<span class="token1">.</span>w<span class="token1">;</span> <span class="token2">// Move the vertex in directly screen space. No need for CameraUp/Right_worlspace here.</span> gl_Position<span class="token1">.</span>xy <span class="token">+</span><span class="token">=</span> squareVertices<span class="token1">.</span>xy <span class="token">*</span> <span class="token3">vec2</span><span class="token1">(</span><span class="token6">0.2</span><span class="token1">,</span> <span class="token6">0.05</span><span class="token1">)</span><span class="token1">;</span> ``` ``` ![](https://box.kancloud.cn/2015-11-02_5636f30bb2797.gif) ## 方案4:限制垂直旋轉法 一些引擎以公告板表示遠處的樹和燈。不過,這些樹可不能任意轉向,**必須**是豎直的。So you need an hybrid system that rotates only around one axis.(存疑待查) 這個方案作為練習留給讀者。 # 粒子(Particles)與實例(Instancing) 粒子與3D公告板很類似。不過,粒子有如下四個特點: - 數量較大 - 可以運動 - 有生有死 - 半透明 伴隨這些特點而來的是一系列問題。本課僅介紹**其中一種**解決方案,其他解決方案還多著呢…… ## 一大波粒子正在接近中…… 首先想到的思路就是套用上一課的代碼,調用`glDrawArrays`逐個繪制粒子。這可不是個好辦法。因為這種思路意味著你那锃光瓦亮的GTX 512顯卡一次只能繪制**一個**四邊形(很明顯,性能損失高達99%)。就這么一個接一個地繪制公告板。 顯然,我們得一次性繪制所有的粒子。 方法有很多種,如下是其中三種: - 生成一個VBO,將所有粒子置于其中。簡單,有效,在各種平臺上均可行。 - 使用geometry shader。這不在本教程范圍內,主要是因為50%的機器不支持該特性。 - 使用實例(instancing)。大部分機器都支持該特性。 本課將采用第三種方法。這種方法兼具性能優勢和普適性,更重要的是,如果此法行得通,那第一種方法也就輕而易舉了。 ## 實例 “實例”的意思是以一個網格(比如本課中由兩個三角形組成的四邊形)為藍本,創建多個該網格的實例。 具體地講,我們通過如下一些buffer實現instancing: - 一部分用于描述原始網格 - 一部分用于描述各實例的特性 這些buffer的內容可自行選擇。在我們這個簡單的例子包含了: - 一個網格頂點buffer。沒有index buffer,因此一共有6個`vec3`變量,構成兩個三角形,進而組合成一個四邊形。 - 一個buffer存儲粒子的中心。 - 一個buffer存儲粒子的顏色。 這些buffer都是標準buffer。創建方式如下: ``` <pre class="calibre16">``` <span class="token2">// The VBO containing the 4 vertices of the particles.</span> <span class="token2">// Thanks to instancing, they will be shared by all particles.</span> static const GLfloat g_vertex_buffer_data<span class="token1">[</span><span class="token1">]</span> <span class="token">=</span> <span class="token1">{</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.5</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span> <span class="token1">}</span><span class="token1">;</span> GLuint billboard_vertex_buffer<span class="token1">;</span> <span class="token3">glGenBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>billboard_vertex_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> billboard_vertex_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> <span class="token3">sizeof</span><span class="token1">(</span>g_vertex_buffer_data<span class="token1">)</span><span class="token1">,</span> g_vertex_buffer_data<span class="token1">,</span> GL_STATIC_DRAW<span class="token1">)</span><span class="token1">;</span> <span class="token2">// The VBO containing the positions and sizes of the particles</span> GLuint particles_position_buffer<span class="token1">;</span> <span class="token3">glGenBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>particles_position_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_position_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Initialize with empty (NULL) buffer : it will be updated later, each frame.</span> <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> MaxParticles <span class="token">*</span> <span class="token6">4</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLfloat<span class="token1">)</span><span class="token1">,</span> NULL<span class="token1">,</span> GL_STREAM_DRAW<span class="token1">)</span><span class="token1">;</span> <span class="token2">// The VBO containing the colors of the particles</span> GLuint particles_color_buffer<span class="token1">;</span> <span class="token3">glGenBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>particles_color_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_color_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Initialize with empty (NULL) buffer : it will be updated later, each frame.</span> <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> MaxParticles <span class="token">*</span> <span class="token6">4</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLubyte<span class="token1">)</span><span class="token1">,</span> NULL<span class="token1">,</span> GL_STREAM_DRAW<span class="token1">)</span><span class="token1">;</span> ``` ``` 粒子更新方法如下: ``` <pre class="calibre16">``` <span class="token2">// Update the buffers that OpenGL uses for rendering.</span> <span class="token2">// There are much more sophisticated means to stream data from the CPU to the GPU,</span> <span class="token2">// but this is outside the scope of this tutorial.</span> <span class="token2">// http://www.opengl.org/wiki/Buffer_Object_Streaming</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_position_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> MaxParticles <span class="token">*</span> <span class="token6">4</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLfloat<span class="token1">)</span><span class="token1">,</span> NULL<span class="token1">,</span> GL_STREAM_DRAW<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Buffer orphaning, a common way to improve streaming perf. See above link for details.</span> <span class="token3">glBufferSubData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> ParticlesCount <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLfloat<span class="token1">)</span> <span class="token">*</span> <span class="token6">4</span><span class="token1">,</span> g_particule_position_size_data<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_color_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> MaxParticles <span class="token">*</span> <span class="token6">4</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLubyte<span class="token1">)</span><span class="token1">,</span> NULL<span class="token1">,</span> GL_STREAM_DRAW<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Buffer orphaning, a common way to improve streaming perf. See above link for details.</span> <span class="token3">glBufferSubData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> ParticlesCount <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>GLubyte<span class="token1">)</span> <span class="token">*</span> <span class="token6">4</span><span class="token1">,</span> g_particule_color_data<span class="token1">)</span><span class="token1">;</span> ``` ``` 繪制之前還需綁定buffer。綁定方法如下: ``` <pre class="calibre16">``` <span class="token2">// 1rst attribute buffer : vertices</span> <span class="token3">glEnableVertexAttribArray</span><span class="token1">(</span><span class="token6">0</span><span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> billboard_vertex_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glVertexAttribPointer</span><span class="token1">(</span> <span class="token6">0</span><span class="token1">,</span> <span class="token2">// attribute. No particular reason for 0, but must match the layout in the shader.</span> <span class="token6">3</span><span class="token1">,</span> <span class="token2">// size</span> GL_FLOAT<span class="token1">,</span> <span class="token2">// type</span> GL_FALSE<span class="token1">,</span> <span class="token2">// normalized?</span> <span class="token6">0</span><span class="token1">,</span> <span class="token2">// stride</span> <span class="token1">(</span>void<span class="token">*</span><span class="token1">)</span><span class="token6">0</span> <span class="token2">// array buffer offset</span> <span class="token1">)</span><span class="token1">;</span> <span class="token2">// 2nd attribute buffer : positions of particles' centers</span> <span class="token3">glEnableVertexAttribArray</span><span class="token1">(</span><span class="token6">1</span><span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_position_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glVertexAttribPointer</span><span class="token1">(</span> <span class="token6">1</span><span class="token1">,</span> <span class="token2">// attribute. No particular reason for 1, but must match the layout in the shader.</span> <span class="token6">4</span><span class="token1">,</span> <span class="token2">// size : x + y + z + size => 4</span> GL_FLOAT<span class="token1">,</span> <span class="token2">// type</span> GL_FALSE<span class="token1">,</span> <span class="token2">// normalized?</span> <span class="token6">0</span><span class="token1">,</span> <span class="token2">// stride</span> <span class="token1">(</span>void<span class="token">*</span><span class="token1">)</span><span class="token6">0</span> <span class="token2">// array buffer offset</span> <span class="token1">)</span><span class="token1">;</span> <span class="token2">// 3rd attribute buffer : particles' colors</span> <span class="token3">glEnableVertexAttribArray</span><span class="token1">(</span><span class="token6">2</span><span class="token1">)</span><span class="token1">;</span> <span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> particles_color_buffer<span class="token1">)</span><span class="token1">;</span> <span class="token3">glVertexAttribPointer</span><span class="token1">(</span> <span class="token6">2</span><span class="token1">,</span> <span class="token2">// attribute. No particular reason for 1, but must match the layout in the shader.</span> <span class="token6">4</span><span class="token1">,</span> <span class="token2">// size : r + g + b + a => 4</span> GL_UNSIGNED_BYTE<span class="token1">,</span> <span class="token2">// type</span> GL_TRUE<span class="token1">,</span> <span class="token2">// normalized? *** YES, this means that the unsigned char[4] will be accessible with a vec4 (floats) in the shader ***</span> <span class="token6">0</span><span class="token1">,</span> <span class="token2">// stride</span> <span class="token1">(</span>void<span class="token">*</span><span class="token1">)</span><span class="token6">0</span> <span class="token2">// array buffer offset</span> <span class="token1">)</span><span class="token1">;</span> ``` ``` 繪制方法與以往有所不同。這次不使用`glDrawArrays`或者`glDrawElements`(如果原始網格有index buffer的話)。這次用的是`glDrawArraysInstanced`或者`glDrawElementsInstanced`,效果等同于調用`glDrawArrays`N次(N是最后一個參數,此例中即`ParticlesCount`)。 ``` <pre class="calibre16">``` <span class="token3">glDrawArraysInstanced</span><span class="token1">(</span>GL_TRIANGLE_STRIP<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> <span class="token6">4</span><span class="token1">,</span> ParticlesCount<span class="token1">)</span><span class="token1">;</span> ``` ``` 有件事差點忘了。我們還沒告訴OpenGL哪個buffer是原始網格,哪些buffer是各實例的特性。調用`glVertexAttribDivisor`即可完成。有完整注釋的代碼如下: ``` <pre class="calibre16">``` <span class="token2">// These functions are specific to glDrawArrays*Instanced*.</span> <span class="token2">// The first parameter is the attribute buffer we're talking about.</span> <span class="token2">// The second parameter is the "rate at which generic vertex attributes advance when rendering multiple instances"</span> <span class="token2">// http://www.opengl.org/sdk/docs/man/xhtml/glVertexAttribDivisor.xml</span> <span class="token3">glVertexAttribDivisor</span><span class="token1">(</span><span class="token6">0</span><span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// particles vertices : always reuse the same 4 vertices -> 0</span> <span class="token3">glVertexAttribDivisor</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token6">1</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// positions : one per quad (its center) -> 1</span> <span class="token3">glVertexAttribDivisor</span><span class="token1">(</span><span class="token6">2</span><span class="token1">,</span> <span class="token6">1</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// color : one per quad -> 1</span> <span class="token2">// Draw the particules !</span> <span class="token2">// This draws many times a small triangle_strip (which looks like a quad).</span> <span class="token2">// This is equivalent to :</span> <span class="token2">// for(i in ParticlesCount) : glDrawArrays(GL_TRIANGLE_STRIP, 0, 4),</span> <span class="token2">// but faster.</span> <span class="token3">glDrawArraysInstanced</span><span class="token1">(</span>GL_TRIANGLE_STRIP<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> <span class="token6">4</span><span class="token1">,</span> ParticlesCount<span class="token1">)</span><span class="token1">;</span> ``` ``` 如你所見,instancing是很靈活的,你可以將`AttribDivisor`設為任意整數。例如,'glVertexAttribDivisor(2, 10)'即設置后續10個實例都擁有相同的顏色。 ## 意義何在? 意義在于如今我們只需在每幀中更新一個很小的buffer(粒子中心位置),而非整個網格。如此一來,帶寬利用效率提升了4倍。 ## 生與死 于場景中其它對象不同的是,粒子的生死更替十分頻繁。我們得用一種速度相當快的方式來創建新粒子,拋棄舊粒子。`new Particle()`這種辦法顯然不夠好。 ## 創建新粒子 首先得創建一個大的粒子容器: ``` <pre class="calibre16">``` <span class="token2">// CPU representation of a particle</span> struct Particle<span class="token1">{</span> glm<span class="token1">:</span><span class="token1">:</span>vec3 pos<span class="token1">,</span> speed<span class="token1">;</span> unsigned char r<span class="token1">,</span>g<span class="token1">,</span>b<span class="token1">,</span>a<span class="token1">;</span> <span class="token2">// Color</span> float size<span class="token1">,</span> angle<span class="token1">,</span> weight<span class="token1">;</span> float life<span class="token1">;</span> <span class="token2">// Remaining life of the particle. if < 0 : dead and unused.</span> <span class="token1">}</span><span class="token1">;</span> const int MaxParticles <span class="token">=</span> <span class="token6">100000</span><span class="token1">;</span> Particle ParticlesContainer<span class="token1">[</span>MaxParticles<span class="token1">]</span><span class="token1">;</span> ``` ``` 接下來,我們得想辦法創建新粒子。如下的函數在`ParticleContainer`中線性搜索(聽起來有些暴力)新粒子。不過,它是從上次已知位置開始搜索的,因此一般很快就返回了。 ``` <pre class="calibre16">``` int LastUsedParticle <span class="token">=</span> <span class="token6">0</span><span class="token1">;</span> <span class="token2">// Finds a Particle in ParticlesContainer which isn't used yet.</span> <span class="token2">// (i.e. life < 0);</span> int <span class="token3">FindUnusedParticle</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span> <span class="token4">for</span><span class="token1">(</span>int i<span class="token">=</span>LastUsedParticle<span class="token1">;</span> i<span class="token"><</span>MaxParticles<span class="token1">;</span> i<span class="token">++</span><span class="token1">)</span><span class="token1">{</span> <span class="token4">if</span> <span class="token1">(</span>ParticlesContainer<span class="token1">[</span>i<span class="token1">]</span><span class="token1">.</span>life <span class="token"><</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">{</span> LastUsedParticle <span class="token">=</span> i<span class="token1">;</span> <span class="token4">return</span> i<span class="token1">;</span> <span class="token1">}</span> <span class="token1">}</span> <span class="token4">for</span><span class="token1">(</span>int i<span class="token">=</span><span class="token6">0</span><span class="token1">;</span> i<span class="token"><</span>LastUsedParticle<span class="token1">;</span> i<span class="token">++</span><span class="token1">)</span><span class="token1">{</span> <span class="token4">if</span> <span class="token1">(</span>ParticlesContainer<span class="token1">[</span>i<span class="token1">]</span><span class="token1">.</span>life <span class="token"><</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">{</span> LastUsedParticle <span class="token">=</span> i<span class="token1">;</span> <span class="token4">return</span> i<span class="token1">;</span> <span class="token1">}</span> <span class="token1">}</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span> <span class="token2">// All particles are taken, override the first one</span> <span class="token1">}</span> ``` ``` 現在我們可以把`ParticlesContainer[particleIndex]`當中的`life`、`color`、`speed`和`position`設置成一些有趣的值。欲知詳情請看代碼,此處大有文章可作。我們比較關心的是每一幀中要生成多少粒子。這跟具體的應用有關,我們就設為每秒10000個(噢噢,略多啊)新粒子好了: ``` <pre class="calibre16">``` int newparticles <span class="token">=</span> <span class="token1">(</span>int<span class="token1">)</span><span class="token1">(</span>deltaTime<span class="token">*</span><span class="token6">10000.0</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 記得把個數限定在一個固定范圍內: ``` <pre class="calibre16">``` <span class="token2">// Generate 10 new particule each millisecond,</span> <span class="token2">// but limit this to 16 ms (60 fps), or if you have 1 long frame (1sec),</span> <span class="token2">// newparticles will be huge and the next frame even longer.</span> int newparticles <span class="token">=</span> <span class="token1">(</span>int<span class="token1">)</span><span class="token1">(</span>deltaTime<span class="token">*</span><span class="token6">10000.0</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>newparticles <span class="token">></span> <span class="token1">(</span>int<span class="token1">)</span><span class="token1">(</span><span class="token6">0.016</span>f<span class="token">*</span><span class="token6">10000.0</span><span class="token1">)</span><span class="token1">)</span> newparticles <span class="token">=</span> <span class="token1">(</span>int<span class="token1">)</span><span class="token1">(</span><span class="token6">0.016</span>f<span class="token">*</span><span class="token6">10000.0</span><span class="token1">)</span><span class="token1">;</span> ``` ``` ## 刪除舊粒子 這個需要一些技巧,參見下文=) ## 仿真主循環 `ParticlesContainer`同時容納了“活著的”和“死亡的”粒子,但發送到GPU的buffer僅含活著的粒子。 所以,我們要遍歷每個粒子,看它是否是活著的,是否應該“處死”。如果一切正常,那就添加重力,最后將其拷貝到GPU上相應的buffer中。 ``` <pre class="calibre16">``` <span class="token2">// Simulate all particles</span> int ParticlesCount <span class="token">=</span> <span class="token6">0</span><span class="token1">;</span> <span class="token4">for</span><span class="token1">(</span>int i<span class="token">=</span><span class="token6">0</span><span class="token1">;</span> i<span class="token"><</span>MaxParticles<span class="token1">;</span> i<span class="token">++</span><span class="token1">)</span><span class="token1">{</span> Particle<span class="token">&</span> p <span class="token">=</span> ParticlesContainer<span class="token1">[</span>i<span class="token1">]</span><span class="token1">;</span> <span class="token2">// shortcut</span> <span class="token4">if</span><span class="token1">(</span>p<span class="token1">.</span>life <span class="token">></span> <span class="token6">0.0</span>f<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Decrease life</span> p<span class="token1">.</span>life <span class="token">-</span><span class="token">=</span> delta<span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>p<span class="token1">.</span>life <span class="token">></span> <span class="token6">0.0</span>f<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Simulate simple physics : gravity only, no collisions</span> p<span class="token1">.</span>speed <span class="token">+</span><span class="token">=</span> glm<span class="token1">:</span><span class="token1">:</span><span class="token3">vec3</span><span class="token1">(</span><span class="token6">0.0</span>f<span class="token1">,</span><span class="token">-</span><span class="token6">9.81</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">)</span> <span class="token">*</span> <span class="token1">(</span>float<span class="token1">)</span>delta <span class="token">*</span> <span class="token6">0.5</span>f<span class="token1">;</span> p<span class="token1">.</span>pos <span class="token">+</span><span class="token">=</span> p<span class="token1">.</span>speed <span class="token">*</span> <span class="token1">(</span>float<span class="token1">)</span>delta<span class="token1">;</span> p<span class="token1">.</span>cameradistance <span class="token">=</span> glm<span class="token1">:</span><span class="token1">:</span><span class="token3">length2</span><span class="token1">(</span> p<span class="token1">.</span>pos <span class="token">-</span> CameraPosition <span class="token1">)</span><span class="token1">;</span> <span class="token2">//ParticlesContainer[i].pos += glm::vec3(0.0f,10.0f, 0.0f) * (float)delta;</span> <span class="token2">// Fill the GPU buffer</span> g_particule_position_size_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">0</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>pos<span class="token1">.</span>x<span class="token1">;</span> g_particule_position_size_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">1</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>pos<span class="token1">.</span>y<span class="token1">;</span> g_particule_position_size_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">2</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>pos<span class="token1">.</span>z<span class="token1">;</span> g_particule_position_size_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">3</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>size<span class="token1">;</span> g_particule_color_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">0</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>r<span class="token1">;</span> g_particule_color_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">1</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>g<span class="token1">;</span> g_particule_color_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">2</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>b<span class="token1">;</span> g_particule_color_data<span class="token1">[</span><span class="token6">4</span><span class="token">*</span>ParticlesCount<span class="token">+</span><span class="token6">3</span><span class="token1">]</span> <span class="token">=</span> p<span class="token1">.</span>a<span class="token1">;</span> <span class="token1">}</span><span class="token4">else</span><span class="token1">{</span> <span class="token2">// Particles that just died will be put at the end of the buffer in SortParticles();</span> p<span class="token1">.</span>cameradistance <span class="token">=</span> <span class="token">-</span><span class="token6">1.0</span>f<span class="token1">;</span> <span class="token1">}</span> ParticlesCount<span class="token">++</span><span class="token1">;</span> <span class="token1">}</span> <span class="token1">}</span> ``` ``` 如下所示,效果看上去差不多了,不過還有一個問題…… ![](https://box.kancloud.cn/2015-11-02_5636f30be9653.png) ## 排序 正如\[第十課\]\[1\]中所講,你必須按從后往前的順序對半透明對象排序,方可獲得正確的混合效果。 ``` <pre class="calibre16">``` void <span class="token3">SortParticles</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span> std<span class="token1">:</span><span class="token1">:</span><span class="token3">sort</span><span class="token1">(</span><span class="token">&</span>ParticlesContainer<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>ParticlesContainer<span class="token1">[</span>MaxParticles<span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> <span class="token1">}</span> ``` ``` `std::sort`需要一個函數判斷粒子的在容器中的先后順序。重載`Particle::operator<`即可: ``` <pre class="calibre16">``` <span class="token2">// CPU representation of a particle</span> struct Particle<span class="token1">{</span> <span class="token1">.</span><span class="token1">.</span><span class="token1">.</span> bool operator<span class="token"><</span><span class="token1">(</span>Particle<span class="token">&</span> that<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Sort in reverse order : far particles drawn first.</span> <span class="token4">return</span> this<span class="token">-</span><span class="token">></span>cameradistance <span class="token">></span> that<span class="token1">.</span>cameradistance<span class="token1">;</span> <span class="token1">}</span> <span class="token1">}</span><span class="token1">;</span> ``` ``` 這樣`ParticleContainer`中的粒子就是排好序的了,顯示效果已經變正確了: ![](https://box.kancloud.cn/2015-11-02_5636f30c60d30.gif) ## 延伸課題 ## 動畫粒子 你可以用紋理圖集(texture atlas)實現粒子的動畫效果。將各粒子的年齡和位置發送到GPU,按照\[2D字體一課\]\[2\]的方法在shader中計算UV坐標,紋理圖集是這樣的: ![](https://box.kancloud.cn/2015-11-02_5636f30cc5d3f.png) ## 處理多個粒子系統 如果你需要多個粒子系統,有兩種方案可選:要么僅用一個粒子容器,要么每個粒子系統一個。 如果選擇將**所有**粒子放在一個容器中,那么就能很好地對粒子進行排序。主要缺陷是所有的粒子都得使用同一個紋理。這個問題可借助紋理圖集加以解決。紋理圖集是一張包含所有紋理的大紋理,可通過UV坐標訪問各紋理,其使用和編輯并不是很方便。 如果為每個粒子系統設置一個粒子容器,那么只能在各容器內部對粒子進行排序。這就導致一個問題:如果兩粒子系統相互重疊,我們就會看到瑕疵。不過,如果你的應用中不會出現兩粒子系統重疊的情況,那這就不是問題。 當然,你也可以采用一種混合系統:若干個粒子系統,各自配備紋理圖集(足夠小,易于管理)。 ## 平滑粒子 你很快就能發現一個常見的瑕疵:當粒子和幾何體相交時,粒子的邊界變得很明顯,十分難看: ![](https://box.kancloud.cn/2015-11-02_5636f30d05cc9.jpg) (image from <http://www.gamerendering.com/2009/09/16/soft-particles/> ) 一個通常采用的解決方法是測試當前繪制的片斷的深度值。如果該片斷的深度值是“較近”的,就將其淡出。 然而,這就需要對Z-Buffer進行采樣。這在“正常”的Z-Buffer中是不可行的。你得將場景渲染到一個\[渲染目標\]\[3\]。或者,你可以用`glBlitFrameBuffer`把Z-Buffer內容從一個幀緩沖拷貝到另一個。 [http://developer.download.nvidia.com/whitepapers/2007/SDK10/SoftParticles\_hi.pdf](http://developer.download.nvidia.com/whitepapers/2007/SDK10/SoftParticles_hi.pdf) ## 提高填充率 當前GPU的一個主要限制因素就是填充率:在16.6ms內可寫片段(像素)數量要足夠多,以達到60FPS。 這是一個大問題。由于粒子一般需要**很高**的填充率,同一個片段要重復繪制10多次,每次都是不同的粒子。如果不這么做,最終效果就會出現上述瑕疵。 在所有寫入的的片段中,很多都是毫無用處的:比如位于邊界上的片段。你的粒子紋理在邊界上通常是完全透明的,但粒子的網格卻仍然得繪制這些無用的片段,然后用與之前完全相同的值更新顏色緩沖。 這個小工具能夠計算紋理的緊湊包圍網格(這個也就是用`glDrawArraysInstanced()`渲染的那個網格): ![](https://box.kancloud.cn/2015-11-02_5636f30d197cc.jpg) [\[http://www.humus.name/index.php?page=Cool&ID=8\]\[4\]。Emil](http://www.humus.name/index.php?page=Cool&ID=8%5D%5B4%5D%E3%80%82Emil) Person的網站上也有很多精彩的文章。 ## 粒子物理效果 有些應用中,你可能想讓粒子和世界產生一些交互。比如,粒子可以在撞到地面時反彈。 比較簡單的做法是為每個粒子做光線投射(raycasting),投射方向為當前位置與未來位置形成的向量。我們將在\[拾取教程\]\[5\]。但這種做法開銷太大了,你沒法做到在每一幀中為每個粒子做光線投射。 根據你的具體應用,可以用一系列平面來近似幾何體(譯注:k-DOP),然后 對這些平面做光線投射。你也可以采用真正的光線投射,將結果緩存起來,然后據此近似計算附近的碰撞(也可以兼用兩種方法)。 另一種迥異的技術是將現有的Z-Buffer作為幾何體的粗略近似,在此之上進行粒子碰撞。這種方法效果“足夠好”,速度快。不過由于無法在CPU端訪問Z-Buffer(至少速度不夠快),你得完全在GPU上進行仿真。因此,這種方法更加復雜。 如下是一些相關文章:\[\[<http://www.altdevblogaday.com/2012/06/19/hack-day-report/>\][6](http://www.altdevblogaday.com/2012/06/19/hack-day-report/%5D%5B6)\] \[\[[http://www.gdcvault.com/search.php#&category=free&firstfocus=&keyword=Chris+Tchou’s%2BHalo%2BReach%2BEffects&conference\_id=](http://www.gdcvault.com/search.php#&category=free&firstfocus=&keyword=Chris+Tchou%E2%80%99s%2BHalo%2BReach%2BEffects&conference_id=)\][7](http://www.gdcvault.com/search.php#&category=free&firstfocus=&keyword=Chris+Tchou%E2%80%99s%2BHalo%2BReach%2BEffects&conference_id=%5D%5B7)\] ## GPU仿真 如上所述,你可以完全在GPU上模擬粒子的運動。你還是得在CPU端管理粒子的生命周期——至少在創建粒子時。 可選方案很多,不過都不屬于本課程討論范圍。這里僅給出一些指引。 - 采用變換反饋(Transform Feedback)機制。Transform Feedback讓你能夠將頂點著色器的輸出結果存儲到GPU端的VBO中。把新位置存儲到這個VBO,然后在下一幀以這個VBO為起點,然后再將更新的位置存儲到前一個VBO中。原理相同但無需Transform Feedback的方法:將粒子的位置編碼到一張紋理中,然后利用渲染到紋理(Render-To-Texture)更新之。 - 采用通用GPU計算庫:CUDA或OpenCL。這些庫具有與OpenGL互操作的函數。 - 采用計算著色器Compute Shader。這是最漂亮的解決方案,不過只在較新的GPU上可用。 > 請注意,為了簡化問題,在本課的實現中`ParticleContainer`是在GPU buffer都更新之后再排序的。這使得粒子的排序變得不準確了(有一幀的延遲),不過不是太明顯。你可以把主循環拆分成仿真、排序兩部分,然后再更新,就可以解決這個問題。
                  <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>

                              哎呀哎呀视频在线观看