8
\$\begingroup\$

I'm looking to create a halo highlight around the selected objects in my game screen. The main platform is android, but I am writing an iOS version as well, updating it whenever I successfully add the next feature.

I am trying to ensure that the game is capable of working in both OpenGLES 1 and 2, but of course I expect v2 to have more effects eventually.

The effect I want to achieve is to have a halo around the edge of the selected object (I believe Diablo II did something similar). In theory my idea is to render the scene, enabling stencil ops when the selected object is rendered then enable stencil testing, disable depth and set an orthogonal projection and draw the halo colour 5 pixels larger in each of the X and Y dimensions from the object's centre with 0.2 alpha then successively draw again reducing the size and blending.

Using this method, I also need to figure out the most efficient way of determining the centre and extents of the objects.

EDIT: Another technique I just thought of after posting is, after the stencil op render, render a coloured ortho rectangle to texture through the stencil then shrink and re-enlarge the image to blur the edges then render the resultant texture back on top of the scene, around the stencilled area

  1. Are either of these a good/efficient way to achieve the effect? Is there a better/faster/more efficient/more common way of doing this?
  2. Should I bother doing it for v1 and make it a v2 only effect?
  3. Should I bother with v1 at all if I want these sorts of effects?
\$\endgroup\$
1
  • \$\begingroup\$ I have a feeling the solution is going to be different for each of v1 and v2. I've got some great ideas for using shaders but I'll have to do something different for v1. \$\endgroup\$ Jul 30, 2012 at 2:54

3 Answers 3

1
\$\begingroup\$

Are you using 3D? Then you could just add slightly scaled versions of the same object wich are coloured in your halo colour and have 0.2 alpha. Then just render the real object after all these halo-objects

\$\endgroup\$
5
  • 2
    \$\begingroup\$ That'll only work if the objects he's displaying are fully convex. If there are any concavities, they won't get highlighted correctly. \$\endgroup\$ Jul 29, 2012 at 1:51
  • \$\begingroup\$ Good idea @Aron_dc, but issue there is that the halo would be in object co-ordiates and I need it in screen co-ordinates. Also, the edges may be at drastically different depths, so the halo may not be even. Would work well for a neon light effect though. \$\endgroup\$ Jul 29, 2012 at 23:46
  • \$\begingroup\$ @Raceimaztion, you have a good point there, although I think the stencilling may cancel it out, but I'm just theorising here. \$\endgroup\$ Jul 29, 2012 at 23:46
  • 1
    \$\begingroup\$ I have shipped commercial games using this approach, just scaling by a different amount based upon distance, to maintain an (approximately) constant outline width regardless of distance. (With that said, I would not use this approach today; using multiple render targets will be far more effective, and probably faster on modern video cards) \$\endgroup\$ Jul 30, 2012 at 5:57
  • 1
    \$\begingroup\$ @Aron_dc I'm giving you the tick because you've lead me in the right direction. I'm using this principle with some adjustments. I'll be, for the moment, using GLSL to correct the final scale in screen units. \$\endgroup\$ Aug 10, 2012 at 6:35
3
\$\begingroup\$

This question popped to the top because of Aeroson's answer (above/below/adjacent/falling in bits around this one) and thought I'd yoink my answer from this question in the event that it was useful to someone.

What the below shader does:

enter image description here

Downsides:

  • not a mobile-friendly shader, due to using the stencil buffer and rendering the object twice (depending on the capabilities of the device in question this may be acceptable).
  • requires forward lighting (Stencil functionality for objects rendered in the deferred rendering path is somewhat limited, as during the base pass and lighting pass the stencil buffer is used for other purposes).
  • does not render the object itself (though possibly fixable by tweaking the geometry output).

I realize this question specifically asked for mobile solutions, however the more general "draw an outline around an object" question is very common and I spent a number of hours looking for a good solution and ended up having to write this shader by hand because the common solutions were insufficient for my needs.

