Hi, so I’m rendering out a 3D mesh to a Texture2D and drawing that texture in an editor window but when I turn on depth clear flags so that I can have a transparent background, there is a weird outline around the mesh - is there anyway to fix this?
Below are two screenshots - left is with clearflags set to solidcolor (no weird outline) and right is set to depth (has weird outline)
Also below that is the relevant section of code
Thanks
RenderTexture rt = new RenderTexture(renderSize.x, renderSize.y, 24);
rt.Create();
previewRenderer.camera.targetTexture = rt;
previewRenderer.camera.Render();
var currentRT = RenderTexture.active;
RenderTexture.active = rt;
Texture2D render = new Texture2D(rt.width, rt.height);
render.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
render.Apply();
RenderTexture.active = currentRT;
//render is then drawn in the UI
They both have outlines really. When you set the clear to depth only, really you’re saying “use the default clear color of (0,0,0,0)”. This means the background is “clear” because the alpha defaults to fully transparent, but you’re rendering using MSAA, so the anti-aliased edges are a blend of the black default clear color and your rendered object. The short version is the colors in the final image have been premultiplied by the alpha.
The easiest solution is to render the image using a premultiplied alpha shader… of which Unity has none built in that actually work properly for the simple case of using an input texture that has premultiplied alpha. Like this:
You can use that along with something like EditorGUI.DrawPreviewTexture to draw a texture with a custom material rather than just the default alpha blending.
The other options would be to disable MSAA and filtering on your texture, or to use a post process on your images to divide the color values by the alpha to undo the premultiplication, and then dilate the color into the fully transparent areas to ensure filtering doesn’t still produce a dark edge.
Ok so I tried using the custom shader but didn’t seem to change anything so maybe I did it wrong?
Here’s a screenshot (above is new method, below is old)
And here’s the code for drawing it (rendersList is a list of Texture2Ds - converted from RenderTextures as in my original post)
if (rendersList.Count > 0)
{
Rect r = GUILayoutUtility.GetRect(128, 128);
r.width = 128;
EditorGUI.DrawPreviewTexture(r, rendersList[0], new Material(Shader.Find("IconGenerator/ImgShader")));
}
bleh … obviously something isn’t working as there’s almost no difference between those two images.
One random option. Try setting the camera to clear to a solid color, and set that solid color to 0,0,0,0. Otherwise I wonder if custom blend modes are broken for DrawPreviewTexture.
I also tried setting it to 1,0,0,0 and this is what I got so there is some difference?
also, if I turn off AA for the camera it gets rid of the outline as expected, from here is there then a way to do some form of AA/smoothing that wouldn’t make the outline?
So, Unity has a long running bug that it displays textures in the UI using the wrong gamma. A small change to the shader should make the original setup work.
The one thing that did kinda work was rendering the texture at twice the resolution with MXAA turned off, then using EditorGUI.DrawPreviewTexture and drawing at the original size (essentially downscaling it and doing the AA for me)
only issue is I need to be able to save as png but obviously all I have available is the double scale texture2d - not the nicely scaled down one that shows in the UI
Okay, solved it a different way. Go back to the previous shader without correction. The trick is to set the proper sRGB settings for the render texture (sRGB) and the Texture2D (linear).
using UnityEngine;
using UnityEditor;
[RequireComponent(typeof(Camera))]
public class CameraIcon : MonoBehaviour
{
public enum IconResolutions
{
x64 = 64,
x128 = 128,
x256 = 256,
x512 = 512
}
public IconResolutions iconResolution;
public Texture2D icon;
#if UNITY_EDITOR
public void RenderIcon()
{
var cam = GetComponent<Camera>();
int res = (int)iconResolution;
var rtd = new RenderTextureDescriptor(res, res) { depthBufferBits = 24, msaaSamples = 8, useMipMap = false, sRGB = true };
var rt = new RenderTexture(rtd);
rt.Create();
cam.targetTexture = rt;
cam.Render();
cam.targetTexture = null;
if (icon == null)
{
icon = new Texture2D(res, res, TextureFormat.RGBA32, false, true);
}
else if (icon.width != res)
{
icon.Resize(res, res);
}
var oldActive = RenderTexture.active;
RenderTexture.active = rt;
icon.ReadPixels(new Rect(0, 0, res, res), 0, 0);
icon.Apply();
RenderTexture.active = oldActive;
DestroyImmediate(rt);
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(CameraIcon))]
public class CameraIconEditor : Editor
{
private Material iconMat;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var ci = (target as CameraIcon);
if (GUILayout.Button("UpdateIcon"))
ci.RenderIcon();
if (ci.icon != null)
{
if (iconMat == null)
iconMat = new Material(Shader.Find("Editor/Premultiplied Alpha Blend"));
Rect rect = EditorGUILayout.GetControlRect(false, (int)ci.iconResolution);
rect.width = rect.height;
EditorGUI.DrawPreviewTexture(rect, ci.icon, iconMat);
}
}
}
#endif
It’s not a scene camera, its PreviewRenderUtility but I thought this would work
Test t = previewRenderer.camera.gameObject.AddComponent<Test>();
t.iconResolution = Test.IconResolutions.x512;
Texture2D render = t.RenderIcon(); //I made the function return the texture
Then
Material iconMat = new Material(Shader.Find("IconGenerator/ImgShader"));
Rect rect = EditorGUILayout.GetControlRect(false, (int)previewSize);
rect.width = rect.height;
EditorGUI.DrawPreviewTexture(rect, rendersList[0], iconMat); //rendersList[0] is the same as the render Texture2D above
Any thoughts?
Might just rewrite my thing to avoid using the PreviewRenderUtility altogether - could just instantiate a camera in scene view, render the image, then delete it straight away
Ok so I did what I said at the bottom of my last comment and ditched PreviewRenderUtility and it works if I use EditorGUI.DrawPreviewTexture with the custom shader however I need to be able to save the render as a png (and really just need it as a Texture2D as I am drawing a SelectionGrid which takes Texture2Ds) - is there anyway to save the render with the shader applied because otherwise the saved image just looks like the bottom pic rather than the top
The shader isn’t modifying the data, it’s modifying how the data is displayed. If you save the texture2D to a png, then load that png and display it with the same shader it should work exactly the same. Unity’s PNG save and load shouldn’t be modifying the data at all.
The next “step” I would take if you want to try to avoid some of this pain is the “undo the premultiplied alpha” step I mentioned above. This could be done with a blit, or by modifying the data on the CPU before saving it to disk.
Color[] pixels = icon.GetPixels();
for (int i=0; i < pixels.Length; i++)
{
if (pixels[i].a < 1f && pixels[i].a > 0f)
{
pixels[i].r /= pixels[i].a;
pixels[i].g /= pixels[i].a;
pixels[i].b /= pixels[i].a;
}
}
icon.SetPixels(pixels);
var bytes = icon.EncodeToPNG();
// etc
Issue is that I need to be able to view the image without using any special shaders, like in other programs or windows photo viewer for example. Is there anyway to sort of bake the shader into the data if that makes sense?
Sorry for necro posting a bit, but I was wondering if you could elaborate on this?
Using this code it improves the result for sure, but doesn’t quite remove it. It goes from this:
To this
This is using GUI.DrawTexture, and white using EditorGUI.DrawPreviewTexture with the material works, it also will no longer respect GUI masking (like in scroll views).
Of course using point filtering will solve this, but that makes it quite pixel (duh).
Any further recommendations would be appreciated.