Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
5
Question by NathanJSmith · Aug 02, 2018 at 08:28 AM · shadereditorplaymode

Why changing material shader at runtime also affect my asset on disk?

I change shader via script

 public Shader m_TwoSideShader;
 
 public void ChangeShader()
 {
                 MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
                 Material[] materials = meshRenderer.sharedMaterials;
                 for (int i = 0; i < materials.Length; ++i)
                 {
                     materials[i].shader = m_TwoSideShader;
                 }
 }

I think everything should return to original after exit play mode, but after I exit play mode, I see that my asset's material also change the shader. Is this a bug?

Note: this also happen when I change terrain height & texture map at run-time, but I think very few games would change terrain at run-time so Unity wouldn't care about this, but now this also happen to material shader too?

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

· Add your reply
  • Sort: 
avatar image
5
Best Answer

Answer by Zarenityx · Aug 02, 2018 at 08:54 PM

This is due to using sharedMaterials instead of materials in your case, but it seems like your more general question is why this happens at all.

As far as I can tell, this is not a bug. When you exit play mode, all of the fields in all of your components will be reset, but assets won't. This is generally a good thing, since you generally shouldn't be modifying assets at runtime, but it would be very nice to be able to modify them in the editor and see the change at runtime, adjust it to something nice, then keep it that way. Try changing material parameters by hand in play mode. They don't reset, so you can edit a material the way you see it in-game.

The issue here is that Material is a class, and thus a reference type. If you're unfamiliar with the idea of reference vs value types, a brief overview: Value types contain their value itself. Bool, float, etc. are value types, as are structs. Setting "Value A = Value B" makes A a copy of B. Changing one doesn't affect the other. By contrast, reference types have their actual value somewhere in memory, but themselves are just a pointer to that value. Setting "Reference A = Reference B" makes A and B refer to the same value. Changing one changes both. There are plenty of more in-depth posts on this distinction, and if you aren't familiar with it I recommend looking it up. It's a very important and powerful part of C# as a language (although in no way unique to C# either).

So the reason you're seeing your materials change:
MeshRenderer.sharedMaterials is an array of references to a Material asset. If you set a sharedMaterial, this works just fine, since you're just changing what that field refers to, which gets reset after play mode. But if you modify a sharedMaterial, you aren't modifying the reference, you're modifying the value it points to: the asset itself. This applies to terrains, textures, any asset really, even prefabs.

So how can this be fixed?
There are a couple of ways, but both require that you operate on a copy of the material. The simplest way to do this, of course, is to copy each material in the editor and apply the new shader, then use your script to set the sharedMaterial to that material. Of course, this doesn't scale well, and I'd assume if you're doing this in a script that that's a concern. Fortunately, there is Material.CopyPropertiesFromMaterial, which you can use to make a copy in code, then set the material to that. This will create a new material instance that you can modify to your heart's content without touching the material you copied it from. This solves the problem, but has a few problems:

  • It breaks batching. So would switching the material out, but at least that could batch with other objects using that material. Using a runtime material won't. You'll be getting 1 draw call per object unless you're doing instancing.

  • You won't be able to see changes made to the source material in the new material until you copy it again.

  • The copy process isn't super slow, but it's not something to be doing every frame either. Do it when necessary. Startup or when something changes is fine.



Hope this helps! Good luck on your project!

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image NathanJSmith · Aug 03, 2018 at 03:47 AM 0
Share

But I see that if I run the game as release mode, then it won't modify my release-mode-asset on disk. So I think it would be cool if I can adapt this behavior for editor mode.

avatar image Zarenityx · Aug 14, 2018 at 08:35 PM 0
Share

That would be interesting, although I'm not sure what what this would really let you do that wouldn't be better as an editor script. I feel like it would just make testing a nightmare. But I'd be interested if you found a use for it.

avatar image PizzaPie · Aug 14, 2018 at 11:07 PM 1
Share

Fairly disturbing behaviour, this shouldn't really happen.

It doesn't has much to do with reference/ value types rather with serialization (of course you manipulate the actual instance of the material, and changes should apply to all objects using said material, but it shouldn't be directly connected with the serialized data on your hard drive($$anonymous$$aterial asset or any other asset as matter of hand)), meaning the material are serialized upon change or on Play$$anonymous$$ode ter$$anonymous$$ation.

Could be the way they are loaded or reloaded , maybe for synchronization reasons, beats me, hopefully not to easy the editing.

On what other assets did you noticed this behaviour?

avatar image Zarenityx PizzaPie · Aug 15, 2018 at 04:54 PM 0
Share

