私のアプリは、他の 2 つを変更せずにこれらのチャネルの 1 つを変更することがよくあります。何かが変更されるたびにまったく新しい画像をレンダリングするのではなく、GPU で既存の画像を合成できれば、レンダリングが高速化されます。[OP、@ZevEisenberg]
高速性と GPU に関しては、変換関数をフラグメント シェーダーに入れるだけです (例: )。これは、テクスチャまたは 3 つの異なるテクスチャに格納されている HSV を読み取り、ピクセルごとに変換を行い、RGB を出力します。素敵で簡単。H、S、または V のいずれかがすべての RGB チャネルに影響するため、他のレイヤーを変更しないことには何のメリットもありません。おそらく、 などの中間 RGB 結果を保存しhue=hsv2rgb(H,1,1)
、 で更新しfinal=(hue*S+1-S)*V
、hue-to-rgb をキャッシュしますが、それだけの価値はないと思います。
とにかく、各ブレンド モードには単純な式があり、中間テクスチャの非常に複雑なセットを含む HSV 用にそれらをつなぎ合わせることができますが、主に不必要な一時ストレージとメモリ帯域幅が原因で、はるかに遅くなります。言うまでもなく、数式をブレンド関数に書き直そうとするのはかなり難しいように思えます。分岐、除算、fract
クランプ、絶対値など...
イメージを HSV コンポーネントに分割し、Photoshop のブレンド モードを使用して元のイメージを再作成するソリューションに非常に興味があります。[バウンティ、@phisch]
フォトショップに関しては... 私はお金でできていません。したがって、gimpにColours -> Components -> Compose/Decompose
は、これを行うものがあります。これがPhotoshopに存在しない場合はちょっと驚きますが、そうでない場合もあります. おそらく、そうでない場合でもそれを行うことができるPhotoshopスクリプト/プラグインがありますか? しかし、あなたは特にブレンディングと言いました。あなたの質問はhttps://graphicdesign.stackexchange.com/で注目を集めるかもしれません。以下に、関連する複雑さのアイデアを示しましたが、Photoshop が実際にそれを実行できるとは思えません。0 から 1 の範囲外のピクセル値を回避する方法はあるかもしれませんが、精度の問題が発生する可能性があるため、実行すべきではありません。
とにかく、どんなに非現実的であっても、挑戦は挑戦です。以下はただの楽しみです。
次の関数 (ここから) と 3 つの HSV テクスチャから始めます...
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

私はOpenGLしか知らないので、浮動小数点テクスチャや拡張ブレンディング関数を使わずにこれを行う方法がわからないので、それらを使用しています。ただし、ブレンドの使用のみが許可されています (シェーダーは一切使用できません)。定数については、(1,1,1)、(1,2/3,1/3)、(3,3,3)、(6,6,6) (1/255,1) でテクスチャを作成します。 GL_ZERO をGL_DIFFERENCE_NV
.
- 色相テクスチャから始める
- 加算ブレンドを使用して (1,2/3,1/3) を追加します
小数部分を見つける
- 減算ブレンディングでは、0.5 を減算します (これは、 GLが 8 ビットに変換するときに色を丸める
floor()
と想定しているためです。そうでない場合は、これをスキップしてください)
- 1/255に縮小。これは通常のアルファ ブレンディングで行うことができますが、代わりにカラー テクスチャでスケーリングしました。
- 非浮動小数点テクスチャを通過して、最も近い 1/255 に丸めます
255 倍にスケールアップします (浮動小数点テクスチャに戻します)。

これで整数コンポーネントができました。私たちが始めたものからこれを差し引いてください

6倍に拡大
- 減算ブレンディングで、テイク 3
値の絶対値を取る
私は単にGL_DIFFERENCE_NV
これを使用するつもりですが、それがなければ、次のステップで 2 つの別々のクランプを使用する方法があるかもしれません. とにかくネガはクランプされるので、clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1)
.
1を引く
これで色相は完了です
非浮動小数点テクスチャを通過することでクランプできますが、使用するだけですGL_MIN
にアルファ ブレンディングを使用できるようになりましmix()
たが、彩度はアルファ チャネルのない B/W イメージとして読み込まれます。白を混ぜるので、手でやった方が実は楽なのですが…
彩度によるスケーリング
- 1を追加
彩度を引く
飽和が適用されました
値によるスケーリング
そして画像があります
コーヒーブレイク
使用してすべて完了
glBlendEquation
GL_FUNC_REVERSE_SUBTRACT
、GL_MIN
および_GL_DIFFERENCE_NV
glBlendFunc
これが私のコードです...
//const tex init
constTex[0] = makeTex() with 1, 1, 1...
constTex[1] = makeTex() with 1, 2/3, 1/3...
constTex[2] = makeTex() with 3, 3, 3...
constTex[3] = makeTex() with 6, 6, 6...
constTex[4] = makeTex() with 1/255, 1/255, 1/255...
constTex[5] = makeTex() with 255, 255, 255...
constTex[6] = makeTex() with 1/2, 1/2, 1/2...
constTex[7] = makeTex() with 0, 0, 0...
...
fbo[0] = makeFBO() with GL_RGB
fbo[1] = makeFBO() with GL_RGB32F
fbo[2] = makeFBO() with GL_RGB32F
...
hsv[0] = loadTex() hue
hsv[1] = loadTex() value
hsv[2] = loadTex() saturation
...
fbo[1].bind();
glDisable(GL_BLEND);
draw(hsv[0]); //start with hue
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[1]); //(1, 2/3, 1/3)
glBlendFunc(GL_ONE, GL_ONE);
fbo[1].unbind();
//compute integer part
fbo[2].bind();
glDisable(GL_BLEND);
draw(*fbo[1].colour[0]); //copy the last bit
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[6]); //0.5
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale down
draw(constTex[4]); //1/255
fbo[2].unbind();
fbo[0].bind(); //floor to integer
glDisable(GL_BLEND);
draw(*fbo[2].colour[0]);
fbo[0].unbind();
fbo[2].bind(); //scale back up
glDisable(GL_BLEND);
draw(*fbo[0].colour[0]);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale up
draw(constTex[5]); //255
fbo[2].unbind();
//take integer part for fractional
fbo[1].bind();
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(*fbo[2].colour[0]); //integer part
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(constTex[3]); //6
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[2]); //3
glBlendEquation(GL_DIFFERENCE_NV);
glBlendFunc(GL_ZERO, GL_ONE); //take the absolute
draw(constTex[7]); //0
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[0]); //1
glBlendEquation(GL_MIN);
glBlendFunc(GL_ONE, GL_ONE); //clamp (<0 doesn't matter, >1 use min)
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[2]); //saturation
fbo[1].unbind();
fbo[1].blit(); //check result