UNITY-Changing ONLY certain part of 3D model's color

10,908

I just wanted to add that there may be an easier way to achieve what you want using projectors. This is the common tool in unity used to draw various effects on mesh surface in real time, such as bullet holes. Using the same principle you can highlight some area of the mesh. You can think of a projector as flashlight, that everything its light hits changes its texture. There are some example projectors under Standard Assets / Effects. You might want to start there.

To create a projectror, create an empty game object => Add Component => Projector.

EDIT

Another idea you may want to try is to use vertex colors. Each vertex of a mesh contains, in addition to coordinates, a color parameter, which is accessible via shader. So, you can change the color of specific set of vertices in order to highlight them. There are 2 points here you have to pay attention to:

1) Most shaders choose to ignore vertex color, with exception of sprites shader. You will need a custom shader, such as this one.

2) You need to know somehow which vertices exactly you want to highlight. What you can do is to iterate over Mesh.boneWeights. If you want to select an arm, for example, what you need is all vertices that have weight > 1 for arm bone index. How do you find the arm bone index? This index corresponds to the index in SkinnedMeshRenderer.bones. Or just pick some vertex you know for sure belongs to arm and see its bone weights to find the index.

Here a sample script that changes vertex colors based on selected bone index. Attach it to your SkinnedMeshRenderer (usually a second level hierarchy object):

using UnityEngine;
using System.Collections;

public class BoneHiglighter : MonoBehaviour {

    public Color32 highlightColor = Color.red;
    public Color32 regularColor = Color.white;

    public SkinnedMeshRenderer smr;

    // Just for sake of demonstration
    public Transform bone;
    private Transform prevBone;


    // Find bone index given bone transform
    int GetBoneIndex(Transform bone) {
        Debug.Assert(smr != null);
        var bones = smr.bones;

        for (int i = 0; i < bones.Length; ++i) {
            if (bones[i] == bone) return i;
        }

        return -1;
    }

    // Change vertex colors highlighting given bone
    void Highlight(Transform bone) {
        Debug.Assert(smr != null);
        var idx = GetBoneIndex(bone);
        var mesh = smr.sharedMesh;
        var weights = mesh.boneWeights;
        var colors = new Color32[weights.Length];

        for (int i = 0; i < colors.Length; ++i) {
            float sum = 0;
            if (weights[i].boneIndex0 == idx && weights[i].weight0 > 0)
                sum += weights[i].weight0;
            if (weights[i].boneIndex1 == idx && weights[i].weight1 > 0)
                sum += weights[i].weight1;
            if (weights[i].boneIndex2 == idx && weights[i].weight2 > 0)
                sum += weights[i].weight2;
            if (weights[i].boneIndex3 == idx && weights[i].weight3 > 0)
                sum += weights[i].weight3;

            colors[i] = Color32.Lerp(regularColor, highlightColor, sum);
        }

        mesh.colors32 = colors;

    }

    void Start() {
        // If not explicitly specified SkinnedMeshRenderer try to find one
        if (smr == null) smr = GetComponent<SkinnedMeshRenderer>();
        // SkinnedMeshRenderer has only shared mesh. We should not modify it.
        // So we make a copy on startup, and work with it.
        smr.sharedMesh = (Mesh)Instantiate(smr.sharedMesh);

        Highlight(bone);
    }

    void Update() {
        if (prevBone != bone) {
            // User selected different bone
            prevBone = bone;
            Highlight(bone);
        }
    }
}

See a sample project: https://www.dropbox.com/s/yfoqo44bubcr48s/HighlightBone.zip?dl=0

Demo: http://jolly-squirrel.droppages.com/bones/index.html - click on body parts to see them highlight.

Share:
10,908
Richard
Author by

Richard

Master degree student at NTUST Taipei, Currently learning about 3D and animation using Kinect Sensor as its input

Updated on June 04, 2022

