6

さまざまなパラメーターに応じて色相、彩度、および値が変化する画像を作成するアプリに取り組んでいます。パフォーマンス上の理由から、色相、彩度、および値のコンポーネントを個別にレンダリングし、Photoshop スタイルのブレンド モード (乗算、オーバーレイ、スクリーン、色相など) を使用してそれらを合成することは理にかなっています。

RGB 画像に対してこれを行う方法は既に知っています。各チャンネルを、透明からそのチャンネルの色までの範囲の値を持つ独自の赤、緑、または青の画像に分割します。それらを黒の上に重ねてブレンド モードを [スクリーン] に設定すると、カラー イメージが完成します。

コンポーネントから作成された RGB イメージ

HSV値で定義された画像でこれを行うにはどうすればよいですか? 私のアプリは、他の 2 つを変更せずにこれらのチャネルの 1 つを変更することがよくあります。何かが変更されるたびにまったく新しい画像をレンダリングするのではなく、GPU で既存の画像を合成できれば、レンダリングが高速化されます。

次に例を示します。 個別の H、S、および V チャンネルで生成された画像

この例では、色相は円周で 0 度から 360 度まで変化し、彩度は中心から端まで 0% から 100% まで変化し、明度 (V) は円周で 0% から 100% まで変化します。これは、私のアプリが作成する典型的な画像です。これらのチャネルを個別に作成し、数学的に完全な方法で合成するために使用できる一般的なブレンド モードの組み合わせはありますか?

4

1 に答える 1

10

私のアプリは、他の 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. 加算ブレンドを使用して (1,2/3,1/3) を追加します
  3. 小数部分を見つける

    1. 減算ブレンディングでは、0.5 を減算します (これは、 GLが 8 ビットに変換するときに色を丸めるfloor()と想定しているためです。そうでない場合は、これをスキップしてください)
    2. 1/255に縮小。これは通常のアルファ ブレンディングで行うことができますが、代わりにカラー テクスチャでスケーリングしました。
    3. 非浮動小数点テクスチャを通過して、最も近い 1/255 に丸めます
    4. 255 倍にスケールアップします (浮動小数点テクスチャに戻します)。

      整数

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

      分数

  4. 6倍に拡大

  5. 減算ブレンディングで、テイク 3
  6. 値の絶対値を取る

    私は単にGL_DIFFERENCE_NVこれを使用するつもりですが、それがなければ、次のステップで 2 つの別々のクランプを使用する方法があるかもしれません. とにかくネガはクランプされるので、clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1).

  7. 1を引く

    ここに画像の説明を入力これで色相は完了です

  8. 非浮動小数点テクスチャを通過することでクランプできますが、使用するだけですGL_MIN

  9. にアルファ ブレンディングを使用できるようになりましmix()たが、彩度はアルファ チャネルのない B/W イメージとして読み込まれます。白を混ぜるので、手でやった方が実は楽なのですが…

    彩度によるスケーリング

  10. 1を追加
  11. 彩度を引く

    ここに画像の説明を入力飽和が適用されました

  12. 値によるスケーリング

    ここに画像の説明を入力そして画像があります

  13. コーヒーブレイク

使用してすべて完了

  • glBlendEquationGL_FUNC_REVERSE_SUBTRACTGL_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
于 2015-03-06T10:54:31.573 に答える