The color palette is very important for games. It allows to percept the action on a screen more naturally. But it also brings additional complications during development. For example, due to similar tint, the background can mask foreground elements.
This problem can be solved by the designer having those elements properly painted (not just a solid color but also adding some gradients, etc.). But this solution can be applied to the elements that are static. Whenever the background shifts the result becomes broken.
At this moment the most suitable solution would be blending of colors controlled in time. In Unity Game Engine this can be done via shader.
Having UV coordinates it is very simple to retrieve the color of the element. But blending required two colors. Now the main question is: “How to get the color of the background?”.
The answer is quite simple: “GrabPass”.
GrabPass
GrabPass is a special pass type – it grabs the contents of the screen where the object is about to be drawn into a texture. This texture can be used in subsequent passes to do advanced image based effects. 1
Here is a flow of the rendering pipeline with the corresponding point of GrabPass execution:
The description above is quite self-explanatory, but nothing can replace an example.
Problem Statement
As it was mentioned before, similar colors can have pretty unpleasant blending result. The following example has a couple of places where the borders of elements are indistinguishable (pointed with red arrows):
Solution
To retrieve the content of the frame buffer (basically the background image) it is required to add very simple pass:
GrabPass { "_BackgroundTexture" }
The single line inside the block defines the name of the target texture where the frame buffer must be saved. This block can be even empty. In this case, the texture will be accessible via name “_GrabTexture”.
Prior to sampling this texture it is required to get proper UV coordinates. This can be done in two steps:
- Vertex coordinate given as an input for Vertex function cannot be used since they are provided for a particular element but not the whole screen. As an intermediate step these coordinates can be first converted into clip space:
float4 cameraClipSpaceVertexPosition = UnityObjectToClipPos(input.vertex);
- Coordinates from clip space into ‘GrabPass’ space can be converted in the following way:
float4 grabUV = ComputeGrabScreenPos(cameraClipSpaceVertexPosition);
Both functions are well described in Unity Documentation 2.
Now, the result of the last conversion can be used within Fragment function for texture sampling:
float4 color = tex2Dproj(_BackgroundTexture, input.grabUV);
Note coordinates provided by the conversion are homogeneous. That means tex2D function cannot be used in this case without normalizing using W coordinate. Instead tex2Dproj does this operation on its own.
The last step now is actual color blending. For the current example, it was done in the following way:
half4 result = menuColor; if (distance(menuColor, bgColor) < 0.4) { result = menuColor * (1.6 - distance(menuColor, bgColor)); } result.a = menuColor.a; // keep menu transparency
Check the source code of complete example 3.
Result
The UI elements are now more highlighted. Problematic places (pointed with green arrows) are now well distinguishable:
Less flexible but still functional and very cheap solution provided by Unity is Blending 4.
Pitfalls
First of all, it is well known that use of Render Texture is quite an expensive operation. Moreover, it also depends on the screen resolution. This fact brings additional complexity to the testing process. It is quite difficult to predict FPS on a target environment because there can be an infinite number of combinations between hardware capabilities and screen resolution.
The second interesting aspect is a way how the Render Texture is shared between materials. In case if GrabPass has a defined name for the target Render Texture it allows all materials (with the same shader) to have an access to this texture without a necessity to run GrabPass again (only the first one is executed). From the first sight, it saves some cost of execution but brings obvious side effect: all the objects don’t know about each other, they have an information about the underlying background only, so correct blending between them is impossible.
Footnotes
1. https://docs.unity3d.com/Manual/SL-GrabPass.html
2. https://docs.unity3d.com/Manual/SL-BuiltinFunctions.html
3. https://github.com/alexey-anufriev/grabpass-unity
4. https://docs.unity3d.com/Manual/SL-Blend.html
Be First to Comment