You can never rely fully on the shading normal for offsetting the origin as Detlef mentions. Using the geometric normal instead/as-well is for sure the most common solution which can fix most problem scenarios. This is my personal preference of solution as during a triangle intersection test you obtain the plane normal already so this offset is already calculated for us.
Remember though the offset is scene dependent as Detlef mentioned. Also it is related to float accuracy which is not linear/constant over distance. I’m sure there is a better way (?) but I have often resorted to a variable epsilon value which increase with distance to intersection. This accommodate for the floating point accuracy decreasing at larger values:
hitOn = incoming_ray.origin + incoming_ray.direction * t_hit;
hitOffset = geometric_normal * (sceneEpsilon* t_hit + sceneEpsilon);
hitPoint = hitOn + hitOffset ;
I have always wondered if there was a better way to determine what ‘accuracy’ a float value has for a given value/distance. So for example for an intersection at t_hit = 1,000 the accuracy could be 0.01 and at 100,000 the accuracy might be 0.5 but I am not sure where you can easily find any actual numbers from.
UPDATE: It turns out this is much simpler than I had envisioned. Initially I had attempted to use the log of the distance etc to resolve the issue for the epsilon but the solution I have found that works whent eh view is thousands of units away or up close is below:
hitOffset = geometric_normal * ((FLT_EPSILON*2.0f) * t_hit + sceneEpsilon);
I had tried this ebfore but used FLT_EPSILON*t_hit. As there are two values in which numeric precision can be lost (the previous calculated t_hit for intersectiona and the rays new-thit) it appears that doubling this results in pretty good results. This adjustment allows you to use as small a sceneEpsilon as possible without getting noisy shadows at distance.
This could mean you might not get self shadowing on the geometry if used naively. If done at the primitive level you may get artifacts at polygon edge cases where the neighboring polygons is intersected. However if you use the knowledge of hitting the same geometry to introduce a secondary (i.e. larger) tmin in the intersection your intersection program these issues may be able to be avoided somehow.