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!
Well, if you know what is happening on the textures, then in theory you can render them out onto a RenderTexture and read that.
@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
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.
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.
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.
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).
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...
Code (CSharp): using UnityEditor; class MyTexturePostprocessor : AssetPostprocessor { void OnPreprocessTexture() { if (assetPath.Contains("<Path_to_your_lightmaps>")) { TextureImporter textureImporter = (TextureImporter)assetImporter; var tis = new TextureImporterSettings(); textureImporter.ReadTextureSettings(tis); tis.readable = true; textureImporter.SetTextureSettings(tis); } } } 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.
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!
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): using System.Collections; using System.Collections.Generic; using UnityEngine; public class Ninja : MonoBehaviour { public Texture2D tx; void Start () { for (int i = 0; i < 10; i++) { var x = Random.Range(0, tx.width); var y = Random.Range(0, tx.height); var c = tx.GetPixel(x, y); Debug.LogFormat("{0},{1} -> {2}", x, y, c); } } }
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.
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.
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.
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
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.
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.
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.
You overwrite the sv_position or any screen semantics that manage the vertex position with the uv values. Code (CSharp): Shader "MAGIC/LMGBgenerationShadowmask" { // Properties // { // _MainTex ("Texture", 2D) = "white" {} // } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "AutoLight.cginc" #include "../MAGIC.cginc" // half4 unity_LightmapST; //float4 unity_Lightmap; struct d { float4 vertex : POSITION; float2 uv : TEXCOORD1; }; struct v2f { float4 vertex : POSITION; float2 uv : TEXCOORD1; LIGHTING_COORDS(2,3) }; //------------------------------------ v2f vert (d v) { v2f o; float2 uv = float2(v.uv.x, 1-v.uv.y);//* unity_LightmapST.xy + unity_LightmapST.zw;//TRANSFORM_TEX(v.uv, unity_Lightmap );; o.vertex = float4((uv.xy*2)-1,1,v.vertex.w); // // o.vertex = float4((v.uv.xy*2)-1,1,v.vertex.w); return o; } fixed4 frag (v2f i) : COLOR { 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 { ... ). float4 f1 = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv[1]); float3 f2 = DecodeLightmap(f1); return half4(f2,1)*atten; //return (DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv[1]))); } ENDCG } } }
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!