3Dモデルの周囲にアウトラインを描画するにはどうすればよいですか?


47

3Dモデルの周囲にアウトラインを描画するにはどうすればよいですか?私は最近のポケモンゲームのエフェクトのようなものを言及していますが、その周囲にはシングルピクセルの輪郭があります:

ここに画像の説明を入力してください ここに画像の説明を入力してください


OpenGLを使用していますか?その場合、OpenGLを使用してモデルのアウトラインを描画する方法をGoogleで検索する必要があります。
オキシソフト14年

1
あなたがそこに入れ、それらの特定の画像を参照している場合、私はそれらを手2Dスプライトではなく、3Dモデル描画されていることを95%の確信を持って言うことができる
パンダパジャマを

3
@PandaPajama:いいえ、それらはほぼ確実に3Dモデルです。手描きのスプライトからは期待できない、いくつかのフレームのハードラインにあるべきだらけの部分があり、とにかく基本的にゲーム内の3Dモデルがどのように見えるかです。私はそれらの特定の画像について100%を保証することはできないと思いますが、だれかがそれらを偽造する努力をする理由を想像することはできません。
CAマッキャン14

具体的にはどのゲームですか?ゴージャスに見えます。
ベガード14

@Vegard背中にカブのいる生き物は、ゲームポケモンのフシギダネです。
ダミアンジェリック14年

回答:


28

ここでの他の答えがポケモン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はシェーダーの代わりに固定機能パイプラインを使用していることをどこかに読んでいます(コメントにリンクを入れます)私の方法は十分に近いと確信しています。


ニンテンドー3DSハードウェアに関する情報:リンク
KTC 14年

非常に素晴らしい解決策!シェーダーで深度をどのように考慮しますか?(たとえば、別の面の前にある平面の場合、両方とも同じ法線を持っているため、輪郭は描画されません)
ingham

@inghamそのケースは、私がそれを処理する必要がなかったオーガニックキャラクターで十分にまれに発生し、実際のゲームもそれを処理しないように見えます。実際のゲームでは、法線が同じ場合にアウトラインが消えることが時々ありますが、私は人々が通常それに気付かないと思います。
KTC

私は、3DSがそのようなシェーダーベースのフルスクリーンエフェクトを実行できることについて少し懐疑的です。そのシェーダーのサポートは初歩的です(もしあれば)。
タラ

17

この効果は、セルシェーディング効果を使用するゲームで特に一般的ですが、実際にはセルシェーディングスタイルとは無関係に適用できるものです。

あなたが説明しているものは「フィーチャーエッジレンダリング」と呼ばれ、一般的にモデルのさまざまな輪郭や輪郭を強調するプロセスです。利用可能な多くのテクニックと主題に関する多くの論文があります。

シンプルな手法は、シルエットのエッジ、最も外側の輪郭のみをレンダリングすることです。これは、ステンシル書き込みで元のモデルをレンダリングし、次にステンシル値がなかった場合にのみ、太いワイヤフレームモードで再度レンダリングするのと同じように実行できます。実装例については、こちらご覧ください

ただし、内部の輪郭は強調されず、エッジは折り目が付けられます(写真に示すとおり)。一般的に、これを効果的に行うには、メッシュのエッジに関する情報を抽出する必要があります(エッジの両側の面法線の不連続性に基づいて、各エッジを表すデータ構造を構築します)。

次に、シェーダーを作成して、それらのエッジをベースモデルの上に(またはそれと一緒に)通常のジオメトリとして押し出したり、レンダリングしたりできます。エッジの位置、およびビューベクトルに対する相対的な隣接面の法線は、特定のエッジを描画できるかどうかを判断するために使用されます。

インターネットでさまざまな例を使って、さらに議論、詳細、論文を見つけることができます。例えば:


1
(flipcode.comからの)ステンシルメソッドが機能し、本当に見栄えが良いことを確認できます。画面座標で太さを指定して、アウトラインの太さがモデルのサイズに依存しないようにすることができます(モデルの形状にも依存しません)。
ベガード14

1
あなたが言及しなかった一つの技術は、多くの場合、高いとのピクセルを探しCEL-シェーディングと併せて使用後処理国境シェーディング効果でありdz/dx、および/またはdz/dy
bcrist

8

