- Home /
Shader works in Editor/Windows but not on Android
This shader adds a post processor effect, on Windows/Editor it works, but on Android it does not appear. Just nothing, everything works, but the effect does not appear.
Shader "Custom/Highlight" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Texture", 2D) = "black" {}
_OccludeMap ("Occlusion Map", 2D) = "black" {}
}
SubShader {
ZTest Always Cull Off ZWrite Off Fog { Mode Off }
// OVERLAY GLOW
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _OccludeMap;
fixed4 _Color;
fixed4 frag(v2f_img IN) : COLOR
{
fixed3 overCol = tex2D(_OccludeMap, IN.uv).r * _Color.rgb * _Color.a;
return tex2D (_MainTex, IN.uv) + fixed4(overCol, 1.0);
}
ENDCG
}
// OVERLAY SOLID
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _OccludeMap;
fixed4 _Color;
fixed4 frag(v2f_img IN) : COLOR
{
fixed4 mCol = tex2D (_MainTex, IN.uv);
fixed oCol = tex2D (_OccludeMap, IN.uv).r;
fixed solid = step (1.0 - _Color.a, oCol);
return mCol + solid * fixed4(_Color.rgb, 1.0);
}
ENDCG
}
// OCCLUSION
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _OccludeMap;
fixed frag(v2f_img IN) : COLOR
{
return tex2D (_MainTex, IN.uv).r - tex2D(_OccludeMap, IN.uv).r;
}
ENDCG
}
Pass {
Tags {"RenderType"="Opaque"}
ZWrite On
ZTest LEqual
Fog { Mode Off }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION) : POSITION {
return mul (UNITY_MATRIX_MVP, v);
}
fixed frag() : COLOR {
return 1.0;
}
ENDCG
}
Pass {
Tags {"Queue"="Transparent"}
Cull Back
Lighting Off
ZWrite Off
ZTest LEqual
ColorMask RGBA
Blend OneMinusDstColor One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _CameraDepthTexture;
struct v2f {
float4 vertex : POSITION;
float4 projPos : TEXCOORD1;
};
v2f vert( float4 v : POSITION ) {
v2f o;
o.vertex = mul( UNITY_MATRIX_MVP, v );
o.projPos = ComputeScreenPos(o.vertex);
return o;
}
fixed frag( v2f i ) : COLOR {
float depthVal = LinearEyeDepth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)).r);
float zPos = i.projPos.z;
return step( zPos, depthVal );
}
ENDCG
}
}
}
This works perfectly for a Windows build/Editor, but NOT in on an Android device/Emulator; it works via Unity Remote 4.
C# Script that uses it:
using UnityEngine;
using System.Collections.Generic;
using UnityStandardAssets.ImageEffects;
using UnityEngine.Rendering;
[RequireComponent(typeof(Camera))]
public class HighlightsPostEffect : MonoBehaviour
{
#region enums
public enum HighlightType
{
Glow = 0,
Solid = 1
}
public enum SortingType
{
Overlay = 3,
DepthFilter = 4
}
public enum FillType
{
Fill,
Outline
}
public enum RTResolution
{
Quarter = 4,
Half = 2,
Full = 1
}
#endregion
#region public vars
public HighlightType m_selectionType = HighlightType.Glow;
public SortingType m_sortingType = SortingType.DepthFilter;
public FillType m_fillType = FillType.Outline;
public RTResolution m_resolution = RTResolution.Full;
public string m_occludeesTag = "Occludee";
public string m_occludersTag = "Occluder";
public Color m_highlightColor = new Color(1f, 0f, 0f, 0.65f);
public Shader m_highlightShader;
#endregion
#region private field
private BlurOptimized m_blur;
private Renderer[] highlightObjects;
private Renderer[] m_occluders = null;
private Material m_highlightMaterial;
private CommandBuffer m_renderBuffer;
private int m_RTWidth = 512;
private int m_RTHeight = 512;
#endregion
private void Awake()
{
CreateBuffers();
CreateMaterials();
SetOccluderObjects();
//m_blur = gameObject.AddComponent<BlurOptimized>();
m_blur = gameObject.GetComponent<BlurOptimized>();
m_blur.enabled = false;
GameObject[] occludees = GameObject.FindGameObjectsWithTag(m_occludeesTag);
highlightObjects = new Renderer[occludees.Length];
for( int i = 0; i < occludees.Length; i++ )
highlightObjects[i] = occludees[i].GetComponent<Renderer>();
m_RTWidth = (int) (Screen.width / (float) m_resolution);
m_RTHeight = (int) (Screen.height / (float) m_resolution);
}
private void CreateBuffers()
{
m_renderBuffer = new CommandBuffer();
}
private void ClearCommandBuffers()
{
m_renderBuffer.Clear();
}
private void CreateMaterials()
{
m_highlightMaterial = new Material( m_highlightShader );
}
private void SetOccluderObjects()
{
if( string.IsNullOrEmpty(m_occludersTag) )
return;
GameObject[] occluderGOs = GameObject.FindGameObjectsWithTag(m_occludersTag);
List<Renderer> occluders = new List<Renderer>();
foreach( GameObject go in occluderGOs )
{
Renderer renderer = go.GetComponent<Renderer>();
if( renderer != null )
occluders.Add( renderer );
}
m_occluders = occluders.ToArray();
}
private void RenderHighlights( RenderTexture rt)
{
if( highlightObjects == null )
return;
RenderTargetIdentifier rtid = new RenderTargetIdentifier(rt);
m_renderBuffer.SetRenderTarget( rtid );
for(int i = 0; i < highlightObjects.Length; i++)
{
if( highlightObjects[i] == null )
continue;
m_renderBuffer.DrawRenderer( highlightObjects[i], m_highlightMaterial, 0, (int) m_sortingType );
}
RenderTexture.active = rt;
Graphics.ExecuteCommandBuffer(m_renderBuffer);
RenderTexture.active = null;
}
private void RenderOccluders( RenderTexture rt)
{
if( m_occluders == null )
return;
RenderTargetIdentifier rtid = new RenderTargetIdentifier(rt);
m_renderBuffer.SetRenderTarget( rtid );
m_renderBuffer.Clear();
foreach(Renderer renderer in m_occluders)
{
m_renderBuffer.DrawRenderer( renderer, m_highlightMaterial, 0, (int) m_sortingType );
}
RenderTexture.active = rt;
Graphics.ExecuteCommandBuffer(m_renderBuffer);
RenderTexture.active = null;
}
/// Final image composing.
/// 1. Renders all the highlight objects either with Overlay shader or DepthFilter
/// 2. Downsamples and blurs the result image using standard BlurOptimized image effect
/// 3. Renders occluders to the same render texture
/// 4. Substracts the occlusion map from the blurred image, leaving the highlight area
/// 5. Renders the result image over the main camera's G-Buffer
private void OnRenderImage( RenderTexture source, RenderTexture destination )
{
RenderTexture highlightRT;
RenderTexture.active = highlightRT = RenderTexture.GetTemporary(m_RTWidth, m_RTHeight, 0, RenderTextureFormat.R8 );
GL.Clear(true, true, Color.clear);
RenderTexture.active = null;
ClearCommandBuffers();
RenderHighlights(highlightRT);
RenderTexture blurred = RenderTexture.GetTemporary( m_RTWidth, m_RTHeight, 0, RenderTextureFormat.R8 );
m_blur.OnRenderImage( highlightRT, blurred );
RenderOccluders(highlightRT);
if( m_fillType == FillType.Outline )
{
RenderTexture occluded = RenderTexture.GetTemporary( m_RTWidth, m_RTHeight, 0, RenderTextureFormat.R8);
// Excluding the original image from the blurred image, leaving out the areal alone
m_highlightMaterial.SetTexture("_OccludeMap", highlightRT);
Graphics.Blit( blurred, occluded, m_highlightMaterial, 2 );
m_highlightMaterial.SetTexture("_OccludeMap", occluded);
RenderTexture.ReleaseTemporary(occluded);
}
else
{
m_highlightMaterial.SetTexture("_OccludeMap", blurred);
}
m_highlightMaterial.SetColor("_Color", m_highlightColor);
Graphics.Blit (source, destination, m_highlightMaterial, (int) m_selectionType);
RenderTexture.ReleaseTemporary(blurred);
RenderTexture.ReleaseTemporary(highlightRT);
}
public void RefreshHighlight()
{
//CreateBuffers();
//CreateMaterials();
SetOccluderObjects();
//m_blur = gameObject.AddComponent<BlurOptimized>();
//m_blur.enabled = false;
GameObject[] occludees = GameObject.FindGameObjectsWithTag(m_occludeesTag);
highlightObjects = new Renderer[occludees.Length];
for (int i = 0; i < occludees.Length; i++)
highlightObjects[i] = occludees[i].GetComponent<Renderer>();
m_RTWidth = (int)(Screen.width / (float)m_resolution);
m_RTHeight = (int)(Screen.height / (float)m_resolution);
}
}
(sidenote) I never knew CommandBuffers worked on mobile- I'd always associated them with deferred rendering and DX11/desktop only. So thanks for showing me that's not the case!
Answer by Eudaimonium · Jun 07, 2016 at 12:54 AM
Well, you're trying to do post processing on mobile hardware, what did you expect? :D
Seriously though, technical question: Are you using deferred rendering on your main camera?
If so, no wonder. Deferred rendering requires MRT (multiple render targets) tech, basically it means that a pixel shader can write to several render targets at once, instead of just one. This feature is not supported on mobile hardware yet, to the best of my knowledge. Therefore you lose your depth buffer and whatever else is required for your effects to work.
Putting that aside, any kind of post process on mobile will be horrible for performance because mobile devices have insane resolutions, but very low pixel fillrates - usually not enough to render out to the entire screen several times. Even reducing the resolution doesn't help much since simply switching texture targets required for post process will be insanely expensive.
If I may be so bold as to ask, what is the nature of your application? Any particular reason why you can't just stick with desktop platform?
The problem turns out to be a simple fix: RenderTextureFormat.R8 needed to be RenderTextureFormat.RGBA32 and it works perfectly. I'm not sure why though.
I was developing on Windows because it's a mobile multiplayer game but its faster to iterate on the PC build then it is to use an emulator.
Aahh yes then there's that, I believe I saw a platform-format table, so you can crossreference what supports what.
Hmm I can't seem to find it, but there's this link, try running this on your mobile system and check against R8 format:
http://docs.unity3d.com/ScriptReference/SystemInfo.SupportsRenderTextureFormat.html
Perhaps that's why.