3B modellerin etrafına nasıl anahat çizebilirim? Son zamanlarda Pokemon oyunundaki efektler gibi bir şeyden bahsediyorum.
3B modellerin etrafına nasıl anahat çizebilirim? Son zamanlarda Pokemon oyunundaki efektler gibi bir şeyden bahsediyorum.
Yanıtlar:
Pokémon X / Y'de buradaki diğer cevapların hiçbirinin etkisini göstereceğini sanmıyorum. Nasıl yapıldığını tam olarak bilemiyorum ama oyunda yaptıkları gibi görünen bir yol buldum.
Pokémon X / Y'de, ana hatlar hem siluet kenarlarının etrafına hem de diğer siluet olmayan kenarların üzerine çizilir (Raichu'nun kulaklarının başını aşağıdaki ekran görüntüsünde bulduğu gibi).
Raichu'nun Blender'daki ağına baktığımızda, kulağın (yukarıda turuncu ile vurgulanmış) yalnızca kafa ile kesişen ve yüzey normallerinde ani bir değişiklik yaratan, ayrı, bağlantısı kesilmiş bir nesne olduğunu görebilirsiniz.
Buna dayanarak, iki geçişte render gerektiren normları temel alan bir taslak oluşturmaya çalıştım:
İlk geçiş : Modeli (dokulu ve cel-gölgeli) ana hatları olmadan oluşturun ve kamera alanı normallerini ikinci bir oluşturma hedefine dönüştürün.
İkinci geçiş : İlk geçişten itibaren normallerin üzerinde bir tam ekran kenar algılama filtresi yapın.
Aşağıdaki ilk iki resim ilk geçişin çıktılarını göstermektedir. Üçüncüsü, kendi başına taslak ve sonuncusu nihai birleştirilmiş sonuçtur.
İşte ikinci geçişte kenar tespiti için kullandığım OpenGL parça gölgelendiricisi. Bulabildiğim en iyi şeydi, ama daha iyi bir yol olabilir. Muhtemelen de çok iyi bir şekilde optimize edilmemiştir.
// 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);
}
Ve ilk pasoyu oluşturmadan önce normallerin renderleme hedefini kameradan uzağa bakan bir vektöre yönlendiririm:
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 );
Bir yere okudum (yorumlara bir bağlantı koyacağım) Nintendo 3DS'in gölgelendirici yerine sabit işlevli bir boru hattı kullandığını, bu yüzden oyunda tam olarak böyle olamayacağını düşünüyorum, ama şimdilik benim yöntemim yeterince yakın olduğuna ikna oldum.
Bu etki, özellikle cel gölgeleme efektlerini kullanan oyunlarda yaygındır, ancak aslında cel gölgeleme stilinden bağımsız olarak uygulanabilecek bir şeydir.
Tanımladığınız şeye "özellik kenarı oluşturma" adı verilir ve genel olarak bir modelin çeşitli hatlarını ve ana hatlarını vurgulama işlemidir. Konuyla ilgili birçok teknik ve makale var.
Basit bir teknik, sadece dış kenar çizgisi olan siluet kenarını oluşturmaktır. Bu, basit bir modelin bir şablon yazı ile orijinal model haline getirilmesi ve daha sonra tekrar şablon değerinin olmadığı kalın tel kafes modunda tekrar yapılması gibi yapılabilir. Örnek bir uygulama için buraya bakınız .
Bu, iç hatlardaki konturu ve kenar çizgilerini vurgulamayacaktır (resimlerde gösterildiği gibi). Genel olarak, bunu etkin bir şekilde yapmak için, mesh'in kenarları hakkında bilgiyi çıkarmanız gerekir (kenarın iki tarafında da yüz normlarındaki süreksizliklere dayanarak) ve her bir kenarı temsil eden bir veri yapısı oluşturmalısınız.
Daha sonra, bu kenarları temel modelinize göre (veya bununla bağlantılı olarak) üste gelecek şekilde geometri olarak çıkarmak için gölgelendiriciler yazabilirsiniz. Belirli bir kenarın çizilip çizilemeyeceğini belirlemek için bir kenarın konumu ve görünüm vektörüne göre bitişik yüzlerin normalleri kullanılır.
İnternette çeşitli örneklerle daha fazla tartışma, detay ve makale bulabilirsiniz. Örneğin:
dz/dx
ve / veyadz/dy
Bunu yapmanın en basit yolu, eski donanımda piksel / fragman gölgelendiricilerinden önceki ve hala mobilde kullanılan, modeli çoğaltmak, tepe sarım sırasını tersine çevirmek ve böylece modelin içini göstermesini sağlamaktır (veya isterseniz Bunu 3B varlık oluşturma aracınızda yapın, Blender, yüzey normallerini çevirerek - aynı şeyi yapın), sonra tüm kopyayı ortası çevresinde hafifçe genişletin ve bu kopyayı tamamen siyah / renkli hale getirin. Bu, eğer küp gibi basit bir modelse, orijinal modelinizin etrafındaki ana hatları oluşturur. İçbükey formlara sahip daha karmaşık modeller için (aşağıdaki resimde olduğu gibi), yinelenen modeli bir Minkowski Sum gibi orijinal eşdeğerinden biraz daha "daha şişman" olacak şekilde manuel olarak ayarlamak gerekir.3D Blender'ın Küçültme / Yağlama dönüşümü gibi, anahat ağını oluşturmak için her tepe noktasını normal boyunca biraz dışarı iterek başlayabilirsiniz.
Ekran boşluğu / piksel gölgelendirici yaklaşımları daha iyi uygulanması daha yavaş ve daha zor olma eğilimindedir , ancak OTOH dünyanızdaki köşe sayısını iki katına çıkarmaz. Bu yüzden eğer yüksek poli iş yapıyorsanız, bu yaklaşımı tercih edin. Geometriyi işlenmesi için modern bir konsol ve masaüstü kapasitesini göz önüne alındığında, 2 faktör dert ediyorum hiç . Karikatür tarzı = kesinlikle düşük poli, bu nedenle kopya geometrisi en kolay olanıdır.
Efekti kendiniz için örneğin herhangi bir koda dokunmadan Blender'da test edebilirsiniz. Anahatlar, aşağıdaki resme benzemelidir, iç kısımların, örneğin kolların altı gibi iç kısımlara dikkat edin. Daha fazla detay burada .
.
İçin pürüzsüz modelleri (çok önemli), bu etki oldukça basittir. Parça / piksel gölgelendiricinizde, gölgelendirilen parçanın normaline ihtiyacınız olacaktır. Eğer dikine çok yakınsa ( dot(surface_normal,view_vector) <= .01
- bu eşik ile oynamanız gerekebilir), ardından parçayı normal rengi yerine siyahla renklendirin.
Bu yaklaşım, taslak oluşturmak için modelin bir kısmını "tüketir". İstediğiniz bu olabilir veya olmayabilir. Pokemon resminden bunu yapmanın bu olup olmadığını söylemek çok zor. Dış çizginin, karakterin herhangi bir siluetine dahil edilmesini beklemenize veya çerçevenin silueti içermesini tercih etmenize (farklı bir teknik gerektirir) bağlıdır.
Vurgu, "iç kenarlar" dahil (yeşil Pokemon'daki bacaklar veya kafa gibi) ön yüzden arkaya doğru geçtiği yüzeyin herhangi bir yerinde olacak - bazı diğer teknikler bunlara herhangi bir taslak eklemeyecek ).
Sert, pürüzsüz olmayan kenarlara (bir küp gibi) sahip nesneler, bu yaklaşımla istenen konumlarda vurgu elde etmez. Bu, bazı durumlarda bu yaklaşımın bir seçenek olmadığı anlamına gelir; Pokemon modellerinin hepsi düzgün olup olmadığı hakkında hiçbir fikrim yok.
Bunu yaptığım en yaygın yöntem, modelinize ikinci bir render geçişi yapmaktır. Temel olarak, kopyalayın ve normalleri çevirin ve bunu köşe gölgelendiricisine sürün. Gölgelendiricideki her tepe noktasını normal boyunca ölçeklendirin. Piksel / parça gölgelendiricisinde siyah çizin. Bu size dudaklar, gözler, vb. Gibi hem dış hem de iç hatlar verir. Bu, model sayısına ve karmaşıklığına bağlı olarak, çizgiyi işlemeden sonra genellikle daha ucuz bir şey değilse, aslında oldukça ucuz bir beraberlik çağrısıdır. Guilty Gear Xrd bu yöntemi kullanır, çünkü çizginin kalınlığını tepe rengi ile kontrol etmek kolaydır.
İç çizgileri yapmanın ikinci yolu aynı oyundan öğrendim. UV haritanızda, dokunuzu u veya v ekseni boyunca, özellikle de bir iç çizgi istediğiniz alanlarda hizalayın. Her iki eksen boyunca siyah bir çizgi çizin ve UV koordinatlarınızı iç çizgiyi oluşturmak için bu çizginin içine veya dışına taşıyın.
Daha iyi bir açıklama için GDC'deki videoyu izleyin: https://www.youtube.com/watch?v=yhGjCzxJV3E
Taslak oluşturmanın yollarından biri modellerimizi normal vektörleri kullanmaktır. Normal vektörler, yüzeylerine dik olan (yüzeyden uzağa işaret eden) vektörlerdir. Buradaki hile karakter modelinizi iki parçaya bölmektir. Kameraya bakan ve kameradan uzağa bakan köşeler. Bunları sırasıyla ÖN ve GERİ olarak adlandıracağız.
Taslak için GERİ köşelerimizi alıyoruz ve onları normal vektörleri yönünde hafifçe hareket ettiriyoruz. Karakterimizin kameradan uzağa bakan kısmını biraz daha şişmanlaştırıyor gibi düşünün. Bunu yaptıktan sonra, onlara kendi seçtiğimiz bir rengi verdik ve güzel bir taslak oluşturduk.
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
}
}
}
Satır 41: “Cull Front” ayarı, gölgelendiriciye öne bakan köşeler üzerinde bir toplanma gerçekleştirmesini söyler. Bu, bu geçişte öne bakan tüm köşeleri görmezden geleceğimiz anlamına gelir. Biraz manipüle etmek istediğimiz GERİ taraf kaldı.
Satır 51-53: Normal vektörleri boyunca hareket eden köşelerin matematiği.
Satır 54: Köşe rengini, gölgelendirici özelliklerinde tanımlanan seçim rengimize göre ayarlama.
Faydalı Bağlantı: http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse
başka bir örnek
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"
}
Bunu yapmanın en iyi yollarından biri, sahnenizi bir Framebuffer dokusu üzerinde, ardından o dokuyu , her piksel üzerinde bir Sobel Filtresi yaparken , bu da kenar algılama için kolay bir teknik haline getirmektir. Bu şekilde sadece sahneyi pikselleştiremezsiniz (Framebuffer dokusuna düşük bir çözünürlük ayarlayarak) değil, aynı zamanda Sobel'i çalıştırmak için her piksel değerine erişebilirsiniz.