Comments

  • Richard
    Richard almost 2 years

    I'm really new with unity3D and I would like to ask a question I have a 3D human model ( a default unity model) which has a hierarchical bone structure.

    What I want to achieve here is, when I press certain trigger, I want to color one of its limb with different color (just one of its limb). This is the illustration of what I want to achieve

    enter image description here

    I'm really clueless about this, because I've just started learning Unity for about 3 months ago, so I really need your help, this is the property of my renderer if it's helping

    enter image description here

  • Richard
    Richard over 8 years
    Hi, thank you for your answer, this sounds like easier solution for me, but as I read the manual, it stated that "A Projector allows you to project a Material onto all objects that intersect its frustum", I don't think it can "highlight certain body part precisely. For example if I want to highlight the upper arm while the model standing straight, I'm afraid it will also highlight part of the body isn't it?
  • Richard
    Richard over 8 years
    Hi thank you very much for your respond, yes some ppl told me that I need to use more than one material to do it. But I want to find something more simple to do this. I will learn and try your solutions first and I will let you know if it works, thanks a bunch
  • Yuri Nudelman
    Yuri Nudelman over 8 years
    Of cause it will require some fine tuning, you need to place projectors in a smart way, and there is no guarantee everything will look beautiful out of the box. There are many customization parameters available for projectors. For example, projector has far clip plane argument, and anything behind that plane is not affected. So, as you say, if you highlight an arm, and make sure projector far clip plane does not reach the body, the body will not be highlighted. Having said that, of cause it will not be 100% accurate, if you want to be 100% precise, you will have to use multiple materials.
  • Richard
    Richard over 8 years
    Thank you, but I'm stuck at finding the bone index, how can we determine which bone index correspond to which part of the body?
  • Yuri Nudelman
    Yuri Nudelman over 8 years
    You made me curious, so I wrote a small project to see how it works. Looks to work fine, see answer update.
  • Richard
    Richard over 8 years
    Hi, sorry to bother you again, your coding works very well, but when I try to call the Highlight() function from another class, it send me NullReferenceExecption because SkinnedMeshRenderer is null, can you please help me with this? thank you
  • Yuri Nudelman
    Yuri Nudelman over 8 years
    First, don't apologize, the whole purpose of this site is getting answers to your questions, isn't it? About your problem - you have to make sure to set SkinnedMeshRenderer (public variable smr in the example) value to something. In the example above it is set in Start function, so maybe you are calling it before Start? Or maybe the script is attached to some object that does not directly contain SkinnedMeshRenderer, so it can't be found using GetComponent. What you can do is instead of initializing it automatically, manually drag and drop your SkinnedMeshRenderer to the script.
  • Yuri Nudelman
    Yuri Nudelman over 8 years
    Remember that SkinnedMeshRenderer is usually second level hierarchy object in your model. For instance, if your model is 'SomeModel', than its root object is named 'SomeModel', and it has some children like 'SomeModelMesh' and 'SomeModelSkeleton'. The SkinnedMeshRenderer is attached to 'SomeModelMesh'.
  • Richard
    Richard over 8 years
    I have tried it, drag and drop the SkinnedMeshRenderer to the smr public variable, it didn't work. I think it's because I called it from another script from another Game Object? The model's Game object name is Human, while the other one called Comparator, somehow I make it works by putting public GameObject variable inside the comparator script and drag-drop Human into it, then I make a GetComponent<BoneHighlighter>().Highlight() and it works, but really slow and laggy, any suggestion?
  • Yuri Nudelman
    Yuri Nudelman over 8 years
    You describe something that should not occur, it shouldn't matter from which game object you call the code. The problem is probably somewhere else. It would help if you post some code (maybe it is worthy to create a new question for this). I can only guess that it may be related to the use of sharedMesh (see code in Start function), but it is just a guess, sorry.
  • Richard
    Richard over 8 years
    Hi, I've tried another approach to do this, but the result is still laggy and not smooth, I have posted another question here link, maybe you can help me there, thank you