OpenGL: Alpha Hash transparency (and `ALPHA_HASH_SCALE` in a custom shader) is not implemented yet
Tested versions
Godot v4.3.stable - Windows 10.0.26100 - GLES3 (Compatibility) - AMD Radeon RX 6950 XT (Advanced Micro Devices, Inc.; 32.0.12033.1030) - AMD Ryzen 9 5950X 16-Core Processor (32 Threads) Godot v4.4.beta4 - Windows 11 (build 26100) - Single-window, 2 monitors - Vulkan (Forward+) - dedicated AMD Radeon RX 6950 XT (Advanced Micro Devices, Inc.; 32.0.12033.1030) - AMD Ryzen 9 5950X 16-Core Processor (32 threads)
System information
GPU Radeon 6950xt, but also happened on an Intel iGPU; GLES2 affected, GLES3/Vulkan not
Issue description
When using Alpha Hash transparency to get a certain vibe and to use cutout transparency instead of having to work with real Alpha (and the associated sorting issues), it doesn't work on GL_COMPAT. Instead, the mesh just gets darker (in editor) and it really doesn't do anything in game.
I thought maybe it's not supportable for some reason or another, so I searched the documentation and came up empty.
My next step was making a shader that would support this feature anyway, and by copypasting from the engine source code, I was able to set up hash transparency.
shader_type spatial;
uniform vec4 albedo : source_color;
float hash_2d(vec2 p) {
return fract(1.0e4 * sin(17.0 * p.x + 0.1 * p.y) *
(0.1 + abs(sin(13.0 * p.y + p.x))));
}
float hash_3d(vec3 p) {
return hash_2d(vec2(hash_2d(p.xy), p.z));
}
float compute_alpha_hash_threshold(vec3 pos, float hash_scale) {
vec3 dx = dFdx(pos);
vec3 dy = dFdy(pos);
float delta_max_sqr = max(length(dx), length(dy));
float pix_scale = 1.0 / (hash_scale * delta_max_sqr);
vec2 pix_scales =
vec2(exp2(floor(log2(pix_scale))), exp2(ceil(log2(pix_scale))));
vec2 a_thresh = vec2(hash_3d(floor(pix_scales.x * pos.xyz)),
hash_3d(floor(pix_scales.y * pos.xyz)));
float lerp_factor = fract(log2(pix_scale));
float a_interp = (1.0 - lerp_factor) * a_thresh.x + lerp_factor * a_thresh.y;
float min_lerp = min(lerp_factor, 1.0 - lerp_factor);
vec3 cases = vec3(a_interp * a_interp / (2.0 * min_lerp * (1.0 - min_lerp)),
(a_interp - 0.5 * min_lerp) / (1.0 - min_lerp),
1.0 - ((1.0 - a_interp) * (1.0 - a_interp) / (2.0 * min_lerp * (1.0 - min_lerp))));
float alpha_hash_threshold =
(a_interp < (1.0 - min_lerp)) ? ((a_interp < min_lerp) ? cases.x : cases.y) : cases.z;
return clamp(alpha_hash_threshold, 0.00001, 1.0);
}
void fragment() {
vec2 base_uv = UV;
//ALBEDO = albedo.rgb;
ALPHA = COLOR.a;
//ALPHA_HASH_SCALE = 5.0;
vec3 object_pos = (inverse(MODEL_MATRIX) * INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
ALPHA = compute_alpha_hash_threshold(object_pos, 1) < ALPHA ? 1.0 : 0.0;
}
Hash transparency with this looks identical to on Forward+ and Mobile
Steps to reproduce
- Create Project
- Make a quad, give it a Standard Material, set Transparency to Alpha Hash, then set Albedo.a to 0.5 or something.
- Observe on Forward+, Mobile, and Compatibility.
Minimal reproduction project (MRP)
MRP-example-alpha-hash-compatibility.zip
This example has two cubes, one uses the shader that I added above for demonstration that alpha hash transparency works on Compatibility, and one uses the built-in ALPHA_HASH_SCALE method through the standard shader
EDIT:
The following screenshot shows my results on GL_Compatibility. The left cube shows the shader pasted above (that makes it work anyway), the right one uses ALPHA_HASH_SCALE through the Standard Shader:
The alpha hash code isn't present in the Compatibility core shaders at all:
5 results - 3 files
servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl:
11
12: float compute_alpha_hash_threshold(vec3 pos, float hash_scale) {
13 vec3 dx = dFdx(pos);
servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl:
1286 #ifdef MODE_RENDER_MATERIAL
1287: if (alpha < compute_alpha_hash_threshold(object_pos, alpha_hash_scale)) {
1288 alpha = 0.0;
1292 #else
1293: if (alpha < compute_alpha_hash_threshold(object_pos, alpha_hash_scale)) {
1294 discard;
servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl:
1016 #ifdef MODE_RENDER_MATERIAL
1017: if (alpha < compute_alpha_hash_threshold(object_pos, alpha_hash_scale)) {
1018 alpha = 0.0;
1022 #else
1023: if (alpha < compute_alpha_hash_threshold(object_pos, alpha_hash_scale)) {
1024 discard;
(There are no results in drivers/gles3/.)
Ah, I thought that meant it was a bug, but if there was no intention to have it in the first place by now (and it's not just bugged), my opinion is that it would be a good idea to at least have cutout (hash) transparency in the engine in GL_COMPAT, as it's a cheap way to get transparency at all.
Nintendo uses it a lot on the Nintendo switch, which is very much a mobile platform (the current one being weaker than a lot of phones), and as a side note it's a funny development that even modern engines (Unreal) use it a lot now (and then wash over it with TAA). I know TAA isn't supported, but this pixely transparency is kind of a vibe.
It's good that it can be done anyway (through the shader I cobbled from the base code) even as long as this feature doesn't get implemented.
I think it should still be mentioned in the documentation that it's not supported yet, since it's not mentioned.