Contact Refinement Parallax Mapping

This shader is an adaptation created by O3DE of the Contact Refinement Parallax Mapping technique by Andrea Riccardi. You can find O3DE source code here: https://github.com/o3de/o3de/blob/development/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli, and the original article here: https://www.artstation.com/blogs/andreariccardi/3VPo/a-new-approach-for-parallax-mapping-presenting-the-contact-refinement-parallax-mapping-technique. According to the article, this technique is visually superior to any other parallax technique, including the standard parallax occlusion mapping, and has very similar performance.

Note: Unfortunately, this shader does not include self-shadows as I couldn’t find an optimal way to adapt it to Godot’s shader language 🙁

Shader code
//from https://github.com/o3de/o3de/blob/development/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli
shader_type spatial;

group_uniforms albedo;
uniform vec4 albedo : source_color = vec4(1.0);
uniform sampler2D texture_albedo : source_color,filter_linear_mipmap,repeat_enable;
group_uniforms roughness;
uniform float roughness : hint_range(0.0, 1.0, 0.1) = 0.5;
uniform sampler2D texture_roughness : hint_default_white,filter_linear_mipmap,repeat_enable;
group_uniforms metallic;
uniform float metallic : hint_range(0.0, 1.0, 0.01) = 0;
uniform sampler2D texture_metallic : hint_default_white,filter_linear_mipmap,repeat_enable;
uniform float specular : hint_range(0.0, 1.0, 0.01) = 0.5;
group_uniforms normal_map;
uniform sampler2D texture_normal : hint_roughness_normal,filter_linear_mipmap,repeat_enable;
uniform float normal_scale : hint_range(-16,16) = 1;
group_uniforms ambient_occlusion;
uniform sampler2D texture_ao : hint_default_white,filter_linear_mipmap,repeat_enable;
uniform float ao_effect : hint_range(0.0, 1.0, 0.1) = 0.0;
group_uniforms height;
uniform sampler2D texture_heightmap : hint_default_black,filter_linear_mipmap,repeat_enable;
uniform float heightmap_scale : hint_range(0.0, 1.0, 0.01) = 0.5;
uniform int heightmap_layers : hint_range(8, 128, 1) = 32;
uniform float depthOffset : hint_range(0.0, 1.0, 0.1) = 0;
group_uniforms uv;
uniform vec2 uv1_scale = vec2(1.0);
uniform vec2 uv1_offset = vec2(0);

float GetNormalizedDepth(float startDepth, float stopDepth, float inverseDepthRange, vec2 uv) {
    float current_sample = 1.0 - texture(texture_heightmap, uv.xy).r;
    float normalizedDepth = 0.0;
    if(stopDepth - startDepth > 0.0001)
    {
        float minNormalizedDepth = -startDepth * inverseDepthRange;
        normalizedDepth = max(current_sample, minNormalizedDepth);
    }
    return normalizedDepth; 
}

#define PARALLAX_MULTIPLIER 0.65

void vertex() {
	UV=UV*uv1_scale+uv1_offset;
}

void fragment(){
	vec2 base_uv = UV;
	{
		vec3 dirToCameraTS = normalize(normalize(VIEW) * mat3(TANGENT, -BINORMAL, NORMAL));
		float height_scale = heightmap_scale * PARALLAX_MULTIPLIER;

	    float dirToCameraZInverse = 1.0 / dirToCameraTS.z;
	    float steps = 1.0 / float(heightmap_layers);
		float currentStep = 0.0;

	    // the amount to shift per step, shift in the inverse direction of dirToCameraTS
	    vec3 delta = -dirToCameraTS.xyz * height_scale * dirToCameraZInverse * steps;

	    float depthSearchStart = depthOffset;
	    float depthSearchEnd = depthSearchStart + height_scale;
	    float inverseDepthFactor = 1.0 / height_scale;

	    // This is the relative position at which we begin searching for intersection.
	    // It is adjusted according to the depthOffset, raising or lowering the whole surface by depthOffset units.
	    vec3 parallaxOffset = -dirToCameraTS.xyz * dirToCameraZInverse * depthOffset;

	    float currentSample = 1.0 - texture(texture_heightmap, base_uv + parallaxOffset.xy).r;
	    float prevSample;

	    // Do a basic search for the intersect step
	    while(currentSample > currentStep) {
	        currentStep += steps;
	        parallaxOffset += delta;

	        prevSample = currentSample;
	        currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, base_uv + parallaxOffset.xy);
	    }

	    if(currentStep > 0.0)
	    {
	        // Contact refinement propose by Andrea Riccardi 
	        // https://www.artstation.com/andreariccardi/blog/3VPo/a-new-approach-for-parallax-mapping-presenting-the-contact-refinement-parallax-mapping-technique

	        // Based on the rough approximation, rolling back to the previous step along the ray.
	        parallaxOffset -= delta;
	        currentStep -= steps;
	        currentSample = prevSample;

	        // Adjust precision
	        vec3 adjustedDelta = delta * steps;
	        float adjustedStep = steps * steps;

	        // Uses another loop with the same step numbers, this times only covers the distance between previous point and the rough intersection point.
	        while(currentSample > currentStep)
	        {
	            currentStep += adjustedStep;
	            parallaxOffset += adjustedDelta;
	            prevSample = currentSample;

	            currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, base_uv + parallaxOffset.xy);
	        }
	    }
	    if(parallaxOffset.z > 0.0)
	    {
	        parallaxOffset = vec3(0.0);
	    }
		
		base_uv += parallaxOffset.xy;
	}
	ALBEDO = albedo.rgb * texture(texture_albedo, base_uv).rgb;
	METALLIC = metallic * texture(texture_metallic, base_uv).r;
	SPECULAR = specular;
	ROUGHNESS = roughness * texture(texture_roughness, base_uv).r;
	AO = texture(texture_ao, base_uv).r;
	AO_LIGHT_AFFECT = ao_effect;
	NORMAL_MAP = texture(texture_normal, base_uv).rgb;
	NORMAL_MAP_DEPTH = normal_scale;
}
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from LiveTrower

Parallax Occlusion Mapping with self-shadowing

Related shaders

Parallax Occlusion Mapping with self-shadowing

Iterative parallax mapping

Parallax Mapping

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments