nKast

pixel perf-ect

24. November 2012 07:21
by nKast
0 Comments

Stencil Ball Shadows for XNA / WP7

24. November 2012 07:21 by nKast | 0 Comments

 In the early versions of The Juggler, the first pinball game for WP7, the ball had no shadow. Later we added a shadow by using a very simple trick, a transparent texture was drawn on the table right below the ball.  I have seen this on many games; it's not heavy on the GPU and gives good results. It adds volume to the ball by separating it from the table graphics.


A transparent texture is used to 
simulate the ball shadow


 During the development of The World of Dr. Pickaxe I used a lot from our existing codebase, including the shadow trick. We also made a lot of additions; one example is the addition of ramps, rails and multiple levels on the table while we build a powerful table editor. 
  Soon, problems with the ball shadow were spotted. Should the shadow be cast on the table when ball was rolling on rails above? What about when the ball was on the second level or down a ramp? There were many ideas but none was simple or efficient. Should we accept a shadow that breaks the illusion every now  and then or remove the effect altogether?

 
Left: No shadows, Center: decal shadows still looks good, Right: the reason you are here...


Left: No shadows, Center: decal shadows. Half the texture falls inside the ramp, 
Right: Stencil shadows. Perfect !

Left: No shadows, Center: decal shadows. Notice how the wrong shadow creates the illusion that the rail is bend down by the ball weight. Also the ball seems bigger because it is perceived lower and far behind. Right: Stencil shadows. Ball casting shadow on the rail *and* the table below. Notice the curved shadow on the wall and the droptarget !! (full size

 There are basically two methods to generate shadows. Shadow mapping(aka shadow buffering) and stencil shadows (aka shadow volumes). Let's reject the first one because it requires a custom shader, something we can't do on WP7.

 


 Screenshot from Doom 3. Stencil shadows (also known as Shadow volumes)
produce hard shadows with sharp edges. They are best suited
 to simulate
 
strong sunlight or spotlights in dark environments (low ambient light).

 

 

 So, we are left with one choice, Stencil shadows. That didn't look that good either, mainly due to performance issues. The typical steps involved one by one are:

  1. Render the scene with ambient light only.
  2. For each light source
    1. Extract the silhouette of each object casting a shadow.
    2. Extend the silhouette away from the light to generate a shadow volume 
    3. Render the shadow volume (not really) using some stencil rules.
    4. Render the shadow volume, using some other stencil rules, reverse culling this time.
    5. Render the scene again using lighting and the stencil buffer to mask covered areas. 

 

huh, Let's see.

Steps 1 & 2.5 are too expensive. Andreno 200, the GPU on first gen windows phones, has a really low fill rate (around 1.5). Impossible to render the full scene twice. What we normally do is render the scene once with full lighting and shadows burned on the texture.

Step 2, let's limit ourselves to one static light source for now.  

Step 2.1 & 2.2 Too much work for the CPU. But we don't have to do it on every frame for static objects. If we could somehow render the scene as we already do, with full lighting and shadows, and get away with it... we might don't need to bother at all. But we still need to perform those steps for the ball. Don't we?  

Step 2.3 & 2.4 is fast. No, texture is involved, no lighting is performed. Only Z-buffer tests and writing to the stencil buffer. As a bonus, XNA can combine those steps into one! 

Step 2.5, I already made it clear we don't have the luxury to draw the scene again. But then how can we make the shadow to appear in an already lit environment? We need users to see a dark spot there... where the scene intersects with the shadow volume...

 


A generic ball shadow volume. 

 

A picture, they say, is like...well, you get the idea! If it didn't already strike you by looking at the picture above, let me elaborate. From any point you look at it, a ball's silhouette is always a circle. And its shadow volume is always a cylinder. Actually, the far end should expanded the closer you get to the light source, but assuming the ball doesn't fly high above the ground you can ignore that.

Here is how the ball stencil shadow actually work on The World of Dr. Pickaxe.

  1. Render the scene fully lit (shadows and lights burned into texture, lighting disabled in the effect)
  2. Rotate the generic shadow volume to face way from the light source
  3. Render the generic shadow volume using stencilShadowPass1State (combine back face and front face rules in one step)
  4. Render the generic shadow volume again using stencilShadowPass2State and with alpha blending and a dark color. The result is to dim the pixels where scene & shadow volume intersect. Same result if were rendering the full scene, but much more efficient.  

 


Left: No shadows, Center: Stencil shadows. Right: Shadow Volume

 

DepthStencilState stencilShadowPass1State;
DepthStencilState stencilShadowPass2State;

stencilShadowPass1State = new DepthStencilState()
{
DepthBufferWriteEnable = false,
  StencilEnable = true,
  TwoSidedStencilMode = true,
  StencilPass = StencilOperation.Increment,
  CounterClockwiseStencilPass = StencilOperation.Decrement
};
stencilShadowPass2State = new DepthStencilState()
{
  DepthBufferWriteEnable = false,
  StencilEnable = true,
  StencilFunction = CompareFunction.Equal,
  ReferenceStencil = 1
};

Conclusion

This method is really the best replacement for our previous shadowing method. Dare I say, it could possibly be faster! We replaced decal draw (texture), with two draws of a lightweight model. (no normals, no color vertices, no texture).

 

Limitations

As with the previous method, only the ball is casting shadow on other objects. The scene does not cast shadow on the ball. . In case you would like that a possible solution would be to pre-calculate volume shadows for each object and check with boundingBoxes/farseer before performing stencil shadow. For moving objects you still need to implement silhouette extraction and volume construction on every frame. Again, check against a worst case bounding box before performing any calculations.



Another problem with this method is that shadows pass through other objects. Place the light source higher, or limit the rotation of the shadow volume to avoid such artifacts. 

 

Code

The sample is based on Marble Maze tutorial
MarbleMazeStencil.zip (7.26 mb)

 

29. October 2012 03:06
by nKast
0 Comments

Motion API for XNA AR camera

29. October 2012 03:06 by nKast | 0 Comments

One of the coolest things you can do on mobile phones are Augmented reality apps. 
Combining the camera with the Motion API of windows phone is all you need. So, here's what you need to make AR on XNA.

First, Initialize the Motion API
Here I also set the update interval to 60FPS 

motion = new Motion();
motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(16.666);
motion.Start();
 

The important part is to align your XNA camera to the physical camera. For that we need the Attitude property from the motion API. In order to use it we must first apply some transformations. The last line is needed for landscape apps. Remove it If your app works in portrait mode. 

//corrent the rotation matrix from Motion api 
AttitudeReading attitude = motion.CurrentValue.Attitude;
Matrix orientation = Matrix.Identity;
orientation = Matrix.CreateRotationX(MathHelper.PiOver2); //device->screen cordinate
orientation *= attitude.RotationMatrix; 
orientation *= Matrix.CreateRotationZ(MathHelper.PiOver2); //portrait->landscape cordinate
 

What you have now is a View matrix. You can use it to draw your stuff with Model.Draw(...) or assign it to Effect.View. 
Sometimes this isn't enough. What I wanted was the actual orientation of the camera/phone as a 3D vector.
My first thought was to use Vector3.Transform(...) and transform a Vector3.Forward using the orientation matrix. That didn't work. Then I tried to get the orientation.Forward & orientation.Up but that didn't work either. Finally i wrote a small method that extracts the two vectors from a view matrix.
As you can see, you can use the results in order to create your own view matrix or use it in your Camera class.  

//extract oriantation from view matrixs
Vector3 cameraForward = Vector3.Zero;
Vector3 cameraUp = Vector3.Zero;
GetViewOrientation(ref orientation, out cameraForward, out cameraUp);
 
view = Matrix.CreateLookAt(cameraPosition, cameraPosition + cameraForward, cameraUp);
 

And here is the method to extracts the Fordward and Up vectors 

public void GetViewOrientation(ref Matrix view, out Vector3 forward, out Vector3 up)
{
    forward = new Vector3(-view.M13, -view.M23, -view.M33);
    up = new Vector3(view.M12, view.M22, view.M32);

}

 

MotionAPI XNA AR.zip (773.14 kb)

19. September 2010 11:52
by nKast
0 Comments

AccelKit, an Augmented Reality Accelerometer Kit for Windows Phone 7 Emulator

19. September 2010 11:52 by nKast | 0 Comments

AccelKit is an Augmented reality tool that simulates an accelerometer sensor for those who develop applications for the upcoming Windows Phone 7.
I used ARToolKit, an open source AR library that can track the position & orientation of a marker moving in front of a webcam.  Those data are then translated into accelerometer measurements and made available to any program through port 88. You can get the source code at accelkit.codeplex.com or just the executable at tainicom.


How to use it
 

Print the Print_This_Cutout.pdf on plain paper and glue it on a sheet of cardboard. 
Then cut out the phones. 

Place your webcam in  straight position, forming a 90o angle with the floor. 

Run the accelKit.exe found inside the Executable folder. 
On the first screen you are asked to select the desired web resolution. Because tracking can be very CPU intensive, select the lowest possible resolution (That screen might vary from webcam to webcam).
You should now see feed from your webcam. If not, check for other video capture devices in your system (like TV tuners for example) and disable them. 
Move the marker smoothly in front of the webcam. If accelKit keeps loosing track of the marker add some more light to the room to improve the image sharpness. If you still have problems, try again with the next resolution until you get consistent results. It is normal to lose track once in a while for a few frames, especial when the marker moves too fast or on extreme angles. 

Open a web browser and go to http://127.0.0.1:88/. You should see something in the form of "-0.068880,-0.997565,-0.010901". In case you are eager to see it in action get the samples at tainicom.net

Now, let's add support for accelKit in your applications. The code below (C#) demonstrates how you can get the accelKit data.

..
WebClient wc;
wc = new WebClient();
wc.AllowReadStreamBuffering = false;
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri("http://127.0.0.1:88/"));
..

