![]() |
|
[XNA] Multiple effects |
blargmob
Member #8,356
February 2007
![]() |
I've gotten my hands on an HLSL .fx shader from Microsoft that distorts (ripple-style) a texture given a set of parameters. I've been playing around with the .fx file for a little to make it a little more customized. However, this shader only distorts once... I want to be able to add more then 1 ripple effect to my scene at any given moment and I also have plans for adding 2 more pixel shaders. At the moment this doesn't seem within my reach because I can not figure out how to utilize more then 1 pixel shader per frame. I done loads of googling to no avail, so I'm really just asking how do I apply more than 1 shader at a time?
--- |
Erin Maus
Member #7,537
July 2006
![]() |
You can either perform multiple rendering passes are adapt the shader to support more than one ripple. Either of which is an easy task. --- |
Billybob
Member #3,136
January 2003
|
Are you talking about a post-process shader, or something applied to a model? It sounds like a post-process to me, in which case Aaron Bolyard is correct. I found this code, related to a ripple: 1// Water pp
2
3float4 tint = (0,0,0,1);
4sampler samplerState;
5
6float wave ; // pi/.75 is a good default
7float distortion ; // 1 is a good default
8float2 centerCoord; // 0.5,0.5 is the screen center
9
10float time;
11
12struct PS_INPUT
13{
14 float2 TexCoord : TEXCOORD0;
15};
16
17float4 Invert(PS_INPUT Input) : COLOR0
18{
19 float2 distance = abs(Input.TexCoord - centerCoord)/5;
20 float scalar = length(distance);
21
22 // invert the scale so 1 is centerpoint
23 scalar = abs(1 - scalar);
24
25 // calculate how far to distort for this pixel
26 float sinoffset = cos(wave / scalar);
27 sinoffset = clamp(sinoffset, 0, 1);
28
29 // calculate which direction to distort
30 float sinsign = cos(wave / scalar);
31 // reduce the distortion effect
32 sinoffset = sinoffset * distortion/32;
33
34 // pick a pixel on the screen for this pixel, based on
35 // the calculated offset and direction
36 float4 color = tex2D(samplerState, Input.TexCoord+(sinoffset*sinsign)) * tint;
37
38 return color;
39}
40
41technique PostInvert
42{
43 pass P0
44 {
45 PixelShader = compile ps_2_0 Invert();
46 }
47}
If your code is similar to that, then you have the two options posted by Aaron. Either write more stuff inside the existing shader, or do multiple passes: technique PostInvert { pass P0 { PixelShader = compile ps_2_0 Invert(); } pass P1 { PixelShader = compile ps_2_0 DoSomeOtherShader(); } } The C# side of things is like this: for (int ps = 0; ps < ppShader.CurrentTechnique.Passes.Count; ps++) { ppShader.CurrentTechnique.Passes[ps].Begin(); mySpriteBatch.Draw(PPTarget.GetTexture(), Vector2.Zero, Color.White); ppShader.CurrentTechnique.Passes[ps].End(); } You can also just use multiple effects, it isn't much different than doing multiple passes with the same effect.
|
blargmob
Member #8,356
February 2007
![]() |
Aaron Bolyard said: perform multiple rendering passes You mean one for each ripple? If that is the case, wouldn't I just copy and paste the original code from the method over and over again for each pass? But then how do I set the different parameters for each ripple? Quote: adapt the shader to support more than one ripple
Billybob, same questions as above ^ --- |
CursedTyrant
Member #7,080
April 2006
![]() |
Use a RenderTarget with Vector2 data instead of RGBA to store distortion data, then just distort everything once using that RenderTarget as an distortion data input texture (just use sampler2D = sampler_state { texture = <my_texture> }; and tex2D with the current UV). There are tutorials on doing that somewhere (there was one on ZiggyWare, but it seems it has been down for some time now). You could also (possibly) use 8bit R and G channels to store x and y distortions respectively. --------- |
Billybob
Member #3,136
January 2003
|
blargmob, if possible, please post distilled code. That's easier for me to work with. Otherwise it is difficult for me to guess how you have everything setup. In general, though, if your shader is a post process, you can just call it multiple times, setting the parms different each time.
|
blargmob
Member #8,356
February 2007
![]() |
Billybob said: blargmob, if possible, please post distilled code. Well right now I have this for drawing the ripples in my draw method: 1
2WorldEffects.Ripple.Begin();
3foreach (RippleEntity ripple in Ripples)
4{
5 WorldEffects.Ripple.Parameters["wave"].SetValue(ripple.Wave); WorldEffects.Ripple.Parameters["distortion"].SetValue(ripple.Distortion);
6 WorldEffects.Ripple.Parameters["centerCoord"].SetValue(ripple.Position);
7 WorldEffects.Ripple.Parameters["divisor"].SetValue(ripple.Divisor);
8 WorldEffects.Ripple.CommitChanges();
9
10 foreach (EffectPass pass in WorldEffects.Ripple.CurrentTechnique.Passes)
11 {
12 pass.Begin();
13 spriteBatch.Draw(RenderTarget.GetTexture(), Vector2.Zero, Color.White);
14 pass.End();
15 }
16}
17WorldEffects.Ripple.End();
As you may notice, my intentions were to have the ripple effect be drawn for all the items in the list "Ripples". But that is not what happened. I have tried using multiple passes and using arrays for the parameters but that did not work either.
--- |
CursedTyrant
Member #7,080
April 2006
![]() |
You're drawing to the screen but repeatedly use the same renderTarget, therefore discarding all the changes you made. --------- |
blargmob
Member #8,356
February 2007
![]() |
So how do I "update" the texture that I am passing to the shader? I've tried using multiple render targets, but that is not an option because it really eats up video memory
--- |
CursedTyrant
Member #7,080
April 2006
![]() |
You're only option (that I can see atm) is to use a ResolveTexture2D and just copy the screen buffer there after each pass, then draw it to the screen again (because calling GraphicsDevice.ResolveTexture (or something like that) clears the screen). --------- |
blargmob
Member #8,356
February 2007
![]() |
Okay, I'm trying to utilize ResolveTexture2D, and this what I've got: 1public void DrawRipples(SpriteBatch spriteBatch)
2 {
3 ResolveTexture2D tex = new ResolveTexture2D(Game.GraphicsDevice, 1024, 768, 0, SurfaceFormat.Color);
4 WorldEffects.Ripple.Begin();
5 foreach (RippleEntity ripple in Ripples)
6 {
7 //Texture2D texture = RenderTarget.GetTexture();
8 WorldEffects.Ripple.Parameters["wave"].SetValue(ripple.Wave);
9 WorldEffects.Ripple.Parameters["distortion"].SetValue(ripple.Distortion);
10 WorldEffects.Ripple.Parameters["centerCoord"].SetValue(ripple.Position);
11 WorldEffects.Ripple.Parameters["divisor"].SetValue(ripple.Divisor);
12 WorldEffects.Ripple.CommitChanges();
13 spriteBatch.Draw(RenderTarget.GetTexture(), Vector2.Zero, Color.White);
14 foreach (EffectPass pass in WorldEffects.Ripple.CurrentTechnique.Passes)
15 {
16 pass.Begin();
17 Game.GraphicsDevice.ResolveBackBuffer(tex);
18 spriteBatch.Draw(tex, Vector2.Zero, Color.White);
19 pass.End();
20 }
21 }
22 WorldEffects.Ripple.End();
23 }
However, the screen is just black when ripples are supposed to be drawn.
--- |
CursedTyrant
Member #7,080
April 2006
![]() |
Resolving the backbuffer in the middle of an effect might not be such a great idea. You should try doing it some other way. Also, do you really want to recreate a 1024x768 texture each loop? On a side note, you don't have to call CommitChanges(). You should post your shader code. --------- |
|