From what I have done now, integrating the particle system from creator club into my deferred shading system. I have 2 options as this case also can be applied to you too for the approaches I gonna use to integrate this stuff.
First, it’s the simplest one which can be done without much effect. NO modifications are need for ParticleEffect.fx file of the particle system, thus giving you the most comfortable one. What the first one does exactly? Well it does render the particle scene on top of the finished deferred scene by just combining their textures. In this way, although the work is well reduced, but the particles still visible through the solid objects. Thus it the early simple work now comes to the cumbersome, in which you much get the depth buffer from deferred scene, whether or not it comes from your render target or the graphics device’s current depth buffer. We use it to just occlude the particles that must not be seen if they are behind our objects in deferred scene. Also, this is not enough if we choose not to modify any part of the ParticleEffect.fx file, then we must get particle scene’s depth buffer out too. In conclusion, we need two more textures of depth buffer externally from the ParticleEffect.fx to correctly draw the particles on screen, and thus another effect to combine them so more passes required for the final scene. But if we choose to modify some parts of that file, we need to calculate the location of the depth buffer to sample the depth at the particular position of a single particle. Compare them and then correctly draw. Yes, I think it can be called the nice way too. But this is not the approach I will use, as I want the overall rendering pass to not be increased anymore.
Second approach, this is what the approach I chose. The reason is to reduce as much the render pass for my rendering system. It requires no additional rendering pass at all, as it will now become the part of the deferred shading itself. For what I done with this approach is this, I have treated the particle scene as the normal scene for deferred shading. It can be called for updating, and drawing like the normal scene existed in deferred pass. In this way, we reduce at much the overhead to switch between the effect, and time to produce the final scene.
How to do it? I have to add some codes into the ParticleEffect.fx file.
The following is the list for my addtion to the code. List of addition
1. add the copy of transformed position (vector4) for vertex output structure => we only do the calculatation for depth value here
2. add the receiving parameter (corresponding to 1.)
3. add the output of pixel shader structure, writes to all the related and only neccessary render target
4. add the part of code inside pixel shader to write the output.
Let’s see the real action below
We must add the copy of transformed position (vector4) into the vertex output, thus we can store our particle’s depth into the depth render target, thus this will solve our “particle through solid object” problem.
struct VertexShaderOutput { float4 Position : POSITION0; float Size : PSIZE0; float4 Color : COLOR0; float4 Rotation : COLOR1; float4 CopyPosition: TEXCOORD0; };
Note above that we use TEXCOORD0 semantic to hold our data. And now take a look at the vertex shader function, as we will add single line to copy the transformed position (vector4) and route it to the output. See below
// Custom vertex shader animates particles entirely on the GPU. VertexShaderOutput VertexShader(VertexShaderInput input) { VertexShaderOutput output; // Compute the age of the particle. float age = CurrentTime - input.Time; // Apply a random factor to make different particles age at different rates. age *= 1 + input.Random.x * DurationRandomness; // Normalize the age into the range zero to one. float normalizedAge = saturate(age / Duration); // Compute the particle position, size, color, and rotation. output.Position = ComputeParticlePosition(input.Position, input.Velocity, age, normalizedAge); output.Size = ComputeParticleSize(output.Position, input.Random.y, normalizedAge); output.Color = ComputeParticleColor(output.Position, input.Random.z, normalizedAge); output.Rotation = ComputeParticleRotation(input.Random.w, age); output.CopyPosition = output.Position; return output; }
See the line “output.CopyPosition = output.Position”. Isn’t it simple? Now we move to the pixel shader part. But before moving into it, I must tell you what about my GBuffer layout first, so you can adapt it into your own and work correctly.
For my layout, I have 4 render targets as follow
RT1, Color (ARGB with 8 bit pixel format for each channel), this render target just contain the albedo color
RT2, Depth t (32 bit float format using 32 bit for red channel), this render target contains pure depth value
RT3, LightOcclusion (same format as RT2), this render target contains information about whether or not the light will lit each pixel (for example the skydome, you dont want to light it, so settings this value to 1 to disable light effect when drawing skydome.)
RT4, Normal (same format as RT1), in fact you can use 16-bit for x and y, then calculate z using 1 - x^2 - y^2.
Okay, let’s see the code
struct PixelShaderOutput { float4 Color: COLOR0; float4 Depth: COLOR1; float4 LightOcclusion: COLOR2; };
You may ask at this point. Why I don’t include the normal as output in this structure? Because the hlsl rule, as when you include the output part inside the structure, you must write it too, whether or not you do not want to be messed with its value. Thus as you may expect, I rearrange the order or render target just to take the normal to the last and not include it in the output structure, as I don’t want to modify its current value in the buffer. Because of we don’t want any one of the particle to affect the lighting calculation at the later stage, so do not mess with the current normal resided in the buffer already. Leaving this way, the lighting will not concern about the existence of individual particle. (But at the later you will see that I have set the value of lightOcclusion to 0, contradict to what I have told you, it’s because if we turn off the lighting effect on individual particle in which that particle already draw on top of our normal scene, we will see the square of pure albedo color. Thus if your scene is in night, then your rain particle system is enabled, you will see the bright square everywhere which again not what we want.) Now let’s have a look about input structure, and its pixel shader function (separate for the type of output, as you will learn that the particle system created from creators club is well implemented. The type includes “Non rotating particle”, and “Rotating particle” version).
For non-rotating version.
// Pixel shader input structure for particles that do not rotate. struct NonRotatingPixelShaderInput { float4 Color : COLOR0; #ifdef XBOX float2 TextureCoordinate : SPRITETEXCOORD; #else float2 TextureCoordinate : TEXCOORD0; #endif float4 CopyPosition: TEXCOORD1; }; // Pixel shader for drawing particles that do not rotate. PixelShaderOutput NonRotatingPixelShader(NonRotatingPixelShaderInput input) { PixelShaderOutput output; //write to the GBuffer output.Color = tex2D(Sampler, input.TextureCoordinate) * input.Color; output.Depth = input.CopyPosition.z / input.CopyPosition.w; output.LightOcclusion = 0; return output; }
For rotating version
// Pixel shader input structure for particles that can rotate. struct RotatingPixelShaderInput { float4 Color : COLOR0; float4 Rotation : COLOR1; #ifdef XBOX float2 TextureCoordinate : SPRITETEXCOORD; #else float2 TextureCoordinate : TEXCOORD0; #endif float4 CopyPosition: TEXCOORD1; }; // Pixel shader for drawing particles that can rotate. It is not actually // possible to rotate a point sprite, so instead we rotate our texture // coordinates. Leaving the sprite the regular way up but rotating the // texture has the exact same effect as if we were able to rotate the // point sprite itself. PixelShaderOutput RotatingPixelShader(RotatingPixelShaderInput input) { PixelShaderOutput output; float2 textureCoordinate = input.TextureCoordinate; // We want to rotate around the middle of the particle, not the origin, // so we offset the texture coordinate accordingly. textureCoordinate -= 0.5; // Apply the rotation matrix, after rescaling it back from the packed // color interpolator format into a full -1 to 1 range. float4 rotation = input.Rotation * 2 - 1; textureCoordinate = mul(textureCoordinate, float2x2(rotation)); // Point sprites are squares. So are textures. When we rotate one square // inside another square, the corners of the texture will go past the // edge of the point sprite and get clipped. To avoid this, we scale // our texture coordinates to make sure the entire square can be rotated // inside the point sprite without any clipping. textureCoordinate *= sqrt(2); // Undo the offset used to control the rotation origin. textureCoordinate += 0.5; //write to the GBuffer output.Color = tex2D(Sampler, textureCoordinate) * input.Color; output.Depth = input.CopyPosition.z / input.CopyPosition.w; output.LightOcclusion = 0; return output; }
Now, we have done it.
For all the detail I described to you so far. I have done integrated it into my RealKo Engine.
I open for your suggestion and comment about the approach I made, you can suggest the better way to do it. Thus note for my designing, I chose the one that is not that too much difficult to implement, and does not require too much time (with close to deadline of my project), thus all they have come to be the way as I explained to you so far.
Farewell for now, and see you next time.































