Search Unity

Adding PostProcess Layer and Volume through Editor Script

Discussion in 'Image Effects' started by Yandalf, Apr 2, 2019.

  1. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Hey everyone!
    I'm currently creating an Editor Tool to quickly set up Postprocessing in my project but I'm hitting a strange snag.
    When I use AddComponent to add the PostProcessLayer and PostProcessVolume, the effects of the profile I assign to the Volume do not get applied to the rendered image.
    This happens regardless of me doing the rest of the setup (assigning of the profile, setting the layermask and trigger, setting the volume to be global) either through script or manually.
    As soon as I add the components manually instead of through script, everything works fine.
    Also, for some reason setting the layer for the PostProcessLayer results in mixed instead of the expected PostProcessing layer. Logging the value returns the correct layer, however, it just doesn't look correct in the inspector.
    Anyone got an idea what's going on?
    Code:
    Code (CSharp):
    1.     void OnWizardCreate()
    2.     {
    3.         //Open the scene
    4.         EditorSceneManager.OpenScene(_scenePath);
    5.         //Get or Add PostProcessLayer and PostProcessVolume to Main Camera
    6.         GameObject camera = FindObjectOfType<Camera>().gameObject;
    7.         PostProcessLayer layer = camera.GetComponent<PostProcessLayer>();
    8.         if (layer == null)
    9.         {
    10.             layer = camera.AddComponent<PostProcessLayer>();
    11.             var resources = AssetDatabase.FindAssets("t:PostProcessResources");
    12.             string resourcesPath = AssetDatabase.GUIDToAssetPath(resources[0]);
    13.             layer.Init((PostProcessResources)AssetDatabase.LoadAssetAtPath<PostProcessResources>(resourcesPath)); //This is necessary in Runtime at least, according to docs?
    14.             layer.InitBundles();
    15.         }
    16.         PostProcessVolume volume = camera.GetComponent<PostProcessVolume>();
    17.         if (volume == null)
    18.             volume = camera.AddComponent<PostProcessVolume>();
    19.         //Set Main Camera layer to Postprocessing
    20.         camera.layer = LayerMask.NameToLayer("PostProcessing");
    21.         //Set PostProcessLayer
    22.         layer.volumeTrigger = camera.transform;
    23.         layer.volumeLayer = LayerMask.NameToLayer("PostProcessing");
    24.         Debug.Log(LayerMask.LayerToName(layer.volumeLayer));
    25.         //Set PostProcessVolume
    26.         volume.isGlobal = true;
    27.         //Check for SSAO profile, make it if not found in the project assets
    28.         var assets = AssetDatabase.FindAssets("t:PostProcessProfile");
    29.         PostProcessProfile profile;
    30.         if (assets.Length == 0)
    31.         {
    32.             CreatePostProcessingProfile();
    33.             assets = AssetDatabase.FindAssets("t:PostProcessProfile");
    34.         }
    35.         string path = AssetDatabase.GUIDToAssetPath(assets[0]);
    36.         profile = (PostProcessProfile)AssetDatabase.LoadAssetAtPath(path, typeof(PostProcessProfile));
    37.         //Set PostProcessVolume profile to SSAO profile
    38.         volume.sharedProfile = profile;
    39.         //Save the scene
    40.         EditorSceneManager.MarkAllScenesDirty();
    41.         EditorSceneManager.SaveOpenScenes();
    42.     }
    43.  
    44.     void CreatePostProcessingProfile()
    45.     {
    46.         string path = "Assets/postprocessing/SSAO.asset";
    47.         AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<PostProcessProfile>(), path);
    48.  
    49.         PostProcessProfile profile = AssetDatabase.LoadAssetAtPath<PostProcessProfile>(path);
    50.         AmbientOcclusion AO = profile.AddSettings<AmbientOcclusion>();
    51.         AO.mode = new AmbientOcclusionModeParameter { value = AmbientOcclusionMode.ScalableAmbientObscurance };
    52.         AO.intensity = new FloatParameter { value = 1f };
    53.         AO.radius = new FloatParameter { value = 0.05f };
    54.         AO.quality = new AmbientOcclusionQualityParameter { value = AmbientOcclusionQuality.Ultra };
    55.         AssetDatabase.AddObjectToAsset(AO, profile);
    56.         EditorUtility.SetDirty(profile);
    57.         AssetDatabase.SaveAssets();
    58.     }
     
  2. TobyKaos

    TobyKaos

    Joined:
    Mar 4, 2015
    Posts:
    214
    If post process and profile is not on a scene when building. Then no shader are generated. Must include it in build config.
    https://docs.unity3d.com/Packages/com.unity.postprocessing@2.1/manual/Writing-Custom-Effects.html


    Important: if the shader is never referenced in any of your scenes it won't get built and the effect will not work when running the game outside of the editor. Either add it to a Resources folder or put it in the Always Included Shaders list in Edit -> Project Settings -> Graphics.
     
    Last edited: Feb 13, 2020
  3. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Hey Toby!
    Thanks for the reply.
    However, this is not an issue specific to builds (although it might present itself as an issue there as well), but a problem with editor scripting as I clearly stated in the opening post.
     
  4. Bodyclock

    Bodyclock

    Joined:
    May 8, 2018
    Posts:
    172
    I'm having exactly this problem at the moment. Did it ever get solved?
     
  5. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Never found a solution unfortunately.
     
  6. StellarVeil

    StellarVeil

    Joined:
    Aug 31, 2018
    Posts:
    73
    Here's a solution that works, you can then add the rest:
    Code (CSharp):
    1. void OnWizardCreate()
    2. {
    3.         GameObject mainCamera = FindObjectWithType<Camera>() ?? new GameObject("Main Camera", typeof(Camera));
    4.         mainCamera.tag = "MainCamera";
    5.  
    6.         PostProcessLayer postProcessLayer = mainCamera.AddComponent<PostProcessLayer>();
    7.         postProcessLayer.volumeLayer = LayerMask.GetMask("Post Processing");
    8.  
    9.         PostProcessVolume postProcessVolume = FindObjectOfType<PostProcessVolume>();
    10.  
    11.         if (!postProcessVolume)
    12.             postProcessVolume = new GameObject("PostProcessVolume").AddComponent<PostProcessVolume>();
    13.  
    14.         postProcessVolume.profile = AssetDatabase.LoadAssetAtPath<PostProcessProfile>("Assets/YourProfile.asset");
    15.         postProcessVolume.isGlobal = true;
    16.         postProcessVolume.gameObject.layer = LayerMask.NameToLayer("Post Processing");
    17. }
    Important Notes:
    • To assign the desired layer for postProcessLayer.volumeLayer you need to use LayerMask.GetMask().
    • Make sure the MainCamera tag is applied on the cam that has the PostProcLayer: if you have 2 cams one that has that tag but is disabled or doesn't have PostProcLayer (or layer disabled) and then you create a 2nd cam & give it a layer the postproc won't work. Also make sure the MainCamera tag is on only 1 cam at a time even if the others are disabled or that your camera had it first.
    Hope this solves your issue.
     
    Last edited: May 16, 2020
  7. WingWeaver

    WingWeaver

    Joined:
    Oct 3, 2018
    Posts:
    2
    Sorry for the Necro but could somone explain exactly what

    Code (CSharp):
    1. (PostProcessResources)AssetDatabase.LoadAssetAtPath<PostProcessResources>(resourcesPath)
    Is does and means and what this guy is searching for in Assets as there is NO documentation
    @Yandalf It would be great if you could explain what Resource I need to load for the Init Fucntion!!

    You seem to be loading "
    Code (CSharp):
    1. t:postProcessResources
    " but what is that and how do I create a PostProcessResource Class and all the rest as Unity tells us NOTHING here (</3)

    I cannot even find or create that Type in Unity only in code...
     
  8. StellarVeil

    StellarVeil

    Joined:
    Aug 31, 2018
    Posts:
    73
    Code (CSharp):
    1. (PostProcessResources)AssetDatabase.LoadAssetAtPath<PostProcessResources>(resourcesPath)
    Edit: my bad I thought you were referring to the (PostProcessProfile)AssetDatabase.LoadAssetAtPath() line.
    That line searches for a Post Process Profile file in the path stored in the resourcesPath string variable.
    You don't have to create the file in code but inside the editor, a quick Google/Youtube search will give you simple tutorials.
     
    Last edited: Apr 12, 2023
  9. Gulliver

    Gulliver

    Joined:
    Jan 8, 2013
    Posts:
    101
    Does it mean that it's impossible to attach PostProcessLayer component to the Camera created in run-time ?
    Found no ways to get resources from existing PostProcessLayer or static RuntimeUtilities -- always private.
    What idiot coded this ?
     
  10. StellarVeil

    StellarVeil

    Joined:
    Aug 31, 2018
    Posts:
    73
    Now that I read the line again I noticed the user was referring to the one that returns a PostProcessResources when I thought he was asking about the (PostProcessProfile)AssetDatabase.LoadAssetAtPath() line instead so my reply doesn't really answer his actual question my bad.

    Haven't tried adding a PPLayer to a camera at runtime and I'd say it's best practice to have your cameras already created & preconfigured in edit mode and keep them disabled until when you need them simply enable them and disable the previous main camera.
    There's no point in creating PPProfiles or PPLayers through code in most cases cuz it's much more practical to do in edit mode manually.
    You can also just change profiles at runtime by doing something like
    Code (CSharp):
    1. volume.profile =  AssetDatabase.LoadAssetAtPath<PostProcessProfile>(yourPathHere);
    or if you don't want to use the Resources folder and AssetDatabase.LoadAssetAtPath you can just assign the desired profile in a serialized field from the inspector and set your volume.profile to that.

    Note: if your script runs in edit mode for convenience purposes then use volume.sharedProfile field instead.