16bit vertex displacement

Does anyone know how to get a 16bit texture to displace vertices in the vertex shader?
Is it something I can set in the shader from GDScript?

I thought I could maybe store one byte in the red channel and the other in the green and bitshift it back inside the vertex shader, but texelFetch returns a vec4 of floats between 0.0 and 1.0 which makes it a little hard...

Here I'm using the red channel of a PNG (all channels are the exact same result) and you can see the steps in the height.

Comments

  • DschoonmakerDschoonmaker Posts: 274Member

    Godot limits PNG precision to 8 bits. Some other formats allow more precision:

    https://docs.godotengine.org/en/stable/getting_started/workflow/assets/importing_images.html

  • Lethal Raptor GamesLethal Raptor Games Posts: 26Member

    Do they remain accessible as 16 bits in the shader though?

  • CalinouCalinou Posts: 1,140Admin Godot Developer
    edited July 2021

    @Lethal Raptor Games said:
    Do they remain accessible as 16 bits in the shader though?

    No, you need to load a texture in the Radiance HDR or OpenEXR format. GIMP allows saving images in OpenEXR (.exr extension), so you can use that to convert your PNG to an OpenEXR image.

  • MegalomaniakMegalomaniak Posts: 4,430Admin

    @Calinou Hmm, always wondered if there was any particular reason behind why 16 bit png's aren't importable though? Or just something no body has implemented yet?

  • Lethal Raptor GamesLethal Raptor Games Posts: 26Member

    @Calinou said:

    @Lethal Raptor Games said:
    Do they remain accessible as 16 bits in the shader though?

    No, you need to load a texture in the Radiance HDR or OpenEXR format. GIMP allows saving images in OpenEXR (.exr extension), so you can use that to convert your PNG to an OpenEXR image.

    How should an OpenEXR image be used within a shader? I tried to use it the same as a PNG but I saw no change to the mesh. I use it like this;

    uniform sampler2D mymap;

    vec4 data = texelFetch(mymap, ivec2(x, z), 0);
    VERTEX.y = float(data.r) * 1000.0;

  • CalinouCalinou Posts: 1,140Admin Godot Developer
    edited July 2021

    @Megalomaniak said:
    @Calinou Hmm, always wondered if there was any particular reason behind why 16 bit png's aren't importable though? Or just something no body has implemented yet?

    It's a design decision to avoid wasting memory when you don't need higher precision than 8 bpc. Most people are not even aware that PNGs can store more than 8 bpc, yet most image editing software will happily create such PNGs and not warn people about the VRAM/storage size implications.

    By forcing the use of .hdr/.exr for high dynamic range images, there is no such ambiguity.

    How should an OpenEXR image be used within a shader? I tried to use it the same as a PNG but I saw no change to the mesh. I use it like this;

    It should work out of the box. Double-check that the OpenEXR image actually contains higher precision than 8 bpc. You can use the ImageMagick identify command to do so.

  • Lethal Raptor GamesLethal Raptor Games Posts: 26Member
    edited July 2021

    I verified it is 16 bit. However the only way I could get it too work was to load a r16 file and put each byte into an image and assign to a usampler2D slot in GDScript.

    I could kind of get it working by dragging the EXR image onto a samlper2D slot in the material, but the data wasn't quite complete. It looked like it was still only reading from one channel.

    Anyway, this is the code I used to get it working (from the link I posted earlier)

    func _createHeightMapTexture(sourceMap: TerrainMap):
        var image = Image.new()
        var data = PoolByteArray()
        data.resize(1024 * 1024 * 2)
    
        var mapX = 0
        var mapZ = 0
    
        var index = 0
        for z in 1024:
            for x in 1024:
                data[index] = sourceMap.getByte(mapX + x, mapZ + z, 0)
                data[index + 1] = sourceMap.getByte(mapX + x, mapZ + z, 1)
                index += 2
    
        image.create_from_data(1024, 1024, false, Image.FORMAT_RH, data)
        #image.save_exr("res://Terrain//Data//Map.exr")
    
        var texture = ImageTexture.new()
        texture.create_from_image(image, 0)
        material.set_shader_param("heightmap", texture)
    

    And in the vertex shader;

    uniform usampler2D heightmap;
    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);
    }
    uvec4 getExponent(uvec4 data) {
        return godot_hack_mix(uvec4((uvec4((ivec4((data & uvec4(0x7F800000)) >> uvec4(23)) - ivec4(127)) + ivec4(15)) & uvec4(0x1F)) << uvec4(10)),
        uvec4(ivec4(31 << 10)),
        equal((data) & uvec4(0x7F800000), uvec4(0x7F800000)));
    }
    uvec4 getValue(uvec4 data) {
        uvec4 exponent = getExponent(data);
        return godot_hack_mix(((data & uvec4(0x007FFFFF)) >> uvec4(13)) | exponent | (((data >> uvec4(31)) & uvec4(1)) << uvec4(15)),
        (((floatBitsToUint(uintBitsToFloat(data & uvec4(0x7FFFFFFF)) + 1.0/16384.0) & uvec4(0x007FFFFF)) >> uvec4(13))) | (((data >> uvec4(31)) & uvec4(1)) << uvec4(15)),
        lessThanEqual((data) & uvec4(0x7F800000), uvec4(0x38000000)));
    }
    float getMapValue(int x, int z) {
        uvec4 data = texelFetch(heightmap, ivec2(x, z),0);
        data = getValue(data);
        return float(data.r) / heightScale;
    }
    

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file