3Dモデルの周囲にアウトラインを描画するにはどうすればよいですか?私は最近のポケモンゲームのエフェクトのようなものを言及していますが、その周囲にはシングルピクセルの輪郭があります:
3Dモデルの周囲にアウトラインを描画するにはどうすればよいですか?私は最近のポケモンゲームのエフェクトのようなものを言及していますが、その周囲にはシングルピクセルの輪郭があります:
回答:
ここでの他の答えがポケモンX / Yで効果を達成することはないと思います。私はそれがどのように行われたのか正確に知ることはできませんが、私は彼らがゲームでほとんど何をするかのように見える方法を見つけました。
ポケモンX / Yでは、シルエットの縁の周りと、シルエット以外の縁の両方にアウトラインが描かれます(次のスクリーンショットでライチュウの耳が頭と出会う場所など)。
BlenderのRaichuのメッシュを見ると、耳(上のオレンジ色で強調表示されている)が、頭部と交差する別個の切断されたオブジェクトであり、表面の法線に急激な変化が生じていることがわかります。
それに基づいて、法線に基づいてアウトラインを生成しようとしました。これには、2つのパスでのレンダリングが必要です。
最初のパス:モデル(テクスチャおよびセルシェーディング)をアウトラインなしでレンダリングし、カメラ空間法線を2番目のレンダーターゲットにレンダーします。
2番目のパス:最初のパスからの法線に対してフルスクリーンエッジ検出フィルターを実行します。
以下の最初の2つの画像は、最初のパスの出力を示しています。3番目はアウトライン自体であり、最後は最終的な結合結果です。
これが、2回目のパスでエッジ検出に使用したOpenGLフラグメントシェーダーです。それは私が思いついた最高のものですが、より良い方法があるかもしれません。おそらくあまり最適化されていません。
// first render target from the first pass
uniform sampler2D uTexColor;
// second render target from the first pass
uniform sampler2D uTexNormals;
uniform vec2 uResolution;
in vec2 fsInUV;
out vec4 fsOut0;
void main(void)
{
float dx = 1.0 / uResolution.x;
float dy = 1.0 / uResolution.y;
vec3 center = sampleNrm( uTexNormals, vec2(0.0, 0.0) );
// sampling just these 3 neighboring fragments keeps the outline thin.
vec3 top = sampleNrm( uTexNormals, vec2(0.0, dy) );
vec3 topRight = sampleNrm( uTexNormals, vec2(dx, dy) );
vec3 right = sampleNrm( uTexNormals, vec2(dx, 0.0) );
// the rest is pretty arbitrary, but seemed to give me the
// best-looking results for whatever reason.
vec3 t = center - top;
vec3 r = center - right;
vec3 tr = center - topRight;
t = abs( t );
r = abs( r );
tr = abs( tr );
float n;
n = max( n, t.x );
n = max( n, t.y );
n = max( n, t.z );
n = max( n, r.x );
n = max( n, r.y );
n = max( n, r.z );
n = max( n, tr.x );
n = max( n, tr.y );
n = max( n, tr.z );
// threshold and scale.
n = 1.0 - clamp( clamp((n * 2.0) - 0.8, 0.0, 1.0) * 1.5, 0.0, 1.0 );
fsOut0.rgb = texture(uTexColor, fsInUV).rgb * (0.1 + 0.9*n);
}
そして、最初のパスをレンダリングする前に、法線のレンダリングターゲットを、カメラとは反対側のベクトルにクリアします。
glDrawBuffer( GL_COLOR_ATTACHMENT1 );
Vec3f clearVec( 0.0, 0.0, -1.0f );
// from normalized vector to rgb color; from [-1,1] to [0,1]
clearVec = (clearVec + Vec3f(1.0f, 1.0f, 1.0f)) * 0.5f;
glClearColor( clearVec.x, clearVec.y, clearVec.z, 0.0f );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
ニンテンドー3DSはシェーダーの代わりに固定機能パイプラインを使用していることをどこかに読んでいます(コメントにリンクを入れます)私の方法は十分に近いと確信しています。
この効果は、セルシェーディング効果を使用するゲームで特に一般的ですが、実際にはセルシェーディングスタイルとは無関係に適用できるものです。
あなたが説明しているものは「フィーチャーエッジレンダリング」と呼ばれ、一般的にモデルのさまざまな輪郭や輪郭を強調するプロセスです。利用可能な多くのテクニックと主題に関する多くの論文があります。
シンプルな手法は、シルエットのエッジ、最も外側の輪郭のみをレンダリングすることです。これは、ステンシル書き込みで元のモデルをレンダリングし、次にステンシル値がなかった場合にのみ、太いワイヤフレームモードで再度レンダリングするのと同じように実行できます。実装例については、こちらをご覧ください。
ただし、内部の輪郭は強調されず、エッジは折り目が付けられます(写真に示すとおり)。一般的に、これを効果的に行うには、メッシュのエッジに関する情報を抽出する必要があります(エッジの両側の面法線の不連続性に基づいて、各エッジを表すデータ構造を構築します)。
次に、シェーダーを作成して、それらのエッジをベースモデルの上に(またはそれと一緒に)通常のジオメトリとして押し出したり、レンダリングしたりできます。エッジの位置、およびビューベクトルに対する相対的な隣接面の法線は、特定のエッジを描画できるかどうかを判断するために使用されます。
インターネットでさまざまな例を使って、さらに議論、詳細、論文を見つけることができます。例えば:
dz/dx
、および/またはdz/dy
これを行う最も簡単な方法は、ピクセル/フラグメントシェーダーの前の古いハードウェアで一般的であり、モバイルでも使用されますが、モデルを複製し、頂点の巻き順を逆にしてモデルが裏返しに表示されるようにします(または必要に応じて、 Blenderなどの3Dアセット作成ツールでサーフェス法線を反転させてこれを行います(同じことです)。次に、複製の全体を中央付近でわずかに拡張し、最後にこの複製を完全に黒に色付け/テクスチャリングします。これにより、元のモデルが立方体などの単純なモデルである場合、元のモデルの周囲にアウトラインが作成されます。凹形のより複雑なモデル(下の画像のような)の場合、複製モデルを手動で微調整して、ミンコフスキー和のように元のモデルよりやや「太く」する必要があります。3Dで。BlenderのShrink / Fattenトランスフォームが行うように、各頂点を法線に沿って少し押し出してアウトラインメッシュを形成することから始めることができます。
画面スペース/ピクセルシェーダアプローチは実装に遅くなると難しくなる傾向があるだけでなく、しかし、大藤は、あなたの世界の頂点の数を倍増していません。したがって、高ポリゴンの作業を行う場合は、そのアプローチを選択するのが最適です。ジオメトリを処理するために近代的なコンソールおよびデスクトップの容量を考えると、私は2の要因を心配していないと思いますすべての。Cartoon-style = low polyは確かなので、ジオメトリを複製するのが最も簡単です。
たとえば、Blenderでコードに触れることなく、自分で効果をテストできます。アウトラインは下の画像のように見えるはずです。一部が内部、たとえば脇の下にあることに注意してください。詳細はこちら。
。
以下のためにスムーズなモデル(非常に重要)、この効果は非常に簡単です。フラグメント/ピクセルシェーダーでは、シェーディングされるフラグメントの法線が必要になります。垂直に非常に近い場合(dot(surface_normal,view_vector) <= .01
-そのしきい値で遊ぶ必要があるかもしれません)、フラグメントの色を通常の色の代わりに黒にします。
このアプローチは、モデルの一部を使用してアウトラインを「消費」します。これは、必要な場合とそうでない場合があります。これが行われていることであるかどうかをポケモンの絵から伝えることは非常に困難です。アウトラインがキャラクターのシルエットに含まれることを期待するか、またはアウトラインをシルエットで囲む必要があるかによって異なります(異なるテクニックが必要)。
ハイライトは、「内側の端」(緑のポケモンの脚や頭など)を含む、表面から裏面に移行する表面の任意の部分になります。他のテクニックでは輪郭が追加されません。 )。
このアプローチでは、硬くて滑らかでないエッジ(立方体など)を持つオブジェクトは、目的の場所でハイライトを受け取りません。つまり、このアプローチは場合によってはまったくオプションではありません。ポケモンのモデルがすべてスムーズかどうかはわかりません。
私がこれを確認した最も一般的な方法は、モデルの2番目のレンダーパスを使用することです。基本的に、複製して法線を反転し、頂点シェーダーに押し込みます。シェーダーで、各頂点をその法線に沿ってスケーリングします。ピクセル/フラグメントシェーダーで、黒を描画します。これは、唇、目の周りなど、外部と内部の両方のアウトラインを提供します。これは、実際にはかなり安いドローコールです。Guilty Gear Xrdは、頂点カラーを使用して線の太さを簡単に制御できるため、この方法を使用します。
同じゲームから学んだ内線の2番目の方法。UVマップで、特に内側の線が必要な領域で、u軸またはv軸に沿ってテクスチャを揃えます。いずれかの軸に沿って黒い線を描画し、UV座標をその線の内外に移動して、内側の線を作成します。
より良い説明については、GDCのビデオをご覧ください:https : //www.youtube.com/watch?v=yhGjCzxJV3E
アウトラインを作成する方法の1つは、モデルの法線ベクトルを使用することです。法線ベクトルは、表面に垂直な(表面から離れる方向を向く)ベクトルです。ここでのコツは、キャラクターモデルを2つの部分に分割することです。カメラに面している頂点とカメラから離れた面にある頂点。それらをそれぞれFRONTおよびBACKと呼びます。
アウトラインについては、BACK頂点を取り、法線ベクトルの方向にわずかに移動します。私たちのキャラクターのカメラとは反対側の部分を少し太くするようなものだと考えてください。それが完了したら、選択した色を割り当て、素敵なアウトラインを作成します。
Shader "Custom/OutlineShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Outline("Outline Thickness", Range(0.0, 0.3)) = 0.002
_OutlineColor("Outline Color", Color) = (0,0,0,1)
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_ST;
half _Outline;
half4 _OutlineColor;
struct appdata {
half4 vertex : POSITION;
half4 uv : TEXCOORD0;
half3 normal : NORMAL;
fixed4 color : COLOR;
};
struct v2f {
half4 pos : POSITION;
half2 uv : TEXCOORD0;
fixed4 color : COLOR;
};
ENDCG
SubShader
{
Tags {
"RenderType"="Opaque"
"Queue" = "Transparent"
}
Pass{
Name "OUTLINE"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
half3 norm = mul((half3x3)UNITY_MATRIX_IT_MV, v.normal);
half2 offset = TransformViewToProjection(norm.xy);
o.pos.xy += offset * o.pos.z * _Outline;
o.color = _OutlineColor;
return o;
}
fixed4 frag(v2f i) : COLOR
{
fixed4 o;
o = i.color;
return o;
}
ENDCG
}
Pass
{
Name "TEXTURE"
Cull Back
ZWrite On
ZTest LEqual
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.color = v.color;
return o;
}
fixed4 frag(v2f i) : COLOR
{
fixed4 o;
o = tex2D(_MainTex, i.uv.xy);
return o;
}
ENDCG
}
}
}
41行目:「前面をカル」設定は、シェーダーに、正面の頂点でカリングを実行するように指示します。これは、このパスのすべての正面向きの頂点を無視することを意味します。少し操作したいBACKサイドが残っています。
51〜53行目:法線ベクトルに沿って頂点を移動する数学。
54行目:頂点の色を、シェーダープロパティで定義された選択色に設定します。
便利なリンク:http : //wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse
もう一つの例
Shader "Custom/CustomOutline" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_Outline ("Outline Color", Color) = (0,0,0,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Size ("Outline Thickness", Float) = 1.5
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
// render outline
Pass {
Stencil {
Ref 1
Comp NotEqual
}
Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _Size;
fixed4 _Outline;
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert (appdata_base v) {
v2f o;
v.vertex.xyz += v.normal * _Size;
o.pos = UnityObjectToClipPos (v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
return _Outline;
}
ENDCG
}
Tags { "RenderType"="Opaque" }
LOD 200
// render model
Stencil {
Ref 1
Comp always
Pass replace
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
それを行う素晴らしい方法の1つは、シーンをフレームバッファーテクスチャにレンダリングし、すべてのピクセルでSobelフィルタリングを実行しながらそのテクスチャをレンダリングすることです。これは、エッジ検出の簡単な手法です。この方法では、シーンをピクセル化する(フレームバッファーテクスチャに低解像度を設定する)だけでなく、すべてのピクセル値にアクセスしてSobelを機能させることもできます。