Extrude Walls via Shader

Hi all,

I’m looking to recreate this 3D wall effect in some capacity (from Portal in-game editor):

From this Pro-builder mesh (shader should keep modifying levels a non-destructive process hopefully):

3167858--241227--upload_2017-8-1_16-58-3.png

I’m pretty new to shader programming so any advice/help would be very appreciated. I’ve slightly modified this shader from a YouTube tutorial and got the following result (code at bottom of post):

3167858--241228--upload_2017-8-1_17-4-48.png

There’s a few issues:

  • (A) Corners don’t connect
  • (B) Generated mesh is messy
  • (C) Walls nearest the camera don’t cull

3167858--241231--upload_2017-8-1_17-12-9.png

From my limited shader knowledge, this is my current guess of what needs to be done:
(A) Move generated vertex along its normal (mesh will have to be smoothed)
(B) Skip vertexes that fail a condition (not quite sure what the condition would be yet)
(C) Do a cull pass first (?)

Any advice or help would be very valuable even if it’s just confirming I’m headed on the correct path. Thanks!

Shader "Custom/Geometry/Extrude"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Factor ("Factor", Range(-2., 2.)) = 0.2
        _FrontColor("Front Color", Color) = (1.,1.,1.,1)
        _BackColor("Back Color", Color) = (.0,.0,.0,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom
            #include "UnityCG.cginc"
            struct v2g
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            struct g2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed4 col : COLOR;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
          
            v2g vert (appdata_base v)
            {
                v2g o;
                o.vertex = v.vertex;
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normal = v.normal;
                return o;
            }
            float _Factor;
            fixed4 _FrontColor;
            fixed4 _BackColor;
            [maxvertexcount(24)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> tristream)
            {
                g2f o;
                float3 edgeA = IN[1].vertex - IN[0].vertex;
                float3 edgeB = IN[2].vertex - IN[0].vertex;
                float3 normalFace = normalize(cross(edgeA, edgeB));
                for(int i = 0; i < 3; i++)
                {
                    o.pos = UnityObjectToClipPos(IN[i].vertex);
                    o.uv = IN[i].uv;
                    o.col = _BackColor;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[i].vertex + IN[i].normal * _Factor); //+ float4(normalFace, 0)
                    o.uv = IN[i].uv;
                    o.col = _FrontColor;
                    tristream.Append(o);
                    int inext = (i+1) % 3;
                    o.pos = UnityObjectToClipPos(IN[inext].vertex);
                    o.uv = IN[inext].uv;
                    o.col = _BackColor;
                    tristream.Append(o);
                    tristream.RestartStrip();
                    o.pos = UnityObjectToClipPos(IN[i].vertex + float4(normalFace, 0) * _Factor);
                    o.uv = IN[i].uv;
                    o.col = _FrontColor;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[inext].vertex);
                    o.uv = IN[inext].uv;
                    o.col = _BackColor;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[inext].vertex + float4(normalFace, 0) * _Factor);
                    o.uv = IN[inext].uv;
                    o.col = _FrontColor;
                    tristream.Append(o);
                    tristream.RestartStrip();
                }
                for(int i = 0; i < 3; i++)
                {
                    o.pos = UnityObjectToClipPos(IN[i].vertex + float4(normalFace, 0) * _Factor);
                    o.uv = IN[i].uv;
                    o.col = _FrontColor;
                    tristream.Append(o);
                }
                tristream.RestartStrip();
                for(int i = 0; i < 3; i++)
                {
                    o.pos = UnityObjectToClipPos(IN[i].vertex);
                    o.uv = IN[i].uv;
                    o.col = _BackColor;
                    tristream.Append(o);
                }
                tristream.RestartStrip();
            }
          
            fixed4 frag (g2f i) : SV_Target
            {
                fixed4 col = ((tex2D(_MainTex, i.uv) * i.col) * i.col.a) + ((1-i.col.a) * _FrontColor);
                return col;
            }
            ENDCG
        }
    }
}

What do you mean corner don’t connect? In that editor it seems they are using 2d “isometric” (sorry purist).

I’d like it to look like a continuous wall, more like this:
3168778--241308--upload_2017-8-2_10-14-33.png

Instead of what it’s currently like:

3168778--241309--upload_2017-8-2_10-15-4.png

Nice suggestion! Playing around a bit and making my camera perspective projection got this result:
Not quite 100% what I’m hoping the final result would look like, but is pretty close
3168778--241310--upload_2017-8-2_10-35-33.png

3168778--241307--upload_2017-8-2_10-13-17.png

OH then you simply need corner wall, I would also go a modular way, instead of having a continuous geometry.

Thanks for the modular idea, but I’m trying to stay within the Pro-builder workflow

Don’t use shaders. Simply have a method that scans the existing geo and generates brand new thick edge meshes that aren’t part of the original mesh. Whenever you update the probuilder geo, click a button and your edge geo is rebuilt.

As you know your walls are always certain angles, and axis aligned, it is trivial to determine which are edges of interest to build strips from.

Using a shader for this only makes sense if you’re planning to rotate the camera a lot, and is probably a last resort to use shaders for many reasons.

Perhaps you can get some ideas if still interested in shaders by looking at boolean style ones perhaps such as https://www.assetstore.unity3d.com/en/#!/content/66300 (free).

This uses the stencil buffer to good effect.

Oups don’t know much about pro builder, didn’t notice the mention.

The basic problem is the data the shaders have access to don’t know about those other walls. The only way to have the shaders know that information is to parse the mesh in script and encode some extra data in the vertices to tell it which direction to extrude in. But if you’re already doing the work to figure that out in script it’ll likely be cheaper to just go the next step and build the extra mesh parts needed in script and just render that instead of using a geometry shader.

1 Like

Ok, seems like a mesh-based solution would be best. Thanks for all the help!

If you are creating the mesh, you could set the vert color on the corners so you know which way to fudge them so they’d meet.

Disclaimer: This is “a way to do the thing you’re trying to do” not an endorsement of “the thing you’re trying to do”. You are sort of asking why your hammer is a bad screwdriver.