Standard Shader (modified to be double sided) is very shiny on the underside

Hello!

I have zero experience in shaders aside from using a forum post to modify the Standard Shader to make it double sided.

However, the “back side” that now shows is very shiny, much more than the front side. Is there a way to modify this or otherwise fix this visual effect?

Thanks!!

The problem is the normals for the back side are still pointing the same way, which is away from you. Most PBR calculations (Unity’s Standard shader included) don’t really handle this case because in the real world it’s not possible for a surface to not be pointing toward the viewer and still be seen.

The fix is testing if the normals are facing towards the camera or not and flip the Z if they’re facing away. There’s some examples on the forum if you search for them. The correct way to do this is using the VFACE semantic, but most people just use a dot product of the normal and the view direction.

2 Likes

Thanks! Per a unity doc, I found the following code. I have about 99% no idea what’s going on, but best I can tell, the last line is basically saying “if it’s front, show this, if it’s back ,show that”

float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return mul(UNITY_MATRIX_MVP, vertex);
            }

            fixed4 _ColorFront;
            fixed4 _ColorBack;

            fixed4 frag (fixed facing : VFACE) : SV_Target
            {
                // VFACE input positive for frontbaces,
                // negative for backfaces. Output one
                // of the two colors depending on that.
                return facing > 0 ? _ColorFront : _ColorBack;
            }

However, _ColorFront & _ColorBack are black – I replace that with _Color, which is part of the shader, and sure enough that color shows up. I think I need it to display _MainTex, though, which throws an error when I try to use it.

" cannot implicitly convert from ‘const sampler2D’ to ‘half4’ at line 99 (on glcore)"

Any ideas how to get the main texture to be used instead?

Sounds like you’re doing:

return facing > 0 ? _MainTex : _ColorBack;

You can’t do that since you need to actually sample the texture to get a color, ie:

fixed4 col = tex2D(_MainTex, i.uv.xy); // need to pass uvs through from vert function
return facing > 0 ? col : _ColorBack;
1 Like

Huh. Ok – thanks! I’m sure that’s helpful, but I’m looking at the nvidia and unity docs for “tex2D”, and while the concept roughly makes sense, I’m not at all sure how to go about getting any other aspect of what’s required to fill that function. Unfortunately I can’t see any similar code in the Unity standard shader script either to jump off from.

Im not sure if this would help you, but, i had a problem with double sided normals and i added the code

SubShader {
         Tags { "RenderType" = "Opaque" }
         Cull off
// Cull off supports double sided normals

Adding Cull off made the toon lit shader support double sided normals without a hitch, maybe it will work with your shader too?
Im noob, so not sure if this would work. :slight_smile:

He’s already done this. The problem is that this doesn’t do double sided normals, this does double sided surfaces with single sided normals.

Imagine you have an opaque ball with light coming from above. The top of the ball will naturally be lit. Now let’s turn on front face culling so we’re only seeing back faces. The result will look about the same as the “top” faces are lit even though we’re now seeing the inside of the ball and it’s the bottom faces that should now be lit because the back faces’ normals are still pointing the same way as the front faces.

1 Like

Yep. Unfortunately I can’t figure out how to get the back sides to render properly. I have no idea where to even start looking – I went through the standard shader and it’s includes, but nothing made sense to me.

Anyone care to give me an assist?

Thanks!!

2570740--179318--Screen Shot 2016-03-26 at 6.15.19 PM.png

You should be able to use VFACE with a surface shader, just add

fixed facing : VFACE;

to the input struct. Then at the end of your surf function do

o.Normal.z *= saturate(IN.facing) * 2.0 - 1.0;

Or

o.Normal.s *= facing > 0.5 ? 1.0 : -1.0;

facing should be either 1 for front facing and either 0 or -1 for back facing (I forgot which, but the above code should work either way).

What’s the “surf function”?

I’m guessing it’s the last bit from the unity doc examples?

This throws an error, as I didn’t declare “o” variable. All of the shader stuff is really really new to me – as in I’ve never looked at it before :slight_smile:

Below is part of my code – I’m attaching the current shader I have – the “working” one, that’s 2-sided but looks funky, and the one I’m working on. Where (And how?) do I declare the o variable? And is that last function where I should put the code? (the “surf” function?)

float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return mul(UNITY_MATRIX_MVP, vertex);
            }

            struct Input {
                fixed facing : VFACE;
            };

            fixed4 _ColorFront;
            fixed4 _ColorBack;

            fixed4 frag (fixed facing : VFACE) : SV_Target
            {
                o.Normal.z *= saturate(IN.facing) * 2.0 - 1.0;

                // VFACE input positive for frontbaces,
                // negative for backfaces. Output one
                // of the two colors depending on that.
               // fixed4 col = tex2D(_MainTex, uv_MainTex); // need to pass uvs through from vert function
                //return facing > 0 ? col : _ColorBack;
            }

2570819–179329–SFB_StandardHair.shader (8.26 KB)
2570819–179330–SFB_StandardHair Test.shader (9.04 KB)

