Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Using Shader in AssetPostprocessor

Discussion in 'Editor & General Support' started by fleity, Apr 5, 2019.

  1. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    Hey everyone,

    i thought this would be a simple task but I feel very stupid by now. I want to create an assetpostprocessor which does some image manipulation on all textures in a folder. However since my textures will be quite big 1024 or 2048 I wanted to use shaders to do the job.

    So far all the shader does is simply inverting the color, but not even that works. All I get is black on my texture. However if I edit the shader to just return red (ignoring the texture alltogether) it works. I feel like the texture does not reach the shader for some reason. I tried setting it as maintexture or with Material.SetTexture but neither changed the results.

    help please

    Code (csharp):
    1.  
    2.     void OnPostprocessTexture(Texture2D texture)
    3.     {
    4.     string lowerCaseAssetPath = assetPath.ToLower();
    5.     if (!lowerCaseAssetPath.Contains("/sdf/"))
    6.     return;
    7.  
    8.             ProcessTexture(texture);
    9.     }
    10.  
    11.     public static void ProcessTexture(Texture2D texture)
    12.     {
    13.         // setup render texture
    14.         RenderTextureDescriptor rtd = new RenderTextureDescriptor(
    15.             texture.width,
    16.             texture.height,
    17.             RenderTextureFormat.ARGB32,
    18.             0);
    19.         RenderTexture rt = RenderTexture.GetTemporary(rtd);
    20.  
    21.         // create material and blit texture with shader into render texture
    22.         Material material = new Material(Shader.Find("Hidden/NewImageEffectShader"));
    23.         Graphics.Blit(texture, rt, material);
    24.  
    25.         // pixel back from render texture and return in texture
    26.         texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0, false);
    27.         texture.Apply();
    28.  
    29.         // release temporary render texture
    30.         RenderTexture.ReleaseTemporary(rt);
    31.     }
    32.  
     
    AlejMC likes this.
  2. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    in case anyone stumbles upon this:
    idk why the texture data is not available at this point but passing the pixels as array to a compute shader worked
     
    viceca and hippocoder like this.
  3. viceca

    viceca

    Joined:
    Nov 10, 2016
    Posts:
    14
    You're correct @fleity, on top of that you can also just call Texture.Apply in the beginning of the method and then use Shaders
     
    Alekxss, Menion-Leah and AlejMC like this.
  4. AlejMC

    AlejMC

    Joined:
    Oct 15, 2013
    Posts:
    149
    This is good too know! So it would be Texture.Apply and then do the Graphics Blit'ing using that texture.

    Any idea of how complex and far can it go? Let's suppose I would like to create normal maps, blur it, etc... would command buffers work? That would be neat and never thought about this.
     
  5. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    @Alejandro-Martinez-Chacin
    As you might already figured out I never ended up trying using frag shaders as the compute shader solution worked fine for me. If what viveca says works (first Texture.Apply, then run shader, then apply texture again to get the result back to the cpu in order to write it to disk?), there only thing limiting you in terms of what you can do that way is how much time are you willing to wait during the import ^^ (and maybe how much memory is available).
    So yes blurring, generating normals from the images should work, after all this is the same thing other programs (i.e. Xnormal) are doing too.

    Command buffers as such need an injection point of the renderpipeline though. You could make it work like command buffers but it probably won't be that api.
     
    Menion-Leah likes this.
  6. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    Do you have an example of using the compute shader in a texture post processor?

    I am getting an 'importer generated inconsistent result for asset' error sadly

    My code looks like this

    Code (CSharp):
    1.  
    2. private void OnPostprocessTexture(Texture2D texture)
    3. {
    4.     var importer = assetImporter as UnityEditor.TextureImporter;
    5.     if (importer == null) return;
    6.  
    7.     var textureName = Path.GetFileNameWithoutExtension(importer.assetPath);
    8.  
    9.     if (textureName.Contains("sdf"))
    10.     {
    11.         // create render texture to work with
    12.         var renderTexture = new RenderTexture(texture.width, texture.height, 0);
    13.         renderTexture.enableRandomWrite = true;
    14.         renderTexture.Create();
    15.  
    16.         // copy texture contents to render texture
    17.         // Graphics.Blit(texture, renderTexture);
    18.  
    19.         // load compute shader and its properties
    20.         var computeShader = GetComputeShader("SdfBaker");
    21.         var kernel = computeShader.FindKernel("BakeSdf");
    22.         var sourceTextureProperty = Shader.PropertyToID("source_texture");
    23.  
    24.         // set properties
    25.         computeShader.SetTexture(kernel, sourceTextureProperty, renderTexture);
    26.  
    27.         // dispatch
    28.         var dimensionX = Mathf.Max(1, texture.width / 8);
    29.         var dimensionY = Mathf.Max(1, texture.height / 8);
    30.         computeShader.Dispatch(kernel, dimensionX, dimensionY, 1);
    31.  
    32.         // copy render texture result to texture
    33.         RenderTexture.active = renderTexture;
    34.         texture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
    35.         texture.Apply();
    36.     }
    37. }
    38.  
    Edit: Solved! I should have read this thread better.. So calling texture.Apply() at the beginning of the OnPostprocessTexture function did the trick!
     
    Last edited: Aug 16, 2022
    fleity likes this.
  7. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    Nice to hear that you figured it out. I saw your reply yesterday already and wanted to answer but I would have to look at my old file for that as well and that is buried on an external backup drive and I didnt get around to doing that yesterday. Today though I would have done it.

    This run multiple iterations of a compute shader to generate sdfs is about the fastest way I have ever encountered to make an sdf. It's not really necessary to have it that fast most of the time but hey nice :D
    At one point towards the end of the project I needed this for we had to regenerate all sdf textures and because they were not checked into git, every user had to wait for this after pulling that commit... no one really noticed, it just happened.
     
  8. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    Ah so your use case is also to generate sdfs? I still have to implement the actual compute shader but I have already done JFA in hlsl so it should be okay I hope. Glad to hear it's fast :)
     
  9. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    Yes it was exactly that. I feel like the script pretty much looks the same.
    I had the idea to do it that way without realizing that it was using JFA because I didn't knew what that was at the time.
    I could not post the code back then because I was doing it for a job. I see you are overwriting the texture in place as well which is super convienent obviously. I started that way too, but later on switched to saving the sdf texture as seperate asset when we added atlas packing, because the packer worked with the png files directly instead of the unity serialized texture2ds. Just a heads up to consider if you need this to work as well :)
     
    alexanderameye likes this.
  10. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    @alexanderameye

    This is the core part of my sdf post processor. I have a few functions checking correct file path and getting some settings from the filename (kernel size and passes) before that and then afterwards a function which encodes the resulting tex2d to a png and writes the file to disk but this is really the important bit.

    Code (CSharp):
    1.     public static Texture2D ProcessTexture(Texture2D texture, int kernelSize, int passes, SDFType sdfType)
    2.     {
    3.         ...
    4.         if (guids.Length == 0)
    5.         {
    6.             LogError("trying to process sdf texture " + texture.name + ", but couldn't find compute shader");
    7.             return texture;
    8.         }
    9.  
    10.         // load compute shader from assetdb
    11.         ComputeShader computeShader = (ComputeShader)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[0]), typeof(ComputeShader));
    12.  
    13.         // create structured buffers
    14.         // 16 because sizeof(float) = 4 und datatype ist vector4 (4*4)
    15.         int bufferSize = texture.width * texture.height;
    16.         ComputeBuffer buffer1 = new ComputeBuffer(bufferSize, 16);
    17.         ComputeBuffer buffer2 = new ComputeBuffer(bufferSize, 16);
    18.         buffer1.SetData(texture.GetPixels());
    19.  
    20.         // prepare compute shader
    21.         int kernelId = computeShader.FindKernel("CSMain");
    22.         computeShader.SetInt("width", texture.width);
    23.         computeShader.SetInt("height", texture.height);
    24.         computeShader.SetInt("kernelSize", kernelSize);
    25.         computeShader.SetBuffer(kernelId, "inputBuffer", buffer1);
    26.         computeShader.SetBuffer(kernelId, "outputBuffer", buffer2);
    27.  
    28.         // run compute shader
    29.         // int passes = 12;
    30.         computeShader.SetInt("passes", passes);
    31.         bool swapped = false;
    32.         for (int i = 0; i < passes; i++)
    33.         {
    34.             computeShader.Dispatch(kernelId, texture.width / 8, texture.height / 8, 1);
    35.             if (swapped)
    36.             {
    37.                 computeShader.SetBuffer(kernelId, "inputBuffer", buffer1);
    38.                 computeShader.SetBuffer(kernelId, "outputBuffer", buffer2);
    39.                 swapped = false;
    40.             }
    41.             else
    42.             {
    43.                 computeShader.SetBuffer(kernelId, "inputBuffer", buffer2);
    44.                 computeShader.SetBuffer(kernelId, "outputBuffer", buffer1);
    45.                 swapped = true;
    46.             }
    47.         }
    48.  
    49.         // set pixels
    50.         ComputeBuffer outBuffer = swapped ? buffer1 : buffer2;
    51.         Color[] output = new Color[bufferSize];
    52.         outBuffer.GetData(output);
    53.         texture.SetPixels(output);
    54.         texture.Apply();
    55.  
    56.         // release buffers
    57.         buffer2.Release();
    58.         buffer1.Release();
    59.  
    60.         return texture;
    61.     }
     
    alexanderameye likes this.
  11. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    One more question: was there a reason you used a compute shader instead of a 'regular' shader?
     
  12. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    No specific reason but I just couldn't get the frag shader to return anything useful. If I remember correctly the texture just stayed blank / black.
    The post up top from viceca mentions calling texture.apply before the blit. That might have done the trick though but I never checked because I already had the compute shader solution in place.
     
    alexanderameye likes this.
  13. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    Took me a while to get this working well but finally I have a nice SDF importer which runs automatically :)

    Thanks for the help/input!

     
    fleity likes this.
  14. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    nice to hear and looks great. Can yours do different width (/ JFA steps) settings? Any good ideas how / where to store that? (I did that in the name of the file, which I didn't like at all)
     
  15. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    I have an editor tool where I can do this but so no way atm to control it for the auto-import process. For my use case it is not needed and the default width is fine so probably not going to look into it for the moment.

     
    fleity likes this.