It's not actually internally connected to what's on your hard drive really, I just didn't want to make my answer even longer. As far as I can tell the material is loaded into memory when you're messing around with it in the editor, and that memory can of course be changed (thus the reference issue). The Unity editor marks any assets that have changed as 'dirty', and then updates the disk version accordingly.

While I can see your point that it can have unintended side effects, not having it means not being able to edit materials while playing your game, which could really hurt the iteration process for designers and artists. And for coders, it's a pretty simple consideration to handle.

avatar image
5

Answer by goldbug · May 01, 2019 at 02:35 AM

This annoys me to no end. I animate materials at runtime, and they are persisted causing a lot of changes for my version control.

Here is my solution. Add this script in an Editor folder:

 using System.IO;
 using System.Linq;
 using UnityEditor;
 
 // prevents materials from being saved while we are in play mode
 public class PlayModeMaterials : UnityEditor.AssetModificationProcessor
 {
     static string[] OnWillSaveAssets(string[] paths)
     {
         if (EditorApplication.isPlaying)
         {
             return paths.Where(path => Path.GetExtension(path) != ".mat").ToArray();
         }
         else
         {
             return paths;
         }
     }
 }
 
Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image OldHarryMTX · Mar 18, 2020 at 09:02 PM 0
Share

Sorry, i haven't understand where/how use that. Can you give me an explanation?

Thanks!

avatar image pjbaron · Jun 29, 2020 at 10:27 PM 0
Share

Still very useful in 2019.3, thanks! To address the previous comment: create a folder Assets/Editor and put it in there. It will work automatically.

avatar image JonPQ · Jul 16, 2020 at 10:29 PM 4
Share

I tried this out... but when I save the project, it still alters all the materials.... the material appears modified in git, how to fix please ?

avatar image Menion-Leah JonPQ · Aug 30, 2021 at 03:33 PM 2
Share

I can confirm this doesn't work at all using Unity 2019.4.21f1

avatar image terriblyfun Menion-Leah · Jan 20 at 06:28 PM 1
Share

Looking at it now, I don't even get how it's supposed to work to begin with. This would prevent it writing changes for materials if you clicked Save Project while also having it running in the editor. However, once you stopped running it, it wouldn't revert the changes. The next time you saved your project, those changes would be saved.

I guess this worked for the OP because their code set the materials back to their initial state. However, if you stopped playing partway through the app or it crashed, you'd still wind up with changed materials and this script wouldn't prevent the save.

avatar image
0

Answer by terriblyfun · Jan 20 at 06:47 PM

This is an annoying feature of trying to debug apps that need to modify a material, especially one shared by several different objects. Like if I want to change all my objects of that type from one color to another to highlight them.


The main way around this is to put in a block protected by a compiler directive. This block instantiates the material so you're working on a copy. This is similar to the suggestion above only it works behind the scenes and doesn't get compiled into your final build.


This is an example bit of code you could either put in the object's pre-existing script (you probably already have one on it, if you're modifying the material anyway). Or you could put it in a standalone script and attach it to the object:

     private void Awake()
     {
 #if UNITY_EDITOR
         // make copy if you're in the editor to avoid editing the saved one
         // simply accessing .material the first time causes an instantiation 
         var material = GetComponent<MeshRenderer>().material; 
 #endif
     }
 


If you wanted to do this for several objects and maintain static batching, you'd want to do it more like this in one central script:

 #if UNITY_EDITOR
         // make copy if you're in the editor to avoid editing the saved one
         // this finds all the objects of a certain type and reassigns their material to all point to a single new
         // instance, so batching is maintained
         Material material = null;
         // This doesn't have to be GameObject.FindGameObjectsWithTag(). It can be any code that can find the list of
         // objects you actually care about.
         var objs = GameObject.FindGameObjectsWithTag("projectile");
         foreach (var obj in objs)
         {
             if (!material)
             {
                 // accessing .material causes it to create an instance
                 material = obj.GetComponent<MeshRenderer>().material;
             }
             else
             {
                 // assign the previous instance to all the other ones
                 obj.GetComponent<MeshRenderer>().sharedMaterial = material;
             }
         }
 #endif


Now they all share the same material and can have it all changed with no effects on your material on disk. And since this is a compiler directive, none of this will be included in your finished app.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

168 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Shader not refreshing after changes 0 Answers

Find all materials using a specific shader 1 Answer

Gfx.waitForPresent causing massive lag spikes in editor 8 Answers

Shader isn't displaying properly in editor view, but when built it displays properly. 0 Answers

Is it possible to change the shader of collision bodies in editor? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges