Refractions

Refractions are one of the main advantages of ray tracing. They are extremely difficult to simulate using non ray tracing techniques, so most games do not use them yet.

To simulate transparency and opacity, an alpha texture is usually used, to indicate that the light can pass thought the object. At first glance, transparency and refractions might look similar, but in a transparent material light goes through in a straight line, so light rays enter and exit the material in the same direction. Refractions bend the light inside the object and change the direction of the ray, allowing artists to create impressive effects that are not feasible without ray tracing. Transparency is a special case of refractions with a refraction index of 1.0, however there are more efficient and simple ways to simulate ray tracing transparency and opacity than using refractions.

Image Alt Text:Example 1: No refractions or transparency Example 1: No refractions or transparency
Image Alt Text:Example: Ray tracing refractions Example: Ray tracing refractions
Image Alt Text:Example: Transparency Example: Transparency

Refractions are simulated using Snell’s law. Similarly to reflections, GLSL has a built-in function refract that allows you to compute the direction of the refracted ray using the direction of the incident ray, the surface normal, and the ratio between materials’ refraction indices.

Image Alt Text:Diagram of refractions Diagram of refractions

The refractions algorithm is like the reflection algorithm. You start by retrieving some information from the G-buffer like normal, position or refractive index. Then you use this information to generate a ray, that you trace using either ray tracing pipeline or ray query.

Image Alt Text:Diagram of our refraction algorithm Diagram of our refraction algorithm

Just like reflections, you use bindless to get the material information necessary to illuminate the hit object. Refractions require a careful handling of back faces and the blending of multiple layers to obtain a correct result. If you hit a back face triangle you are exiting the refractive material, so you will not illuminate the hit, but you will still need to refract the ray. Finally, you will need to do an inverse blending of all refracted hit results.

    

        
        
            vec4 main()
{
    // Retrieve some data from the G-buffer
    // See reflections sample for more details
    vec2 g_buffer_uv = vec2(gl_FragCoord.xy) / current_resolution;

    ivec2 texture_size = textureSize(depth_sampler, 0);
    ivec2 depth_coord_texel = ivec2(g_buffer_uv * texture_size);
    float depth = get_g_buffer_depth(depth_coord_texel);
    if (depth <= 0)
    {
        o_color = vec3(0);
        return;
    }

    const vec3 pos = get_g_buffer_world_pos(g_buffer_uv);
    const vec4 material_properties = get_g_buffer_material_properties(g_buffer_uv);
    const vec3 normal = get_g_buffer_normal(g_buffer_uv);
    const bool is_refractive = get_material_is_refractive(material_properties);
    o_color = get_g_buffer_base_color(g_buffer_uv).rgb;

    const int MAX_BOUNCES = 4;        // Multiple bounces for refractions

    int remaining_ray_bounces = MAX_BOUNCES;

    bool is_valid_ray = remaining_ray_bounces > 0 && is_reflective;

    vec3 current_pos = pos;
    vec3 prev_pos = camera_pos;
    vec3 current_normal = normal;

    float inv_alpha = 1.0;

    // Launch a refraction ray
    while (is_valid_ray)
    {
        const vec3 incident_dir = normalize(current_pos - prev_pos);
        vec4 hit_material_properties = vec4(0);
        vec3 hit_pos = vec3(0);
        vec3 hit_normal = vec3(0);

        const vec3 ray_dir = refract(incident_dir, normal);
        // Evaluate ray using ray query.
        // See reflections form more details
        rayQueryEXT rayQuery;
        valid_hit = trace_ray(ray_orig, ray_dir, rayQuery);
        if (is_valid_ray)
        {
            vec2 hit_uv;
            uint material_id;
            obtain_rq_hit_data(rayQuery, hit_material_properties, hit_pos, hit_normal, hit_uv, material_id);

            const bool is_front_face = rayQueryGetIntersectionFrontFaceEXT(rayQuery, true);
            if (is_front_face)
            {
                // For back faces you bend the ray
                // For front faces you also compute the hit color
                // See reflection example for an example of how to illuminate a ray query hit
                vec4 refraction_color = vec4(0);
                illuminate_hit_data(hit_material_properties, hit_pos, hit_normal, hit_uv, material_id, illuminated_hit_color);

                float alpha = illuminated_hit_color.a;
                o_color += inv_alpha * alpha * refraction_color.rgb;
                inv_alpha *= 1.0 - alpha;
            }

            prev_pos = current_pos;
            current_pos = hit_pos;
            current_normal = hit_normal;
            is_refractive = get_material_is_refractive(hit_material_properties);
            remaining_ray_bounces -= 1;
            is_valid_ray = is_valid_ray && remaining_ray_bounces > 0 && is_refractive;
        }
    }
}
        
    

To obtain a good refractive result, you will need to launch a ray with multiple bounces; to obtain a useful result you will need to use a minimum of two bounces. Increasing the number of bounces produces better visual quality, however refractions are a costly effect, and launching rays with multiple bounces can have a significant performance cost, so one should considered the performance impact.

Image Alt Text:Example 1: No refractions Example 1: No refractions
Image Alt Text:Example: Ray tracing refractions with 1 bounce Example: Ray tracing refractions with 1 bounce
Image Alt Text:Example: Ray tracing refractions with 2 bounces Example: Ray tracing refractions with 2 bounces
Image Alt Text:Example: Ray tracing refractions with 4 bounces Example: Ray tracing refractions with 4 bounces
Image Alt Text:Example: Ray tracing refractions with 10 bounces Example: Ray tracing refractions with 10 bounces
Back
Next