- Home /
Procedurally generated uGUI elements render under world space objects
Got to the point where I'm banging my head against a wall now, so here goes!
I have a uGUI canvas prefab with a non-trivial hierarchy:
HackingUI_DeviceCanvas is the object in question. This canvas is loaded (as a prefab) using Instantiate() when the game starts, and I assign the relevant camera to it:
private void CreateHackingDeviceDetailsUI(Camera cam) {
// Create the UI overlay
var hackingUIDeviceDetailsGO = Instantiate(HackingUIDeviceDetailsCanvasPrefab) as GameObject;
if (hackingUIDeviceDetailsGO == null) {
Debug.LogError("Unable to instantiate the hacking UI device details UI canvas");
return;
}
_hackingUIDeviceDetailsCanvas = hackingUIDeviceDetailsGO.GetComponent<Canvas>();
_hackingUIDeviceDetailsCanvas.worldCamera = cam;
// Get all UI panel references
_pnlCamera = GetChildGameObjectByName(_hackingUIDeviceDetailsCanvas.gameObject, "pnlCameraView");
_pnlRelayRules = GetChildGameObjectByName(_hackingUIDeviceDetailsCanvas.gameObject, "pnlRelayRules");
_pnlRelayRuleList = _pnlRelayRules.transform.Find("pnlRuleList").Find("pnlContent") as RectTransform;
var deviceNamePanel = GetChildGameObjectByName(_hackingUIDeviceDetailsCanvas.gameObject, "pnlDeviceName");
if (deviceNamePanel != null) {
_txtDeviceName = deviceNamePanel.GetComponentInChildren<Text>();
}
var deviceStatePanel = GetChildGameObjectByName(_hackingUIDeviceDetailsCanvas.gameObject, "pnlDeviceState");
if (deviceStatePanel != null) {
_txtDeviceState = deviceStatePanel.GetComponentInChildren<Text>();
}
var deviceSecurityRatingPanel = GetChildGameObjectByName(_hackingUIDeviceDetailsCanvas.gameObject, "pnlSecurityRating");
if (deviceSecurityRatingPanel != null) {
_txtDeviceSecurityRating = deviceSecurityRatingPanel.transform.Find("txtSecurityRating").GetComponent<Text>();
}
// Disable the canvas / some panels by default
_hackingUIDeviceDetailsCanvas.enabled = false;
ActivateCameraPanel(false);
_pnlRelayRules.SetActive(false);
}
This all works beautifully. However, when I try to add another prefab (instantiated in the same way), and child it to the _pnlRelayRules object above, it renders behind the camera's world space objects. Code (the prefab is instantiated in the for loop):
protected void OnMessage_ShowRelayRules(CyberspaceUIDevice device) {
var rules = device.RelatedNetworkDevice.NetworkDeviceRelayRules;
if (rules.Length <= 0) {
return;
}
foreach (var rule in rules) {
var ruleListItem = Instantiate(HackingUIRelayRuleListItemPrefab) as GameObject;
ruleListItem.transform.localScale = Vector3.one;
ruleListItem.transform.localRotation = Quaternion.identity;
ruleListItem.transform.SetAsLastSibling();
var controller = ruleListItem.GetComponent<pnlRuleController>();
// TODO: Set values
// Save to the panel list
ruleListItem.transform.SetParent(_pnlRelayRuleList, false);
}
_pnlRelayRules.SetActive(true);
}
Rendered camera:
You can see the other (3D) menu that is rendered to the camera (which is overlayed and resized on the game's main camera) overlays ONLY the instantiated child. The hierarchy that is created with the above code is as follows:
If I create pnlRule in the editor, things work perfectly fine - but when I add it programatically, it renders like this. I have messed about with layering and hierarchy ordering, as well as adjusting / removing / adding various layout components to test with, to no avail.
Can anyone point me in the right direction?
UPDATE
I have since found that changing the second camera's view to orthographic mode sorts the problem. The problem I have here, though, is that the second camera needs to be in perspective mode!
UPDATE 2
I have just tried disabling a mask on pnlRuleList - this also makes the child elements render in the correct order, but obviously the mask no longer works!
UPDATE 3
OK, creating pnlRule in the editor also creates this problem. Sorry for the misinformation above! This is definitely a "Screen Space - Camera" + using a mask issue!
UPDATE 4
Another revelation! Just tried changing the lighting mode; I use Deferred Lighting - changing it to Vertex or Forward lighting solves the problem. Again, not the desired effect, but at least I'm getting closer to a solution.
did you ever fix this problem? I'm also trying to render Ugui in world space behind 3D models int he scene so some GUI elements can be seen even if they are behind objects.
I did, yes - I had to apply a shader I found as a mask on top of the UI elements in question. It's been a while, so I can't remember ther specific implementation, but I will post the shader and a quick screenshot in an answer
Answer by Spikeh · Feb 27, 2015 at 08:30 AM
In response to a comment, here's the solution I found.
I had to create a new uGUI element, size it to the same size as the internal panel (pnlRuleList), but render it on top of pnlRuleList:
"MaskClear" is a custom material, which uses the following shader (I found this after searching for days - I can't find it again, and I probably renamed the shader itself as I tend to do to make more sense to me):
Shader "UI/MaskClear"
{
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref 0
Comp Always
Pass Zero
ReadMask 255
WriteMask 255
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Fog { Mode Off }
Blend Zero One
ColorMask 0
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
fixed4 _Color;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
OUT.color = IN.color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
return IN.color;
}
ENDCG
}
}
}