3D 모델 주위에 윤곽선을 그리려면 어떻게해야합니까? 최근 Pokemon 게임의 효과와 관련하여 단일 픽셀 외곽선이있는 것으로 보입니다.
3D 모델 주위에 윤곽선을 그리려면 어떻게해야합니까? 최근 Pokemon 게임의 효과와 관련하여 단일 픽셀 외곽선이있는 것으로 보입니다.
답변:
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가 쉐이더 대신 고정 기능 파이프 라인을 사용한다는 것을 어딘가에서 읽습니다 (따라서 주석에 링크를 넣을 것입니다). 나는 내 방법이 충분히 가깝다고 확신했다.
이 효과는 셀 음영 효과를 사용하는 게임에서 특히 일반적이지만 셀 음영 스타일과 독립적으로 적용 할 수있는 효과입니다.
설명하는 것을 "피처 엣지 렌더링"이라고하며 일반적으로 모델의 다양한 윤곽과 윤곽을 강조 표시하는 프로세스입니다. 주제에 대한 많은 기술과 논문이 있습니다.
간단한 기술은 가장 바깥 쪽 윤곽선 인 실루엣 가장자리 만 렌더링하는 것입니다. 이는 스텐실 쓰기로 원본 모델을 렌더링 한 다음 스텐실 값이없는 경우에만 두꺼운 와이어 프레임 모드에서 다시 렌더링하는 것처럼 간단하게 수행 할 수 있습니다. 구현 예는 여기 를 참조하십시오 .
그래도 내부 윤곽이 강조되지 않으며 가장자리가 구겨지지 않습니다 (그림 참조). 일반적으로이를 효과적으로 수행하려면 가장자리의 양쪽면 법선 불연속성에 따라 메쉬 가장자리에 대한 정보를 추출하고 각 가장자리를 나타내는 데이터 구조를 구축해야합니다.
그런 다음 쉐이더를 작성하여 해당 가장자리를 기본 모델 위에 (또는 이와 함께) 일반 지오메트리로 돌출하거나 렌더링 할 수 있습니다. 모서리의 위치와 뷰 벡터를 기준으로 인접한면의 법선을 사용하여 특정 모서리를 그릴 수 있는지 여부를 결정합니다.
인터넷에서 다양한 예를 통해 추가 토론, 세부 사항 및 논문을 찾을 수 있습니다. 예를 들면 다음과 같습니다.
dz/dx
및 / 또는dz/dy
픽셀 / 조각 셰이더 이전의 구형 하드웨어에서 일반적으로 사용되며 여전히 모바일에서 사용되는 가장 간단한 방법은 모델을 복제하고 정점 와인딩 순서를 반대로하여 모델이 내부에 표시되도록하는 것입니다 (또는 원하는 경우 블렌더와 같은 3D 에셋 생성 도구에서 표면 법선을 뒤집어 같은 방식으로이 작업을 수행 한 다음 중앙 주위를 약간 복제 한 다음 마지막으로이 복제본을 완전히 채색 / 질감하십시오. 큐브와 같은 간단한 모델 인 경우 원래 모델 주위에 윤곽이 생깁니다. 오목한 형태의 더 복잡한 모델 (예 : 아래 이미지)의 경우 복제 모델을 Minkowski Sum 과 같이 원래 모델보다 다소 "더 희미"하게 수동으로 조정해야합니다.3D로. Blender의 Shrink / Fatten 변환과 마찬가지로 각 정점을 법선을 따라 조금씩 밀어 윤곽 메쉬를 형성하는 것으로 시작할 수 있습니다.
화면 공간 / 픽셀 쉐이더 방식은 구현하기 위해 느린 어려워하는 경향이 아니라 ,하지만 OTOH는 세계에서 정점의 수를 두 배로하지 않습니다. 따라서 높은 폴리 작업을 수행하는 경우 해당 접근 방식을 선택하는 것이 가장 좋습니다. 형상을 처리하기 위해 현대 콘솔 및 데스크톱 능력을 감안할 때, 나는 2의 요인에 대해 걱정하지 않는 게 좋을 전혀 . 만화 스타일 = 낮은 폴리는 확실하므로 지오메트리 복제가 가장 쉽습니다.
코드를 건드리지 않고 Blender 등에서 직접 효과를 테스트 할 수 있습니다. 외곽선은 아래 이미지와 같아야합니다. 예를 들어 팔 아래와 같은 일부는 내부에 있습니다. 자세한 내용은 여기를 참조하십시오 .
.
들어 부드러운 모델 (매우 중요),이 효과는 매우 간단하다. 프래그먼트 / 픽셀 셰이더에서는 음영 처리되는 노멀이 필요합니다. 그것이 직각에 매우 가깝다면 ( dot(surface_normal,view_vector) <= .01
-당신은 그 임계 값을 가지고 놀아야 할 수도 있습니다), 보통의 색 대신에 검은 색 조각을 채색하십시오.
이 접근법은 개요를 수행하기 위해 약간의 모델을 "소비"합니다. 이것은 당신이 원하는 것일 수도 아닐 수도 있습니다. 이것이 수행되고 있다면 포켓몬 사진에서 말하기는 매우 어렵습니다. 윤곽선이 캐릭터의 실루엣에 포함될 것으로 기대하는지 또는 윤곽선이 실루엣을 묶어야하는지 (다른 기술이 필요함)에 달려 있습니다.
하이라이트는 표면의 어느 부분에서나 "내부 가장자리"(녹색 포켓몬의 다리 또는 머리와 같은)를 포함하여 전면에서 후면으로 전환됩니다. ).
입방체와 같이 단단하고 부드럽 지 않은 가장자리가있는 개체는이 방법으로 원하는 위치에서 강조 표시를받지 않습니다. 즉,이 방법은 경우에 따라 전혀 옵션이 아닙니다. 포켓몬 모델이 모두 부드럽 지 않은지 전혀 모르겠습니다.
내가 본 가장 일반적인 방법은 모델의 두 번째 렌더 패스를 이용하는 것입니다. 본질적으로 그것을 복제하고 법선을 뒤집어 버텍스 쉐이더에 넣습니다. 셰이더에서 각 정점의 법선을 따라 크기를 조정합니다. 픽셀 / 조각 셰이더에서 검은 색을 그립니다. 그것은 입술, 눈 등과 같은 외부 및 내부 윤곽을 모두 제공합니다. 이것은 모델의 수와 복잡성에 따라 라인을 처리하는 것보다 일반적으로 저렴한 것이 아니라면 실제로 상당히 저렴한 드로우 콜입니다. Guilty Gear Xrd는 정점 색을 통해 선의 두께를 쉽게 제어 할 수 있기 때문에이 방법을 사용합니다.
같은 게임에서 배운 내부 선을 만드는 두 번째 방법. UV 맵에서, 특히 내부 선이 필요한 영역에서 u 또는 v 축을 따라 텍스처를 정렬합니다. 축을 따라 검은 선을 그리고 UV 좌표를 그 선 안이나 밖으로 이동하여 내부 선을 만듭니다.
자세한 설명은 GDC의 비디오를 참조하십시오. https://www.youtube.com/watch?v=yhGjCzxJV3E
윤곽을 만드는 방법 중 하나는 모델 법선 벡터를 사용하는 것입니다. 법선 벡터는 표면에 수직 인 벡터입니다 (표면에서 멀어짐). 여기서 트릭은 캐릭터 모델을 두 부분으로 나누는 것입니다. 카메라를 향한 정점 및 카메라를 향한 정점 우리는 그것들을 각각 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"
}