Author Topic: One substance, multiple instances  (Read 12898 times)

Hi I'm looking to have a single substance material and then have multiple meshes each with that substance material applied to it but with different parameters on each.
I want to be able to do this at runtime - I can't determine at the start how many I will need so cannot use the '+ Add substance from prototype' button.

eg. Substance.sbar - contains one substance material.
At runtime I create a mesh and want to apply that substance to it with a set of parameters.
Then I create another mesh and want to apply the same substance to it *but* with a different set of parameters.
etc. etc.


Here is some code I have tried but it doesn't work:
Code: [Select]
public Transform t; // Applied to one mesh
public Transform t2; // Applied to another
public ProceduralMaterial mToUse; // Reference to the material in the Asset Database

void Awake()
{
ProceduralMaterial inst0 = mToUse as ProceduralMaterial;
t.gameObject.renderer.material = inst0;

ProceduralMaterial inst1 = mToUse as ProceduralMaterial;
t2.gameObject.renderer.material = inst1;

// This affects 't' and 't2'
inst0.SetProceduralFloat("bg_opacity1", 1.0f);
inst0.RebuildTextures();
}


Thanks

If you keep only one substance, the parameter value change will affect all materials and therefore all objects. The only way to do that is to instantiate your substance as many times as you have objects.
"Hinhin!" :-D

Ah ok. And when you say instantiate it, which part do you mean?
The ProceduralMaterial (mToUse in my case above)?
If I do:
Code: [Select]
ProceduralMaterial inst0 = Instantiate(mToUse) as ProceduralMaterial;
t.gameObject.renderer.material = inst0;

ProceduralMaterial inst1 = Instantiate(mToUse) as ProceduralMaterial;
t2.gameObject.renderer.material = inst1;

If I do that I get an error:
Code: [Select]
material->IsFlagEnabled(ProceduralMaterial::Flag_Clone) || m_PingedMaterial==NULL || m_PingedMaterial==material

Or do you mean another way?
Thanks for replying.

Hey.

We're encountering the same problem right now with the same error message.
Is instantiating the correct way to use one substance material multiple times or is there another way to do this?

Hey.

We're encountering the same problem right now with the same error message.
Is instantiating the correct way to use one substance material multiple times or is there another way to do this?

Hey,

Do you need to instantiate in code? You could duplicate the substance in the editor and target the duplicated substance in code from that point.

Cheers,

Wes
Head of Substance Demo Art Team
the3dninja@adobe.com
Twitter: The3DNinja

Hey Wes.

Yes, we need to instantiate from code at runtime. We use the instantiated material for aprox. 50 different models and they may be all customized by the values in the procedural material (which should be instantiated for that as the values differ for each model).

Instanciating a substance is done through this:

Code: [Select]
ProceduralMaterial mat;
        mat = (ProceduralMaterial)renderer.material;

Using renderer.material instead of renderer.sharedMaterial will create a clone of that substance taht can then be modified without altering the original one.

Was the source of the error ever found/explained? When instantiating a material in Unity, we get the following error:
Quote
material->IsFlagEnabled(ProceduralMaterial::Flag_Clone) || m_PingedMaterial==NULL || m_PingedMaterial==material
UnityEngine.Object:Instantiate(Object)
UMA.<workerMethod>d__0:MoveNext()
UMA.WorkerCoroutine:Work()
UMA.WorkerCoroutine:Work()
UMA.UMAGeneratorBuiltin:HandleDirtyUpdate(UMAData)
UMA.UMAGeneratorBuiltin:OnDirtyUpdate()
UMA.UMAGeneratorBuiltin:Update()

UMA is the Unity Multipurpose Avatar project. As far as I can tell this is the same issue as described above, ie. when instantiating in code something goes wrong. The last post (above this one) does not explain what is happening nor how to solve it. And yes, we do need to do this in code. Assigning materials in the editor will not work in this case as the avatar is created at runtime and only then assigned materials.

I would really appreciate some clarification on this issue so we can proceed to implement Substances in our project. Thanks in advance!

We too experience this.

material->IsFlagEnabled(ProceduralMaterial::Flag_Clone) || m_PingedMaterial==NULL || m_PingedMaterial==material

Any clues?

Sorry to bump this old thread, but this is still an issue. Is there any way to instantiate a ProceduralMaterial on multiple objects without the baker overwriting the output textures? The lots of methods described in various places don't work.
Quote
public class TerrainHandler : MonoBehaviour
{
    public ProceduralMaterial _referenceSubstance;

