Weird artifacts in my ray tracer

I am getting weird artifacts in my simple ray tracer that I made using the book Ray Tracing in One Weekend by Peter Shirley.

Example:

(circular pattern on the ground and black tint on the spheres.)

I suspect these artifacts are due to hit point of the primary rays being under or above the surface, which causes secondary rays to intersect with the object itself.

I tried to fix this by moving the hit point of primary rays along the surface normal at that point. Here's the result :

The artifacts are gone but that broke the dielectric material (sphere on the left). It is acting as a reflective sphere rather than a transparent one.

Code for the dielectric material :

virtual bool scatter(const Ray &inRay, const HitInfo &hitInfo, Vector3 &attenuation, Ray &scatteredRay) const {
attenuation = Vector3(1.0f);
Vector3 normal;
Vector3 refracted;
float refractiveIndexRatio; //n1/n2 = sin(a2)/sin(a)
float cosine = 0.0f;

if (hitInfo.hitNormal * inRay.direction() > 0) {
normal = -hitInfo.hitNormal;
refractiveIndexRatio = refractiveIndex;
cosine = hitInfo.hitNormal * inRay.direction();
}
else {
normal = hitInfo.hitNormal;
refractiveIndexRatio = 1.0f/refractiveIndex;
cosine = -hitInfo.hitNormal * inRay.direction();
}

float reflectProb = 0.0f;

if (Vector3::refract(inRay.direction(), normal, refractiveIndexRatio, refracted)) {
reflectProb = schlickApproximation(cosine, refractiveIndex);
}
else {
reflectProb = 1.0f;
}

if (randomfloatGenerator() < reflectProb) {
Vector3 reflected(Vector3::reflect(inRay.direction(), normal));
scatteredRay = Ray(hitInfo.hitPoint + normal * CORRECTION_FACTOR, reflected);
}
else {
scatteredRay = Ray(hitInfo.hitPoint + normal * CORRECTION_FACTOR, refracted);
}

return true;
}


I changed hitInfo.hitPoint to hitInfo.hitPoint + normal * CORRECTION_FACTOR, in the if - else case at the bottom of the function, to correct those artifacts. (CORRECTION_FACTOR = 0.0001f)

How can I correct this "self intersection" problem without breaking my materials ?

When spawning the refraction/transmission ray, don't adjust the ray origin by adding the normal: adjust it by subtracting the normal. This moves the ray origin inside the object, so it won't immediately self-intersect. Your current problem is caused by all your refraction rays immediately hitting the surface.

Don't forget that for accurate refractions, the ray needs to be able to intersect with the inside or back face of the object as well. If you're intersecting back-facing surfaces, you need to flip the normal before shading or adding/subtracting it to the origin.