Advertisement

How to change the color of only one part of a 3D model in Unity

Started by June 30, 2019 04:10 PM
1 comment, last by bandages 5 years, 7 months ago

I want to achieve the following effect in a Unity game: I have a 3D model and, under certain conditions, I want to change the color of one of its limbs only, let's say right leg (see screenshot below, with an example of how I want it to look). This is intended to highlight the limb in question. 

I've been doing a lot of research and using custom shaders comes up often, but in all of the examples I've seen they only show how to draw an outline around the 3D model, which is not what I want. The only other suggestions I've found are to either use projectors (for drawing effects on mesh surfaces in real time) or to change the vertex colors programatically during run-time. Both of those suggestions are from the following question: https://stackoverflow.com/questions/34460587/unity-changing-only-certain-part-of-3d-models-color. The latter suggestion is the closest to what I want to achieve, but it doesn't seem to be working for me. Here is the C# code for accomplishing the change in vertex colors:


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);
        }
    }
}

What is the best way to achieve the effect I want in Unity?

Change_color_question.png

You need to mark off the area you want to change color somehow.  You can do that by separating to a new material (not recommended) or by using a texture mask.

Let's say texture mask.  If you do it with a texture mask, you do it in the shader.

You mix between output depending on state && mask value:


	#define STATECOLOR float3(1,0.4,0)
	if (state) (
	     float maskVal = tex2D(maskSamp, UV).r;
	     color.rgb = lerp(color.rgb, STATECOLOR, maskVal);
	}
	

 

And, of course, you make a texture for the model defining the area you want to change color.  In the example above, red would indicate it should change color, while black would indicate it shouldn't.  Green and blue are unused.

This topic is closed to new replies.

Advertisement