How it works...

There's quite a bit here to sort through. Let's start with the vertex shader.

The vertex shader is broken up into two primary functions (update and render). The update function is used during the first pass, and uses Euler's method to update the position and velocity of the particle. The render function is used during the second pass. It computes the transparency based on the age of the particle and sends the position and transparency along to the fragment shader.

The vertex shader has three output variables that are used during the first pass: Position, Velocity, and Age. They are used to write to the feedback buffers. 

The update function updates the particle position and velocity using Euler's method unless the particle is not alive yet, or has passed its lifetime. If its age is greater than the lifetime of a particle, we recycle the particle by resetting its position to the emitter position, updating the particle's age by subtracting ParticleLifetime, and setting its velocity to a new random velocity determined by the randomInitialVelocity function. Note that we do the same thing if the particle hasn't been born for the first time yet (the age is less than zero), except that we just update the age by DeltaT.

The render function is fairly straightforward. It draws the quad by offsetting the particle's position in camera coordinates in much the same way as the previous recipe. The VertexAge variable is used to determine the transparency of the particle, assigning the result to the Transp output variable. It transforms the vertex position into clip coordinates and places the result in the built-in gl_Position output variable.

The fragment shader is only utilized during the second pass. It is disabled during the first. It colors the fragment based on the ParticleTex texture and the transparency delivered from the vertex shader (Transp).

The next code segment is placed prior to linking the shader program and is responsible for setting up the correspondence between shader output variables and feedback buffers (buffers that are bound to indices of the GL_TRANSFORM_FEEDBACK_BUFFER binding point). The glTransformFeedbackVaryings function takes three arguments. The first is the handle to the shader program object. The second is the number of output variable names that will be provided. The third is an array of output variable names. The order of the names in this list corresponds to the indices of the feedback buffers. In this case, Position corresponds to index zero, Velocity to index one, and Age to index two. Check the previous code that creates our feedback buffer objects (the glBindBufferBase calls) to verify that this is indeed the case.

glTransformFeedbackVaryings can be used to send data into an interleaved buffer instead (rather than separate buffers for each variable). Take a look at the OpenGL documentation for details.

The next code segments describe how you might implement the render function within the main OpenGL program. In this example, there two important GLuint arrays: feedback and particleArray. They are each of size two and contain the handles to the two feedback buffer objects, and the two vertex array objects respectively. The drawBuf variable is just an integer used to alternate between the two sets of buffers. At any given frame, drawBuf will be either zero or one.

The code for the first pass sets the Pass uniform to 1 to enable the update functionality within the vertex shader. The next call, glEnable(GL_RASTERIZER_DISCARD), turns rasterization off so that nothing is rendered during this pass. The call to glBindTransformFeedback selects the set of buffers corresponding to the drawBuf variable as the target for the transform feedback output.

Before drawing the points (and thereby triggering our vertex shader), we call glBeginTransformFeedback to enable transform feedback. The argument is the kind of primitive that will be sent down the pipeline. In this case, we use GL_POINTS even though we'll actually be drawing triangles because we're not actually drawing any primitives. This pass is just used to update the particles, so there's no need to invoke the shader more than once per particle. This also indicates why we need to set the divisor to zero for our attributes in this pass. We're not using instancing in this pass, so we just want to invoke the vertex shader once per particle. We do so by calling glDrawArrays.

Output from the vertex shader will go to the buffers that are bound to the GL_TRANSFORM_FEEDBACK_BUFFER binding point until glEndTransformFeedback is called. In this case, we bound the vertex array corresponding to 1 - drawBuf (if drawBuf is 0, we use 1 and vice versa).  

At the end of the update pass, we re-enable rasterization with glEnable(GL_RASTERIZER_DISCARD), and move on to the render pass.

The render pass is straightforward; we just set Pass to 2 and draw the particles from the vertex array corresponding to drawBuf. That vertex array object contains the set of buffers that were written to in the previous pass. 

Here, we use instancing in the same fashion as described in the previous recipe, so we set the divisor for all of our attributes back to one.  

Finally, at the end of the render pass, we swap our buffers by setting drawBuf to 1 - drawBuf.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.125.171