Skip to content

Instantly share code, notes, and snippets.

@lyuma
Last active September 3, 2022 09:43
Show Gist options
  • Save lyuma/7e605e758f942f61c14551f19479b75d to your computer and use it in GitHub Desktop.
Save lyuma/7e605e758f942f61c14551f19479b75d to your computer and use it in GitHub Desktop.
Godot Shader to extract the raw 16-bit integer in a FORMAT_RGBAH texture.
shader_type spatial;
render_mode unshaded;
uniform usampler2D data_tex;
/*
Use with the following GDScript code:
var data_img = Image.new()
var data := PoolByteArray()
var indexin = 0x8400
data.append(indexin&0xff)
data.append((indexin>>8)&0xff)
data_img.create_from_data(1, 1, false, Image.FORMAT_RH, data)
var data_tex = ImageTexture.new()
data_tex.create_from_image(data_img, 0)
self.get_surface_material(0).set_shader_param("data_tex", data_tex)
*/
// Extract the raw 16-bit integer assigned in a GL_RGBA16F or similar texture format
// (in Godot, Image.FORMAT_RGBAH or Image.FORMAT_RH)
// vectorized ?:
// In GLSL, you are supposed to do this using a mix(uvec4, uvec4, bvec4)
// However, in Godot, they forgot this overload existed, so we reimplement it.
uvec4 godot_hack_mix(uvec4 a, uvec4 b, bvec4 c) {
return uvec4(c.x ? b.x : a.x, c.y ? b.y : a.y, c.z ? b.z : a.z, c.w ? b.w : a.w);
}
void fragment() {
// Fetch half precision texture using usampler2D.
// For some reason, it first converts to 32-bit float (but losslessly????)
// and so we have to convert the 32-bit float back to 16-bits.
uvec4 data = texelFetch(data_tex, ivec2(0,0), 0);
// Calculate bit shifted exponent
uvec4 exps = godot_hack_mix(
// Adjust floating point from 8-bit in float32 (127 bias) to 5-bit in half (15 bias)
uvec4((uvec4((ivec4((data & uvec4(0x7F800000)) >> uvec4(23)) - ivec4(127)) + ivec4(15)) & uvec4(0x1F)) << uvec4(10)),
// Inf and NaN will remain Inf and Nan (maximum half exponent = 31)
uvec4(ivec4(31 << 10)),
// Check for maximum float32 exponent = 255
equal((data) & uvec4(0x7F800000), uvec4(0x7F800000)));
// calculate final data.
data = godot_hack_mix(
// mantissa | exponent | signbit
((data & uvec4(0x007FFFFF)) >> uvec4(13)) | exps | (((data >> uvec4(31)) & uvec4(1)) << uvec4(15)),
// Denormalized case: exponent is all over the place, so instead we add the smallest non-denormalized half, then extract the mantessa bits.
// Finally, or the sign bit (and exponent = 0)
(((floatBitsToUint(uintBitsToFloat(data & uvec4(0x7FFFFFFF)) + 1.0/16384.0) & uvec4(0x007FFFFF)) >> uvec4(13))) | (((data >> uvec4(31)) & uvec4(1)) << uvec4(15)),
// Denormalized case can be detected with half exponent <= -15.
lessThanEqual((data) & uvec4(0x7F800000), uvec4(0x38000000)));
// Parse out individual bits if needed
ivec2 datar = ivec2((uvec2(data.r) >> uvec2(0,8)) & uvec2(0xFF));
ivec2 datag = ivec2((uvec2(data.g) >> uvec2(0,8)) & uvec2(0xFF));
ivec2 datab = ivec2((uvec2(data.b) >> uvec2(0,8)) & uvec2(0xFF));
ivec2 dataa = ivec2((uvec2(data.a) >> uvec2(0,8)) & uvec2(0xFF));
// Example code: check for precise 16-bit value that matches GDScript.
ALBEDO = vec3(0,0,0);
if (data.r == uint(0xFFFF)) {
ALBEDO = vec3(1,1,1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment