Get Material At Raycast

How do you get a material from a raycast point? I looked it up and everything I found was ancient and didn’t seem to work at all.

I tried using hit.triangle index, and while it is hitting the object(i did a print of the hit.collider.gameObject) the triangle index is always returning -1.

I’m trying to select a specific material on a mesh, and alter it’s colour or change it’s texture.

Also, my model is set to read/write enabled.

I don’t really know what other information I can provide, basically I just want to get the specific material of a mesh that the raycast hits. So a single mesh might have 5 different materials, I want to get the one that was clicked on. and assign it to my “mat”. Here’s my script, but it’s kinda bare-bones, I was just trying out some things I found, none of which worked…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClickManager : MonoBehaviour
{
    [SerializeField] Material mat;
    Camera cam;
    int triangleIdx;
    Mesh mesh;
    int subMeshesNr;
    int matI = -1;
    Renderer rend;
    MeshCollider meshCollider;
    Ray ray;
    void Start()
    {
        cam = Camera.main;
    }
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            RaycastHit hit;
            if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))
            {
                MeshCollider meshCollider = hit.collider as MeshCollider;
                if (meshCollider != null || meshCollider.sharedMesh != null)
                {
                    mesh = meshCollider.sharedMesh;
                    Renderer renderer = hit.collider.GetComponent<MeshRenderer>();
      
                    int[] hitTriangle = new int[]
                    {
                        mesh.triangles[hit.triangleIndex * 3],
                        mesh.triangles[hit.triangleIndex * 3 + 1],
                        mesh.triangles[hit.triangleIndex * 3 + 2]
                    };
                    for (int i = 0; i < mesh.subMeshCount; i++)
                    {
                        int[] subMeshTris = mesh.GetTriangles(i);
                        for (int j = 0; j < subMeshTris.Length; j += 3)
                        {
                            if (subMeshTris[j] == hitTriangle[0] &&
                                subMeshTris[j + 1] == hitTriangle[1] &&
                                subMeshTris[j + 2] == hitTriangle[2])
                            {
                                mat = renderer.materials[i];
                            }
                        }
                    }
                }
            }
        }
    }
}

Thanks in advance.

Edit: I’m dumb, I feel like I already did this before, but it’s working now. I had to add a nonconvex meshcollider to my model.(lol, i’m loopy, i just put a semicolon and pressed ctrl+s while typing this). I updated my code to the correct script. This works.

3 Likes

I think you would start here:

(note the stated limitation on collider type!)

Once you have the triangle index, you probably have to look it up in the different triangle lists for each submesh:

I imagine you could do the lookup at object load time and produce a Dictionary<int,int> from the GetTriangles info and use that to instantly find the submesh number.

Finally from the submesh number look at the MeshRenderer and grab the Material[ ] materials array and off you go.

1 Like

Thanks, I found out what my issue was, updated the post… I had a meshcollider, but i was setting it to convex which apparently doesn’t work. :stuck_out_tongue:

I updated my script…

Thanks for the helpful reply anyway :slight_smile:

1 Like

Ah yeah, I imagine that proxy-remakes the entire collider with all-new triangles to be convex, losing the connection to the underlying meshes.

1 Like

Please consider that the only way for a mesh to have 5 materials, is by having 5 submeshes.
You don’t need to test for every triangle when you know that submeshes are stored in a contiguous uninterrupted way.

All you need is to know where each submesh starts and ends, and check whether triangleIndex falls inside that range. Potentially this will improve your hit performance from checking hundreds of triangles into at most 5 simple ‘less than’ ‘greater than’ expressions.

Mesh.GetSubMesh | indexStart | indexCount

7 Likes