Search Unity

Get Material At Raycast

Discussion in 'Scripting' started by Mashimaro7, Sep 2, 2021.

  1. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    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...

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. public class ClickManager : MonoBehaviour
    6. {
    7.     [SerializeField] Material mat;
    8.     Camera cam;
    9.     int triangleIdx;
    10.     Mesh mesh;
    11.     int subMeshesNr;
    12.     int matI = -1;
    13.     Renderer rend;
    14.     MeshCollider meshCollider;
    15.     Ray ray;
    16.     void Start()
    17.     {
    18.         cam = Camera.main;
    19.     }
    20.     void Update()
    21.     {
    22.         if (Input.GetMouseButtonDown(0))
    23.         {
    24.             RaycastHit hit;
    25.             if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))
    26.             {
    27.                 MeshCollider meshCollider = hit.collider as MeshCollider;
    28.                 if (meshCollider != null || meshCollider.sharedMesh != null)
    29.                 {
    30.                     mesh = meshCollider.sharedMesh;
    31.                     Renderer renderer = hit.collider.GetComponent<MeshRenderer>();
    32.      
    33.                     int[] hitTriangle = new int[]
    34.                     {
    35.                         mesh.triangles[hit.triangleIndex * 3],
    36.                         mesh.triangles[hit.triangleIndex * 3 + 1],
    37.                         mesh.triangles[hit.triangleIndex * 3 + 2]
    38.                     };
    39.                     for (int i = 0; i < mesh.subMeshCount; i++)
    40.                     {
    41.                         int[] subMeshTris = mesh.GetTriangles(i);
    42.                         for (int j = 0; j < subMeshTris.Length; j += 3)
    43.                         {
    44.                             if (subMeshTris[j] == hitTriangle[0] &&
    45.                                 subMeshTris[j + 1] == hitTriangle[1] &&
    46.                                 subMeshTris[j + 2] == hitTriangle[2])
    47.                             {
    48.                                 mat = renderer.materials[i];
    49.                             }
    50.                         }
    51.                     }
    52.                 }
    53.             }
    54.         }
    55.     }
    56. }
    57.  
    58.  
    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.
     
    Last edited: Sep 2, 2021
    Ndogg27 and UnrealSoftware like this.
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,744
    I think you would start here:

    https://docs.unity3d.com/ScriptReference/RaycastHit-triangleIndex.html

    (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:

    https://docs.unity3d.com/ScriptReference/Mesh.GetTriangles.html

    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.
     
    Mashimaro7 likes this.
  3. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    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. :p

    I updated my script...

    Thanks for the helpful reply anyway :)
     
    Kurt-Dekker likes this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,744
    Ah yeah, I imagine that proxy-remakes the entire collider with all-new triangles to be convex, losing the connection to the underlying meshes.
     
    Mashimaro7 likes this.
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    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