- Home /
Render White Pixels as Transparent
I have a camera in a 2D scene that renders black and white. I'd like to overlay it on top of another camera's output, but only the black pixels, ignoring the white ones. How do I do this?
Edit: an alternative solution could be to have a sprite renderer that is invisible wherever there is no shadows.
In what way are you rendering the scene in black and white with that camera? Is it seeing other objects entirely? Is it using a shader which limits the colors it sees? Is it rendering to a more-limited output type with fewer color channels available?
This information would help to know what your starting point is in this scenario.
For context, this is a top-down 2d scene. The black and white camera is only rendering one layer. The only object in that layer is a big white square. There's a 2d point light attached to the player that only targets that layer, and all shadow casters also target that layer. Basically the goal is to cover up everything not in the player's line of sight. Normally I would just use lighting in the main camera with shadow intensity set to 1, but there will be lights at other places in the scene that I don't want the player to be able to see unless they have a direct line of sight to it.
Answer by Eno-Khaon · Sep 30, 2021 at 05:53 AM
Well, here's a bit to get you started. I kind've forget the "ideal" way to set this sort of thing up, since I just adapted a much more complex full screen shader for this example as a proof of concept. This is also why the shaders are formatted the way they are; the original versions had more passes that reused functions.
I apologize in advance if I overlooked anything significant, since I made these conversions fairly hastily.
MaskRegion.cs -- Attach to your main camera
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class MaskRegion : MonoBehaviour
{
public GameObject camObj;
public Camera cam;
Material mat;
Shader s;
RenderTexture rt;
int maskTexID;
Camera thisCam;
bool initialized = false;
// If you don't need full resolution, reducing
// maskScale will be a meaningful workload reduction
public float maskScale = 0.5f;
int lastWidth;
int lastHeight;
private void Start()
{
// If you already have this camera created,
// you can skip these and just assign
// the camera instead
camObj = new GameObject("Mask Camera");
cam = camObj.AddComponent<Camera>();
thisCam = GetComponent<Camera>();
// In this camera-cloning example, this matches clipping planes and FOV and such
cam.CopyFrom(thisCam);
// Change the cullingMask to use your layer(s) for your overlay
cam.cullingMask = thisCam.cullingMask;
// You only potentially need to link the camera's transform
// to your main camera if it's going to see anything the main camera will as well
camObj.SetActive(false);
camObj.transform.parent = transform;
// ----------------------------------------
// Ensure that the camera won't draw anything unnecessary. The clearing will be handled by your main camera instead.
cam.clearFlags = CameraClearFlags.Nothing;
// Safety net in case of window/screen resizing... I think these variables' use covers it adequately?
lastWidth = Screen.width;
lastHeight = Screen.height;
rt = new RenderTexture((int)(lastWidth * maskScale), (int)(lastHeight * maskScale), 16, RenderTextureFormat.Default);
rt.name = "Mask RenderTexture";
// Your choice of filterMode, using Point as a hard-edged example
rt.filterMode = FilterMode.Point;
cam.targetTexture = rt;
s = Shader.Find("EK/MaskSilhouette");
mat = new Material(Shader.Find("EK/MaskRegion"));
maskTexID = Shader.PropertyToID("_MaskTex");
initialized = true;
}
private void OnApplicationFocus(bool focus)
{
if(initialized)
{
if(lastWidth != Screen.width || lastHeight != Screen.height)
{
lastWidth = Screen.width;
lastHeight = Screen.height;
rt = new RenderTexture((int)(lastWidth * maskScale), (int)(lastHeight * maskScale), 16, RenderTextureFormat.Default);
rt.filterMode = FilterMode.Bilinear;
cam.targetTexture = rt;
}
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// Prepare current render to use the alternate camera
RenderTexture rtActive = RenderTexture.active;
RenderTexture.active = rt;
// Clear background, most importantly, 0 alpha
GL.Clear(true, true, Color.clear);
cam.RenderWithShader(s, "");
// SetTexture needs to be used consistently because
// RenderTextures break at the drop of a hat
mat.SetTexture(maskTexID, rt);
// Return regular rendering to your main camera
RenderTexture.active = rtActive;
// Merge with original, passing in the original (Color) and masking (low res) RenderTextures
Graphics.Blit(source, destination, mat);
}
}
MaskSilhouette.shader -- This draws any objects visible to the camera in white.
More importantly, objects have alpha of 1, while background has alpha of 0.
Shader "EK/MaskSilhouette"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 simpleColor (v2f i) : SV_Target
{
// Return fixed4(1,1,1,1), solid white
return 1.0;
}
ENDCG
SubShader
{
// Pass 0, draw visible objects in white
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment simpleColor
ENDCG
}
}
}
MaskRegion.shader -- Using the texture obtained from MaskSilhouette, draw ONLY the occluded regions:
(1.0 - silhouette.a)
.
Shader "EK/MaskRegion"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
sampler2D _MaskTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 baseColor = tex2D(_MainTex, i.uv);
fixed4 silhouette = tex2D(_MaskTex, i.uv);
return lerp(silhouette, baseColor, silhouette.a);
}
ENDCG
SubShader
{
// Pass 0
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
Again, I apologize if I made any mistakes in here; I *think* I removed all of my personal script calls, and didn't put this in a new project for testing or anything.
Also, let me know if there's anything you'd like clarified more. Most of this conversion process was spent on comments (including reminding myself what I wrote here), but I was also just trying to make sure I gutted what was no longer necessary.
Thanks for the response! I've imported the script and the two shaders, and attached the script to the camera. I've commented out the portion of the script that sets up the mask camera since I already have it set up, and I just put that in for cam and camObj.
To be honest, I have no clue what I'm doing with shaders, as I've never really used them before, so I don't know where to go from here. I'm assu$$anonymous$$g that more setup is required, because nothing has really changed yet.
Edit: typo
Well, let's break down one of my shader examples quickly:
https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html
https://docs.unity3d.com/Manual/SL-ShaderSemantics.html
Shader "EK/MaskRegion"
{
// These are what you'll see in the inspector
// It's not really important in this context, so it's probably
// even safe to remove it
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
// Because the "Pass" is at the bottom of the script,
// this is the script's body instead, started by CGINCLUDE
// and ended by ENDCG
CGINCLUDE
#include "UnityCG.cginc"
// This is the structure which provides position and
// UV-coordinate information to the vertex shader.
// The variables and structures can be named
// freely, with the variable "types" having stricter
// requirements (e.g. POSITION)
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// This is the structure that will be filled in the
// vertex shader and passed through to the
// fragment/pixel shader.
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
// The texture (sampler2D), and its Scale/Translation
// (tiling/offset), as seen in the inspector
sampler2D _MainTex;
float4 _MainTex_ST;
// (Whoops, didn't need this one for this shader. This
// is used to get pixel-size data where needed)
float4 _MainTex_TexelSize;
// This is the texture applied to this shader from the
// C# script. This contains the render of the mask
// GameObject, which will be the only area rendered
// in normal colors by this shader.
sampler2D _MaskTex;
// This is the vertex shader, called using
// [#pragma vertex vert] in the shader Pass below.
// It dictates WHERE the pixels will be drawn.
// In this case, it's taking a quad and fitting it
// to the screen, since this is a full-screen effect
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
// This is the fragment/pixel shader, called using
// [#pragma fragment frag] in the shader Pass below.
// This dictates the colors of the pixels drawn in the region
// specified by the vertex shader (the whole screen in
// this shader, or the visible objects in the silhouette shader).
fixed4 frag(v2f i) : SV_Target
{
fixed4 baseColor = tex2D(_MainTex, i.uv);
fixed4 silhouette = tex2D(_MaskTex, i.uv);
// This takes the original render [baseColor]
// and displays it only where the [silhouette] allows
return lerp(silhouette, baseColor, silhouette.a);
}
ENDCG
}
Whoops, ran out of comment space.
// This is a relocation of the Subshader and Pass(es) for the shader.
// This formatting lets you call individual passes in Blit() calls easily
SubShader
{
// Pass 0 (As called by Blit())
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
I somewhat can understand how that works now, but that doesn't really help with why it's not doing anything.
Answer by Joey-The-Great · Sep 30, 2021 at 02:29 AM
Bumping, because this has become a serious roadblock.
Your answer
Follow this Question
Related Questions
Calling Render() twice using the same camera 0 Answers
Export customized camera view to image sequences 0 Answers
Showing/Visualizing the camera cone 3 Answers
Scene cam vs Game cam 1 Answer
Animation disappears 0 Answers