How do I obtain the surface normal for a point on a collider (can't use RaycastHit.normal)?

Is there an alternate way of obtaining the true surface normal given a point on a collider surface other than RaycastHit.normal?

It seems as if RaycastHit.normal is bugged and doesn't always return a surface normal, but something else.

I don't think the fault is in the RaycastHit.normal but rather the call that populates it (SphereCast). However, if you get the triangle you hit, you can use the hit.barycentricCoordinate to calculate the surface normal. This will only work on mesh colliders as only mesh colliders can provide mesh data.

private Vector3 GetMeshColliderNormal(RaycastHit hit)
{
    MeshCollider collider = (MeshCollider)hit.collider;
    Mesh mesh = collider.sharedMesh;
    Vector3[] normals = mesh.normals;
    int[] triangles = mesh.triangles;

    Vector3 n0 = normals[triangles[hit.triangleIndex * 3 + 0]];
    Vector3 n1 = normals[triangles[hit.triangleIndex * 3 + 1]];
    Vector3 n2 = normals[triangles[hit.triangleIndex * 3 + 2]];

    Vector3 baryCenter = hit.barycentricCoordinate; 
    Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
    interpolatedNormal.Normalize();
    interpolatedNormal = hit.transform.TransformDirection(interpolatedNormal);
    return interpolatedNormal;
}

I tested this code right after your previous question with the sphere cast. It seems to work well with the plane but the position of the sphere cast hit is still arbitrary with regard to large radii.

i use the

void OnCollisionEnter(Collision collisionInfo)
{
}

the normal vector3 can be obtained really easy just need this

collisionInfo.contacts[0].normal;

i used this code on a bouncing ball on irregular planes, really useful this simplify the need of the GetMeshColliderNormal, it have all that code inside

hope this helps as it helped me =)

It’s an old thread, but I stumbled upon it and it did not provide the answer I was looking for.

Raycasting will get you the expected normals, but if you’re using SphereCast or CapsuleCast, the normals that are given by these functions are not the normals you expect to receive.
The solution to this is simply to then do a Raycast to this collider to acquire the surface normal (as opposed to the collision normal).
This works for EVERY type of collider except the Non-convex mesh collider.
In that scenario, you could use the solution provided by user @Statement, however I do not recommend it.
Your game needs and should only have convex mesh colliders, that’s just the unfortunate reality.
There are some assets in the store that making non-convex mesh colliders into multiple convex mesh colliders.

    public static RaycastHit CreateRaycastHitFromCollider(Vector3 _rayOrigin, Collider _collider)
    {
        var colliderTr = _collider.transform;


        // Returns a point on the given collider that is closest to the specified location.
        // Note that in case the specified location is inside the collider, or exactly on the boundary of it, the input location is returned instead.
        // The collider can only be BoxCollider, SphereCollider, CapsuleCollider or a convex MeshCollider.
        var closestPoint = Physics.ClosestPoint(_rayOrigin, _collider, colliderTr.position, colliderTr.rotation);
  
        if (_collider is MeshCollider {convex: false} meshCollider)
        {
            Debug.LogWarning($"do not use convex mesh-colliders as it does not deal well with physics at all. " +
                             $"There are solutions provided in the asset store to automatically transform non-convex meshes to convex meshes. The problematic mesh: {_collider.transform.GetFullPathName()} meshName:{meshCollider.sharedMesh.name}");
            // This is not great. If we have complex meshColliders we will encounter issues.
            closestPoint = _collider.ClosestPointOnBounds(_rayOrigin);
        }
        
        var dir = (closestPoint - _rayOrigin).normalized;
        var ray = new Ray(_rayOrigin, dir);
        var hasHit = _collider.Raycast(ray, out var hitInfo, float.MaxValue);
        
        if (hasHit == false)
        {
            Debug.LogError($"This case will never happen!");
        }

        return hitInfo;
    }