3D 모델 주위에 윤곽선을 그리려면 어떻게해야합니까?


47

3D 모델 주위에 윤곽선을 그리려면 어떻게해야합니까? 최근 Pokemon 게임의 효과와 관련하여 단일 픽셀 외곽선이있는 것으로 보입니다.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오


OpenGL을 사용하고 있습니까? 그렇다면 Google에서 OpenGL을 사용하여 모델의 윤곽선을 그리는 방법을 검색해야합니다.
oxysoft

1
당신이 거기에 넣은 특정 이미지를 언급한다면, 나는 그것들이 3D 모델이 아니라 2D 스프라이트로 그려진다고 95 % 확신 할 수 있습니다.
Panda Pajama

3
@PandaPajama : 아니요. 거의 3D 모델입니다. 손으로 그린 ​​스프라이트에서 기대하지 않는 일부 프레임에서 딱딱한 선이 무엇인지에 대해서는 약간의 문제가 있습니다. 어쨌든 기본적으로 게임 내 3D 모델의 모습입니다. 특정 이미지에 대해 100 %를 보장 할 수는 없지만 누군가가 왜 가짜 이미지를 보려고 노력하는지 상상할 수 없습니다.
CA McCann

구체적으로 어떤 게임입니까? 화려 해 보인다.
Vegard

@Vegard 뒷면에 순무가있는 생물은 게임 포켓몬의 Bulbasaur입니다.
Damian Yerrick

답변:


28

Pokémon X / Y에서 다른 답변을 얻을 수 있다고 생각하지 않습니다. 정확히 어떻게했는지 알 수는 없지만 게임에서하는 것과 거의 같은 방법을 찾았습니다.

포켓몬 X / Y에서 윤곽선은 실루엣 가장자리와 다른 비 실루엣 가장자리 모두에 그려집니다 (예 : Raichu의 귀가 다음 스크린 샷에서 머리를 만나는 위치).

라이 추

블렌더에서 Raichu의 메시를 살펴보면 귀 (위의 주황색으로 강조 표시됨)가 머리를 가로 지르는 분리 된 분리 된 개체이므로 표면 법선이 급격히 변화합니다.

이를 바탕으로 법선을 기반으로 윤곽선을 생성하려고 시도했지만 두 단계로 렌더링해야합니다.

첫 번째 패스 : 외곽선없이 모델 (텍스처 및 셀 음영 처리)을 렌더링하고 카메라 공간 법선을 두 번째 렌더링 대상으로 렌더링합니다.

두 번째 패스 : 첫 번째 패스의 법선에 대해 전체 화면 에지 감지 필터를 수행합니다.

아래의 첫 두 이미지는 첫 번째 패스의 출력을 보여줍니다. 세 번째는 개요 자체이며, 마지막은 최종 결합 결과입니다.

드라 티니

다음은 두 번째 패스에서 가장자리 감지에 사용한 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가 쉐이더 대신 고정 기능 파이프 라인을 사용한다는 것을 어딘가에서 읽습니다 (따라서 주석에 링크를 넣을 것입니다). 나는 내 방법이 충분히 가깝다고 확신했다.


Nintendo 3DS 하드웨어에 대한 정보 : link
KTC

아주 좋은 해결책! 셰이더에서 깊이를 어떻게 고려 하시겠습니까? (예를 들어 비행기가 다른 비행기 앞에있는 경우 둘 다 같은 법선을 가지므로 외곽선이 그려지지 않습니다)
ingham

@ingham이 사건은 내가 처리 할 필요가없는 유기적 인 캐릭터에서 드물게 나타나며 실제 게임도 처리하지 않는 것처럼 보입니다. 실제 게임에서는 법선이 같을 때 윤곽선이 사라지는 것을 볼 수 있지만 사람들이 일반적으로 눈치 채지 못할 것이라고 생각합니다.
KTC

나는 3DS가 그와 같은 쉐이더 기반의 전체 화면 효과를 실행할 수 있다는 것에 회의적입니다. 셰이더 지원은 초보적입니다 (있는 경우).
Tara

17

이 효과는 셀 음영 효과를 사용하는 게임에서 특히 일반적이지만 셀 음영 스타일과 독립적으로 적용 할 수있는 효과입니다.

설명하는 것을 "피처 엣지 렌더링"이라고하며 일반적으로 모델의 다양한 윤곽과 윤곽을 강조 표시하는 프로세스입니다. 주제에 대한 많은 기술과 논문이 있습니다.

