Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Is this possible with a Shader?

Discussion in 'Shader Graph' started by HungryCatGames, Jul 26, 2021.

  1. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Hi. I'm looking to make an object in my game sway in the breeze. Can I do it with a Shader?

    But wait... there's more. :)

    The object does not exist in the game until run time.

    I've got a game that allows for users to create 3D objects which at run time the game will bring in, turn into an object and then allow the user to click and place this object (via instantiating of the original "imported" one). This all is working quite well but the objects are static. No movement. I've seen enough videos on creating shaders for wind movement simulation however all of these tutorials end up assigning a material, which is what your object has applied to it, before attaching the shader to the object/prefab.

    Since the object and the material does not even exist until run time would it be possible to apply a shader with no material but still have it trigger the movement?

    Sorry if I'm not phrasing this properly as I've yet to see any tutorial or example out there for run time imports let alone then applying movement so I feel like I'm in somewhat uncharted territory here.

    Thanks in advance for any suggestions.
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,580
    You need material with shader attached to object, even if is created, to be able apply any shader effect, like wind etc.
    You can create materials and objects at runtime.
     
  3. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    For premade objects I understand this and can create these just fine. The issue is that my object is not premade. It literally does not exist as an object, asset or even in code. It's only after the game is started that the object is then created dynamically via a 3d model import which creates the object and the assigned material.

    The issue for me then is how do I create/connect a shader to this runtime created object+material? I've been googling for days and been unable to find even a remotely similar example and am at a loss as to how to go about this.
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,580
    I suggest don't use material from importer.
    I expect it is just basics default material in Unity after import.

    Instead
    In editor:
    Create own material.
    Assign shader to material.

    At runtime programmatically:
    Import model.
    You need grab meshRenderer component on game object.
    Assign your material to model.
    Assign imported texture, or multiple textures to material.

    You should be good to go.
     
  5. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Thanks for the feedback.

    In concept I agree, but in practice I'm not seeing that this is possible. If you're familiar with Cities:Skylines, for example, it is a city building game. Comes with a bunch of buildings. Players can use a 3D application, like Blender, to create their own buildings to go in it. When the game launches it scans for any outside content that you have added and imports those objects (and textures) into the game.

    In my case I allow the player to create a 3D object, export to a gltf format which includes the object and texture all packaged into one file. The import routine I'm using opens this gltf file and creates, at run time, an object with the material. The imported texture could be simple colors, or a full blown UV map. In the event this imported object is a plant I want to apply the shader to allow it to "sway in the breeze".

    So I can't use my own material as the material doesn't exist until run time and if I used a material I created it would be different than one the player created. At best colors could be wrong, at worst the object would be ruined due to UV map and transparency not being what my material has.

    What I need to do, at run time:

    1) Import model - OK, already doing this
    2) get meshRenderer - OK, already doing this
    3) attach a "wind" shader - which I can't do (or haven't figured out how to) without losing the material that came in with the object. Also I can't see any way to make a custom wind shader at run time as from my research it appears shaders have to all be assets existing at compilation time.

    I was hoping with 2020.2+ and the Shader stack with vertex separated out that I could apply a movement shader without having to also apply a material, thus leaving the imported material intact and still getting the movement. But from all the tests I've done this appears to be impossible, without a material linked to the shader you don't get movement (or at least visible movement). I was thinking perhaps assign two shaders, one with the imported material and a second with a generic color with the alpha turned down and the movement on it but that didn't work either. If I put two shaders on both with color then I get a frozen object and a moving object and still no way to get the run time imported texture into the shader.

    At the end I don't think what I want to achieve is going to be possible and I may have to either abandon the player created objects (which could hurt longevity of the game) or settle on plants that don't move in the wind. Both choices seem to me to be a pretty big compromise but I'm not seeing any other option. Hours of Googling and hours more of Youtube videos have yielded nothing new. The Cities game doesn't require (want/need) buildings to sway in the breeze so I don't think they've run into this issue, otherwise I'd try to reach out to them.
     
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,580
    When you import materials from other 3d stare, most of features are stripped down anyway. So most likely all materials are generic.
    Did you try import these model into editor, and see, if materials differ anyhow?

    As modder you don't care about imported material. The textures are important.
    You can not use for example procedural or fancy material from blender in Unity.
    You crate textures and assign them to predefined material.

    So you create material, which can accommodate variation of textures.
    I.s albedo, heigh map, details, illumination etc.

    Then there is no problem with materials importing and łac compatibility at all.
    If you look into any moddable games, non of them imports materials. They import mesh and textures. Each engine handles materials differently.
     
  7. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Correct, I'm not importing materials. The model and the texture is what is coming in. The import routine I use turns that texture into a material. That aspect all works fine.

    For reference the importer library I'm using is https://github.com/KhronosGroup/UnityGLTF

    I think I get what you're saying, create a material, assign the material to the imported object and assign the imported texture to the material. But, if I use that material on another object it would need a different texture. If that's accurate then I'd have to create a bunch of materials at run time (very doable) but then I'm circled back to the shader not supporting run time creation of the materials thus defeating the advantage of managing all the materials myself.

    Unless I'm creating one generic material with a shader on it, then instanting that material, applying the new texture to that instance and assigning that to an imported object. Then repeat for each additional object. Not sure if this would be supported but is the only option I can think of that might be viable. I'll have to prototype something and see if I can get it to work.
     
  8. lilacsky824

    lilacsky824

    Joined:
    May 19, 2018
    Posts:
    164
    This remind me my previous project.
    A project is use for visualize interior design.
    I have pack so much assets into packs and loading when runtime.
    Like models and textures. Some of textures even is procedural at runtime.
    And you can change some material properties, or swap specific material on a mesh
    Also you can change material's texture scaling or Triplanar mapping.
    Which require change shader.

    You can do this, but it is not relate to shader so much.
    It is relate to scripting much more.
     
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,580
  10. lilacsky824

    lilacsky824

    Joined:
    May 19, 2018
    Posts:
    164
    Create Material at run-time is a bit tricky.
    When you create or modified material at editor, editor will do something like enable keyword or set some properties' value.
    Such as enable normal map, editor will enable some keyword that make shader use normal map.
    Which doesn't when run-time. And you may encounter missing some shader variant that is not including in build.
    So I suggest create prototype material at editor mode, then load when playing.
    You just need to change textures. :)

    And the other problem is change material. When change material, check material's shader is different or not?
    Since shader's properties may change between shaders.
    Like some shader name colormap as "_BaseMap", some name "_AlbedoMap". You may need remap them.
    If you make shader by yourself, then keep them as same as possible.
     
  11. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Thanks all. I'll dig into this further using your suggestions and see if I can cobble together something that works. If I can get a functional system I'll post back.
     
  12. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Partially there, but still no success. I can apply a material OR a shader but I can't seem to get both to work.

    Code (CSharp):
    1.  
    2. // object that has the material (texture) I want to use (imported at run time)
    3. MeshRenderer MyRend = model.GetComponent<MeshRenderer>();
    4.  
    5. // test to verify I got a material and a shader, I did
    6. Debug.Log("Material: " + MyRend.material);
    7. Debug.Log("Shader: " + MyRend.material.shader);
    8.  
    9. // create a test cube
    10. GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    11.  
    12. // get renderer for test cube
    13. Renderer rend = cube.GetComponent<Renderer>();
    14.  
    15. // get the texture from the original object & verify I got it. It reports back as a UnityEngine.Texture2D
    16. Texture testTexture = MyRend.GetComponent<Renderer>().material.mainTexture;
    17. Debug.Log(">" + testTexture + "<");
    18.  
    19.  // change cube to use the "wind" shader - works fine. test shader is a sine wave movement and cube moves but is grey
    20. rend.material.shader = Shader.Find("Shader Graphs/TestShader");
    21.        
    22. // so try to set material to the texture. No errors but also cube stays grey      
    23. rend.material.SetTexture("_MainTex", testTexture);
    24.  
    So it appears simple enough to get a shader onto a runtime object for movement but I have been unable to figure out how to also get a material/texture applied.

    If I remove the "rend.material.shader" line and add in
    rend.GetComponent<Renderer>().material.mainTexture = MyRend.GetComponent<Renderer>().material.mainTexture;

    Then I can get a texture applied to the object, however if I put the "rend.material.shader" line back in then that seems to overwrite the above line and I again lose the texture. Doesn't matter which order in the code the lines are, the shader line seems to override the mainTexture line.
     
  13. lilacsky824

    lilacsky824

    Joined:
    May 19, 2018
    Posts:
    164
    Renderer.material will instantiated new materiall every time.
    So you can cache material then apply?
    Like this.
    Code (CSharp):
    1. Material mat = rend.material;
    2. mat.shader = Shader.Find("Shader Graphs/TestShader");
    3. mat.SetTexture("_MainTex", testTexture);
    4. rend.sharedMaterial = mat;
     
  14. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    This kind of works, but isn't any further along than what I had. The shader definitely gets applied and I see the movement, but still grey. If I comment out the shader line then I just get a grey cube. So no texture either way. I tried a few dozen variations on material, shared material, etc. but still unable to get the texture/material to apply. I'd think something is wrong with my texture source but this

    Code (CSharp):
    1. rend.GetComponent<Renderer>().material.mainTexture = MyRend.GetComponent<Renderer>().material.mainTexture;
    works and applies the texture to the cube... so I know my source is valid.
     
  15. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
  16. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    I tried modifying the movement shader. Added a Texture2D Asset node and a Sample Texture 2D. Asset has an image and feeds sample. Sample feeds Fragment (Base Color). This gives me an image on my previous grey cube, however I have not been able to figure out how to swap the texture. It's shown in the Shader in the inspector as the second item down and listed as Texture2D, but is also grey. I can drop something into "MyTexture" but that never shows up on the object.

    Not sure if I'm going the wrong direction here but it is the first time I got a texture (wrong one) and movement on the same object.
     
  17. lilacsky824

    lilacsky824

    Joined:
    May 19, 2018
    Posts:
    164
    So strange. I tested and my method is work.
    Can you take screenshot about what you properties set?
    Just want to make sure property name is same as script's.
    And property name I mentioned is labeled as "Reference" in shader graph. Not display name. :)
     
    Last edited: Aug 2, 2021
  18. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Use .sharedMaterial etc to modify at runtime or Unity will cone it when you modify it, and you might grab a ref to the wrong one (common mistake).
     
  19. JJRivers

    JJRivers

    Joined:
    Oct 16, 2018
    Posts:
    137
    What renderpipeline are you on?

    If on the SRP's
    This all seems rather superfluous and complicated for what should be somewhat simple, you should be able to simply import the asset with its textures, make a custom shader graph which you can make without any previous experience quite quickly. (and ensure teh properties you set from script is set to Unexposed).

    Then create a material from that and assign it to the object, in code create a material property block with the textures you just imported and apply it to the instance of the material.


    Edit also ensure you are handling the texture importing properly and make some presets you can easily apply to all textures without having to make a lot of boilerplate code.
     
  20. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Using URP.



    I created a brand new project with just the minimum to test this and still no good. Here is the output. Note the cube does not move, but the texture is applied. The Sphere is a runtime imported gltf object.

    upload_2021-8-3_21-15-20.png


    Code (CSharp):
    1. GameObject model = Importer.LoadFromFile(Application.dataPath +"/Resources/testshape.gltf");
    2.         model.transform.localPosition = new Vector3(0, 0, 0);
    3.         model.name = "TestShape";
    4.  
    5.         MeshRenderer MyRend = model.GetComponent<MeshRenderer>();
    6.         Texture testTexture = MyRend.material.GetTexture("_MainTex");
    7.  
    8.         GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    9.         cube.transform.localPosition = new Vector3(3, 0, 0);
    10.         MeshRenderer rend = cube.GetComponent<MeshRenderer>();
    11.  
    12.         rend.GetComponent<MeshRenderer>().material.mainTexture = MyRend.material.GetTexture("_MainTex");
    13.  
    That is using the last line of code which has consistently worked for me to get the texture from one object to another, but doesn't seem to work with a shader attached.

    So I changed the code to reflect the recommendation above and now the cube is moving back and forth (simply sine wave/time movement shader) however the texture does not come across.

    upload_2021-8-3_21-17-45.png

    upload_2021-8-3_21-17-59.png


    And the code that gets the movement shader (and should be the texture but it doesn't) is:

    Code (CSharp):
    1. void Start()
    2.     {
    3.        
    4.         GameObject model = Importer.LoadFromFile(Application.dataPath +"/Resources/testshape.gltf");
    5.         model.transform.localPosition = new Vector3(0, 0, 0);
    6.         model.name = "TestShape";
    7.  
    8.         MeshRenderer MyRend = model.GetComponent<MeshRenderer>();
    9.         Texture testTexture = MyRend.material.GetTexture("_MainTex");
    10.  
    11.         GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    12.         cube.transform.localPosition = new Vector3(3, 0, 0);
    13.         MeshRenderer rend = cube.GetComponent<MeshRenderer>();
    14.  
    15.         //rend.GetComponent<MeshRenderer>().material.mainTexture = MyRend.material.GetTexture("_MainTex");
    16.  
    17.         Material mat = rend.material;
    18.         mat.shader = Shader.Find("Shader Graphs/TestShader");
    19.         mat.SetTexture("_MainTex", testTexture);
    20.         rend.sharedMaterial = mat;
    21.     }

    The Importer.Load is from the gltf importer "utility" that I'm using. It has worked perfectly for bringing in models and textures. https://github.com/Siccity/GLTFUtility

    upload_2021-8-3_21-15-20.png upload_2021-8-3_21-17-45.png upload_2021-8-3_21-17-59.png
     
  21. JJRivers

    JJRivers

    Joined:
    Oct 16, 2018
    Posts:
    137
    Has worked Feels like a valid thing but the obvious fact is it doesn't here :)

    Could you screencap the TestShader graph with both the blackboard / graph inspector visible and your property selected, since this is looking quite a bit like your shader is atleast a part of the issue here? If so i can help with that quickly.
     
  22. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    upload_2021-8-6_18-28-32.png

    upload_2021-8-6_18-28-57.png
     
  23. JJRivers

    JJRivers

    Joined:
    Oct 16, 2018
    Posts:
    137
    You do need to declare the property in the blackboard, it is not implicitly created. Part of what makes shader graph useable performance wise is that it will aggressively attempt to inline absolutely everything you do not declare/use in the graph, you did not declare _MainTex so most likely it got inlined.

    edit: also i would recommend against using _MainTex as it can in some edge cases cause some issues, using _BaseMap for property name (SRP default value for main diffuse texture) is safe and recommended and you also get the benefit that when you change shaders the saved values of the materials migrate automatically. Open URP/Lit from your project for a list of property names and their default values.

    Here's an example of what it could look like. (If you can know for certain what the sampler state should be for these textures you want to attach the *optional sampler state to the Sample Texture node to ensure you only use a single sampler for this shader).

    https://i.gyazo.com/e9408b286256f015a8fa88bf897a8e2d.mp4
    https://i.gyazo.com/15f6881070c8ac325e24bde886669ea3.png

    Also use ready made functions in the graph when they exist and don't contain extra instructions you don't use, they will generally optimize better. Here that means grab sine time from the Time node directly. Example when Not to do that would be 0-1 to 1-0 remap which has a lot more logic than what you need which you could simply do with (X * -1) + 1 = Y
     
    Last edited: Aug 7, 2021
  24. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40
    Thanks. I'll take a look at your suggestions. As to the SINE node... that was simply for testing as it's a quick and dirty way to setup movement. The actual shader is a more complex wind movement structure.
     
  25. HungryCatGames

    HungryCatGames

    Joined:
    Jun 24, 2020
    Posts:
    40

    Yep, that fixed it! Many Thanks!

    I had earlier gone down this route before of adding the texture but had never named it... so when it didn't work I assumed this wasn't the issue.
     
    JJRivers likes this.