- Home /
Subtractive Rendering?
I am trying to figure out how to create this rendering scenario below, but I'm not sure exactly how to go about it...
Object A- Solid object
Object B- Transparent object
Object C- Background geo
If the transparent object B travels behind object A it would act by default as expected. But if it travels in front of object A it would "subtractively render" to show through that object A, revealing whatever is behind it. (All objects would have collision so there would be no intersecting)
Would this mean a custom shader, or could this be achieved with layers alone? Any suggestions would be appreciated. Thank you.
Hmm... for this specific scenario, I want to say it can be done with a mix of both shaders and camera layers, but for it to work like you want it to, opaque objects have to be rendered "front to back", that is, drawn closest to farthest in relation to the camera. I don't know if Unity does this, though. The front to back rendering is important because it would mean that B will be rendered before A if B closer to the camera; that way B gets access to the color and depth buffers first.
Here's why the front to back is important - When an object is drawn, they are usually drawn to two buffers. The obvious one is the color buffer, but the other one is the depth buffer which stores how far away the object is from the camera. I guess you can visualize the depth buffer kind of like those pin art toys.
The depth buffer is just as important as the color buffer, because it deter$$anonymous$$es what gets drawn and what doesn't by following one basic rule: If a pixel is about to be drawn, but it's "farther away" than what's in the depth buffer at those coordinates, it's NOT drawn.
Using the objects in your example: If A gets drawn before B and B is closer to the camera, then when B is drawn it will overwrite the colors in the buffer to give the proper perspective. On the other hand, if B gets drawn before A, when it comes time to draw A it can't overwrite the pixels that B occupied because B is closer. So essentially depth buffer makes it so that the renderer gives the same image no matter what order they are drawn in.
This gives you the correct image for completely opaque objects. Transparent objects are done differently, since transparency relies on pixel blending to take what's already in the color buffer and mix it with the color you're trying to draw and replaces the color in the buffer, resulting in a transparent look.
That's why transparency is done with objects being rendered "back to front". A would need to be processed first to get it in the color buffer first, so that there can be a color mix for you to "see it through" B.
Since you DON'T want to see A behind B at all, this is the other reason you should do "front to back" rendering.
So, for your effect, it looks like you would need a shader for object B that would draw it RGBA=[0, 0, 0, 0] (completely transparent) for every pixel it occupies. That way, only the depth buffer gets modified while rendering B, but the color buffer doesn't. So, as long as B is drawn first, there's nothing from A in the color buffer to blend with, and the pixels that A shares with B will never get drawn in.
The C part is tricky, because you want it to be treated as if its rendered regularly in respect to A and B. For that, I'd say that as long is its always going to be the farthest away and never intersect with the others, render it on a separate camera.
but i gotta say again, this all works only if Unity let's you specify render order
Which information is given? Do we know both C and A, or only one of them? Do we know B? Is layering possible?
@jbarba_ballytech, This sounds like a solid plan if it indeed renders front to back. Thanks for the help! I will start doing my research to see if this is possible.
Answer by hoy_smallfry · Feb 25, 2013 at 10:42 PM
BAM!!! I did it. I didn't try it myself the other day because I was a bit busy, but here you go:
It possible to force Unity to fake object ordering, if each object is its own separate camera and you put the cameras in sequence you want the objects ordered.
With my example there are three cameras at the same transform. They are under a parent, and I'm treating the parent as if its one camera. These are the camera settings:
Camera 1 - Depth: 0 (it's drawn first) - Clear Flag: Solid Color (it fills the background with a color) - Culling Mask: Camera 1 (will rendering everything with "Camera 1" layer)
Camera 2 - Depth: 1 (it's drawn second) - Clear Flag: Don't Clear (it doesn't remove anything from the depth and color buffer that was drawn previously by Camera 1) - Culling Mask: Camera 2
Camera 3 - Depth: 2 (it's drawn last) - Clear Flag: Don't Clear (it doesn't remove anything from the depth and color buffer that was drawn previously) - Culling Mask: Camera 3
Then...
Set object C under the "Camera 1" layer, B under "Camera 2", and A under "Camera 3". |
I tried using the Transparent/Diffuse shader that came with Unity for object B, but it seems that those shaders purposely don't write to the depth buffer. Instead, use the following shader:
Shader "Subtractive"
{
SubShader
{
Pass
{
// use a pixel blend, so that it takes from the color from the buffer
Blend SrcAlpha OneMinusSrcAlpha
// pixel blending only works with lighting, so turn it on
Lighting On
// make sure the depth gets written in
ZWrite On
// set the material color to invisible
Material
{
Diffuse (0,0,0,0)
}
}
}
}
The clear flag settings will make sure cameras create a composite image instead of completely overwriting one another. The camera ordering forces a rendering order of C, B, A; B will definitely get prior access to the buffers before A. The shader will make sure B writes to the depth duffer but does not write to the color buffer in a way that changes it.
I hope that's the solution you're looking for! Cheers!
This is fantastic! I'm testing it on my setup as well and it's working perfectly.
I really did not expect to get such an in depth response for my question and I can't thank you enough. You are my hero. THAN$$anonymous$$S!
Well, it was a learning experience for us both. I'm quite familiar with graphics program$$anonymous$$g, but I'm just beginning to learn Unity graphics pipeline. So it was just a matter of how Unity could be manipulated to do whats already available in OpenGL/DirectX.
Late to the party, I know -- but just now have a need for this effect. This is awesome.
One question: Is there a way we can apply some soft of alpha mask to that the cutout edges can have a softer fade?
Thanks in advance. $$anonymous$$anny
Answer by Bunny83 · Feb 26, 2013 at 12:18 AM
Just like to add that it's also possible with one camera and using the renderQueue property of the material. The Depthmask shader on the wiki does a quite similar job.
The only issue i know is that it's difficult to get this to work with Unity terrain shader and the default skybox is masked as well since the skybox is drawn quite late in the renderorder to avoid overdraw. This can be fixed by using a custom skybox or i guess by using multiple cameras but haven't tested it.
unfortunately the one at the wiki does not affect the buffer shadow of the hidden object.
What can we add to also hide the shadow??
Answer by Nickkk · Dec 10, 2014 at 06:56 PM
I achieved the same effect without using any additional cameras. Just used the following shader for the sphere:
Shader "Custom/Subtractive" {
SubShader {
Tags { "Queue" = "Background" }
Pass
{
Blend Zero One
Lighting On
ZWrite On
Material
{
Diffuse (0,0,0,0)
}
}
}
}
Pretty much what i wrote but less efficient. You should set the Colormask to 0 to not write to the color buffer at all. Blending still requires the fragment shader to calculate the color and actually rewrite the pixel to the framebuffer. Also "Lighting On"? This would cause even additional processing which doesn't affect the endresult. Take a look at the depth-mask shader on the wiki.
Answer by HUBA-GAMES.Sixx93 · Jul 24, 2015 at 12:51 PM
I know that this question is a bit old but I have some problems with the posted solution. This is what i'm getting:
The floor, the player and everything else is my C. There's a big sphere (my B) over the player which you can spot in the center. All the walls in the bottom are my A.
If I pause and resume I get this:
Which is what I'm trying to achieve, but when I move the player through hotkeys (it has a character controller) I get the effect of the first image.
I can't find the reason, help pls :(
Your answer
Follow this Question
Related Questions
Layer Dependent Reflections 1 Answer
Layering Transparent Objects 0 Answers
How to render a RenderTexture into a specific mipLevel 0 Answers
Dynamic Batching not working even adhering to all rules 3 Answers