Search Unity

Enumeration of asset's colours, imported from MagicaVoxel .obj

Discussion in 'Scripting' started by jujinski, Oct 10, 2019.

  1. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    Hi, I'm trying to programmatically enumerate the 3 colours used in a simple .obj asset I imported from MagicaVoxel but am not yielding any sensible results.

    I wondered if I could persuade someone to slap a brief example together to show me how, tested against the attached files? I could really use the help, chrs
     

    Attached Files:

  2. ProtagonistKun

    ProtagonistKun

    Joined:
    Nov 26, 2015
    Posts:
    352
    You wont get much reply asking for code, but people will help you if you get started on something. I am wondering though, why do you need the colors from the obj file? if you import the obj with its attached mtl it should load the mesh with the materials.
     
  3. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    After trying a range of things I've determined that I don't understand the logical structure of the imported asset, I don't think posting all my attempts would be helpful.

    I need the colours so I can assign them to content that's dynamically generated from other generic types/resources.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    When you drag that into your scene, somewhere in the scene will be at least one MeshRender rendering the object.

    On that same GameObject there will be a MeshFilter. That contains a reference to the Mesh.

    Using that Mesh you can write code to traverse the various parts of it: vertices, triangles, uv coordinates, normals, etc.

    You can use values from the uv coordinates to read pixels from your texture map to see what color is at a particular vertex.
     
    jujinski likes this.
  5. ProtagonistKun

    ProtagonistKun

    Joined:
    Nov 26, 2015
    Posts:
    352
    you could also like, export the materials from the imported data into unity materials and use those for assigning to other objects.
     
  6. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    I need to enumerate the colours for programmatic assignment elsewhere, assigning a material as a whole won't work I'm afraid.

    Thanks Kurt, this is very helpful and shows I was barking up the right tree. I recognise the things you're referring to and am accessing the mesh succesfully via:
    Code (CSharp):
    1. MeshRenderer mr = gameObject.GetComponentInChildren<MeshRenderer>();
    2. MeshFilter mf = gameObject.GetComponentInChildren<MeshFilter>();
    It was only because I saw an example somewhere that I discovered how to do this, is there a way to dump an asset's properties/methods etc at runtime, a bit like one would using JSON? This would help me understand the hierarchical structure much better.

    Regardless, previously I'd noticed that mesh.Colors[] & mesh.Colors32[] are empty, which threw me and then I saw this post, which kind of matches what you're saying, although it sounds a bit convoluted?

    And the doco says "For every vertex there can be a normal, two texture coordinates, color and tangent. These are optional though and can be removed at will. All vertex information is stored in separate arrays of the same size, so if your mesh has 10 vertices, you would also have 10-size arrays for normals and other attributes." So I think I surmised that Colors[] would have values, if the .obj imports and renders the colours accurately.

    Is this wrong? Are Colors[] and the colours accessed via the method you're referring to different?
     
    Last edited: Oct 10, 2019
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    That info is correct. Most likely the model includes UVs that reference your texture file.

    If you grab the mesh.uvs array and use that, you can .GetPixel() the colors you want out of your texture.

    UVs in this case are normalized to map from 0 to 1, but may go beyond it. Behavior if they reference beyond 0..1 is based on whether the image is imported as WrapMode set to Clamp or Repeat
     
    jujinski likes this.
  8. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    Are you essentially referring to Texture3D.GetPixel(uv[index])?

    I'd been trying to access the texture via MeshRenderer.material.GetTexture(), seeing that dereferencing textures (and most things under MR) requires a name. I saw this and the need to enable specific keywords to access certain textures. None return a Texture3D however.

    How do I get the texture you're referring to? So that I can look up the colours.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    You want the Texture2D, and you want to make sure it has been imported as Read/Write enabled so you can use .GetPixel() on it. And as far as I know, .GetPixel only comes in a flavor that takes (int x, int y), so you gotta use the .width and .height properties to expand it and cast it to an integer, unless there is a new overload that takes a Vector2.

    Also, if your materials only have one texture, you can use the .mainTexture shortcut to get at it.

    Ideally, if you want to expose this to the rest of your program in a sorta generic way (as in have lots of different objects you consider for their colors), make a script that you put on each prefab that can be filled out with just the right sub-object whose material you care about, etc. That way the rest of your program just goes "load this thing, look for this script, access its properties," and that way you can centralize all your Get-ting and .mainTexturing in one place.
     
    jujinski likes this.
  10. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    No worries re GetPixels fn prototype and yeah what I'm doing will end up in a lib somewhere.

    Yeah there's only 1 texture and I'd seen and tried mainTexture, but it's of Texture type, so I've since tried casting it to Texture2D and am now getting:

    UnityException: Texture 'ship' is not readable, the texture memory can not be accessed from scripts. You can make the texture readable in the Texture Import Settings.
    UnityEngine.Texture2D.GetPixel (System.Int32 x, System.Int32 y)...


    I've got Read/Write enabled under the mesh section of the "ship" asset's inspector, but I can't see Texture Import Settings anywhere or where I can set Read/Write of the texture. I see that the ship's material is embedded, does that prevent me from setting the read/write flag for the texture? See attached snaps.
     

    Attached Files:

  11. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    When I expand the ship asset, I see a "palette" material that doesn't refer to any texture at all and there's no advanced section under which Read/Write enable could appear. See expanded and material snapshots.
     

    Attached Files:

  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    When I unzip your original post at the top here and drop it in, I see these files once Unity imports it:

    Screen Shot 2019-10-11 at 8.21.15 PM.png

    The ship.png file is what I would set to read/write. If the material in-scene is NOT pointing to that (like it's pointing to some other transient imported material), just redo your setup so that it is, or make a new material and use that... the power of Prefabs!

    Here is my test script:

    Code (csharp):
    1.     void Start () {
    2.         var mf = GetComponent<MeshFilter>();
    3.         var mr = GetComponent<MeshRenderer>();
    4.  
    5.         var mtl = mr.material;
    6.  
    7.         Texture2D t2d = (Texture2D)mtl.mainTexture;
    8.  
    9.         for (int i = 0; i < 10; i++)
    10.         {
    11.             Debug.Log( t2d.GetPixel( i, 0));
    12.         }
    13.     }
    I put it right on the GameObject where your geometry is... it's called 'default' I think?

    And the console looks like this:

    Screen Shot 2019-10-11 at 8.26.09 PM.png

    One final note: I am using Unity 5.6.6 here tonight, so newer Unity might have different texture import processing, but I imagine that either in the object import settings or in the ship.png itself, you should be able to set read/write. Maybe there's an "advanced" tab once again?
     
    jujinski likes this.
  13. jujinski

    jujinski

    Joined:
    Oct 3, 2019
    Posts:
    9
    Setting read/write on the png did the trick. I still have to call GetComponentInChildren<>() vs GetComponent<>() to get the renderer and filter, not sure why just yet, but after some experimentation and letting your other comments sink in, I'm enumerating the colours without any worries:

    Code (csharp):
    1. MeshRenderer mr = this.go.GetComponentInChildren<MeshRenderer>();
    2. MeshFilter mf = this.go.GetComponentInChildren<MeshFilter>();
    3. Vector2[] uv = mf.mesh.uv;
    4. Texture2D t2d = (Texture2D)mr.material.mainTexture;
    5.  
    6. // iterate the uv
    7. for (int i = 0; i < uv.Length; i++)
    8. {
    9.     Vector2 v2 = uv[i];
    10.     int ix = (int)(v2.x * t2d.width);
    11.     int iy = (int)(v2.y * t2d.height);
    12.  
    13.     ...
    14. }
    Code tucked away nicely in a generic lib. Thank you kindly for the tips mate, really very helpful.
     
    Kurt-Dekker likes this.
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Because the actual renderer you seek was located one object below the root GameObject. :)

    "The renderer is in another castle!"

    Good to hear you are off and running again!