How to add a parameter to StandardSurfaceOutput?

I’m trying to add an extra parameter to StandardSurfaceOutput to do some additional calculations inside the lighting function.

I created a custom struct that mimics SurfaceOutputStandard, like so…

   struct SurfaceOutputCustom
    {
        fixed3 Albedo;      // base (diffuse or specular) color
        float3 Normal;      // tangent space normal, if written
        half3 Emission;
        half Metallic;      // 0=non-metal, 1=metal
        half Smoothness;    // 0=rough, 1=smooth
        half Occlusion;     // occlusion (default 1)
        fixed Alpha;        // alpha for transparencies
        // extra parameter...
    };

    inline half4 LightingCustom(SurfaceOutputCustom s, half3 viewDir, UnityGI gi)
    {
        // extra calculations...

        SurfaceOutputStandard standard;
        standard.Albedo = s.Albedo;
        standard.Normal = s.Normal;
        standard.Emission = s.Emission;
        standard.Metallic = s.Metallic;
        standard.Smoothness = s.Smoothness;
        standard.Occlusion = s.Occlusion;
        standard.Alpha = s.Alpha;
        return LightingStandard(standard, viewDir, gi) + extraStuff;
    }

However, when I change my surface shader to use this new struct, the alpha behaviour of my shader changes.

When I looked at the compiled shader code, I noticed that changing to the custom struct also modified these two lines:

I’m not sure why changing this struct should cause these changes in the compiled shader. I can’t figure out a way to use the custom struct while maintaining the old alpha behaviour.

Does anyone know what’s going on here?

You’re probably using alpha in the #pragma surface line by itself. That auto chooses the alpha blending mode based on if you’re using the standard lighting model or a custom one, though it appears it decides that based on that struct rather than the lighting function. The solution is to use: alpha:premul

Hey @bgolus , thank you for the explanation!

This almost works, but it doesn’t restore the

#define _ALPHAPREMULTIPLY_ON 1

into the new compiled shader.

From manually editing the compiled shader, I see that this line is necessary to maintain the alpha behaviour I want. How can I get this flag into the compiled shader from the surface shader?

I believe it will subtly change how it looks yes. But it’s easy enough to fix. Just add that #define to your shader code.

I’ve tried to include the #define _ALPHAPREMULTIPLY_ON 1 in my surface shader code, but it doesn’t seem to have any effect. I placed this line directly below the CGPROGRAM line. I’ve also tried a few other places but it doesn’t seem to work in any of them.

When I look at the compiled shader code, I’ve noticed that the line appears like so:

...
// Original surface shader snippet:
#line 36 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif
/* UNITY: Original start of shader */

        #define _ALPHAPREMULTIPLY_ON 1  //  <============ IT APPEARS HERE ===========
        //#pragma surface surf Custom vertex:vert finalcolor:ResetAlpha alpha:premul
        //#pragma target 3.0

        #include "UnityPBSLighting.cginc"

        struct Input
        {
            float3 worldPos;
            float4 screenPos;
        };
 
        ...

This doesn’t seem to achieve the desired effect. However, in the old version (that used SurfaceOutputStandard) the compiled code looked like this:

...

// reads from normal: no
// 0 texcoords actually used
#define _ALPHAPREMULTIPLY_ON 1 <========= IT APPEARS HERE INSTEAD ============
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

#define INTERNAL_DATA half3 internalSurfaceTtoW0; half3 internalSurfaceTtoW1; half3 internalSurfaceTtoW2;
#define WorldReflectionVector(data,normal) reflect (data.worldRefl, half3(dot(data.internalSurfaceTtoW0,normal), dot(data.internalSurfaceTtoW1,normal), dot(data.internalSurfaceTtoW2,normal)))
#define WorldNormalVector(data,normal) fixed3(dot(data.internalSurfaceTtoW0,normal), dot(data.internalSurfaceTtoW1,normal), dot(data.internalSurfaceTtoW2,normal))

// Original surface shader snippet:
#line 27 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif
/* UNITY: Original start of shader */

        //#pragma surface surf Custom vertex:vert finalcolor:ResetAlpha alpha:premul
        //#pragma target 3.0

        #include "UnityPBSLighting.cginc"

...

For some reason, that position of the define symbol seems to matter in the compiled code. From my surface shader, I can’t seem to make it appear in the right spot. I’m not sure why the position of the line matters like this.

For my situation, the impact of this line has a big effect on the look of the shader. To be honest, I don’t even know what _ALPHAPREMULTIPLY_ON does (my searches online have failed to yield a result) or why it’s affecting my shader (it looks like the emissive values are doubled somehow with it) but I’d like to get it as close to the original as possible.

I apologize if the solution is obvious, a lot of aspects of writing Unity shaders are still a mystery to me. I’m still learning new things about them every day!

Hmm. You could try adding this just above your CGPROGRAM block.

CGINCLUDE
#define _ALPHAPREMULTIPLY_ON 1
ENDCG

That should insert it at the start of the shader, but it shouldn’t matter as the define just needs to exist before the #include "UnityPBSLighting.cginc" to work.

Thanks for your help again!

From some trial and error testing within the compiled code, I noticed that #define _ALPHAPREMULTIPLY_ON 1 needs to come before #include "Lighting.cginc" before it will work properly.

Unfortunately, the CGINCLUDE code block only inserts the define symbol after Lighting.cginc, no matter where I put it in my surface shader.

I wonder if there’s any way I can work around this or if I’ll have to change my approach entirely…

I got an idea for an extremely hacky workaround.

I downloaded the built-in shaders and copied Lighting.cginc over to an isolated folder with my shader.

I modified Lighting.cginc to include the define symbol at the top.
Not sure what downsides I’ll see to this approach, but it seems to work so far!

Thanks for helping me look into this matter! I couldn’t have figured it out without your help.

I had the same issue (took ages to realize this was the issue).

Same workaround worked for me too. However, I found out I didn’t have to copy over the Lighting.cginc file to the project. Just doing this in the shader after #pragma etc. worked for me:

#define _ALPHAPREMULTIPLY_ON 1
#include "Lighting.cginc"

If I don’t include Lighting.cginc there then it doesn’t work. Not sure why, I guess it might be redefined somewhere else before the Lighting.cginc file gets indirectly included, if not included there?

If it doesn’t work, try closing the project, deleting the Library folder, and reopening the project, which causes a complete project reimport (if you can spare the time). I ended up doing that, though I can’t be certain if that made a difference, since I was trying out many things in various combinations until I got something working.

I will also say it’s kind of wild the length you have to go to just to be able to switch out the lighting function for an identical custom one and ACTUALLY have it working. This has been a painful journey.

1 Like