`TilemapChunk` does not display tile colors correctly
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
## Bevy version and features
main, commit 196606c
## Relevant system information
Rendering related - adapter info:
`AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, device_pci_bus_id: "", driver: "", driver_info: "", backend: Metal, subgroup_min_size: 4, subgroup_max_size: 64, transient_saves_memory: true }`
## What you did
I modified the `tilemap_chunk_orientation` example to display a gradient of tile colors, replace the existing code with:
```rust
let chunk_size = uvec2(17, 1);
let tile_display_size = UVec2::splat(32);
let tile_data = (0..chunk_size.element_product())
.map(|i| {
let v = i as f32 / 16.0;
Some(TileData {
tileset_index: 0,
color: Color::srgba(v, v, v, 1.0),
visible: true,
orientation: TileOrientation::Default,
})
})
.collect();
```
I ran this code and checked the displayed colors of the tiles.
## What went wrong
Expected: The pixels in the "P" are white in the tileset, and so should be exactly the requested color in the output, forming a visually linear gradient with even steps in the sRGB output.
Observed: The output shows an incorrect color gradient (first step is too big, final steps are too small):
Using the macOs "Digital Color Meter" onscreen, or picking colors from the saved screenshot, the gradient gives grey with values of:
`0, 71, 99, 120, ... 240, 248, 255`
This should be approximately (+/-1?):
`0, 15, 31, 47, ... 223, 239, 255`
## Additional information
Looking at the code, I think the issue is that the `TileData` `color` values are being:
1. Converted to sRGBA in `tilemap_chunk_material`; the conversion to `PackedTileData` uses `color: color.to_srgba().to_u8_array()`
2. Treated as linear in `tilemap_chunk_material.wgsl`, in `get_tile_data`.
I'm not that familiar with shaders, but from reading around it seems like it's correct to have a linear tile color in the shader, since we want to multiply the tileset texture color by it (I'm assuming the tileset texture will produce linear colors so we're multiplying linear by linear to produce a linear shader output?). The issue seems to be that the data going to the shader is sRGB.
Since the color is packed with one byte per color channel it seems like a good idea to use sRGB to avoid losing precision for darker colors, so it seems like this could be addressed by sRGB to linear conversion in the shader, e.g.:
```wgsl
let color_r = pow(f32(data.g & 0xFFu) / 255.0, 2.2);
let color_g = pow(f32((data.g >> 8u) & 0xFFu) / 255.0, 2.2);
let color_b = pow(f32(data.b & 0xFFu) / 255.0, 2.2);
```
This doesn't seem ideal since the `pow` call will have a cost, and in addition this is just an approximation of the sRGB to linear conversion, but it does make the gradient look roughly correct:
This gives improved sRGB values on screen:
`0, 7, 26, 44, ... 224, 239, 255`
The first 4 values are a bit off (presumably because we're using plain gamma rather than the proper curve with an initial linear section), but after that it's within +/-1 of the expected values.
Is there a better (faster/more accurate) way of doing the sRGB to linear conversion?
Another approach could be to expand the data passed into the `tile_data` texture, so it can have higher-precision linear data, e.g. two pixels per tile, one for the linear RGBA and the other for the tile index and flags?
Contributor guide
Research direction
Start with the tilemap_chunk_orientation example, then inspect the color packing in tilemap_chunk_material and the get_tile_data path in tilemap_chunk_material.wgsl. Reproduce the gradient on the reported Metal adapter and trace the color-space conversions. Done means tile colors render with the expected sRGB gradient without unacceptable precision or shader-cost tradeoffs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics, game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100