void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)

if (e.Error != null) { timer.Begin(); return; }
if (e.Result == null) { timer.Begin(); return; }
string[] vc = e.Result.Split(new Char[] {',', ' '});
double x = Convert.ToDouble(vc[0]);
double y = Convert.ToDouble(vc[1]);
double z = Convert.ToDouble(vc[2]);

Inside the release you will find the AccelerometerEmu class that you can use as an in-place replacement of the Accelerometer class. Unfortunately, the Accelerometer is sealed so I couldn't inherit from it and had to use composition instead. AccelerometerEmu connects to the accelKit and retrieves data in a constant rate when run in the Windows Phone 7 emulator. When run in a real device, it uses the real Accelerometer (since I don't have access to a real device I couldn't test how good that works). Here's what you have to do:

Replace all instances of Microsoft.Devices.Sensors.Accelerometer with NKast.Sensors.AccelerometerEmu.

Add an event listener to ReadingEmuChanged.

..  
accelerometer.ReadingEmuChanged += new EventHandler<AccelerometerEmuReadingEventArgs>(accelerometer_ReadingEmuChanged);
..


void accelerometer_ReadingEmuChanged(object sender, NKast.Sensors.AccelerometerEmuReadingEventArgs e)  
{
Deployment.Current.Dispatcher.BeginInvoke(() => MyReadingChanged(e));
}


