Search Unity

Sample lightmap texture?

Discussion in 'Scripting' started by annabd351, Jun 22, 2020.

  1. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Hey all -

    Is it possible to sample lightmap colors from a script? If so, what's the standard technique?

    Here's the use case: I'm writing a script to copy lightmap colors to mesh vertices. I'm able to access all the relevant data, but when I try to sample the lightmap texture (via LightmapData.lightmapColor), I get this error:

    "UnityException: Texture is not readable, the texture memory can not be accessed from scripts. You can make the texture readable in the Texture Import Settings."

    Problem is, there are no Texture Import Settings to set: I'm just looking at the texture generated by light baking.

    Is there any way to programmatically duplicate and/or re-import the lightmap texture so I can set the isReadable flag? Or, is there some other way to do this?

    Thanks!
     
  2. Well, if you know what is happening on the textures, then in theory you can render them out onto a RenderTexture and read that.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,691
    @Lurking-Ninja Interesting... I guess if you can get the lightmap texture reference out of the material that the original object uses, and assign that texture as the primary albedo in an unlit material on a quad, then point a camera at the quad, then use a RenderTexture to strike it to a texture, then use getpixel on that texture to read the resulting pixel...

    Wow... I think my head just exploded. :O
     
  4. :D That would be awesome, but no.

    https://docs.unity3d.com/ScriptReference/LightmapData-lightmapColor.html
    You have access to the textures. Just render them out into a RenderTexture and access the color values.
     
  5. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Yeah, I tried that, but the texture formats are different. The lightmap is a DXT5 format compressed file. Not sure how to convert that into a format you can blit into the RenderTexture.
     
  6. I believe you need to set this
    https://docs.unity3d.com/ScriptReference/RenderTexture-graphicsFormat.html
    to
    https://docs.unity3d.com/ScriptReference/Experimental.Rendering.GraphicsFormat.RGBA_DXT5_SRGB.html

    But I'm not sure, I have never tried. Also, it's experimental API, so there is always a possibility that it is not working. :)
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,691
    TIL! I hope OP figures it out and helps us all by posting back here what happened...
     
    Lurking-Ninja likes this.
  8. Okay, I know that you know this, but I will make myself a little bit more precise for the visitors:

    So, you have reference to the lightmap Texture2Ds through the LightmapData. These textures aren't readable from C# code since they are uploaded to the graphics card.
    RenderTexture in reality isn't just a C# object, if we want to simplify, it is a command sent to the graphics card to render something. The Rendertexture then read back to the CPU so you can access it from C# code (this is the reason it is slow as hell).
     
  9. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Unfortunately, Texture2D and RenderTexture use a different set of formats. GraphicsFormat.RGBA_DXT5_SRGB isn't necessarily the same as TextureFormat.DXT5 (which is what Texture2D uses). Also, there might be some native platform dependencies, there... not sure.

    Trying to write a reliable tool, so I can't just guess.

    Seems like there should be a method to make a CPU-accessible copy of a texture. Hmm...
     
  10. Code (CSharp):
    1. using UnityEditor;
    2.  
    3. class MyTexturePostprocessor : AssetPostprocessor
    4. {
    5.     void OnPreprocessTexture()
    6.     {
    7.         if (assetPath.Contains("<Path_to_your_lightmaps>"))
    8.         {
    9.             TextureImporter textureImporter  = (TextureImporter)assetImporter;
    10.             var tis = new TextureImporterSettings();
    11.             textureImporter.ReadTextureSettings(tis);
    12.             tis.readable = true;
    13.             textureImporter.SetTextureSettings(tis);
    14.         }
    15.     }
    16. }
    I have tried on a dummy project, it is setting the readable to true. Although I haven't tried to actually read the textures, that's up to you. :)
    Also you can choose anything to identify your textures, you don't have to use the path. I advise to do that since usually the reflection data goes into the same folder too and you don't want everything to be readable, that significantly raise the memory consumption and slow down your application.
     
  11. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    That might sorta work, but... lightmaps are very strange. If you are using iterative/auto-generated lightmaps, the textures are never written out as assets. So, you can't import them. Ugh.

    But, they *do* get written out as .exr's if you're in manual mode. That'll sorta work. Thanks!

    Hmph. Guess Unity really doesn't want anyone messing with lightmaps!
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,691
    Ninja! You brilliant lurking-around tied-scarf-wearin' game controller eyeglass genius!!!

    OP, I had to bake maps explicitly, didn't try auto-baking, and I also did NOT try it from a build, which also might fail.

    But in any case, used Ninja's importer, added this script, and I am reading pixels from the lightmap:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Ninja : MonoBehaviour
    6. {
    7.     public Texture2D tx;
    8.  
    9.     void Start ()
    10.     {
    11.         for (int i = 0; i < 10; i++)
    12.         {
    13.             var x = Random.Range(0, tx.width);
    14.             var y = Random.Range(0, tx.height);
    15.  
    16.             var c = tx.GetPixel(x, y);
    17.  
    18.             Debug.LogFormat("{0},{1} -> {2}", x, y, c);
    19.         }
    20.     }
    21. }

    Screen Shot 2020-06-22 at 6.18.49 PM.png
     
    Lurking-Ninja likes this.
  13. Well, I guess either this or fiddling around with rendering the texture to the RenderTexture from memory.
    So, it is up to you. :)

    And yes, Unity didn't intend lightmaps to be used this way. You either just set them to use in the scene or generate your own.
     
  14. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Well, I think they don't store the lightmap as an asset because you'd hit race conditions with the live updates.

    But a lightmap texture is a just a texture. Plenty of reasons to access it. I get how they're trying to keep data structures transferable to the GPU; but you can't even make a duplicate of that texture. Bit heavy-handed in my opinion. It's frustrating when people design SDK's for the lowest common denominator, or whatever it is they're doing. o_O
     
  15. Well it is written out to the disk sooner or later. Maybe not right after the generation, but you know, you can't keep it in the memory indefinitely.
     
  16. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    I do a shader that unwrap the mesh with the lightmap rendered to the screen space, then use a camera to render to a texture
     
    Kurt-Dekker likes this.
  17. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    True, but I haven't been able to find it! I'm assuming it's stored as temp data somewhere, and only finalized in the build process.
     
  18. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Doesn't that potentially change the values if the screen resolution is different than the texture?
     
  19. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    I don't render to screen, I render using a custom camera, I use screen coordinate as in, the camera parameter in the shader, if you render to texture resolution it's the same minus padding.
     
  20. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Argh, tried RGBA_DXT5_SRGB just for fun, and got:
    "ArgumentException: RenderTextureDesc graphicsFormat must be a supported GraphicsFormat. RGBA_DXT5_SRGB is not supported."

    So, that confirms that it's platform-dependent.
     
  21. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Oh, right. Gotcha.

    How do you unwrap the mesh?
     
  22. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    You overwrite the sv_position or any screen semantics that manage the vertex position with the uv values.
    Code (CSharp):
    1. Shader "MAGIC/LMGBgenerationShadowmask"
    2. {
    3.      // Properties
    4.     // {
    5.     //     _MainTex ("Texture", 2D) = "white" {}
    6.     // }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 100
    11.         Cull Off
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #include "UnityCG.cginc"
    18.             #include "AutoLight.cginc"
    19.             #include "../MAGIC.cginc"
    20.  
    21.              // half4 unity_LightmapST;
    22.             //float4 unity_Lightmap;
    23.  
    24.             struct d
    25.             {
    26.                 float4 vertex    : POSITION;
    27.                 float2 uv        : TEXCOORD1;
    28.             };
    29.  
    30.             struct v2f
    31.             {
    32.                 float4 vertex    : POSITION;
    33.                 float2 uv       : TEXCOORD1;
    34.                 LIGHTING_COORDS(2,3)
    35.             };
    36.             //------------------------------------
    37.             v2f vert (d v)
    38.             {
    39.                 v2f o;
    40.                 float2 uv = float2(v.uv.x, 1-v.uv.y);//* unity_LightmapST.xy + unity_LightmapST.zw;//TRANSFORM_TEX(v.uv, unity_Lightmap );;
    41.                 o.vertex = float4((uv.xy*2)-1,1,v.vertex.w);
    42.  
    43.                 //
    44.                // o.vertex = float4((v.uv.xy*2)-1,1,v.vertex.w);
    45.                 return o;
    46.             }
    47.  
    48.             fixed4 frag (v2f i) : COLOR
    49.             {
    50.                 float atten = LIGHT_ATTENUATION(i); // This is a float for your shadow/attenuation value, multiply your lighting value by this to get shadows. Replace i with whatever you've defined your input struct to be called (e.g. frag(v2f [b]i[/b]) : COLOR { ... ).
    51.                 float4 f1 = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv[1]);
    52.                 float3 f2 = DecodeLightmap(f1);
    53.                 return half4(f2,1)*atten;
    54.                 //return (DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv[1])));
    55.             }
    56.             ENDCG
    57.         }
    58.     }
    59. }
    60.  
     
  23. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Cool, thanks!
     
  24. annabd351

    annabd351

    Joined:
    Jun 22, 2020
    Posts:
    13
    Follow-up -- here's how I ended up solving the problem:
    1) Bake lightmaps
    2) Turn *OFF* auto-generate lightmaps. When in auto-generate mode, Unity stores the lightmap in an inaccessible cache. When you turn it off, Unity creates a Texture2D asset of the lightmap in your Assets/Scene folder.
    3) Once that asset is created, you can locate it with something like this:
    AssetDatabase.GetAssetPath(LightmapSettings.lightmaps[lightmapIndex].lightmapColor)

    4) Then, you reimport the texture using
    AssetDatabase.ImportAsset(path)
    and process it an AssetPostprocessor delegate method as per @Lurking-Ninja 's example below.

    Thanks for the help!


     
  25. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,691
    And thanks from all of us here for the good followup. Appreciate it.