-
|
|
Post Processing Problem And Optimization Questions
|
Hi!
Im currently developing a small 3d game in which i have a sun object and a lot other objects (space ships, asteroids, etc), and some background images. Im using bloom pp shader for the sun effect, but i dont want to use any pp on the other objects. So now here what im doing is :
1. i render the sun with pp, and save it to a rendertarget,
2. i render a background sprite, then with additive blending i render the previously saved sun above the background sprite,
3. after it i render the other objects.
The problem with it is that the sun will be opaqe due to the additive blending, therefore the darker a sun's pixel the more blended it will be (so i can see the background behind the sun because of the additive blending). However only this additive blending is the only usable solution, as SpriteBlendMode.None would cause that we wont see the background (because the sun rt pixels would override the background pixels).
So my first question is : What should i do to prevent the sun's opacity?
My next question is about the sun's pp effect. This is the effect (very simple) :
| float mag = 0.0008; |
| |
| float alpha; |
| |
| const float2 offsets[8] = { |
| -2.0, 2.0, |
| -2.0, -2.0, |
| -1.0, 1.0, |
| -1.0, -1.0, |
| 1.0, 1.0, |
| 1.0, -1.0, |
| 2.0, 2.0, |
| 2.0, -2.0, |
| }; |
| |
| struct PS_INPUT |
| { |
| float2 TexCoord : TEXCOORD0; |
| }; |
| |
| float4 bloomEffect(PS_INPUT Input) : COLOR0 { |
| float4 sum = tex2D(samplerState, Input.TexCoord); |
| float4 tex; |
| |
| for(int i = 0; i < 8; i++){ |
| tex = tex2D(samplerState, Input.TexCoord + mag * offsets[i]); |
| sum += tex; |
| } |
| |
| sum /= 8; |
| |
| sum.r = (sum.r - 0.5) * 1.5; |
| sum.g = (sum.g - 0.5) * 1.5; |
| sum.b = (sum.b - 0.5) * 1.2; |
| |
| sum.a = alpha; |
| |
| return sum; |
| } |
| |
| |
| |
| technique Bloom { |
| pass P0{ |
| PixelShader = compile ps_2_0 bloomEffect(); |
| } |
| } |
This is the way that im achieving the sun's "glowing" effect :
1. Save the original render of the sun into a RT (lets call it RT1).
2. Bloom RT1 as a 16x16 image and save the result into a RT (lets call it RT2).
3. Bloom RT1 as a 32x32 image, additively blend it with the previoulsy saved RT2 (make RT2 as a 32x32 image of course), and save the result into a RT2.
4. Bloom RT1 as a 64x64 image, additively blend it with the previoulsy
saved RT2 (make RT2 as a 64x64 image of course), and save the result
into a RT2.
5.-7. (repeat 4. with 128, 256 and 512)
8. Additively blend RT1 and RT2
So in the final step i will have the original sun image and a "glowing" sun-crown (a one time bloom wouldn't give this kind of result). The problem with it that it is a 'bit' too much even for a mid-category vga (on my 8800gts the fps will go from ~255 to ~114, but imagine the laptop with integrated intel 965 chipset...).
So my question is : Is there any better way to get the same result, but with less fps drop, or this is obvious to have this poor performance with this solution?
I think this is all for now. :) Many thanks in advance!
|
|
-
-
- (2124)
-
premium membership
-
Posts
1,162
|
Re: Post Processing Problem And Optimization Questions
|
The more typical way of doing what you want is to render a per-pixel value into the alpha channel of your main render target (or into a secondary render target) that indicates whether or not that pixel should glow. Then afterwards you apply bloom post-processing to the whole screen. This gives you the following benefits:
-It makes your rendering pipeline simpler (no weird rendering->post-processing->rendering->blending stuff
-It fixes your blending problem
-It's much more extensible. If somewhere down the line you decide you want some other object to glow (another planet, spaceship engines, etc.), it's trivial.
How you're doing the bloom is also a bit atypical. Typically you'll downscale to some size (1/4 or so), apply a threshold, blur horizontally, then blur vertically. Apply those steps at multiple sizes may give better results, but it's certainly going to be a performance drain so you might want to reconsider whether it's worth it. You'll also definitely want to threshold before blooming...this is what gives you the "bloom" effect in the first place! Separating the can also be a big performance win. In your case you're using a 3x3 blur kernel (which is generally considered small for these purposes). If you do it one pass you'll have 9 texture samples. If you do it in two passes, you'll have 6 samples. Not such a big difference...but if you use something like a 7x7 gaussian blur kernel it's a HUGE difference.
Matt Pettineo | DirectX/XNA MVP Ride into The Danger Zone | PIX With XNA Tutorial
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
" ...render a per-pixel value into the alpha channel of your main render target..."
Would you give me some hints about the codes to achieve this?
" How you're doing the bloom is also a bit atypical."
I didn't want to make typical bloom. I used this article as the base. This one seemed to be more robust. However i didn't find code about how to downscale an image (as i have read SpriteBatch is doing it on its own if i set the destination rectangle to NxN).
" You'll also definitely want to threshold before blooming...this is what gives you the "bloom" effect in the first place!"
Yes, but for the sun (as its pixels' color are usually above the threshold) i wanted to save as much performance as i can. Ok, thresholing is not a big thing, but can mean 1-2 fps in bigger resolution.
" Separating the can also be a big performance win. In your case...(to the end)"
I'm not sure what you mean (sry, im not an expert, only a hobbyist). Could you give me a bit more info on it? Many thx!
|
|
-
-
- (263)
-
premium membership
-
Posts
105
|
Re: Post Processing Problem And Optimization Questions
|
kuzanth:"...render a per-pixel value into the alpha channel of your main render target..."
Would you give me some hints about the codes to achieve this?
I *THINK* your solution would look something like (excuse the pseudocode):
| SetRenderTarget(MAIN_RENDER_TARGET) |
| For Each Object |
| If Object.ShouldBloom Then |
| SetShaderParameter( "xEnableBloom", 1.0f ) |
| Else |
| SetShaderParameter( "xEnableBloom", 0.0f ) |
| End If |
| |
| DrawObject |
| Next |
With a shader something like this (again, excuse the pseudocode, I don't think in HLSL yet)
| float xEnableBloom; |
| |
| void PixelShader( out Pixel:COLOR ) |
| { |
| pixel.a = xEnableBloom; |
| } |
Obviously you would a fair bit more code than this, but what you'll end up with is your main render target having a "Bloom Mask" in its alpha channel.
I'm too busy writing computer graphics for my day job to have any games in play-test or peer-review. Twitter: twitter/guyshermanBlog: www.guysherman.com
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
Does this mean that i have to render the original whole scene into a rendertarget (RT1), then render this scene again into another rendertarget (RT2) specifying which object is bloomable with an alpha value (in my case only the sun would have 1.0 value, all the other objects would have 0.0f, therefore only the sun would be seen in RT2), then use this RT2 for bloom, and finally blend the bloomed and the original RT1 together? Is it necessary to render the whole scene twice? Someone has mentioned to use stencilbuffer, but so far i havent used that, and i dont want to work on a solution that is basicly wrong.
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
I think he means:
1. Set the shaders bloom indicator parameter for your background (probably off) and render it. The shader writes the bloom indicator into the alpha for each pixel output.
2. Do the same for the sun, setting the shader parameter on or off as appropriate.
3. Do the same for your objects.
4. Then apply the bloom to the above scene, the shader checks the bloom indicator in the alpha for each pixel before applying.
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
I may have misunderstood you, so I'd rather ask before doing what i think you mean. So :
2. Do the same for the sun, setting the shader parameter on or off as appropriate.
This step is ok.
1. and 3.
This is the step that i dont really understand. If i set 0.0f for the
other objects, what will we see on the image? Only the sun, wont we?
What should i do with the other unbloomed objects? This is not clear
for me.
4. Then apply the bloom to the above scene, the shader checks the bloom indicator in the alpha for each pixel before applying.
Ok, i think i know what you mean, but 1 and 3 stil confuse me.
(Am i right that with these steps i wont need to blend the original scene and the bloomed scene, because the final image will contain the bloomed area?)
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
Exactly how DynamicSamurai shows
set shader param = off
draw background
set shader param = on
draw sun (maybe your sun is just another object so will get rendered in the loop below and there would be no need for this separate draw).
draw each object, setting the shader param = on or off per object
you now have your original scene with the bloom indicators in the alpha channel of each pixel
apply the bloom to the whole scene, ignoring pixels where the indicator is off
kuzanth:(Am i right that with these steps i wont need to
blend the original scene and the bloomed scene, because the final image
will contain the bloomed area?)
Yes thats correct, you render the whole scene once, then apply the bloom directly on that for the final result.
I hope thats right because I want to be doing this soon :)
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
-
- (2124)
-
premium membership
-
Posts
1,162
|
Re: Post Processing Problem And Optimization Questions
|
Here let me clear up some things...
With regards to rendering data to the alpha channel, what DynamicSamurai posted is basically what I meant. You set a shader parameter to indicate whether a particular object should glow or not, and then you write that parameter to the alpha channel of your final color (with the rgb channels containing the color you'd normally output). So it would be something like this:
float fGlowAmount;
float4 PS(in PSInput input) : COLOR0
{
// Do normal texture sampling and calculations to determine color
float3 vColor = DoTexturingAndLighting();
// Write out color and glow amount
return float4(color, fGlowAmount);
}
Then later on, this controls whether or not a pixel glows. More on this after I explain the blur...
For blurring, like I said it's desirable to use a seperable blur. What this means is that you do the blur in two passes: one horizontal, one vertical. When you do this you end up with 2N texture samples instead of N*N texture samples, where N is the size of your blur kernel. So for example, this here is a pretty standard 7x7 blur pixel shader:
float4 BlurPS(in float2 vTexCoord : TEXCOORD0) : COLOR0
{
float4 vColor = 0;
for (int x = -3; x <= 3; x++)
{
for (int y = -3; y <=3; y++)
{
float2 vSampleCoord = vTexCoord + (float2(x, y) / vSourceTextureDimensions);
vColor += tex2D(SourceSampler, vSampleCoord);
}
}
return vColor / 49;
}
Now if we were to do it in two passes, it looks like this:
float4 BlurHorizontalPS(in float2 vTexCoord : TEXCOORD0) : COLOR0
{
float4 vColor = 0;
for (int x = -3; x <= 3; x++)
{
float2 vSampleCoord = vTexCoord + (float2(x, 0) / vSourceTextureDimensions);
vColor += tex2D(SourceSampler, vSampleCoord);
}
return vColor / 7;
}
float4 BlurVerticalPS(in float2 vTexCoord : TEXCOORD0) : COLOR0
{
float4 vColor = 0;
for (int y = -3; y <=3; y++)
{
float2 vSampleCoord = vTexCoord + (float2(0, y) / vSourceTextureDimensions);
vColor += tex2D(SourceSampler, vSampleCoord);
}
return vColor / 7;
}
In our app we would apply the horizontal pixel shader first, and then use the result of that pass as the source for the vertical pixel shader.
Now let's say we're using this blur for our glow effect. We just need to make a simple modification so that we make use of the glow value we stored in the alpha channel:
float4 GlowBlurHorizontalPS(in float2 vTexCoord : TEXCOORD0) : COLOR0
{
float4 vColor = 0;
for (int x = -3; x <= 3; x++)
{
float2 vSampleCoord = vTexCoord + (float2(x, 0) / vSourceTextureDimensions);
float4 vSample = tex2D(SourceSampler, vSampleCoord);
vSample.rgb *= vSample.a;
vColor += vSample;
}
return vColor / 7;
}
float4 GlowBlurVerticalPS(in float2 vTexCoord : TEXCOORD0) : COLOR0
{
float4 vColor = 0;
for (int y = -3; y <=3; y++)
{
float2 vSampleCoord = vTexCoord + (float2(0, y) / vSourceTextureDimensions);
vColor += tex2D(SourceSampler, vSampleCoord);
}
return vColor / 7;
}
Then once you've done the blur, you just need to combine it with your original render target. You can either use SpriteBatch with additive blending (make sure your RenderTarget is set to PreserveContents so it doesn't get cleared), or just use a shader that takes the original RT and your blurred RT as sources and adds them together.
As for scaling...hardware filtering will take care of everyting for you when the render target is larger or smaller than the source texture. However this has 2 main limitations: the device needs to support hardware filtering for the source texture format (SurfaceFormat.Color is always fine, but floating-point formats may not be), and it only works for scaling to sizes 2x larger or 2x smaller. If either of those conditions aren't met, you have to scale manually in the shader. Another alternative (if you have hardware filtering but need to downscale or upscale more than 2x) is to just do the scaling in multiple passes.
PS: with regards to that sample you're using...I've ranted about that particular sample before. My main beef with it is that while it produces some pretty results, it does so in an almost completely impractical way. It basically has no consideration for performance or extensibility. I'd be okay with it if this were stated up-front so readers were aware of the drawbacks, but that's not the case. The fact that the author states he "whipped it up in an hour" also helped get that sample onto my naughty list.
Matt Pettineo | DirectX/XNA MVP Ride into The Danger Zone | PIX With XNA Tutorial
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
Many thx for the clarification. I got your point on the bloom part, however - and I'm really sorry for it - I still cannot understand the alpha parameter result.
From the bloom-side i understand why and how i need to use this :
float fGlowAmount;
float4 PS(in PSInput input) : COLOR0
{
// Do normal texture sampling and calculations to determine color
float3 vColor = DoTexturingAndLighting();
// Write out color and glow amount
return float4(color, fGlowAmount);
}
but it will result that the 'unbloomed' objects will be transparent (well, black), wont it? I can understand that it is the way to tell the bloom shader 'what-to-bloom'. Its ok. But in this solution how can i store the original alpha value? Or how many images will i have at the end? Do you see what my poblem is?
|
|
-
-
- (2124)
-
premium membership
-
Posts
1,162
|
Re: Post Processing Problem And Optimization Questions
|
I apologize I should have made this point clear: you can't use this technique with alpha-blending since you're using the alpha-channel to store your bloom amount. You would have to disable alpha-blending so that all of the geometry remains visible.
If none of your alpha-blended geometry needs to glow, you can render them with alpha-blending enabled but with writing for the alpha channel disabled (RenderState.ColorWriteChannels = ColorWriteChannels.Red|ColorWriteChannels.Blue|ColorWriteChannels.Green). This will make them transparent, but will prevent them from glowing.
Another work-around is to use a second rendertarget and store your glow amount on that using either multiple rendertargets in a single pass, or during a second pass.
Matt Pettineo | DirectX/XNA MVP Ride into The Danger Zone | PIX With XNA Tutorial
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
Hi again,
I'm sorry for my late reply on your suggestions, but i had some other things to do. Now back to the business, here is what i have done so far :
- i have modified the models' shaders to take a parameter for controlling the alpha value as you suggested (in the sun's shader it is 1.0f, all the other object's are 0.0f). This is what i had misunderstood, because i thought that the bloom shader needs to control this... :)
- I have implemented the shader you have given to me, and it is working (of course i still need some workaround on that but it is ok for now).
Now im a bit confused about this alphablending thing.
"You would have to disable alpha-blending so that all of the geometry remains visible."
I tried to achieve it by two ways :
1. In the .cs code just before anything would be rendered :
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
or
2. In the models' shaders
technique xy
{
pass P0
{
AlphaBlendEnable = false;
SrcBlend = 0.0f;
DestBlend = 1.0f;
VertexShader = compile ...
PixelShader = compile ...
...
}
}
The problem is that it doesnt matter which option im using, when i set the alphaAmount in the models' shader to 0.0f, the model will be transparent. Any other alphaAmount higher than 0.0f makes the model 100% visible.
Could you tell me what's wrong in my solution?
Thanks
|
|
-
-
- (2124)
-
premium membership
-
Posts
1,162
|
Re: Post Processing Problem And Optimization Questions
|
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
Now everything seems to be working, except one thing. :) When i draw something unbloomed in front of a bloomed object, it will look like this object. I checked that the bloomed object is far behind the unbloomed one, so it is not covering it or similar.
Does the bloomed object's alpha value (1.0f) override the unbloomed alpha value (0.0f)?
|
|
-
-
- (2124)
-
premium membership
-
Posts
1,162
|
Re: Post Processing Problem And Optimization Questions
|
It's hard to tell from your picture, but it looks like the yellow from the sun is just blurred over the pixels where the ship was drawn. If you're concerned that your alpha values are incorrect, you can always just view your render target in PIX and inspect the values for individual pixels.
Matt Pettineo | DirectX/XNA MVP Ride into The Danger Zone | PIX With XNA Tutorial
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
I haven't used PIX, but I think the problem lies within the solution itself.
When I draw something unbloomed over a bloomed object, the shader will bloom those pixels with alpha value of 1.0f. Therefore it doesn't matter if the unbloomed object with alpha value of 0.0f is in front, the bloom shader will bloom the object in the background that has the alpha value of 1.0f. So I'm not sure if this kind of solution is good or not? Or can I do something with the zbuffer to change the the final image's "wrong" alpha values that I send to the bloom shader?
|
|
-
|
|
Re: Post Processing Problem And Optimization Questions
|
"Then once you've done the blur, you just need to combine it with your
original render target. You can either use SpriteBatch with additive
blending (make sure your RenderTarget is set to PreserveContents so it
doesn't get cleared), or just use a shader that takes the original RT
and your blurred RT as sources and adds them together."
I have a question about this last step where the blurred render target needs to be combined with the one containing the original scene. The scene target will contain the pixels that make up the original rendered image, and the blur target will be a mostly black background with certain areas containing color where objects that used the blur effect were drawn. It seems like when you go to add these two images together, in the blurred areas you're going to be basically adding the original opaque pixels from the scene plus opaque pixels resulting from the blur which is going to blow out the colors in these locations. I assume the desired effect is to have a final image consisting of pixels from the original scene where there was no blur and only the results of the blur calculations on the pixels that specified blur in the 'blur mask'.
I'm trying to implement a similar system to this but I'm using a complete second render target for the effect mask (so I can control 4 parameters with the rgba components of each pixel). So what I effectively have is one render target containing my normal scene, and a second render target containing the rgba mask. I'm trying to combine them together so that if one or more effects are specified as being on in the mask then something like the weighted sum of the results of those effects will be placed in the final pixel, and if none of them are used then just the original pixel from the scene will go there. I can't quite figure out a way to do this though without explicitly checking 'If mask is completely zero, write scene pixel Else write effect pixel'. Is there a more elegant way to blend these two render targets together without having to resort to if statements?
I thought about using one of my 4 components to specify a weight for the scene pixel so that objects that don't want to use any effects can just write a 1 there, but then that requires rending all these objects in the effects pass (in addition to the objects ones that need effects) just so they can say that they don't want any effects.
Evan
|
|
|