local refract / single face parallax
This shader aims to recreate the source engine’s $localrefract feature, used e.g. for the wavy light panels seen in portal 2.
It was designed to be used with flat, square/rectangular surfaces.
Parallax snippet borrowed from binbun’s Parallax mapping shader
Projection Depth sets the distance the parallax face is set back.
Mesh Dimensions: enter the dimensions of the mesh here, or create a script to pass these values to the shader. If not set, Projection Depth will scale with the size of the mesh, and non-square meshes will produce warped projections.
Projection Scale (x / y) scale the texture applied to the parallax face. Set these higher if you don’t want the edges of the projected texture to be visible.
Projection Tiling lets you toggle whether the projected texture should tile. Disabled by default.
If you leave tiling disabled, I would recommend your projected textures have a single-colour background covering at least the whole outer edge. This produces the best results, but is especially warranted if you do not use a high scaling factor.
Dimension Inherit lets you toggle whether the projected texture dimensions should be inherited from the mesh’s. Enabled by default. If disabled, and Mesh Dimensions is set correctly, it will be projected as a 1 by 1 unit square. (if no scaling has been applied)
Refract Strength sets the strength of the normal map, for projected textures without a clean background as described above, you may want to lower the strength, because you’ll get very noisy results with steep normals or steep viewing angles.
Make sure your normal maps are imported lossless, set to high quality, or have normal map detection disabled, or the parallax effect may not work as intended.
Please excuse the poor preview recording.
Shader code
shader_type spatial;
render_mode unshaded;
uniform sampler2D normal_texture: hint_normal, filter_linear ;
uniform sampler2D projection_texture: source_color, filter_linear ;
uniform float projection_depth: hint_range(0.0, 1.0, 0.01) = 0.25;
uniform float projection_scale_x: hint_range(1.0, 10.0, 0.1) = 1.0;
uniform float projection_scale_y: hint_range(1.0, 10.0, 0.1) = 1.0;
uniform float dimension_inherit: hint_range(0.0, 1.0, 1.0) = 1.0;
uniform float refract_strength: hint_range(0.0, 1.0, 0.1) = 1.0;
uniform float projection_tiling: hint_range(0.0, 1.0, 1.0) = 0.0;
uniform vec2 mesh_dimensions = vec2(1.0, 1.0);
vec2 parallax(float depth, vec3 n, vec3 t, vec3 v) { // borrowed from https://godotshaders.com/shader/parallax-mapping-2/
vec3 normal = normalize(n);
vec3 tangent = normalize(t);
vec3 bitangent = cross(normal, tangent);
vec3 view = normalize(v);
vec3 view_tangent = vec3(dot(view, tangent), dot(view, bitangent), dot(view, normal));
vec2 offset = (view_tangent.xy / max(view_tangent.z, 0.001)) * (depth/mesh_dimensions);
offset = vec2(-offset.x, offset.y);
return offset;
}
void fragment() {
NORMAL = normalize((INV_VIEW_MATRIX*vec4(NORMAL, 0.0)).xyz); // move surface normal from view space to world space
vec3 nmpd = texture(normal_texture, UV).rgb * 2.0 - 1.0; // define normal map
vec3 normal = normalize(TANGENT * nmpd.x + BINORMAL * nmpd.y + NORMAL * nmpd.z); // blend normal with normal map
NORMAL = mix(NORMAL, normal, refract_strength); // apply normal map strength
NORMAL = normalize((VIEW_MATRIX*vec4(NORMAL, 0.0)).xyz); // move surface normal from world space to view space
vec2 projection_scale = vec2(projection_scale_x, projection_scale_y); // combine projection scale x and y
projection_scale = (projection_scale*dimension_inherit) + (projection_scale/mesh_dimensions)*(1.0-dimension_inherit); // project texture scaled independently
vec2 parallax_uv = (UV-0.5)/projection_scale+0.5; // scale up projected texture
parallax_uv = parallax_uv + parallax(projection_depth, NORMAL, TANGENT, VIEW)/projection_scale; // generate parallax UV
parallax_uv = (clamp(parallax_uv, 0.0, 1.0)*(1.0-projection_tiling)) + (parallax_uv*projection_tiling); // clamp edges to prevent tiling
vec3 proj = texture(projection_texture, parallax_uv).rgb; // define projected texture with parallax UV
ALBEDO = proj;
}
