如何围绕3D模型绘制轮廓?我指的是最近的《神奇宝贝》游戏中的特效,它们周围似乎只有一个像素的轮廓:
如何围绕3D模型绘制轮廓?我指的是最近的《神奇宝贝》游戏中的特效,它们周围似乎只有一个像素的轮廓:
Answers:
我认为这里的任何其他答案都不会在《神奇宝贝X / Y》中达到效果。我无法确切知道它是如何完成的,但是我想出了一种方法,似乎它们在游戏中的作用差不多。
在《PokémonX / Y》中,轮廓线边缘和其他非轮廓线边缘都绘制了轮廓(如以下屏幕截图中Raichu的耳朵与他的头部交汇处)。
在Blender中查看Raichu的网格,您可以看到(上面突出显示为橙色的)耳朵是与头部相交的单独的,断开的对象,从而使表面法线发生了突然变化。
基于此,我尝试根据法线生成轮廓,这需要通过两遍渲染:
第一遍:渲染没有轮廓的模型(纹理化和cel着色),然后将摄影机空间法线渲染到第二个渲染目标。
第二遍:从第一遍开始对法线进行全屏边缘检测滤镜。
下面的前两个图像显示了第一遍的输出。第三个是轮廓本身,最后一个是最终的组合结果。
这是我在第二遍中用于边缘检测的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 );
我在某处读到(我会在评论中放一个链接)Nintendo 3DS使用固定功能管线而不是着色器,所以我想这不可能完全在游戏中完成,但是现在我我坚信我的方法足够接近。
此效果在使用cel阴影效果的游戏中特别常见,但实际上可以独立于cel阴影样式应用。
您所描述的称为“特征边缘渲染”,通常是突出显示模型的各种轮廓和轮廓的过程。有许多可用的技术和关于该主题的许多论文。
一种简单的技术是仅渲染轮廓边缘,即最外轮廓。这可以简单地完成,就像使用模具写操作渲染原始模型,然后仅在没有模具值的情况下以粗线框模式再次渲染它一样。请参阅此处的示例实现。
但是,这不会突出内部轮廓和折痕边缘(如您的图片所示)。通常,要有效地做到这一点,您需要提取有关网格边缘的信息(基于边缘任一侧的面法线的不连续性,并建立代表每个边缘的数据结构。
然后,您可以编写着色器以将这些边缘拉伸或渲染为基础模型上方(或与其结合)的常规几何形状。边缘的位置以及相对于视图矢量的相邻面的法线用于确定是否可以绘制特定的边缘。
您可以在互联网上找到有关各种示例的进一步讨论,详细信息和论文。例如:
dz/dx
和/或dz/dy
最简单的方法(通常在像素/片段着色器之前的较旧硬件上使用,并且仍在移动设备上使用)是复制模型,反转顶点缠绕顺序,以便模型从内向外显示(或者,如果需要,可以可以在3D资产创建工具(例如Blender)中执行此操作,方法是翻转表面法线(同样的操作),然后将整个副本略微扩展到其中心,最后对该副本进行全黑上色/纹理处理。如果是简单的模型(例如多维数据集),则会在原始模型周围产生轮廓。对于具有凹形的更复杂模型(例如下图中的模型),有必要手动调整重复模型,使其比原始模型稍微“笨拙”,例如Minkowski Sum。在3D中。您可以像沿Blender的“收缩/变形”变换那样,将每个顶点沿其法线稍微向外推以形成轮廓网格。
屏幕空间/像素着色器方法趋向于更慢且难以很好地实现,但是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。
对于轮廓,我们取回顶点,并沿其法线向量的方向稍微移动它们。考虑一下它,就像使我们背离镜头的角色部分变得更胖。完成此操作后,我们为他们分配我们选择的颜色,并且轮廓很漂亮。
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)设置告诉着色器在面向前的顶点上进行消隐。这意味着我们将忽略此遍中的所有正面顶点。我们只剩下想要稍作操作的背面。
第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"
}