¿why does my OKLCH color profile shader look so strange?
Asked Answered
E

50

0

i made a shader that interprets the RGB color channels als OKLCH:

RGB channel | interpreted as
---|---
Red, 0 – 255 | Lightness, 0.0 – 1.0
Green, 0 – 255 | Chroma, 0.0 – 0.34
Blue, 0 – 255 | Hue, 0 – 360w

but the result looks less consistent than it shoud:

  • the Lightness is not as consistent as expected
  • the Chroma is brighter and less consistent than expected

the strange thing is that the GDScript version of the converter is accurate despite using the exact same formulas in the exact same order:
if i ask the shader to translate oklch(0.7 0.1 248), i get this:
a bit too saturated;
but if i ask the GDScript to do it, i get rgb(103.8397, 164.4775, 217.0177, 255), the same as this trusted OKLCH picker.

rgb-as-oklch.zip
2MB

here are these 2 files in the project:

file | path
---|---
Shader | res://srgb_as_oklch.gdshader
GDScript | res://oklch_calculator.tscn::GDScript

Expansive answered 11/4, 2023 at 19:46 Comment(0)
E
0

Rupiah Your issue is that the code in GDScript and the code in the shader is using completely different functions.

Expansive ¿what are these differences that i missed?

Rupiah It's not differences. It's that the functions are completely different. Totally different names, logic, code.

now that i have the right result, i know that the strange result was from not understanding shader language good enough.
in my second attempt i rewrote the shader from scratch, doing the same thing i did in my first attempt — copying the logic from the GDScript.

rgb-as-oklch.zip
2MB
Expansive answered 14/4, 2023 at 19:45 Comment(0)
R
0

You can upload pictures. What format do you have them in?

Shaders use sRGB for some textures, but the calculations are linear. Depending on how you are using it you might have to add a hint like hint_albedo.

Rupiah answered 11/4, 2023 at 20:4 Comment(0)
E
0

Rupiah You can upload pictures. What format do you have them in?

these pictures are in WEBP format

Expansive answered 11/4, 2023 at 21:16 Comment(0)
R
0

I just added support. Try to upload the images again.

Rupiah answered 11/4, 2023 at 22:14 Comment(0)
M
0

Hydrostatics
A bit offtopic. I didn't know about OKLCH. It sounds great. I do a lot of stuff with procedurally generated palettes where perceptual uniformity is of utmost importance. The best results I had with radial CIELAB (LCH) but stumbled upon the exact issues Bjorn is mentioning in the article (problems with hue wonkiness). Had an idea to deduce a better perceptual remap from observational data, but figured it'd be too much work. Bjorn did exactly that and it looks like a success.

You should perhaps "officially" release a GDScript conversion library if you have it implemented. I'd most certainly use it.

Myke answered 11/4, 2023 at 22:30 Comment(0)
E
0