これを行う最も簡単な方法は、ピクセル/フラグメントシェーダーの前の古いハードウェアで一般的であり、モバイルでも使用されますが、モデルを複製し、頂点の巻き順を逆にしてモデルが裏返しに表示されるようにします(または必要に応じて、 Blenderなどの3Dアセット作成ツールでサーフェス法線を反転させてこれを行います(同じことです)。次に、複製の全体を中央付近でわずかに拡張し、最後にこの複製を完全に黒に色付け/テクスチャリングします。これにより、元のモデルが立方体などの単純なモデルである場合、元のモデルの周囲にアウトラインが作成されます。凹形のより複雑なモデル(下の画像のような)の場合、複製モデルを手動で微調整して、ミンコフスキー和のように元のモデルよりやや「太く」する必要があります。3Dで。BlenderのShrink / Fattenトランスフォームが行うように、各頂点を法線に沿って少し押し出してアウトラインメッシュを形成することから始めることができます。

画面スペース/ピクセルシェーダアプローチは実装に遅くなると難しくなる傾向があるだけでなく、しかし、大藤は、あなたの世界の頂点の数を倍増していません。したがって、高ポリゴンの作業を行う場合は、そのアプローチを選択するのが最適です。ジオメトリを処理するために近代的なコンソールおよびデスクトップの容量を考えると、私は2の要因を心配していないと思いますすべての。Cartoon-style = low polyは確かなので、ジオメトリを複製するのが最も簡単です。

たとえば、Blenderでコードに触れることなく、自分で効果をテストできます。アウトラインは下の画像のように見えるはずです。一部が内部、たとえば脇の下にあることに注意してください。詳細はこちら

ここに画像の説明を入力してください


1
中心の周りの単純なスケーリングは、同心ではない腕や他の部分では機能しないため、「中心の周りの複製全体をわずかに拡張する」がこの図にどのように適合するかを説明してもらえますか?その穴。
Kromsterは、サポートモニカ14

@KromStern 場合によっては、対応するために頂点のサブセットを手動でスケーリングする必要があります。修正された回答。
エンジニア14

1
これは、通常、地元の表面に沿って頂点を微調整するのが一般的だが、これは、拡張アウトラインがハードエッジに沿って分割するメッシュ引き起こす可能性があります
DMGregory

ありがとう!複製が平らな単色に着色されることを考えると、法線を反転させることに意味はないと思います(つまり、法線に依存する派手な照明計算はありません)。スケーリング、フラットカラーリング、および複製の前面のカリングによって、同じ効果を達成しました。
ジェットブルー

6

以下のためにスムーズなモデル(非常に重要)、この効果は非常に簡単です。フラグメント/ピクセルシェーダーでは、シェーディングされるフラグメントの法線が必要になります。垂直に非常に近い場合(dot(surface_normal,view_vector) <= .01-そのしきい値で遊ぶ必要があるかもしれません)、フラグメントの色を通常の色の代わりに黒にします。

このアプローチは、モデルの一部を使用してアウトラインを「消費」します。これは、必要な場合とそうでない場合があります。これが行われていることであるかどうかをポケモンの絵から伝えることは非常に困難です。アウトラインがキャラクターのシルエットに含まれることを期待するか、またはアウトラインをシルエットで囲む必要があるかによって異なります(異なるテクニックが必要)。

ハイライトは、「内側の端」(緑のポケモンの脚や頭など)を含む、表面から裏面に移行する表面の任意の部分になります。他のテクニックでは輪郭が追加されません。 )。

このアプローチでは、硬くて滑らかでないエッジ(立方体など)を持つオブジェクトは、目的の場所でハイライトを受け取りません。つまり、このアプローチは場合によってはまったくオプションではありません。ポケモンのモデルがすべてスムーズかどうかはわかりません。


5

私がこれを確認した最も一般的な方法は、モデルの2番目のレンダーパスを使用することです。基本的に、複製して法線を反転し、頂点シェーダーに押し込みます。シェーダーで、各頂点をその法線に沿ってスケーリングします。ピクセル/フラグメントシェーダーで、黒を描画します。これは、唇、目の周りなど、外部と内部の両方のアウトラインを提供します。これは、実際にはかなり安いドローコールです。Guilty Gear Xrdは、頂点カラーを使用して線の太さを簡単に制御できるため、この方法を使用します。

同じゲームから学んだ内線の2番目の方法。UVマップで、特に内側の線が必要な領域で、u軸またはv軸に沿ってテクスチャを揃えます。いずれかの軸に沿って黒い線を描画し、UV座標をその線の内外に移動して、内側の線を作成します。

より良い説明については、GDCのビデオをご覧くださいhttps : //www.youtube.com/watch?v=yhGjCzxJV3E


5

アウトラインを作成する方法の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"
        }

更新された例でステンシルバッファーを使用する理由
タラ

ああ、私は今それを得た。2番目の例では、最初のアウトラインとは異なり、外部アウトラインのみを生成するアプローチを使用しています。あなたの答えでそれを言及したいと思うかもしれません。
タラ

0

それを行う素晴らしい方法の1つは、シーンをフレームバッファーテクスチャにレンダリングし、すべてのピクセルでSobelフィルタリングを実行しながらそのテクスチャをレンダリングすることです。これは、エッジ検出の簡単な手法です。この方法では、シーンをピクセル化する(フレームバッファーテクスチャに低解像度を設定する)だけでなく、すべてのピクセル値にアクセスしてSobelを機能させることもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.