    void Start()
    {
        ProceduralMaterial.substanceProcessorUsage = ProceduralProcessorUsage.All;

        for (int x = 0; x < 3; x++)
        {
            for (int z = 0; z < 3; z++)
            {
                GameObject go = new GameObject("Terrain" + x + z);
                go.transform.position = new Vector3(10f * x, 0, 10f * z);

                HeightMapGenerator h = new HeightMapGenerator(10f * x, 10f * z, 1, 4, 256, 256);

                TileMapFromHeight t = new TileMapFromHeight(h.heightMap, 32);
                go.AddComponent<MeshCollider>().sharedMesh = go.AddComponent<MeshFilter>().mesh = t.mesh;
                go.AddComponent<MeshRenderer>().sharedMaterial = _referenceSubstance;

                ProceduralMaterial m;
                m = (ProceduralMaterial)go.GetComponent<Renderer>().material;
                m.SetProceduralTexture("height", h.heightMap.GetTexture());
                m.SetProceduralTexture("biomeMap", h.heightMap.GetTexture(GradientPresets.Terrain));
                m.RebuildTextures();
            }
        }
    }


This instantiates the substance but the baker writes them to the obviously referenced outputs of the sharedMaterial.
I've tried a lot of suggestions like dummy objects among other things, nothing appears to prevent this behaviour.
I have to instantiate at runtime... there a solution to this? I'm on Unity 5.2.1f1

edit: Never mind, 5.2.1p1 fixes this behavior!
Quote
(725995) - Substance: Runtime-instantiated ProceduralMaterials now update their own textures.
Last Edit: September 30, 2015, 11:49:17 pm

Quick Test
Put this script a game object to instance procedural materials at start up

Please note you will have to change the resource path to your substance locations

Code: [Select]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AssignmentTest : MonoBehaviour {

    public GameObject cube1;
    public GameObject cube2;
    public GameObject cube3;

    // Use this for initialization
    void Start()
    {
        // Load some procedural materials
        ProceduralMaterial mat1 = Resources.Load("Allegorithmic Database 2.1/Brick/boulder_stone_wall", typeof(ProceduralMaterial)) as ProceduralMaterial;
        ProceduralMaterial mat2 = Resources.Load("Allegorithmic Database 2.1/Brick/brick_castle_bumpy", typeof(ProceduralMaterial)) as ProceduralMaterial;

        // Instance some procedural materials
        InstanceSubstance(cube1, mat1);
        InstanceSubstance(cube2, mat1);
        InstanceSubstance(cube3, mat2);


        // Change some standard shader values
        cube1.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(1f, 1f));
        cube2.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(2f, 2f));
        cube3.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(3f, 3f));

        // Change some standard procedural values
        ((ProceduralMaterial)cube1.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.5f);
        ((ProceduralMaterial)cube2.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.7f);
        ((ProceduralMaterial)cube3.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.9f);
    }

    private void InstanceSubstance(GameObject go, ProceduralMaterial mat)
    {
        GameObject tmpGo = new GameObject("_tmpObj");
        MeshRenderer tmpRenderer = tmpGo.AddComponent<MeshRenderer>();
        tmpRenderer.material = mat;
        go.GetComponent<MeshRenderer>().material = tmpRenderer.material;
        DestroyImmediate(tmpGo);
    }
}

Last Edit: March 13, 2017, 06:22:50 pm

Quick Test
Put this script a game object to instance procedural materials at start up

Please note you will have to change the resource path to your substance locations

Code: [Select]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AssignmentTest : MonoBehaviour {

    public GameObject cube1;
    public GameObject cube2;
    public GameObject cube3;

    // Use this for initialization
    void Start()
    {
        // Load some procedural materials
        ProceduralMaterial mat1 = Resources.Load("Allegorithmic Database 2.1/Brick/boulder_stone_wall", typeof(ProceduralMaterial)) as ProceduralMaterial;
        ProceduralMaterial mat2 = Resources.Load("Allegorithmic Database 2.1/Brick/brick_castle_bumpy", typeof(ProceduralMaterial)) as ProceduralMaterial;

        // Instance some procedural materials
        InstanceSubstance(cube1, mat1);
        InstanceSubstance(cube2, mat1);
        InstanceSubstance(cube3, mat2);


        // Change some standard shader values
        cube1.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(1f, 1f));
        cube2.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(2f, 2f));
        cube3.GetComponent<MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(3f, 3f));

        // Change some standard procedural values
        ((ProceduralMaterial)cube1.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.5f);
        ((ProceduralMaterial)cube2.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.7f);
        ((ProceduralMaterial)cube3.GetComponent<MeshRenderer>().material).SetProceduralFloat("saturation", 0.9f);
    }

    private void InstanceSubstance(GameObject go, ProceduralMaterial mat)
    {
        GameObject tmpGo = new GameObject("_tmpObj");
        MeshRenderer tmpRenderer = tmpGo.AddComponent<MeshRenderer>();
        tmpRenderer.material = mat;
        go.GetComponent<MeshRenderer>().material = tmpRenderer.material;
        DestroyImmediate(tmpGo);
    }
}



Thanks Mark for posting this code. Does this fix the issues mentioned above? Are you seeing a bug in this case?

Cheers,
Wes
Head of Substance Demo Art Team
the3dninja@adobe.com
Twitter: The3DNinja