Expansive (sorry, Godot Forums didn't let me upload the pictures. please fix this.)

Rupiah You can upload pictures. What format do you have them in?

Expansive these pictures are in WEBP format

Rupiah I just added support. Try to upload the images again.

Thank you Very much!
now i could add the pictures in the post itself — not just linking to them on Google Drive.

Expansive answered 12/4, 2023 at 6:12 Comment(0)
E
0

Rupiah Shaders use sRGB for some textures, but the calculations are linear. Depending on how you are using it you might have to add a hint like hint_albedo.

i couldn't find how to add a hint to texture(TEXTURE, UV);.
i want to use the Sprite2D's sprite as the shader texture — and later i might want a variation that uses the screen texture.

Expansive answered 12/4, 2023 at 7:10 Comment(0)
R
0

So for shaders (especially 2D) I think everything is in linear space. If there is any conversion, it is automatic at the end and shouldn't affect the calculations.

Your issue is that the code in GDScript and the code in the shader is using completely different functions.

Rupiah answered 12/4, 2023 at 7:39 Comment(0)
E
0

Rupiah Your issue is that the code in GDScript and the code in the shader is using completely different functions.

¿what are these differences that i missed?

Expansive answered 13/4, 2023 at 12:27 Comment(0)
M
0

Expansive Try to disable sRGB flag in texture's import options.

Myke answered 13/4, 2023 at 12:40 Comment(0)
R
0

Expansive ¿what are these differences that i missed?

It's not differences. It's that the functions are completely different. Totally different names, logic, code.

Rupiah answered 13/4, 2023 at 12:49 Comment(0)
E
0

Myke Try to disable sRGB flag in texture's import options.

¿do you mean "HDR as sRGB"?
it was disabled, and enabling it did not make the colors look better:
lightness is more consistent but not by much — chroma is too desaturated and sometimes the hue becomes wrong
(i did not take a photo, i was too busy fixing the colors)

Expansive answered 14/4, 2023 at 19:45 Comment(0)
E
0

Rupiah Your issue is that the code in GDScript and the code in the shader is using completely different functions.

Expansive ¿what are these differences that i missed?

Rupiah It's not differences. It's that the functions are completely different. Totally different names, logic, code.

now that i have the right result, i know that the strange result was from not understanding shader language good enough.
in my second attempt i rewrote the shader from scratch, doing the same thing i did in my first attempt — copying the logic from the GDScript.

rgb-as-oklch.zip
2MB
Expansive answered 14/4, 2023 at 19:45 Comment(0)
E
0

Myke A bit offtopic. I didn't know about OKLCH. It sounds great. I do a lot of stuff with procedurally generated palettes where perceptual uniformity is of utmost importance. The best results I had with radial CIELAB (LCH) but stumbled upon the exact issues Bjorn is mentioning in the article (problems with hue wonkiness). Had an idea to deduce a better perceptual remap from observational data, but figured it'd be too much work. Bjorn did exactly that and it looks like a success.

You should perhaps "officially" release a GDScript conversion library if you have it implemented. I'd most certainly use it.

this sounds great.
i think that the best implementation of a Godot OKLCH library would be an add-on with these things, please correct me:

  • OklchConverter node (autoload)
    can convert colors between sRGB and OKLCH
  • ColorOklch custom resource type
    for storing 1 OKLCH color, just like Color stores 1 RGB color
    (i don't know anything about custom resources yet, but i just feel like this is right)
Expansive answered 14/4, 2023 at 20:18 Comment(0)
M
0

Expansive It doesn't need to be a resource. It can be a regular class/object or even just conversion functions that take/return LCH values in an array.

Myke answered 14/4, 2023 at 20:30 Comment(0)
E
0

Myke ¿which is the best way? please only answer this if you know.

Expansive answered 14/4, 2023 at 20:34 Comment(0)
M
0

Expansive There's no best way 🙂
I'd start with just making static conversion functions in a regular script file. If file is added to the project the functions become available everywhere in the project.

class_name LCH

static func to_rgb(lch: Array) -> Color:
	# code here
	
static func from_rgb(rgb: Color) -> Array:
	# code here

The usage would then be:

LCH.to_rgb([1.0, 1.0, 1.0])
LCH.from_rgb(Color(1.0, 1.0, 1.0))
Myke answered 14/4, 2023 at 20:48 Comment(0)
M
0

I'd also maybe add a convenience function to blend/interpolate two LCH colors. So:

static func blend(lch1: Array, lch2: Array, factor: float) -> Array
Myke answered 14/4, 2023 at 20:57 Comment(0)
M
0

Just to add some more thoughts on the topic. CIELAB in great for two-color blending. The gradients it produces all look perceptually "natural". However, in procedural generation there is a lot of need for intuitive color shifts - i.e. changing the value of one of the luma/chroma/hue components but keeping other two components perceptually the same. This is problematic in all CIEXYZ based spaces including LAB. Hue shifts in particular can give unpredictable results. Not as bad as HSL/HSV but still not ideal.

I even think that ensuring both - perceptually uniform blending and perceptually uniform shifts - is not entirely possible inside a single space. At least not without some transformation black magic. But that then is equivalent of using an alternate space. It'd be good to see some tests on how OKLCH does with shifts.

Ideally I'd like to have a library that lets you do perceptually uniform blends/shifts, but uses rgb at input/output. It can internally convert to whatever space is best for a particular operation. The library user need not care about it. So interface might look something like this:

class_name Perceptual
func blend(rgb1: Color, rgb2: Color, factor: float) -> Color
func shift_hue(rgb: Color, shift: float) -> Color
func shift_luma(rgb: Color, shift: float) -> Color
func shift_chroma(rgb: Color, shift: float) -> Color
Myke answered 15/4, 2023 at 12:20 Comment(0)
E
0

now i'm only a licensing away from releasing an OKLCH addon as an open-source project.

rgb-as-oklch.zip
2MB

my original plan was to have 2 things in this addon:

Expansive

  • OklchConverter node (autoload)
    can convert colors between sRGB and OKLCH
  • ColorOklch custom resource type
    for storing 1 OKLCH color, just like Color stores 1 RGB color
    (i don't know anything about custom resources yet, but i just feel like this is right)

in the real addon, i have 2 other things:

  • ColorOKLCH custom resource type
    • stores a color in OKLCH format
    • can convert a color from OKLCH to sRGB
    • can convert a color from sRGB to OKLCH
  • sRGB as OKLCH shader, also known as OKLCH color profile shader
    • interprets the sRGB pixels as OKLCH and converts them back to sRGB
Expansive answered 15/4, 2023 at 14:1 Comment(0)
E
0

i'm thinking about which license to choose. if i choose GPL,
¿will other projects have to open-source the whole project before using the addon, or just the addon (and of course other GPL and similar licensed parts)?

Expansive answered 15/4, 2023 at 20:7 Comment(0)
H
0

GPL would definitely be problematic, I want to say LGPL might be less so, but at that point a fair question might be why not Apache public license?

Hoseahoseia answered 15/4, 2023 at 22:33 Comment(0)
R
0

GPL is actually really bad for plug-ins or add-ons. It makes sense for self contained applications, but for add-on things (like libraries) it's better to use permissive licensing like MIT, BSD, or Apache.

Rupiah answered 16/4, 2023 at 0:8 Comment(0)
E
0

Hoseahoseia Rupiah now i consider 3 licenses:

i want

  • others can use the addon in proprietary software
  • others must open-source their modified version of the addon

Apache and MIT licenses don't have the 'disclose source' condition, so
if there is no objection, GNU LGPL it is

Expansive answered 16/4, 2023 at 7:54 Comment(0)
R
0

Lesser GPL might work.

Rupiah answered 16/4, 2023 at 8:1 Comment(0)
H
0

No, Apache doesn't require the changes be contributed back, but it does require changes to be stated. Basically a name and shame clause in a sense. Honestly this addon would be of such nature not contributing changes back wouldn't even make sense.

Hoseahoseia answered 16/4, 2023 at 9:22 Comment(0)
E
0

Hoseahoseia this addon would be of such nature not contributing changes back wouldn't even make sense.

¿even if that change is adding a color picker with graphs? (like this one)

Expansive answered 16/4, 2023 at 10:41 Comment(0)
H
0

Not contributing back would mean having to maintain the code themselves, so more technical debt to the forker.

I'd see someone making the decision not to contribute back, if they choose to make a commercial offshoot, but this addon would be such a small thing I don't see someone successfully monetizing it anyways.

Hoseahoseia answered 16/4, 2023 at 11:14 Comment(0)
M
0

Not sure why your class inherits Resource though. I'd say that's too heavyweight for this almost atomic type. Are you sure you need resource functionality with this? I mean it holds path and name strings as well as local to scene flag (and associated duplication behavior).

I'd go as basic as possible and inherit RefCounted instead (or its equivalent Reference in v3.x)

As for licensing, I think you're overthinking it. It's a very short snippet of easily implementable code. Why not just put it in public domain, with basic usage conditions/disclaimers in the comments. If it grows into a bigger library/plugin you can then think about more involved licensing modalities.

Myke answered 16/4, 2023 at 12:6 Comment(0)
E
0

Myke Are you sure you need resource functionality with this?

yes. when i changed the type of ColorOKLCH from Resource to RefCounted, i can no longer @export variables of type ColorOKLCH because this error comes:

Export type can only be built-in, a resource, a node or an enum

this error means that i can't export RefCounteds — changing the type back to Resource fixed it

Expansive answered 16/4, 2023 at 12:36 Comment(0)
E
0

Hoseahoseia this addon would be such a small thing I don't see someone successfully monetizing it

if they decide to add a lot more color spaces to their commercial offshoot, they could successfully monetize it.
but i don't think that companies who would monetize color related tools — especially Adobe — would use Godot.

so if i don't change my mind again, i may use MIT license
so that Godot-native OKLCH tools can be made more easily by basing them off my addon.
i guess that Godot-native OKLCH tools will be added in around 5 years when all our screens have wide color ranges like Display-P3 or wider.

Expansive answered 16/4, 2023 at 12:55 Comment(0)
M
0

Expansive You sure you need to export it though? I mean what's the intended usage? Is it worth dragging along all the Resource baggage just to store 3 float values?

That's why I initially suggested to skip making a dedicated type but instead provide standalone conversion functions that pass data via arrays. You could simply use PackedFloat32Array which is fast and compact, and also can be exported. Even using Vector3 or Vector4 wouldn't be a bad idea.

Myke answered 16/4, 2023 at 13:1 Comment(0)
E
0

Myke yes, i'm sure i need to export. my ColorOKLCH resource type has functions for converting between sRGB ⟷ OKLCH — but they are lossy,
so the OKLCH color l=0.7 c=0.1 h=246 becomes l=0.69999994219011 c=0.0999999830005 h=-114.000006417675 when converted to sRGB and then back to OKLCH.
after the conversion the color looks the same, but the values are harder to read.

the purpose of this addon is to use OKLCH colors in a way that is as native as possible —
so converting the colors to sRGB after picking them in the OKLCH color picker, only to convert them back to OKLCH,
is pointless when i could just copy the OKLCH values into the ColorOKLCH resource.

Expansive answered 16/4, 2023 at 14:12 Comment(0)
E
0

code should be written for the people reading it first and for the computers running it second
because maintenance is most often much more important than performance.

using a resource means that i can reference the values with clear names like l c h a instead of the unfitting x y z w — these are color channels, not coordinates.
referencing the color channels by x y z w instead of l c h a made the shader code of my first attempt of my shader hard to read — which is part of why i failed to make the shader in my first attempt.

Expansive answered 16/4, 2023 at 14:13 Comment(0)
M
0

@"Sosasees"#p108816 code should be written for the people reading it first and for the computers running it second

Not in the game engines 😉 Data driven design is a thing in performance critical systems.

It's your call though. But if you think about it, LAB based spaces are really of little use when specifying colors via gui. Their main usage is in color transformation via code. So all color properties that are exported to the editor should be simply kept as builtin Color types anyway, for the convenience of specifying the value visually with pickers.

I'd strongly suggest to acquaint yourself well with the behavior of Reference objects before inheriting the class for this. There may be too much tradeoff just to get letters l,c,h appearing in the inspector.

One example. What happens if I want to store packed array of 1000 lch triplets (or quadruplets)?. Using Resource, firstly I can't pack them, and secondly the engine would have created 2000 useless string objects as a side effect. Not a good thing when dealing with something as basic as color information.

Myke answered 16/4, 2023 at 14:17 Comment(0)
E
0

Myke so i think i will make 2 editions of the add-on: both would be free and open-source, but

  • personal edition focuses on ease-of-use
    • it's the edition that i have made so far. it's just not called personal edition yet
  • professional editon focuses on performance
Expansive answered 16/4, 2023 at 14:21 Comment(0)
M
0

Expansive Try it and see what happens. Imho it's not just a matter of performance/footprint. Storing this as a Resource is strange simply from the standpoint of engine architecture. A lch triplet alone is not a resource, hence it makes no sense to represent it with a type that inherits the Resource class. In fact, it's so basic that I'm not sure it's even worth inheriting from Object. I'd definitely cram it into one of the built in types. Most likely Vector. Thinking about it, lch is in fact - a vector 🙂

Myke answered 16/4, 2023 at 14:43 Comment(0)
H
0

Expansive if they decide to add a lot more color spaces to their commercial offshoot, they could successfully monetize it.
but i don't think that companies who would monetize color related tools — especially Adobe — would use Godot.

Adobe would be more likely to bring something like substance and/or photoshop(or straight up Creative Cloud) integration plugin that would likely be entirely their own code. And it would probably be a free(as in beer) plugin since for them it would act as a value add to their other offerings.

It would likely be some smaller entity then to do this instead. But yes, if some entity were to extend it that extensively, they'd still be more likely to write something from scratch. And if you make it any kind of open source at all then they'd still be able to look at it for reference, so still seems unlikely to me they'd try to monetize your work directly. But we are just speculating in here.

Mind, I do still think that LGPL could work too, just some would definitely unnecessarily end up avoiding it because they can't tell the difference between LGPL and GPL which is why I'm inclined to recommend APL instead. Mind I do remember there being at least one other license requiring modification be contributed back similar to LGPL but I fail to recall the name...aaand just found it again, EPL(ecliplse) & CDDL (Common Development and Distribution License). Also Mozilla, but I find that one more awkward.

Anyways, it is your work and your decision, whatever license agrees with you the most is the right choice.

Hoseahoseia answered 16/4, 2023 at 14:50 Comment(0)
M
0

Hoseahoseia MIT is just fine for this in the present state imho.

Myke answered 16/4, 2023 at 14:54 Comment(0)
E
0

Myke There may be too much tradeoff just to get letters l,c,h appearing in the inspector

not just in the inspector — more importantly in scripts.
having the variables named l c h a means that scripts look more like this:

extends Node


@export var color : ColorOKLCH


func _ready():
	print(
		"L: ", color.l, "\tC: ", color.c,
		"\tH: ", color.h, "\tA: ", color.a
	)

rather than this:

extends Node


@export var color_oklch : Vector4


func _ready():
	print(
		"L: ", color_oklch.x, "\tC: ", color_oklch.y,
		"\tH: ", color_oklch.z, "\tA: ", color_oklch.w
	)
Expansive answered 16/4, 2023 at 15:26 Comment(0)
M
0

Expansive I get what you're saying and you'd be perfectly right if this was a general purpose object oriented language. However inheriting from root base class Object is much more expensive than using a built in type. Object in GDScript is not some "empty" abstract base class like in JavaScript or other oo languages. It packs a considerable amount of internal state. Do you need your lhc triplet to send signals, store metadata, have a script attached, call methods via strings etc. You need none of that yet you try to inherit from a class with all that functionality.

I suggest you write in code as many typical use cases of your addon as you can think of. See how bad it looks with Vector and think how best to encapsulate it away from the user and/or provide helper/converter functions to make their life easier.

Myke answered 16/4, 2023 at 15:46 Comment(0)
H
0

Honestly the best outcome would be for this not to be a plugin at all and be included in core IMO as part of Color. Perhaps replacing HSV.

Hoseahoseia answered 16/4, 2023 at 15:55 Comment(0)
E
0

Hoseahoseia yes that would be the best outcome.
it's much easier to convince the Godot maintainers to make the change when we have the plug-in first

Expansive answered 16/4, 2023 at 16:2 Comment(0)
M
0

Hoseahoseia Honestly the best outcome would be for this not to be a plugin at all and be included in core IMO as part of Color. Perhaps replacing HSV.

Unfortunately that wouldn't be possible. As opposed to RGB<->HSV mapping, RGB<->LAB mapping is not "lossless".

Myke answered 16/4, 2023 at 16:10 Comment(0)
M
0

Here's an interface I'd be perfectly content with. Of course it would be ideal having the LCHA built in type with accordingly named properties instead of Vector4:

class_name LCH


static func to_rgb(lch: Vector4) -> Color:
	return(Color())
	
static func from_rgb(rgb: Color) -> Vector4:
	return (Vector4())
	
static func blend_rgb(rgb1: Color, rgb2: Color, factor: float) -> Color:
	return(Color())

static func blend_rgb_steps(rgb1: Color, rgb2: Color, steps_count: int) -> PackedColorArray:
	return(PackedColorArray())

static func shift_luma(rgb: Color, delta: float) -> Color:
	return(Color())

static func shift_chroma(rgb: Color, delta: float) -> Color:
	return(Color())
	
static func shift_hue(rgb: Color, delta: float) -> Color:
	return(Color())
	
static func to_string(lch: Vector4) -> String:
	return(String())
Myke answered 16/4, 2023 at 16:46 Comment(0)
H
0

Myke Hence the 'Perhaps'. Even if it didn't replace it, it should probably still be a part of Color if it was included in core.

Hoseahoseia answered 16/4, 2023 at 18:29 Comment(0)
E
0

Expansive i will make 2 editions of the add-on

  • i added the OKLCH professional edition addon
  • i renamed the OKLCH addon to OKLCH personal edition
  • i licensed the pictures in the Godot project (not the code yet)
  • i put more information in res://README.md
rgb-as-oklch.zip
2MB
Expansive answered 16/4, 2023 at 18:35 Comment(0)
M
0

Hoseahoseia Hence the 'Perhaps'. Even if it didn't replace it, it should probably still be a part of Color if it was included in core.

Afaik RGB<->HSV is bijective mapping while RGB<->LAB isn't. It can't just simply be added on. Doing so would compromise existing functionality of the Color object where hsv/rgb can be accessed interchangeably, precisely because mapping between them is bijective. I'd like to see it included in the core but it'd probably need its own type.

Myke answered 16/4, 2023 at 18:44 Comment(0)
E
0

i released the addon in a free and open-source git repository.

i put your names as special thanks in the first commit message because i think you are contributors:

Expansive answered 20/4, 2023 at 8:4 Comment(0)
R
0

Awesome job. Also MIT was a good choice for something like this.

Rupiah answered 20/4, 2023 at 9:36 Comment(0)
E
0

Rupiah MIT License is not just a good choice — it's a great choice because the addon (or parts of it) could be ported to base Godot which also uses MIT License

Expansive answered 20/4, 2023 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.