轮廓物体效果


26

如何获得类似于《英雄联盟》或《暗黑破坏神III》中的轮廓效果?

英雄联盟大纲 英雄联盟大纲 暗黑破坏神III的轮廓

使用着色器完成吗?怎么样?
我希望答案不限于任何特定的引擎,而是可以适应我正在使用的任何引擎的答案。

Answers:


19

不得不在某个时刻两次渲染对象。您可以只渲染一次面对相机的面和一次面对远离相机的面,但是这是有其取舍的。

最简单的常见解决方案是通过在同一遍中两次渲染对象来完成的:

  • 您可以使用顶点着色器反转对象的法线,并根据轮廓的大小对其进行“放大”,并使用片段着色器以轮廓颜色进行渲染
  • 在该轮廓渲染上,可以正常渲染对象。由于轮廓是由对象“背面”中的面组成的,而图形本身是由面向相机的面组成的,因此z顺序通常会自动或多或少地正确。

这很容易构建和实现,并且避免了任何“渲染到纹理”的技巧,但是有一些明显的缺点:

  • 如果不按与相机的距离缩放轮廓尺寸,则尺寸会有所不同。较远的物体将比附近的物体具有较小的轮廓。当然,这可能是您真正想要的
  • “爆炸”顶点着色器对于复杂的对象(例如您的示例中的骨架)而言效果不佳,因此很容易在渲染中引入与z对抗的伪像。要修复该问题,需要两次渲染对象,但可以避免反转法线。
  • 当其他对象占据相同的空间时,轮廓和对象可能无法很好地工作,并且通常在与反射和折射着色器结合使用时很难正确。

这样的着色器的基本想法如下所示(Cg,对于Unity来说,代码是我在某处发现的略微修改过的卡通着色器,并且没有注明出处,因此它比概念就绪的书写的概念证明更糟糕,使用着色器):

Shader "Basic Outline" {
    Properties {
        _Color ("Main Color", Color) = (.5,.5,.5,1)
        _OutlineColor ("Outline Color", Color) = (1,0.5,0,1)
        _Outline ("Outline width", Range (0.0, 0.1)) = .05
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        Pass {
            Name "OUTLINE"
            Tags { "LightMode" = "Always" }
CGPROGRAM
#pragma exclude_renderers gles
#pragma exclude_renderers xbox360
#pragma vertex vert

struct appdata {
    float4 vertex;
    float3 normal;
};

struct v2f
{
    float4 pos : POSITION;
    float4 color : COLOR;
    float fog : FOGC;
};
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v)
{
    v2f o;
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    float3 norm = mul ((float3x3)UNITY_MATRIX_MV, v.normal);
    norm.x *= UNITY_MATRIX_P[0][0];
    norm.y *= UNITY_MATRIX_P[1][1];
    o.pos.xy += norm.xy * _Outline;
    o.fog = o.pos.z;
    o.color = _OutlineColor;
    return o;
}
ENDCG
            Cull Front
            ZWrite On
            ColorMask RGB
            Blend SrcAlpha OneMinusSrcAlpha
            SetTexture [_MainTex] { combine primary }
        }
        Pass {
        Name "BASE"
        Tags {"LightMode" = "Always"}
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"

struct v2f {
    float4 pos : SV_POSITION;
    float2    uv            : TEXCOORD0;
    float3    viewDir        : TEXCOORD1;
    float3    normal        : TEXCOORD2;
}; 

v2f vert (appdata_base v)
{
    v2f o;
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.normal = v.normal;
    o.uv = TRANSFORM_UV(0);
    o.viewDir = ObjSpaceViewDir( v.vertex );
    return o;
}

uniform float4 _Color;

uniform sampler2D _MainTex;
float4 frag (v2f i)  : COLOR
{
    half4 texcol = tex2D( _MainTex, i.uv );

    half3 ambient = texcol.rgb * (UNITY_LIGHTMODEL_AMBIENT.rgb);
    return float4( ambient, texcol.a * _Color.a );
}
ENDCG
    }
    }
    FallBack "Diffuse"
}

另一种常见方法也将对象渲染两次,但完全避免了顶点着色器。另一方面,它不容易通过单步完成,并且需要渲染到纹理:使用“平面”轮廓色片段着色器渲染对象一次,并在此渲染中使用(加权)模糊屏幕空间,然后照常在其顶部渲染对象。

还有第三种方法,而且可能是最容易实现的方法,尽管它会使GPU负担更多,并使您的艺术家想在睡眠中谋杀您,除非您使它们易于生成:将对象作为单独的轮廓一直保持网格状,只是完全透明,或者一直移动到看不见的地方(例如地下深处),直到您需要它为止


模板缓冲区不是这里常用的方法吗?
edA-qa mort-ora-y 2012年

1
@ edA-qamort-ora-y:这也可以,但我从未尝试过这种方法,因此无法对此发表评论。:)如果您有一个可行的算法,也可以随时将此方法添加为另一个答案。
马丁·索卡

我自己对此并不太了解,只是在提到模板缓冲区时经常提到轮廓。:)似乎您的第一种方法可能只是基于硬件的版本(两次通过,第一个更大)。
edA-qa mort-ora-y 2012年

模板缓冲区方法可能会在更新和清除模板缓冲区时消耗更多带宽,并且需要多次通过。Martin列出的方法可以在某些有限的情况下一次完成,最多两次,并且需要最小的带宽开销。
肖恩·米德迪奇

这种方法(沿法线放大大小)不适用于立方体之类的对象(尤其是正交摄影机)。有什么解决办法吗?
NPS

4

除了Martin Sojkas的答案之外,对于静态对象(或精灵),您还可以摆脱一些简单的事情。

您可以保存相同的精灵,但将轮廓与纹理图集或其他纹理完全保存在一起,这使切换变得容易。这也将使您能够创建自定义轮廓,这些轮廓在视觉上更具吸引力或看起来有所不同。

另一种方法是将子图形保存为稍大的一种颜色形状,并在渲染子图形本身之前对其进行渲染。这将使您能够轻松更改选择的颜色,并且可能不需要使用方法#1需要轮廓精灵的颜色形状。

两者都会增加您的内存占用。


4

正如对Martin Sojka的回答的评论所指出的那样,也可以通过使用模板或深度缓冲区来实现类似的效果,如Max McGuire在FlipCode上详细介绍的那样:

http://www.flipcode.com/archives/Object_Outlining.shtml

它基本上是在绘制模版的线框版本,并增加线宽(或者在不可能的情况下,例如在D3D中,使用面向相机的四边形作为线),同时将模板缓冲区设置为恒定值。

使用当今的OpenGL,此方法可能有些过时,并且为了使对象轮廓Applear模糊,仍然需要将其渲染为纹理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.