void MyReadingChanged(AccelerometerEmuReadingEventArgs e)
{
double accelx , accely , accelz;
accelx = e.X; 
accely = e.Y;
accelz = e.Z; 
..

My Plans for the next version

Bug fixes!
Code optimization.
Derive acceleration from the mark movement. Right now it only accounts for orientation, therefore you get only gravity acceleration as if you were rotating the device in a fixed position.
Add a mode where the webcam looks straight down. I understand that's necessary for certain types of games. 

Last notice

  • The simulated accelerometer fires events in fixed intervals. A real device might behave differently.
  • The Timestamp property of AccelerometerEmuReadingEventArgs is not yet emulated.
  • Currently it only returns the gravity acceleration. Not acceleration caused by movement.
  • Do not print the paper cutout on glossy or semigloss paper! The less glare, the better!

 

I hope you find it helpful while developing great games for Windows Phone 7 ! 

16. February 2009 13:25
by nKast
0 Comments

Στοιβάζοντας movieclips στο Flash

16. February 2009 13:25 by nKast | 0 Comments

Αν είσαι Flash developer τοτε οι παρακάτω γραμμές κώδικα θα πρέπει κάτι να σου θυμίζουν:

var xPos:Number = 0;
for( var childNode:Xml in rootNode.elements() )

var tmpmc:Movieclip = new MenuBtn();
tmpmc.label.text = childNode.get("Label");
menuPlaceholder.addChild(tmpmc);
//...
tmpmc.x = xPos;
xPos += tmpmc.height + 6;
}


Κατι παρόμοιο έχεις συναντήσει είτε οταν φτιάχνεις ενα δυναμικό μενού που αντλεί στοιχεία απο ενα xml, είτε ενα γκάλερυ για φωτογραφίες.

Αυτή η προσέγγιση όμως πάντα με προβλημάτιζε, έτσι έφτιαξα μια μικρή βιβλιοθήκη που χειρίζεται τέτοιες καταστάσεις με ποιό κομψό τρόπο. Ακολουθεί ενα μικρό demo του τί μπορεί να κάνει..

Get Adobe Flash player

Το πρωτο παράδειγμα θα μπορούσε να γραφεί κάπως ετσι:

var sh:StackHorizontal = new StackHorizontal(menuPlaceholder,6);
for( var childNode:Xml in rootNode.elements() )
{
var tmpmc:Movieclip = new MenuBtn();
tmpmc.label.text = childNode.get("Label);
sh.addChild(tmpmc);
//... 
}

Πολύ καλύτερα, ε;
Προσέξτε οτι δεν καλώ την addChild του menuPlaceholder αλλα της sh.

Η βιβλιοθήκη είναι γραμμένη εξωλοκλήρου σε HaXe και μπορείτε να την βρείτε στο www.codeplex.com/nkHaxeLibrary. Είναι λίγο φτωχή για την ώρα, αλλα με τον καιρό θα εμπλουτιστεί. Θα ακολουθήσουν νέα παραδείγματα καθώς θα προσθέτω νέες κλάσεις.