Iterative parallax mapping

More lightweight than parallax occlussion mapping, better looking than basic parallax.

Shader code
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;

uniform vec4 albedo : source_color;
uniform sampler2D texture_albedo : source_color,filter_linear_mipmap,repeat_enable;
uniform float point_size : hint_range(0,128);
uniform sampler2D texture_orm : hint_roughness_g,filter_linear_mipmap,repeat_enable;
uniform float ao_light_affect : hint_range(0.0, 1.0, 0.01);
uniform sampler2D texture_normal : hint_roughness_normal,filter_linear_mipmap,repeat_enable;
uniform float normal_scale : hint_range(-16,16);
uniform float heightmap_scale;
uniform int height_iters_max : hint_range(1, 30, 1) = 10;
uniform int height_iters_min : hint_range(1, 30, 1) = 4;
uniform vec2 heightmap_flip;
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
uniform vec3 uv2_offset;

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

void fragment() {
	vec2 base_uv = UV; //Parallax code starts here
	{
		vec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*heightmap_flip.x,-BINORMAL*heightmap_flip.y,NORMAL));
		
		float num_layers = mix(float(height_iters_max), float(height_iters_min), abs(dot(vec3(0.0, 0.0, 1.0), view_dir))/2. ); //Distance lod
		num_layers = round(num_layers/3.)*3.; //Layers stepped every 3
		
		for (float i = 0.; i < num_layers; i++){
			float h = (texture(texture_orm, base_uv).a - 0.5); //Uses alpha channel of ORM texture
			h *= heightmap_scale/num_layers;
			h -= (1./(i+1.))*(1./num_layers);
			h = clamp(h, -0.5, 0.5);
			h -= 0.5/num_layers;
			
			vec2 view = view_dir.xy;
			view *= 0.01;
			view *= h;
			
			base_uv += view;
		}
	}
	
	//Material stuff
	vec4 albedo_tex = texture(texture_albedo,base_uv);
	ALBEDO = albedo.rgb * albedo_tex.rgb;
	vec4 orm_tex = texture(texture_orm,base_uv);
	ROUGHNESS = orm_tex.g;
	METALLIC = orm_tex.b;
	NORMAL_MAP = texture(texture_normal,base_uv).rgb;
	NORMAL_MAP_DEPTH = normal_scale;
	AO = orm_tex.r;
	AO_LIGHT_AFFECT = ao_light_affect;
}
Tags
material, parallax
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.

Related shaders

Contact Refinement Parallax Mapping

Parallax Occlusion Mapping with self-shadowing

Cheap parallax planes array

Subscribe
Notify of
guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Unhinged Dev
1 year ago

amazing stuff man!

moonlightart
moonlightart
1 year ago

Hi!

This looks awesome, but for some reason the parallax effect is not working for me.
I have tried different “Heightmap scale” values and used this texture:
https://3dtextures.me/2021/04/22/rocks-hexagons-002/

Do you have a sample project that shows this shader in action?
It is precisely what I need and I can’t figure out why it is not working…

Any answer would be appreciated! 🙂

belzecue
belzecue
3 months ago
Reply to  moonlightart

— there’s a pretty bad bug in the shader code (for Godot 3, but I expect it’s the same for Godot 4).

The original code above sets heightmap_flip to vec2(0.,0.) which zeroes the tangent and binormal.

That’s why there’s no parallax effect. You need to set it to 1.

uniform vec2 heightmap_flip = vec2(1.);

Then you’ll get the parallax effect.

To flip the heightmap in x or y, set that component to -1.0, never to zero!

, this is a very good lightweight, non-occlusion parallax shader. Nice job.

Last edited 3 months ago by belzecue