This is kind of an extension to @jackmott's answer, similar his suggestions, but different.
Instead of using
GameObject.SetActive() to flip the states of everything in the scene, I would instead suggest using shaders, material flags, and your own method that flags objects as being visible or invisible depending on the current render state. (This is also slightly related to point 3, doing it programatically, as well).
The benefits are that you only need to create your scene once, and objects that are only visible in one "realm" are cleverly not-rendered when the player isn't in that realm.
Take a look at
Camera#SetReplacementShader(). In forcing the main camera to use a different shader to render every object in the scene, you can change between the material realm (objects use their own shaders) and the spectral realm (objects are rendered using a custom
SpectralShader that you will need to create) which will do the color shifting, blurring, and vertex distortion.
Can't find my screenshot at the moment (on the wrong computer), but I've got something similar set up for my own project right now. It lets me draw outlines of enemies through walls (if the player has that ability active), not draw enemies that are invisible (but they will get outlines!), or draw them as unlit blobs (visible through walls). I use the last one for things like notification icons, e.g. a red
! that hovers over an objective point.
Which state is active depends on which camera is currently rendering, what attribute flags it has, and the enemy being rendered. I had to do a little hacking to get this to work, but I have an event notification system that fires off an event during
OnPreRender (this only happens on the object the Camera is attached to, not for every object being rendered!) to notify every object about to be rendered the detail specifics of the render being performed (can the player see through walls, is the camera a remove-viewing device, and so on). Objects that should be invisible turn their renderer off, objects that should turn it back on, and a couple other flags get flipped via
Shader pass tags are super important when dealing with SetOverrideShader: When the replaced shader runs, it looks at the original shader's tags. If the replaced shader has the same tag with the same value, the pass runs. See Rendering with Replaced Shaders.
This will be a very hard avenue to pursue, but it will also be the one that will have the best looking result with the most flexibility and least amount of effort spent in level creation. And almost certainly the method employed by Legacy of Kain. The hardest part will be creating the custom shader. My own, for my three or four simple effects, is hundreds of lines long (the outline pass alone is 165!)