간단한 기술은 가장 바깥 쪽 윤곽선 인 실루엣 가장자리 만 렌더링하는 것입니다. 이는 스텐실 쓰기로 원본 모델을 렌더링 한 다음 스텐실 값이없는 경우에만 두꺼운 와이어 프레임 모드에서 다시 렌더링하는 것처럼 간단하게 수행 할 수 있습니다. 구현 예는 여기참조하십시오 .

그래도 내부 윤곽이 강조되지 않으며 가장자리가 구겨지지 않습니다 (그림 참조). 일반적으로이를 효과적으로 수행하려면 가장자리의 양쪽면 법선 불연속성에 따라 메쉬 가장자리에 대한 정보를 추출하고 각 가장자리를 나타내는 데이터 구조를 구축해야합니다.

그런 다음 쉐이더를 작성하여 해당 가장자리를 기본 모델 위에 (또는 이와 함께) 일반 지오메트리로 돌출하거나 렌더링 할 수 있습니다. 모서리의 위치와 뷰 벡터를 기준으로 인접한면의 법선을 사용하여 특정 모서리를 그릴 수 있는지 여부를 결정합니다.

인터넷에서 다양한 예를 통해 추가 토론, 세부 사항 및 논문을 찾을 수 있습니다. 예를 들면 다음과 같습니다.


1
flipcode.com의 스텐실 방법이 작동하고 정말 멋지다는 것을 확인할 수 있습니다. 외곽선의 두께가 모델의 크기 (모델의 모양에 따라 다름)에 의존하지 않도록 화면 좌표에 두께를 지정할 수 있습니다.
Vegard

1
하나 개의 기술 당신은하지 언급은 종종 CEL-음영과 함께 사용 후 처리 국경 음영 효과 않았다 고와 픽셀 외모 dz/dx및 / 또는dz/dy
bcrist

8

픽셀 / 조각 셰이더 이전의 구형 하드웨어에서 일반적으로 사용되며 여전히 모바일에서 사용되는 가장 간단한 방법은 모델을 복제하고 정점 와인딩 순서를 반대로하여 모델이 내부에 표시되도록하는 것입니다 (또는 원하는 경우 블렌더와 같은 3D 에셋 생성 도구에서 표면 법선을 뒤집어 같은 방식으로이 작업을 수행 한 다음 중앙 주위를 약간 복제 한 다음 마지막으로이 복제본을 완전히 채색 / 질감하십시오. 큐브와 같은 간단한 모델 인 경우 원래 모델 주위에 윤곽이 생깁니다. 오목한 형태의 더 복잡한 모델 (예 : 아래 이미지)의 경우 복제 모델을 Minkowski Sum 과 같이 원래 모델보다 다소 "더 희미"하게 수동으로 조정해야합니다.3D로. Blender의 Shrink / Fatten 변환과 마찬가지로 각 정점을 법선을 따라 조금씩 밀어 윤곽 메쉬를 형성하는 것으로 시작할 수 있습니다.

화면 공간 / 픽셀 쉐이더 방식은 구현하기 위해 느린 어려워하는 경향이 아니라 ,하지만 OTOH는 세계에서 정점의 수를 두 배로하지 않습니다. 따라서 높은 폴리 작업을 수행하는 경우 해당 접근 방식을 선택하는 것이 가장 좋습니다. 형상을 처리하기 위해 현대 콘솔 및 데스크톱 능력을 감안할 때, 나는 2의 요인에 대해 걱정하지 않는 게 좋을 전혀 . 만화 스타일 = 낮은 폴리는 확실하므로 지오메트리 복제가 가장 쉽습니다.

코드를 건드리지 않고 Blender 등에서 직접 효과를 테스트 할 수 있습니다. 외곽선은 아래 이미지와 같아야합니다. 예를 들어 팔 아래와 같은 일부는 내부에 있습니다. 자세한 내용은 여기를 참조하십시오 .

여기에 이미지 설명을 입력하십시오.


1
센터 주변의 간단한 스케일링이 동심이 아닌 팔 및 기타 부품에는 작동하지 않기 때문에 "중앙 주변을 약간 확대하여 중앙에 약간 확대"하는 방법에 대해 설명해 주시겠습니까? 구멍.
크롬 스터, 모니카

@KromStern 경우에 따라서 는 꼭짓점의 하위 집합을 수동으로 조정해야합니다. 수정 된 답변.
엔지니어

1
정점을 로컬 표면 법선을 따라 조금씩 밀어내는 것이 일반적이지만 확장 된 윤곽선 메쉬가 단단한 가장자리를 따라 분할 될 수 있습니다.
DMGregory

감사! 복제본이 평평한 단색으로 표시되는 경우 (즉, 법선에 의존하는 멋진 조명 계산이 없음) 법선을 뒤집는 데는 아무런 의미가 없다고 생각합니다. 스케일링, 플랫 컬러링 및 복제본의 전면을 컬링하여 동일한 효과를 얻었습니다.
Jet Blue

6

들어 부드러운 모델 (매우 중요),이 효과는 매우 간단하다. 프래그먼트 / 픽셀 셰이더에서는 음영 처리되는 노멀이 필요합니다. 그것이 직각에 매우 가깝다면 ( dot(surface_normal,view_vector) <= .01-당신은 그 임계 값을 가지고 놀아야 할 수도 있습니다), 보통의 색 대신에 검은 색 조각을 채색하십시오.

이 접근법은 개요를 수행하기 위해 약간의 모델을 "소비"합니다. 이것은 당신이 원하는 것일 수도 아닐 수도 있습니다. 이것이 수행되고 있다면 포켓몬 사진에서 말하기는 매우 어렵습니다. 윤곽선이 캐릭터의 실루엣에 포함될 것으로 기대하는지 또는 윤곽선이 실루엣을 묶어야하는지 (다른 기술이 필요함)에 달려 있습니다.

하이라이트는 표면의 어느 부분에서나 "내부 가장자리"(녹색 포켓몬의 다리 또는 머리와 같은)를 포함하여 전면에서 후면으로 전환됩니다. ).

