Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Accessing lightmap data in a surface shader?

Discussion in 'Shaders' started by art_ganymede, Oct 10, 2023.

  1. art_ganymede

    art_ganymede

    Joined:
    Nov 7, 2021
    Posts:
    13
    Through a workaround, I've gotten Unity's lightmap baking to only bake ambient occlusion for the scene:

    Unity Forum Thread

    What I would like to do now is, in my surface shader, switch off the usual lightmap behaviour - and have the shader only respond to dynamic lights as typical, but have the lightmap data multiplied over it.

    i.e. pseudocode
    Code (CSharp):
    1. void surf(Input IN, inout SurfaceOutput o)
    2. {
    3.     fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    4.     o.Albedo = c.rgb * IN.color.rgb * lightmap.rgb;
    5. }
    I've figured out that you can turn off the regular lightmap behaviour with pragma:

    Code (CSharp):
    1. #pragma surface surf BlinnPhong nolightmap
    But, the question now is accessing the lightmapping information in the shader.
    Unfortunately I don't have much shader experience, I know it's possible to access this information in a HLSL shader, with:
    Code (CSharp):
    1. fixed4 frag(v2f IN) : SV_Target
    2. {
    3.     fixed4 col;
    4.     fixed4 tex = UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.uv0.xy);
    5.     half3 bakedColor = DecodeLightmap(tex);
    But, in the surface shader, I'm not sure how I can access the lightmap UVs, I have this:
    Code (CSharp):
    1. void surf(Input IN, inout SurfaceOutput o)
    2. {
    3.     fixed4 tex = UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.uv_MainTex.xy);
    4.     half3 bakedColor = DecodeLightmap(tex);
    5.  
    6.     fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    7.     fixed4 e = tex2D(_EmissionTex, IN.uv_MainTex);
    8.     o.Albedo = c.rgb * IN.color.rgb * bakedColor.rgb;
    9.     o.Emission = e.rgb * _Emission;
    10.     o.Gloss = _Gloss;
    11.     o.Specular = _Specular;
    12. }
    But, as you can see, I'm using the "IN.uv_MainTex" so the lightmap is using the actual texture UV coords:


    So close!

    Update:
    I've tried pulling in uv2 in the input struct, but unfortunately not getting anything, my full shader code is as follows:
    Code (CSharp):
    1. // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    2.  
    3. Shader "Platformer/Diffuse"
    4. {
    5.     Properties
    6.     {
    7.         _Color("Color", Color) = (1,1,1,1)
    8.         _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
    9.         _Gloss ("Gloss", Range (0.01, 1)) = 0.01
    10.         _Specular("Specular", Range (0.01, 1)) = 0.01
    11.         _Emission("Emission", Color) = (0,0,0,0)
    12.  
    13.         _MainTex("Texture", 2D) = "white" {}
    14.         _EmissionTex("Emissive", 2D) = "white" {}
    15.  
    16.         _GIAlbedoColor("Color Albedo (GI)", Color) = (1,1,1,1)
    17.         _GIAlbedoTex("Albedo (GI)",2D) = "white"{}
    18.     }
    19.     SubShader
    20.     {
    21.         Pass
    22.         {
    23.             Name "META"
    24.             Tags {"LightMode" = "Meta"}
    25.             Cull Off
    26.             CGPROGRAM
    27.  
    28.             #include"UnityStandardMeta.cginc"
    29.  
    30.             sampler2D _GIAlbedoTex;
    31.             fixed4 _GIAlbedoColor;
    32.             float4 frag_meta2(v2f_meta i) : SV_Target
    33.             {
    34.                 // We're interested in diffuse & specular colors
    35.                 // and surface roughness to produce final albedo.
    36.  
    37.                 FragmentCommonData data = UNITY_SETUP_BRDF_INPUT(i.uv);
    38.                 UnityMetaInput o;
    39.                 UNITY_INITIALIZE_OUTPUT(UnityMetaInput, o);
    40.                 fixed4 c = tex2D(_GIAlbedoTex, i.uv);
    41.                 o.Albedo = fixed3(c.rgb * _GIAlbedoColor.rgb);
    42.                 o.Emission = Emission(i.uv.xy);
    43.                 return UnityMetaFragment(o);
    44.             }
    45.  
    46.             #pragma vertex vert_meta
    47.             #pragma fragment frag_meta2
    48.             #pragma shader_feature _EMISSION
    49.             #pragma shader_feature _METALLICGLOSSMAP
    50.             #pragma shader_feature ___ _DETAIL_MULX2
    51.             ENDCG
    52.         }
    53.  
    54.  
    55.         Tags
    56.         {
    57.             "RenderType" = "Opaque"
    58.         }
    59.  
    60.         CGPROGRAM
    61.         #pragma surface surf BlinnPhong nolightmap
    62.  
    63.         struct Input
    64.         {
    65.             float2 uv_MainTex;
    66.             float2 uv2_LightMap;
    67.             half4 color : COLOR;
    68.         };
    69.  
    70.         sampler2D _MainTex;
    71.         sampler2D _EmissionTex;
    72.         float4 _MainTex_TexelSize;
    73.         fixed _Gloss;
    74.         fixed _Specular;
    75.         fixed4 _Color;
    76.         fixed4 _Emission;
    77.  
    78.         void surf(Input IN, inout SurfaceOutput o)
    79.         {
    80.             fixed4 tex = UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.uv2_LightMap.xy);
    81.             half3 bakedColor = DecodeLightmap(tex);
    82.  
    83.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    84.             fixed4 e = tex2D(_EmissionTex, IN.uv_MainTex);
    85.             o.Albedo = c.rgb /** IN.color.rgb*/ * bakedColor.rgb;
    86.             o.Emission = e.rgb * _Emission;
    87.             o.Gloss = _Gloss;
    88.             o.Specular = _Specular;
    89.         }
    90.  
    91.         ENDCG
    92.     }
    93.     Fallback "Diffuse"
    94. }
     
    Last edited: Oct 10, 2023
  2. art_ganymede

    art_ganymede

    Joined:
    Nov 7, 2021
    Posts:
    13
    Update on this, I wondered if the "Generate Lightmap UVs" on asset import wasn't producing "real" second UV sets. But, having tried a custom set of second UVs made in Blender, there still doesn't seem to be anything accessible in the second UV set.

    Using the second UV set to sample to main albedo texture seem to always yield (0,0).
     
  3. art_ganymede

    art_ganymede

    Joined:
    Nov 7, 2021
    Posts:
    13
    Still struggling with this.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Here's a minimal lightmap only shader.
    Code (CSharp):
    1. Shader "Custom/LightmapOnly"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.     SubShader
    7.     {
    8.         Tags { "RenderType"="Opaque" }
    9.         LOD 200
    10.  
    11.         CGPROGRAM
    12.         #pragma surface surf Standard nolightmap vertex:vert
    13.  
    14.         #pragma target 3.0
    15.  
    16.         struct Input
    17.         {
    18.             float2 lmap;
    19.         };
    20.  
    21.         void vert(inout appdata_full v, out Input o)
    22.         {
    23.             o = (Input)0;
    24.             o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
    25.         }
    26.  
    27.         void surf (Input IN, inout SurfaceOutputStandard o)
    28.         {
    29.             o.Emission = UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.lmap);
    30.         }
    31.         ENDCG
    32.     }
    33.     FallBack "Diffuse"
    34. }
    Note, this doesn't support directional or dynamic lightmaps (used by Enlighten light maps). Those are a lot more complex. But the basic non-directional light maps, or even directional lightmaps just without the directional component. The main issue is the weird way Surface Shaders handle
    Input
    struct variables that start with "uv". Mainly it'll always try to apply the texture's scale and offset, the _*texturename*_ST uniform. But it assumes that texture property's name starts with an underscore. The built in lightmap texture starts with
    unity_
    and not just
    _
    , and doesn't have an underscore before the
    ST
    so you can't use the automatic code generation to access it. If you try to use just
    uv2
    on its own, it'll fail because it still tries to apply the scale and offset of a texture named nothing and that fails. If you use a texture that doesn't exist, the code will compile, but since there's no texture with that name the _ST uniform will be all zeros and the UV passed to the surface function will only ever be 0,0. You can use a dummy texture property that you never touch the scale and offset for so it's the proper (1.0, 1.0, 0.0, 0.0) and then apply the
    unity_LightmapST
    to that, but that's error prone and about as much work as this example.
     
    Last edited: Oct 16, 2023
    art_ganymede likes this.
  5. art_ganymede

    art_ganymede

    Joined:
    Nov 7, 2021
    Posts:
    13
    Very informative and helpful, thankyou. It explains why the UV2 always seemed to be at 0,0. Should be possible to adapt this shader for uses.