godot icon indicating copy to clipboard operation
godot copied to clipboard

OpenGL: Alpha Hash transparency (and `ALPHA_HASH_SCALE` in a custom shader) is not implemented yet

Open sinni800 opened this issue 10 months ago • 2 comments

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

  1. Create Project
  2. Make a quad, give it a Standard Material, set Transparency to Alpha Hash, then set Albedo.a to 0.5 or something.
  3. 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:

Image

sinni800 avatar Feb 20 '25 21:02 sinni800

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/.)

Calinou avatar Feb 20 '25 21:02 Calinou

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.

sinni800 avatar Feb 20 '25 21:02 sinni800

I think it should still be mentioned in the documentation that it's not supported yet, since it's not mentioned.

Image

ArtyIF avatar Jun 22 '25 12:06 ArtyIF