입방체와 같이 단단하고 부드럽 지 않은 가장자리가있는 개체는이 방법으로 원하는 위치에서 강조 표시를받지 않습니다. 즉,이 방법은 경우에 따라 전혀 옵션이 아닙니다. 포켓몬 모델이 모두 부드럽 지 않은지 전혀 모르겠습니다.


5

내가 본 가장 일반적인 방법은 모델의 두 번째 렌더 패스를 이용하는 것입니다. 본질적으로 그것을 복제하고 법선을 뒤집어 버텍스 쉐이더에 넣습니다. 셰이더에서 각 정점의 법선을 따라 크기를 조정합니다. 픽셀 / 조각 셰이더에서 검은 색을 그립니다. 그것은 입술, 눈 등과 같은 외부 및 내부 윤곽을 모두 제공합니다. 이것은 모델의 수와 복잡성에 따라 라인을 처리하는 것보다 일반적으로 저렴한 것이 아니라면 실제로 상당히 저렴한 드로우 콜입니다. Guilty Gear Xrd는 정점 색을 통해 선의 두께를 쉽게 제어 할 수 있기 때문에이 방법을 사용합니다.

같은 게임에서 배운 내부 선을 만드는 두 번째 방법. UV 맵에서, 특히 내부 선이 필요한 영역에서 u 또는 v 축을 따라 텍스처를 정렬합니다. 축을 따라 검은 선을 그리고 UV 좌표를 그 선 안이나 밖으로 이동하여 내부 선을 만듭니다.

자세한 설명은 GDC의 비디오를 참조하십시오. https://www.youtube.com/watch?v=yhGjCzxJV3E


5

윤곽을 만드는 방법 중 하나는 모델 법선 벡터를 사용하는 것입니다. 법선 벡터는 표면에 수직 인 벡터입니다 (표면에서 멀어짐). 여기서 트릭은 캐릭터 모델을 두 부분으로 나누는 것입니다. 카메라를 향한 정점 및 카메라를 향한 정점 우리는 그것들을 각각 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 행 :“Cull Front”설정은 셰이더가 정면 정점에서 컬링을 수행하도록 지시합니다. 이는이 패스에서 모든 정면 정점을 무시한다는 의미입니다. 약간 조작하고 싶은 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"
        }

업데이트 된 예에서 스텐실 버퍼를 사용하는 이유는 무엇입니까?
타라

아 이제 알았어 두 번째 예는 첫 번째와 달리 외부 외곽선 만 생성하는 방식을 사용합니다. 당신은 당신의 대답에서 그것을 언급하고 싶을 수도 있습니다.
Tara

0

이를 수행하는 가장 좋은 방법 중 하나는 장면을 프레임 버퍼 텍스처에 렌더링 한 다음 모든 픽셀에 대해 Sobel 필터링 을 수행하는 동안 해당 텍스처를 렌더링하는 것 입니다. 이는 가장자리 감지를위한 쉬운 기술입니다. 이 방법으로 장면을 픽셀 화 (프레임 버퍼 텍스처에 낮은 해상도 설정) 할 수있을뿐만 아니라 모든 픽셀 값에 액세스하여 Sobel을 작동시킬 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.