I started simple. I had already implemented the actual solitaire engine and just wanted to get some cards to render to the framebuffer, while handling mouse events. As can be seen in the screenshot, I started out with immediate mode.
To add a bit of realism, I threw in some random per-card rotation to reflect the fact that life isn't perfect.
This didn't feel 3D enough for me, so I experimented with some different perspective transformations next.
Next, I experimented with bump mapping, lighting and attenuation.
I learned about image-based lighting (IBL) and couldn't resist throwing that in.
Now it was time to add a bit of polish. I added a background and refined the card borders, lighting and bump mapping.
That didn't stop me from experimenting, though. I couldn't resist mixing multiple textures together in the fragment shader to create a "paint-splattered cards" effect.
I generalized the code to support other games too, such as escalator.
At the idea of a friend, I implemented a real-time sketch effect with Gabor filters in the fragment shader. Around this time, I also ported the game to Direct3D to expand my horizons.
Why not add some dynamic fingerprints for realism?
All fun aside, this is what I ended up with. As stated earlier, I used immediate mode when I first started out, but evolved to instanced rendering, texture arrays and buffer objects.
Complete with a win effect:
The raw card bitmaps were generated from an SVG design created by David Bellot. The tablecloth bitmap comes from a royalty-free texture pack.