
後処理(Post-processing)は、元のゲーム画面に対してアルゴリズム処理を実行して、画面の品質を向上させたり、画面の効果を高めたりするテクノロジです。これは、シェーダーShaderプログラムによって実現できます。
概要
変形効果は、画像効果を処理および強化する後処理技術の一種で、美肌や痩身効果、歪んだ鏡効果など、さまざまなカメラショートビデオアプリの特殊効果でよく使用されます。
ビューティーカメラの変形効果
この記事では、主に、さまざまなビューティーカメラからよく使われている変形効果をまとめます。
- 局所歪み(twirl effect)
- 局所膨張(inflate effect)
- 任意の方向に押し出す(pinch effect)
その中で、twirlは目の局所的な回転に使用でき、inflateは目を大きくすることに使用でき、押し出し/ストレッチは顔の可塑化と小顔化などに使用できます。この記事はシェーダーShaderを介してこれらの変形効果を実現する方法に焦点を当てます。(ps:コードのプレビューを切望している方については、文末に移動してください。)
変形の原理
変形効果はあらゆるですが、変形の位置、影響範囲、変形の程度3つの要素から切り離せません。
変形Shaderがキャラクターのきまいダンスを実現した
Shaderでの実装は、変形関数を作成し、元のUV座標、変形位置、範囲Range、および強度Strengthを渡す、計算後に変形しされたサンプリング座標を生成することです。コードは次のとおりです。
#iChannel0 "src/assets/texture/joker.png"
vec2 deform(vec2 uv, vec2 center, float range, float strength) {
// TODO: 変形処理
return uv;
}
void mainImage(out vec4 fragColor, vec2 coord) {
vec2 uv = coord / iResolution.xy;
vec2 mouse = iMouse.xy / iResolution.xy;
uv = deform(uv, mouse, .5, .5);
vec3 color = texture(iChannel0, uv).rgb;
fragColor.rgb = color;
}
本記事のシェーダーコードはGLSL仕様を採用しており、プレビューの便宜上、Shader-Toyの記述方法に従っています。
変形のテクニック:サンプリング距離フィールドの変換
固定点座標Oを設定します。任意の点から点Oまでの距離はdistであり、異なるdist値を半径とし、点Oを中心にして無限の数の等距離のサンプリング円を形成できます。これは点Oの距離フィールドと呼ばれます。
サンプリング距離フィールド
サンプリング円のサイズや位置を変更することで、テクスチャのサンプリング位置を変更し、膨張や収縮、押し出しの変形効果を実現できます。
vec2 deform(vec2 uv, vec2 center, float range, float strength) {
float dist = distance(uv, center);
vec2 direction = normalize(uv - center);
dist = transform(dist, range, strength); // サンプリング円の半径を変更する
center = transform(center, dist, range, strength); // サンプリング円の中心の位置を変更する
return center + dist * direction;
}
このテクニックの適用はしばらくおいて、簡単な歪み変形から始めましょう。
ねじれ
ねじれ効果は渦形状に似ており、中心点に近いほど回転が強くなるという特徴があり、逓減関数を介して中心点からの距離dと対応する回転角θの関係を表すことができます。
下図に示すように、単純な線形関数θ= -A / R * d + Aが使用されます。ここで、Aはねじれ中心の回転角度を表し、Aは正数で回転方向が時計回りであることを意味します。負数は反時計回りを意味し、Rはねじれの境界を意味します。
ねじれ変形の原理
上図に示すように、ねじれ関数の入力パラメータA(中心回転角Angle)とR(変形範囲Range)は次のように記述できます。
1)Aは中心回転角を表し、絶対値が大きいほどねじれの度合いが大きくなります。
2)A> 0はねじれ方向が時計回りであることを意味し、そうでない場合はA<0は反時計回りを意味します。
3)Rはねじれの境界を表し、値が大きいほど影響範囲が大きくなります。
ねじれの動的効果
上図に示すように、時間変数Timeを導入して、Aの値を動的に変更し、ピエロのツイスト効果を生成できます。具体的なShaderコードは次のとおりです。
#iChannel0 "src/assets/texture/joker.png"
#define Range .3
#define Angle .5
#define SPEED 3.
mat2 rotate(float a) // 回転行列
{
float s = sin(a);
float c = cos(a);
return mat2(c,-s,s,c);
}
vec2 twirl(vec2 uv, vec2 center, float range, float angle) {
float d = distance(uv, center);
uv -=center;
// d = clamp(-angle/range * d + angle,0.,angle); // 線形方式
d = smoothstep(0., range, range-d) * angle;
uv *= rotate(d);
uv+=center;
return uv;
}
void mainImage(out vec4 fragColor, vec2 coord) {
vec2 uv = coord / iResolution.xy;
vec2 mouse = iMouse.xy / iResolution.xy;
float cTime = sin(iTime * SPEED);
uv = twirl(uv, mouse, Range, Angle * cTime);
vec4 color = texture(iChannel0, uv);
fragColor = color;
}
注目に値するのは、線形方程式でねじれの関係を表現することを除いて、smoothstep法も使用できることです。次の図に示すように、linear線形関数と比較して、smoothstep法はねじれの境界でより滑らかに見えます。
linearおよびsmoothstepねじれ方程式の効果の比較
境界の滑らかさを考慮して、次の変形方法でも線形方程式の代わりにsmoothstep関数を使用できます。
膨張/収縮
膨張の特徴は膨張中心に近いテクスチャが引き伸ばされ、膨張境界に近いテクスチャが押し出されます。つまり、膨張範囲内で、膨張中心を距離フィールドとして、サンプリングされた各円の半径を元の円よりも小さくし、円間隔は内側から外側に徐々に拡大する必要があります。
下図の右側に示すように、等間隔の黒いサンプリング円をよりまとまりのある赤いサンプリング円にマッピングすることにより、新しいサンプリング円間の間隔を内側から外側に単調に増加させます。
膨張サンプリング距離フィールド変換
平滑逓増関数smoothstepをサンプリングして、サンプリング円の半径distを介して、スケール値scaleを計算できます。
上図の関数は、膨張の中心の近くで、サンプリング円のスケーリングが最もはっきりしており、スケーリング値が最も小さいこと(1-S)を示しています。Distの増加に伴い、スケーリング値scaleがRの境界範囲に到着するまでに1に向かって逓増しつつあります。Scaleは常に1であり、サンプリング円はスケーリングされなくなりました。
float scale = (1.- S) + S * smoothstep(0.,1., dist / R); // 膨張サンプリング半径の収縮値
したがって、上記のサンプリング半径スケーリング式が得られます。ここで、Strength(0 <S <1)は膨張の程度を表すように設定されています。膨張距離場の変換過程に対しては、膨張の逆効果収縮を達成するには、Sを[-1,0]間隔に直接置くだけで済むと簡単に推測できます。
S値は伸縮の程度Strengthに対応する
上図に示すように、膨張関数の入力パラメータS(変形強度)とR(変形範囲Range)は次のように記述できます。
1)Sが[0,1]区間にある場合、膨張効果を示します。Sの値が大きいほど、膨張の度合いが高くなります。
2)Sが[-10]の間隔にある場合、収縮効果を示します。Sの値が小さいほど、収縮の度合いが高くなります。
3)Rは変形の境界を表し、値が大きいほど、影響を受ける領域が大きくなります。
動的膨張効果
上の図に示すように、呼吸アニメーションをシミュレートするために、Strengthの値を動的に変更する時間変数Timeを導入できます。具体的なシェーダーコードは次のとおりです。
#iChannel0 "src/assets/texture/joker.png"
#define SPEED 2. // 速度
#define RANGE .2 // 変形範囲
#define Strength .5 * sin(iTime * SPEED) // 変形程度
vec2 inflate(vec2 uv, vec2 center, float range, float strength) {
float dist = distance(uv , center);
vec2 dir = normalize(uv - center);
float scale = 1.-strength + strength * smoothstep(0., 1. ,dist / range);
float newDist = dist * scale;
return center + newDist * dir;
}
void mainImage(out vec4 fragColor, vec2 coord) {
vec2 uv = coord / iResolution.xy;
vec2 mouse = iMouse.xy / iResolution.xy;
uv = inflate(uv, mouse, RANGE, Strength);
vec3 color = texture(iChannel0, uv).rgb;
fragColor.rgb = color;
}
縦/横向きの引き伸ばし
元の画像-縦向きの引き伸ばし-横向きの引き伸ばし-膨張
以前の膨張は、距離フィールドのサンプリング円をスケーリングすることによって実現され、垂直/水平の引き伸ばしは、サンプリング円のx軸またはy軸のみをスケーリングすることです。一般的に、これは「長足効果」に使用されます。
横向きの引き伸しの距離フィールド変換
横方向に引き伸ばされた距離フィールドが複数の楕円形のサンプリング円に変換されることがわかります、コードの実装は次のように示す。
vec2 inflateX(vec2 uv, vec2 center, float radius, float strength) {
// この前のコードは膨張の実装と同じ
...
return center + vec2(newDist, dist) * dir; // 横に引き伸ばされると、scaleはx軸のみに機能する
}
押し出す
押し出しは通常、作用点と押し出し方向を指定します。これは、作用点の近くのテクスチャを押し出しの終了位置に押し出すことを特徴としています。
下の図に示すように、緑色の作用点Pは押し出しの開始点、矢印は押し出しベクトルVです。ここで、ベクトル方向は押し出し方向を示し、ベクトル長length(V)は押し出し距離を表し、ベクトルの終点は、押し出し後の位置です。
テクスチャの押し出しを実現するには、サンプリング円の中心を押し出しベクトルVにオフセットし、サンプリングの中心点を点Pの位置に移動する必要があります。
押し出しは距離フィールド変換を使用する
サンプリング円の半径distが内側から外側に向かって徐々に大きくなるにつれて、変換された中心オフセットoffsetは徐々に短くなります。-smoothstep平滑逓減関数を使用して、サンプリング円の半径distと円オフセットoffsetの関係を処理できます。
式:offset = length(V)-length(V)* Smoothstep(0、R、dist)、ここでRは押し出し境界の範囲を表します。
押し出しの動的効果
同様に、時間変数Timeを導入して、押し出しベクトルの長さと方向を動的に変更することにより、上記のピエロのクロッチ効果などのジッター効果を実現できます。具体的なシェーダーコードは次のとおりです。
#iChannel0 "src/assets/texture/joker.png"
#define RANGE .25 // 変形範囲
#define PINCH_VECTOR vec2( sin(iTime * 10.), cos(iTime * 20.)) * .03 // ベクトルを押し出す
vec2 pinch(vec2 uv, vec2 targetPoint, vec2 vector, float range)
{
vec2 center = targetPoint + vector;
float dist = distance(uv, targetPoint);
vec2 point = targetPoint + smoothstep(0., 1., dist / range) * vector;
return uv - center + point;
}
void mainImage(out vec4 fragColor, vec2 coord) {
vec2 uv = coord / iResolution.xy;
vec2 mouse = iMouse.xy / iResolution.xy;
uv = pinch(uv, mouse, PINCH_VECTOR, RANGE);
vec3 color = texture(iChannel0, uv).rgb;
fragColor.rgb = color;
}
まとめ
本記事では、主に3種類の局所歪みShaderの実現原理を紹介しました。このシェーダーでは、膨張/収縮効果と押し出し効果がサンプリング距離フィールド変換によって実現されます。前者はサンプリング円のサイズを変換し、後者は円の位置を変換します。
上記で紹介した3つのローカル変形以外に、波効果(wave effect)、ずれ効果、ミラーリングなど、より興味深いグローバル変形効果がいくつかあります。Shaderの実装は比較的簡単なので、これ以上紹介しません。
波―ずれーミラーリング
プレビューコードと効果
ツイスト:https://www.shadertoy.com/view/slfGzN
膨張/収縮:https://www.shadertoy.com/view/7lXGzN
押し出し/引き伸ばし:https://www.shadertoy.com/view/7tX3zN
参考資料:
GLSL基本変換:https://thebookofshaders.com/08/?lan = ch
Photoshopの押し出し効果アルゴリズム:https://blog.csdn.net/kezunhai/
UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com
UWA公式Q&Aコミュニティ(中国語注意):https://answer.uwa4d.com
これも興味あるかも
-
原理から応用まで ゲームでの動的解像度
January 4, 2023 -
Unityゲームの使用メモリを最適化しよう
December 21, 2022 -
ASTC テクスチャ圧縮形式の紹介
December 14, 2022