Here’s a basic example shader. The naming for the normal flip mode isn’t great, but one acts like the surface is a peice of plastic or metal that’s been stamped / molded so when you see the backside it’s the inverted surface normals, and the other makes both sides look the same.

Shader "Custom/Two Sided SurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        [NoScaleOffset] _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        [Gamma] _Metallic ("Metallic", Range(0,1)) = 0.0
        [NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {}
        [Enum(Flip,0,Invert,1)] _BumpFlipMode("Normal Flip Mode", Float) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        Cull Off
        LOD 200
       
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;

        struct Input {
            float2 uv_MainTex;
            fixed facing : VFACE;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        bool _BumpFlipMode;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));

            // Test if looking at the backface.
            if (IN.facing < 0.5)
            {
                if (_BumpFlipMode)
                    o.Normal *= -1.0;
                else
                    o.Normal.z *= -1.0;
            }
        }
        ENDCG
    }
    FallBack "Diffuse"
}
2 Likes

Thanks – I succeeded in adding a Specular input option, but that’s about it :confused:

I think I’m going to have to hire out on this one, it’s above my head for sure. I’ll PM you if you’re interested.

You’re looking for a version of the above based on the standard specular shader?

We have our specular in the alpha of the map, so it’s currently in the MetalRough standard shader.

Below is what it looks like now – using two materials, the top one being “Fade” (shown) and the bottom being a Cutout one with the same maps. That’s the best look I’ve been able to get so far. Using the double-sided version from before makes the hair look a bit more thick and normal (esp. from certain angles), but of course is shiny.

The male head is from the modelers application, and is how he intended it to look, using the standard shader in 3ds Max.

2574602--179704--Hair_3dsMax.jpg 2574602--179703--Screen Shot 2016-03-29 at 4.25.01 PM.jpg 2574602--179704--Hair_3dsMax.jpg

2574602--179703--Screen Shot 2016-03-29 at 4.25.01 PM.jpg

Shader "Custom/Standard Two Sided Soft Blend" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        [NoScaleOffset] _MainTex ("Albedo (RGB)", 2D) = "white" {}
        [Toggle] _UseMetallicMap ("Use Metallic Map", Float) = 0.0
        [NoScaleOffset] _MetallicGlossMap("Metallic", 2D) = "black" {}
        [Gamma] _Metallic ("Metallic", Range(0,1)) = 0.0
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _BumpScale("Scale", Float) = 1.0
        [NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {}
        _Cutoff("Alpha Cutoff", Range(0.01,1)) = 0.5
    }
    SubShader {
        Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 200
        ZWrite Off
        Cull Off

        Pass {
            ColorMask 0
            ZWrite On

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
         
            #include "UnityCG.cginc"

            struct v2f {
                float4 vertex : SV_POSITION;
                float2 texcoord : TEXCOORD0;
            };

            sampler2D _MainTex;
            fixed _Cutoff;

            v2f vert (appdata_img v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.texcoord = v.texcoord;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.texcoord);
                clip(col.a - _Cutoff);
                return 0;
            }
            ENDCG
        }

        Pass
        {
            Tags {"LightMode"="ShadowCaster"}
            ZWrite On
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct v2f {
                V2F_SHADOW_CASTER;
                float2 texcoord : TEXCOORD1;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                o.texcoord = v.texcoord;
                return o;
            }
         
            sampler2D _MainTex;
            fixed _Cutoff;

            float4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.texcoord);
                clip(col.a - _Cutoff);
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
     
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows alpha:fade nolightmap
        #pragma shader_feature _USEMETALLICMAP_ON
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MetallicGlossMap;
        sampler2D _BumpMap;

        struct Input {
            float2 uv_MainTex;
            fixed facing : VFACE;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        half _BumpScale;
        fixed _Cutoff;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;

            #ifdef _USEMETALLICMAP_ON
            fixed4 mg = tex2D(_MetallicGlossMap, IN.uv_MainTex);
            o.Metallic = mg.r;
            o.Smoothness = mg.a;
            #else
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            #endif

            // Rescales the alpha on the blended pass
            o.Alpha = saturate(c.a / _Cutoff);

            o.Normal = UnpackScaleNormal(tex2D(_BumpMap, IN.uv_MainTex), _BumpScale);

            if (IN.facing < 0.5)
                o.Normal *= -1.0;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

edit: fixed shader with my suggestion from below

12 Likes

You have rocked my world! Thank you!

I’m running into a small problem, and i’m not sure where in the shader this is coming from. In some lighting, the transparent parts of the hair give a light visible film to the image. Like in the shot attached. Any idea where this is coming from?

Thanks!!

2584824--180717--Screen-Shot-2016-04-06-at-1.32.jpg

Guessing my shader doesn’t play well with additional lights beyond the main directional light. Try replacing keepalpha with alpha:fade

1 Like

BOOM

That did it. Thanks! :smile::smile::smile: