Ensure MovieWriter output is in gamma space when using HDR 2D

This commit is contained in:
clayjohn 2024-05-28 15:13:24 -07:00
parent be56cab58c
commit 3b9d074fd7
10 changed files with 61 additions and 1 deletions

View File

@ -3475,6 +3475,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges); ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges);
ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha);
ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear);
ClassDB::bind_method(D_METHOD("linear_to_srgb"), &Image::linear_to_srgb);
ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy); ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy);
ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb);
ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0));
@ -3741,6 +3742,37 @@ void Image::srgb_to_linear() {
} }
} }
void Image::linear_to_srgb() {
if (data.size() == 0) {
return;
}
static const uint8_t lin2srgb[256] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255 };
ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8);
if (format == FORMAT_RGBA8) {
int len = data.size() / 4;
uint8_t *data_ptr = data.ptrw();
for (int i = 0; i < len; i++) {
data_ptr[(i << 2) + 0] = lin2srgb[data_ptr[(i << 2) + 0]];
data_ptr[(i << 2) + 1] = lin2srgb[data_ptr[(i << 2) + 1]];
data_ptr[(i << 2) + 2] = lin2srgb[data_ptr[(i << 2) + 2]];
}
} else if (format == FORMAT_RGB8) {
int len = data.size() / 3;
uint8_t *data_ptr = data.ptrw();
for (int i = 0; i < len; i++) {
data_ptr[(i * 3) + 0] = lin2srgb[data_ptr[(i * 3) + 0]];
data_ptr[(i * 3) + 1] = lin2srgb[data_ptr[(i * 3) + 1]];
data_ptr[(i * 3) + 2] = lin2srgb[data_ptr[(i * 3) + 2]];
}
}
}
void Image::premultiply_alpha() { void Image::premultiply_alpha() {
if (data.size() == 0) { if (data.size() == 0) {
return; return;

View File

@ -381,6 +381,7 @@ public:
void fix_alpha_edges(); void fix_alpha_edges();
void premultiply_alpha(); void premultiply_alpha();
void srgb_to_linear(); void srgb_to_linear();
void linear_to_srgb();
void normal_map_to_xy(); void normal_map_to_xy();
Ref<Image> rgbe_to_srgb(); Ref<Image> rgbe_to_srgb();
Ref<Image> get_image_from_mipmap(int p_mipamp) const; Ref<Image> get_image_from_mipmap(int p_mipamp) const;

View File

@ -305,6 +305,12 @@
Returns [code]true[/code] if all the image's pixels have an alpha value of 0. Returns [code]false[/code] if any pixel has an alpha value higher than 0. Returns [code]true[/code] if all the image's pixels have an alpha value of 0. Returns [code]false[/code] if any pixel has an alpha value higher than 0.
</description> </description>
</method> </method>
<method name="linear_to_srgb">
<return type="void" />
<description>
Converts the entire image from the linear colorspace to the sRGB colorspace. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
</description>
</method>
<method name="load"> <method name="load">
<return type="int" enum="Error" /> <return type="int" enum="Error" />
<param index="0" name="path" type="String" /> <param index="0" name="path" type="String" />
@ -574,7 +580,7 @@
<method name="srgb_to_linear"> <method name="srgb_to_linear">
<return type="void" /> <return type="void" />
<description> <description>
Converts the raw data from the sRGB colorspace to a linear scale. Converts the raw data from the sRGB colorspace to a linear scale. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
</description> </description>
</method> </method>
</methods> </methods>

View File

@ -102,6 +102,7 @@
await RenderingServer.frame_post_draw await RenderingServer.frame_post_draw
$Viewport.get_texture().get_image().save_png("user://Screenshot.png") $Viewport.get_texture().get_image().save_png("user://Screenshot.png")
[/codeblock] [/codeblock]
[b]Note:[/b] When [member use_hdr_2d] is [code]true[/code] the returned texture will be an HDR image encoded in linear space.
</description> </description>
</method> </method>
<method name="get_viewport_rid" qualifiers="const"> <method name="get_viewport_rid" qualifiers="const">

View File

@ -8,6 +8,11 @@
To get a [ViewportTexture] in code, use the [method Viewport.get_texture] method on the target viewport. To get a [ViewportTexture] in code, use the [method Viewport.get_texture] method on the target viewport.
[b]Note:[/b] A [ViewportTexture] is always local to its scene (see [member Resource.resource_local_to_scene]). If the scene root is not ready, it may return incorrect data (see [signal Node.ready]). [b]Note:[/b] A [ViewportTexture] is always local to its scene (see [member Resource.resource_local_to_scene]). If the scene root is not ready, it may return incorrect data (see [signal Node.ready]).
[b]Note:[/b] Instantiating scenes containing a high-resolution [ViewportTexture] may cause noticeable stutter. [b]Note:[/b] Instantiating scenes containing a high-resolution [ViewportTexture] may cause noticeable stutter.
[b]Note:[/b] When using a [Viewport] with [member Viewport.use_hdr_2d] set to [code]true[/code] the returned texture will be an HDR image encoded in linear space. This may look darker than normal when displayed directly on screen. To convert to gamma space, you can do the following:
[codeblock]
img.convert(Image.FORMAT_RGBA8)
imb.linear_to_srgb()
[/codeblock]
</description> </description>
<tutorials> <tutorials>
<link title="GUI in 3D Viewport Demo">https://godotengine.org/asset-library/asset/2807</link> <link title="GUI in 3D Viewport Demo">https://godotengine.org/asset-library/asset/2807</link>

View File

@ -185,6 +185,10 @@ void MovieWriter::add_frame() {
RID main_vp_rid = RenderingServer::get_singleton()->viewport_find_from_screen_attachment(DisplayServer::MAIN_WINDOW_ID); RID main_vp_rid = RenderingServer::get_singleton()->viewport_find_from_screen_attachment(DisplayServer::MAIN_WINDOW_ID);
RID main_vp_texture = RenderingServer::get_singleton()->viewport_get_texture(main_vp_rid); RID main_vp_texture = RenderingServer::get_singleton()->viewport_get_texture(main_vp_rid);
Ref<Image> vp_tex = RenderingServer::get_singleton()->texture_2d_get(main_vp_texture); Ref<Image> vp_tex = RenderingServer::get_singleton()->texture_2d_get(main_vp_texture);
if (RenderingServer::get_singleton()->viewport_is_using_hdr_2d(main_vp_rid)) {
vp_tex->convert(Image::FORMAT_RGBA8);
vp_tex->linear_to_srgb();
}
RenderingServer::get_singleton()->viewport_set_measure_render_time(main_vp_rid, true); RenderingServer::get_singleton()->viewport_set_measure_render_time(main_vp_rid, true);
cpu_time += RenderingServer::get_singleton()->viewport_get_measured_render_time_cpu(main_vp_rid); cpu_time += RenderingServer::get_singleton()->viewport_get_measured_render_time_cpu(main_vp_rid);

View File

@ -1270,6 +1270,13 @@ void RendererViewport::viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr_2d
RSG::texture_storage->render_target_set_use_hdr(viewport->render_target, p_use_hdr_2d); RSG::texture_storage->render_target_set_use_hdr(viewport->render_target, p_use_hdr_2d);
} }
bool RendererViewport::viewport_is_using_hdr_2d(RID p_viewport) const {
Viewport *viewport = viewport_owner.get_or_null(p_viewport);
ERR_FAIL_NULL_V(viewport, false);
return viewport->use_hdr_2d;
}
void RendererViewport::viewport_set_screen_space_aa(RID p_viewport, RS::ViewportScreenSpaceAA p_mode) { void RendererViewport::viewport_set_screen_space_aa(RID p_viewport, RS::ViewportScreenSpaceAA p_mode) {
Viewport *viewport = viewport_owner.get_or_null(p_viewport); Viewport *viewport = viewport_owner.get_or_null(p_viewport);
ERR_FAIL_NULL(viewport); ERR_FAIL_NULL(viewport);

View File

@ -262,6 +262,8 @@ public:
void viewport_set_transparent_background(RID p_viewport, bool p_enabled); void viewport_set_transparent_background(RID p_viewport, bool p_enabled);
void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr_2d); void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr_2d);
bool viewport_is_using_hdr_2d(RID p_viewport) const;
void viewport_set_global_canvas_transform(RID p_viewport, const Transform2D &p_transform); void viewport_set_global_canvas_transform(RID p_viewport, const Transform2D &p_transform);
void viewport_set_canvas_stacking(RID p_viewport, RID p_canvas, int p_layer, int p_sublayer); void viewport_set_canvas_stacking(RID p_viewport, RID p_canvas, int p_layer, int p_sublayer);

View File

@ -639,6 +639,7 @@ public:
FUNC3(viewport_set_canvas_transform, RID, RID, const Transform2D &) FUNC3(viewport_set_canvas_transform, RID, RID, const Transform2D &)
FUNC2(viewport_set_transparent_background, RID, bool) FUNC2(viewport_set_transparent_background, RID, bool)
FUNC2(viewport_set_use_hdr_2d, RID, bool) FUNC2(viewport_set_use_hdr_2d, RID, bool)
FUNC1RC(bool, viewport_is_using_hdr_2d, RID)
FUNC2(viewport_set_snap_2d_transforms_to_pixel, RID, bool) FUNC2(viewport_set_snap_2d_transforms_to_pixel, RID, bool)
FUNC2(viewport_set_snap_2d_vertices_to_pixel, RID, bool) FUNC2(viewport_set_snap_2d_vertices_to_pixel, RID, bool)

View File

@ -934,6 +934,7 @@ public:
virtual void viewport_set_canvas_transform(RID p_viewport, RID p_canvas, const Transform2D &p_offset) = 0; virtual void viewport_set_canvas_transform(RID p_viewport, RID p_canvas, const Transform2D &p_offset) = 0;
virtual void viewport_set_transparent_background(RID p_viewport, bool p_enabled) = 0; virtual void viewport_set_transparent_background(RID p_viewport, bool p_enabled) = 0;
virtual void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr) = 0; virtual void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr) = 0;
virtual bool viewport_is_using_hdr_2d(RID p_viewport) const = 0;
virtual void viewport_set_snap_2d_transforms_to_pixel(RID p_viewport, bool p_enabled) = 0; virtual void viewport_set_snap_2d_transforms_to_pixel(RID p_viewport, bool p_enabled) = 0;
virtual void viewport_set_snap_2d_vertices_to_pixel(RID p_viewport, bool p_enabled) = 0; virtual void viewport_set_snap_2d_vertices_to_pixel(RID p_viewport, bool p_enabled) = 0;