# How can I create an aperture-closing effect?

I'm looking to create an effect like this in a game I'm making:

I would describe it as a camera aperture closing.

I have the following requirements:

1. I need to be able to reverse the fade (open the aperture instead of close it)
2. The hole should close/open towards/from a given center point.

I can't really think of a great way to do this. I'm so used to SpriteBatch and Texture2D that I don't know how I could draw an ellipse style effect.

My current thoughts would be to make a few Texture2D frames and then animated them on the screen but that seems crazy.

If I just wanted to create pseudo-code to do it, it would probably look something like this (coming from a GDI/GDI+ background):

Call: Create_Relevant_Ellipse_Bounds;
Call: Set_Graphics_Exclusion_Clip_From_Ellipse;
Call: Fill_BackColor_With_Black_Using_Clip;
Call: Update_Ellipse_Bounds;
Call: Wait_For_Next_Draw;


A point p is inside a circle at c with radius r when

(p.x - c.x)^2 + (p.y - c.y)^2 < r^2


is true. Using this equation you can color any points inside a circle one color (such as "transparent") and anything outside the circle a different color (such as "black"). You can even fade the edges of the circle, if you are so inclined.

You can write a custom shader to do this, and apply it to a full-screen quad. Set up shader constants defining the center of the circle and the current radius, and draw your quad. Every frame, decrease the radius by some factor to animate the circle getting smaller.

SV_Position contains the fragment position in D3D10 and up, but it's not available in D3D9 (which is what XNA is based on). Instead you can use the VPOS semantic (more information here). This will give you the position of p for the above equation. c and r come from your shader constants.

You can use the step function to return 0 or 1 based on which side of the equation is greater. This will allow you to avoid an actual branch in the shader, so you can compute the alpha component of your final color like this:

// alpha will be 1 if (r * r) is greater, meaning c is outside the radius.
// Ptherwise it will be zero.
float alpha = step(pow(p.x - c.x, 2) + pow(p.y - c.y, 2), r * r)

return (0,0,0,alpha);


If you didn't have SV_Position or VPOS available, you could always just put texture coordinates in the quad vertices. These will be interpolated across the face of the quad during rasterization, and can be used as an approximate coordinate system for specifying the position and radius of the camera aperture.

• Wouldn't I have to do return (0,0,0,alpha*255); ? So it'd be black or completely transparent? – test Oct 21 '14 at 15:20
• Also, what is the x variable in x.y? Should that be (p.y - p.x)? – test Oct 21 '14 at 15:32
• In shaders, color component values are generally represented as being in the range 0 to 1. The "x.y" is a typo and should be "c.y" instead; I'll fix it. – Josh Oct 21 '14 at 15:42
• I know you mentioned the VPOS semantic but every quad example for XNA doesn't use this semantic and it isn't supported for vertex shaders. Every example I'm seeing uses a vertex shader and pixel shader to do this. I don't see how I could use the VPOS semantic (I'm not sure it's needed?) – test Oct 21 '14 at 16:30
• You need it in the pixel shader, where it should be supported. If you don't want to use it, you can just use the texture coordinates - just write the transformed positions of the vertices as output from the vertex shader, and use those as the coordinate system in the pixel shader. – Josh Oct 21 '14 at 16:33

Thanks @JoshPetrie for getting me on the right track. I know XNA is basically dead but I could not find a good sample to do something that should be simple. Below I've posted all relevant code to perform an aperture close or open effect.

Before you run, add a sprite called Background to your project. The result of the code should be an aperture closing and opening over the center of your background sprite.

Game1.cs

public class Game1 : Microsoft.Xna.Framework.Game
{

#region fields

GraphicsDeviceManager graphics;
Effect apertureEffect;
SpriteBatch spriteBatch;
Texture2D backgroundSprite;

// flag to indicate direction of aperture effect
private bool open = true;

#endregion

#region constructors

public Game1()
{
this.graphics = new GraphicsDeviceManager(this);

base.Content.RootDirectory = "Content";
}

#endregion

#region methods

{
this.spriteBatch = new SpriteBatch(base.GraphicsDevice);

// center screen for circle start
float x = base.GraphicsDevice.Viewport.Width / 2f;
float y = base.GraphicsDevice.Viewport.Height / 2f;

this.apertureEffect.Parameters["c"].SetValue(new float[] { x, y });
this.apertureEffect.Parameters["r"].SetValue(10f);

}

protected override void Update(GameTime gameTime)
{
base.Update(gameTime);

if (this.open && radius > (base.GraphicsDevice.Viewport.Height / 2f))
this.open = false;
else if (!this.open && radius <= 0f)
this.open = true;

// adjust increment for "shutter speed"
float increment = 1.0f;
if (!this.open)
increment = -increment;

}

protected override void Draw(GameTime gameTime)
{
base.GraphicsDevice.Clear(Color.CornflowerBlue);

// render any background sprite to showcase transparency
// if no background sprite is rendered you will only see black
this.spriteBatch.Begin();
this.spriteBatch.Draw(this.backgroundSprite,
new Rectangle(0, 0,
base.GraphicsDevice.Viewport.Width,
base.GraphicsDevice.Viewport.Height),
Color.White);
this.spriteBatch.End();

base.Draw(gameTime);
}

#endregion

}


QuadRenderer.cs

internal sealed class QuadRenderer
{

#region fields

private VertexPositionTexture[] triangles;
private GraphicsDevice device;
private short[] indexData = new short[] { 0, 1, 2, 2, 3, 0 };

#endregion

#region constructors

{
this.device = device;

// texture coordinates semantic not used or needed
this.triangles = new VertexPositionTexture[]
{
new VertexPositionTexture(new Vector3(1, -1, 0),
Vector2.Zero),
new VertexPositionTexture(new Vector3(-1, -1, 0),
Vector2.Zero),
new VertexPositionTexture(new Vector3(-1, 1, 0),
Vector2.Zero),
new VertexPositionTexture(new Vector3(1, 1, 0),
Vector2.Zero)
};
}

#endregion

#region methods

public void Render(Effect effect)
{
foreach (EffectPass p in effect.CurrentTechnique.Passes)
p.Apply();

this.Render();
}

private void Render()
{
this.device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
this.triangles, 0, 4,
this.indexData, 0, 2);
}

#endregion

}


Aperture.fx

// center point of circle
float2 c;

float r;

float4 ShadeVertex(float3 pos : POSITION0) : POSITION0
{
return float4(pos, 1);
}

float4 ShadePixel(float2 p : VPOS) : COLOR0
{
float alpha = step(r * r, pow(p.x - c.x, 2) + pow(p.y - c.y, 2));

// black or transparent
return float4(0, 0, 0, alpha);
}

technique Simple
{
pass FirstPass
{