Hello everyone!
I am trying to achieve a simple outline shader and have been pulling my hair out over this one problem for a couple of days. I haven’t done graphics programming in a number of years, and never worked with hlsl before this, so I am a little over my head! I mainly adapted this code from Unity Shaderlab: Object Outlines | Will Weissman - Game Developer as well as a sprinkling of some others
Symbol and card in scene view (artwork temporary ;)) Both should be rendered with an outline.
The intended effect is for the card or symbol to have an outline glow when moused over by the user. This currently works to a degree, as shown below. (The mouse over part is not a problem, this has already been extensively tested and I know it works as it triggers an animation, and the problem persists when this is disabled)
How it currently looks. This is not correct, it should be following the shape of the symbol.
The method I used, as adapted from the code linked above, was to render the object on a solid colour on a black texture and blur it out, finally colouring it and combining it with the rest of the scene. When I mouse over the untextured quad, I get a nice outline. The slight curve around the corners is created by the blurring. However, I want the outline to follow the shape of the symbol, and also (when I implement it) the curved edges of the cards. For this, I added an alpha channel to my fragment shader.
The way that Unity itself highlights the symbol in the scene viewer is effectively the desired result of my shaders.
This is when things went wrong. It seems that if I try to input a texture with an alpha channel, and sample it, it won’t draw any outline, let alone the outline of the quad.
So first is the “Single Colour” shader that renders an object as white on a background of black. In the fragment shader, you can see where I tried to implement the alpha channel. I have not found an easy way to output this channel for debugging, so it’s hard to tell in which shader the problem lies.
Shader "Custom/SingleColour"
{
Properties
{
_MainTex("Main Texture", 2D) = ""{}
}
SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
sampler2D _MainTex;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
v2f_img vert(appdata_base v) // An extremely simple vertex shader that returns the position with no changes
{
v2f_img o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f_img i) : SV_Target // Outputs the fragment as a block white colour on the black background, to later be blurred and become an outline
{
float alpha = tex2D(_MainTex, i.uv.xy).a;
return fixed4(1,1,1,alpha);
}
ENDCG
} // End Pass
} // End Subshader
}
The outline shader, which takes the data from the previous, blurs it, and subtracts the initial texture from the blurred one. It then adds these colours over the main scene render.
Shader "Custom/Outline"
{
Properties
{
_MainTex("Main Texture",2D) = "black"{}
_SceneTex("Scene Texture",2D) = "black"{}
_Colour("Outline Colour", Color) = (0, 0, 0, 0)
//_BlurCycles("Blur Cycles", Int) = 0
}
SubShader
{
Pass
{
CGPROGRAM
sampler2D _MainTex;
sampler2D _SceneTex;
half4 _Colour;
//int _BlurCycles;
float2 _MainTex_TexelSize;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
half4 frag(v2f i) : COLOR
{
// Discard the original shape so only an outline remains
if (tex2D(_MainTex,i.uvs.xy).r > 0)
{
return tex2D(_SceneTex,i.uvs.xy);
}
int blurCycles = 15; // Remove and replace all instances with _BlurCycles when fixed
float TX_x = _MainTex_TexelSize.x;
float TX_y = _MainTex_TexelSize.y;
float ColorIntensityInRadius = 0;
// Blur the outline
for (int k = 0; k < blurCycles; k += 1)
{
for (int j = 0; j < blurCycles; j += 1)
{
ColorIntensityInRadius +=
(tex2D( _MainTex, i.uvs.xy + float2 ((k - blurCycles / 2) * TX_x, (j - blurCycles / 2) * TX_y)).r);
}
}
ColorIntensityInRadius *= 0.05;
half4 color = tex2D(_SceneTex,i.uvs.xy) + ColorIntensityInRadius * _Colour;
return color;
}
ENDCG
}
//end pass
}
//end subshader
}
//end shader
This script goes on the camera, and controls the whole process.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RenderOutline : MonoBehaviour
{
public Shader drawAsColour;
public Shader outline;
public Material outlineMaterial;
Camera tempCam;
private void Start()
{
tempCam = new GameObject().AddComponent<Camera>();
}
void OnRenderImage(RenderTexture _source, RenderTexture _destination)
{
tempCam.CopyFrom(Camera.current);
tempCam.backgroundColor = Color.black;
tempCam.clearFlags = CameraClearFlags.Color;
tempCam.cullingMask = 1 << LayerMask.NameToLayer("Outline");
RenderTexture renderTexture = RenderTexture.GetTemporary(_source.width, _source.height, 0, RenderTextureFormat.R8);
tempCam.targetTexture = renderTexture;
tempCam.RenderWithShader(drawAsColour, "");
outlineMaterial.SetTexture("_SceneTex", _source);
Graphics.Blit(renderTexture, _destination, outlineMaterial);
RenderTexture.ReleaseTemporary(renderTexture);
}
}
I hope this is formatted correctly and everything. Thank you in advance to anybody who takes the time out to look over this for me. I really appreciate it.