UPDATE: see end of post for another idea…
One of the ways they make you buy Unity Pro is gimping the non-Pro version by taking away Stencil Buffer and Render-to-Texture (needed for … many (most?) of the truly interesting effect you can think of). Except … it’s only needed by old-school OpenGL programmers, because everything RtT does can be mimicked in Shaders. Stencils … maybe also?
Anyway, I’m working on a hobby project and don’t need or want the Pro version for now (I’d like to support Unity, but these days the jump in price is way too expensive, at $X000 (they price-gouge you if you’re purchasing from within EU) for a simple hobby project). If/when we’re using it at work, and I have lots of cash, sure – but for now: can’t find that much money.
For the most part, I’m happy to go without RtT for now – generally speaking, hobby projects don’t “need” those kind of effects (and if they do: maybe it’s not a hobby any more?). There’s just one exception: reflections.
Reflections are one of 3D’s reasons to exist: they are simple, and powerful, and easy to use, and they entirely rely upon Render to Tex .. – oh, crap.
But I’ve been using this awesome free online book about writing GLSL shaders in Unity and one of the interesting sections is about demonstrating that a GLSL shader can simulate a combination of RtT and Stencils. The GLSL code is very simple, if you know GLSL already it’s just a bit odd getting your head around the “special” way that Unity requires you to write your code (and note: some of the variable names are “magic”: hard-coded into Unity with bonus features).
They use the standard starting point of cloning each of your objects to make a “mirrored” object that you can render in the “mirror universe”. Then they do a neat trick with simulating a stencil buffer, and simulating RtT through some shader-render-order fun and games. Unfortunately, it doesn’t work: In Unity 3.x, the objects “inside the mirror” still appear outside it, if you walk around behind it: this is the point of what the mirror was supposed to prevent (and it so nearly works – Unity renders the “behind” objects with a strange alpha-effect that makes me wonder if it’s a built-in anti-Free-version hack, or if there’s something odd about the default cameras)
The book’s sample code does this:
- Shader for the mirror itself to do cool and clever stuff
- Shader (very simplistic) for the “reflected” objects to render them “inside the mirror but not outside” (which doesn’t quite work, as noted above)
I had to add a few things to use this in practice:
- Convert the script to C#, so I get auto-completion when referencing it
- Write some recursive methods to add the script + its parameters to the cloned Player object when player comes near mirror
- Write some recursive methods to DESTROY EVERY COMPONENT on the “cloned” player, except for: MeshFilter, MeshRenderer (otherwise your mouselook script, your physics RigidBody etc all exist on the clone, and weird and insane freaky sh*t goes down)
- Modify the simple “reflected objects” shader to support standard texture map
- Add a post-attach method that when you mirror a dynamic object (e.g. the player) it grabs the texture from the original and assigns it to the bonus paramter on the mirrored-object shader (the book sample just gave mirrored objects a single colour)
Does it work?
Well, almost. Very nearly.
All of which is great, except for two major bugs:
- the bizarre semi-transparent colour of objects if you walk behind the mirror (e.g. if another room in your building lies behind the mirror) – they shouldn’t be rendering at all, and I’ve double-checked the book’s approach, it all looks like it should work fine to me (or: not at all. Not “partly work”) :(
- the render-order for items in the mirror is FUBAR. They occlude each other back-to-front based on some weird algorithm. The back-to-front occlusion is part of the magic, IIRC, but … it seems to go wrong in the final render :(
For now, since this is a hobby project, I’ve removed everything from the mirror-universe except the player. So these are mirrors that don’t show the room you’re in – they only show you! Looks a bit sucky, but is good enough for now.
If I find a way to fix the bugs, it would be great. Nowhere near as neat (or as performant!) a solution as getting Unity Pro (which I’d certainly encourage you to buy instead of using this poor-man’s approach), but plenty good enough for me to continue my hobbying…
UPDATE: a new technique for Render to Texture
I kicked myself when I saw this, which is a pretty obvious way of simulating RtT. I’m already doing various tricks with multiple, overlapping cameras – so why didn’t I think of this?
Although I fully understand all the techniques above (and can easily write them from scratch), I *don’t* understand how the occlusion is being simulated/dissimulated via the shaders — because if I did, I’d have tried this:
- Create a slave-cam in mirror-world (I’ve already written the script to create a slave-player in mirror-world – doing the same with camera is trivial)
- …in Update(), slave-cam re-sets its absolute position to be the opposite side of the mirror to player-cam, taking into account player-cam’s current/new position
- Draw slave-cam AND player-cam, both full-screen, but depth-sorted (set the camera.depth) so slave-cam is beneath (lower depth number)
- …yes, Unity has “depth” upside-down, the variable should be called “height”
- On slave-cam, “dis-render” everything on slave-cam’s side of the mirror (disable occlusion entirely)
- On slave-cam, “dis-render” the mirror itself (so you can see through it!)
- On player-cam, cut-out (using the link above) the area where mirror appears on-screen, so that slave-cam’s view shows through
As a thought-experiment, that Just Works. Which probably means it’s fundamentally wrong somehow ;). Sadly, I’m out of time for working on this now (remember: hobby project), and I don’t want to spend more time faffing with mirrors, but to anyone else interested, I’d say this technique is definitely worth a try.
UPDATE 2: a new way
I’ve had partial success with a modified version of the above technique. See that post for details (it took fewer steps than I expected, in some ways)