Shader "Stencil/Outline" 
{
    Properties 
    {
        _Color("Color", Color) = (1,0,0,1)
        _Thickness("Thickness", float) = 4
    }
    SubShader 
    {

        Tags { "Queue"="Geometry" "IgnoreProjector"="True" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Back
        ZTest always
        Pass
        {
            Stencil {
                Ref 1
                Comp always
                Pass replace
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct v2g 
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
                float3 viewT : TANGENT;
                float3 normals : NORMAL;
            };

            struct g2f 
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
                float3  viewT : TANGENT;
                float3  normals : NORMAL;
            };

            v2g vert(appdata_base v)
            {
                v2g OUT;
                OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                OUT.uv = v.texcoord; 
                OUT.normals = v.normal;
                OUT.viewT = ObjSpaceViewDir(v.vertex);

                return OUT;
            }

            half4 frag(g2f IN) : COLOR
            {
                //this renders nothing, if you want the base mesh and color
                //fill this in with a standard fragment shader calculation
                return 0;
            }
            ENDCG
        }
        Pass 
        {
            Stencil {
                Ref 0
                Comp equal
            }
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma target 4.0
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag


            half4 _Color;
            float _Thickness;

            struct v2g 
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 viewT : TANGENT;
                float3 normals : NORMAL;
            };

            struct g2f 
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 viewT : TANGENT;
                float3 normals : NORMAL;
            };

            v2g vert(appdata_base v)
            {
                v2g OUT;
                OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                OUT.uv = v.texcoord;
                OUT.normals = v.normal;
                OUT.viewT = ObjSpaceViewDir(v.vertex);

                return OUT;
            }

            void geom2(v2g start, v2g end, inout TriangleStream<g2f> triStream)
            {
                float thisWidth = _Thickness/100;
                float4 parallel = end.pos-start.pos;
                normalize(parallel);
                parallel *= thisWidth;

                float4 perpendicular = float4(parallel.y,-parallel.x, 0, 0);
                perpendicular = normalize(perpendicular) * thisWidth;
                float4 v1 = start.pos-parallel;
                float4 v2 = end.pos+parallel;
                g2f OUT;
                OUT.pos = v1-perpendicular;
                OUT.uv = start.uv;
                OUT.viewT = start.viewT;
                OUT.normals = start.normals;
                triStream.Append(OUT);

                OUT.pos = v1+perpendicular;
                triStream.Append(OUT);

                OUT.pos = v2-perpendicular;
                OUT.uv = end.uv;
                OUT.viewT = end.viewT;
                OUT.normals = end.normals;
                triStream.Append(OUT);

                OUT.pos = v2+perpendicular;
                OUT.uv = end.uv;
                OUT.viewT = end.viewT;
                OUT.normals = end.normals;
                triStream.Append(OUT);
            }

            [maxvertexcount(12)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
            {
                geom2(IN[0],IN[1],triStream);
                geom2(IN[1],IN[2],triStream);
                geom2(IN[2],IN[0],triStream);
            }

            half4 frag(g2f IN) : COLOR
            {
                _Color.a = 1;
                return _Color;
            }

            ENDCG

        }
    }
    FallBack "Diffuse"
}
\$\endgroup\$
2
\$\begingroup\$

The idea is that you extrude the object vertices along their normals.

First render highlight:

  • Render the object with disabled z write but enabled z test.
  • Vertex shader: to each vertex add normal * highlight size before you pass it on
  • Fragment shader: render only color, don't apply lighting

Then render the object:

  • Render with z write and z test enabled.
  • Render with your normal shaders.
  • The object will render on top of it's highlight leaving only edges highlighted.

Here is nice tutorial video for Unity3D completely with shader sources: https://www.youtube.com/watch?v=00qMZlacZQo

It renders sharp highlights, I have no idea how to effectively blur them. Maybe from vertex to fragment you can pass the real vertex position and use the distance to change alpha. But that won't help since the position will be interpolated as well? Maybe use concept from Single-Pass Wireframe Rendering.

\$\endgroup\$
1
  • \$\begingroup\$ Oh hey, someone solved the wireframe mesh drawing exclusion problem I hadn't figured out. Nice, +1. \$\endgroup\$ Feb 4, 2016 at 15:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .