unity 3d物体描边效果_卡通附魔描边效果

按照惯例,先上图:
15ed6bc73fb25b5b95910842bd06fd5b.png

效果分析

  1. 整体上效果在模型外包围,且永远在模型后面

  2. 边缘噪波

  3. 两层描边

思路分析

  • 效果上描边与模型渲染相对分离,且要控制附魔效果的出现与消失,初步思路使用双Pass对卡通模型和附魔效果分别渲染。之前的卡通渲染shader可以直接使用UsePass进行复用

57bf666fb541760816ee4b6d52ab4605.png

由于之前使用的ToonLit shader是表面着色器,如下图,我保留了前向渲染(forward)与延迟渲染(deferred)路径,摒弃了遗留的延迟渲染(prepass)路径,所以在复用时需要指明是哪个pass,此处是前向渲染,所以使用forward,需要注意的是,在复用pass时需要大写

b17d0726fd347875a406789ebef51fe7.png
  • 针对这里的描边效果,需要对附魔范围进行精确的调整,且已经分为单独的pass控制,很容易想到使用模型顶点延法线外扩的方案。但要注意的是,我们这次不能在模型空间来做这项操作(可能我经常这样做)。分析一下这个效果,就会发现它要求我们始终看到附魔效果在模型之后,且不随摄像机位置发生变化(这一点很重要),简单地说这是一个3D物体的2D附魔效果,也可以理解为屏幕空间特效(虽然不是在屏幕空间操作)。由于是模型顶点偏移,我们在齐次裁剪空间执行此次外扩操作(不在屏幕空间对片元进行操作也是为了优化,以及不影响其他后处理效果的使用)

3fae46c28a2b2a287248538ce32d8494.png

此处有一点要注意的是,模型法线的空间转换不同于顶点,法线是向量,如果使用与顶点变换相同的变换矩阵,将有可能出现法线不再垂直于表面的现象(相关详细推理请见《Unity Shader入门精要》86页,乐乐永远的神~),我们使用其逆转置矩阵做为法线的变换矩阵,可以解决这个问题(使用切线与法线始终垂直进行推导)

d922f56176a8254d9a190cc036eb2b34.png
  • 附魔部分的代码如下,

8f3cd55e93f88b378cb9cbeaea900219.png

首先,对于屏幕空间的UV滚动,我们可以使用屏幕空间的位置来做基础量

然后采样一张Noise图,制作边缘噪波备用

使用rim来制作一种边缘淡化的效果,_RimPower用来控制淡化边缘的宽度(一种透明度中心到边缘为1,边缘到边界为1到0的渐变模型)

rim减去noise用来实现不规则边界

edgeRim用来实现这个不规则边界的缩放,这样的话基础层就已经有了(如图深蓝和橘红部分)

extraRim的代码可能比较难以理解,这里可以一步一步来看,可以看到,相比edgeRim,extraRim先是在Rim.a的基础上加了个Edge,因为有saturate函数控制边界,其实就是实现了一个边界扩充的效果(原来为0的地方变成了Edge,边界被扩充了一小部分,Edge的范围也应当较小),减去edgeRim就实现了外边界,使用Brightness控制亮度

代码展示

Shader "MayoHa/VFX/AuraOutline"{    Properties    {        _Color("Color",Color) = (1,1,1,1)        _MainTex ("Texture", 2D) = "white" {}        _RampTex("Ramp Texture",2D) = "gray" {}        [Header(Outline)]        _NoiseTex("Noise Texture",2D) = "white" {}        _OutlineScale("Outline Scale",float) = 0.3        _OutlineZ("Outline Z Offset",Range(-0.06,0)) = -1        _XSpeed("X Speed",float) = 1        _YSpeed("Y Speed",float) = 1        _RimPower("Rim Power",Range(-4,10)) = 1        _OffSet("Noise Opacity",Range(0.01,10)) = 1        _Scale("Scale",Range(0,0.1)) = 0.01        _Edge("Edge",Range(0,1)) = 1        _Brightness("Brightness",float) = 1        _EdgeColor("Edge Color",Color) = (1,1,1,1)        _RimColor("Rim Color",Color) = (1,1,1,1)    }    CGINCLUDE    #include "UnityCG.cginc"    struct appdata{        float4 vertex : POSITION;        float3 normal : NORMAL;    };    struct v2f{        float4 pos : SV_POSITION;        UNITY_FOG_COORDS(0)        float3 normalDir :TEXCOORD1;        float3 viewDir : TEXCOORD2;    };    float _OutlineScale;    float _OutlineZ;    v2f vert(appdata v){        v2f o;        o.pos = UnityObjectToClipPos(v.vertex);        float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));        float2 offset = TransformViewToProjection(viewNormal.xy);        o.pos.xy += offset * _OutlineScale*o.pos.z ;        o.pos.z += _OutlineZ;        o.viewDir = normalize(WorldSpaceViewDir(v.vertex));        o.normalDir = normalize(UnityObjectToWorldNormal(v.normal));        UNITY_TRANSFER_FOG(o,o.pos);        return o;    }    ENDCG    SubShader    {        Tags { "RenderType"="Opaque" }        LOD 100        UsePass "MayoHa/Toon/ToonLit/FORWARD"        pass{            Name "Outline"            Tags{"LightMode" = "Always"}            ZWrite Off            ColorMask RGB            Blend SrcAlpha OneMinusSrcAlpha            Cull Back            CGPROGRAM            #pragma vertex vert            #pragma fragment frag            #pragma multi_compile_fog            float _XSpeed,_YSpeed;            sampler2D _NoiseTex;            float _RimPower;            float _OffSet;            fixed4 _EdgeColor;            fixed4 _RimColor;            float _Edge,_Brightness;            float _Scale;            fixed4 frag(v2f i):SV_TARGET{                float2 uv =float2 (i.pos.x * _Scale - _Time.x * _XSpeed,i.pos.y * _Scale - _Time.x * _YSpeed);                float4 noise = tex2D(_NoiseTex,uv);                //使用Rim 可以实现边缘淡化效果                float4 rim = pow(saturate(dot(i.viewDir,i.normalDir)),_RimPower);                rim -= noise;                float4 edgeRim = saturate(rim.a * _OffSet);                //挤出外描边                float4 extraRim = (saturate((_Edge + rim.a) * _OffSet) - edgeRim) * _Brightness ;                float4 result = (edgeRim * _EdgeColor)+(extraRim * _RimColor);                UNITY_APPLY_FOG(i.fogCoord,result);                return result;            }            ENDCG        }    }}
在使用时需要根据情况来控制附魔效果的产生和消失,由于附魔效果是使用单独一个Pass实现的,所以我们需要在脚本中控制该Pass的开启与关闭,这可以使用Unity自带的 SetShaderPassEnabled方法实现,具体使用如下
using System.Collections;using System.Collections.Generic;using UnityEngine;public class BufferFXMgr : MonoBehaviour{    private Material material;    private bool isBufferd;    private void Awake()    {        material = gameObject.GetComponent().material;        isBufferd = false;    }    // Start is called before the first frame update    private void Start()    {    }    // Update is called once per frame    private void Update()    {        if (Input.GetKeyDown(KeyCode.E))        {            isBufferd = !isBufferd;        }        material.SetShaderPassEnabled("Always", isBufferd);    }}

Always为附魔效果Pass的LightMode标签,SetShaderPassEnabled方法可以通过该标签来获取对应的Pass(可以多个),第二个参数用来控制该Pass的关闭与开启,但貌似只能用LightMode标签。

举个例子,如果你的shader中有LightMode为Always的Pass(用来处理折射效果),但你只想让有折射纹理的材质渲染该Pass,其余的不渲染,那么你可以使用这个函数实现分类

总结

1、边缘淡出效果极为常用,此处使用Rim配合saturate来实现是一种不错的方法

2、描边效果可以在基础效果上对Alpha进行偏移,这样的描边仍然有淡出效果

3、在适合的空间进行顶点操作往往可以减轻很大一部分工作负担,优化代码性能

4、setShaderPassEnabled可以根据LightMode Tag来控制材质shader中对应Pass的开启与关闭


往期精选

Unity3D游戏开发中100+效果的实现和源码大全 - 收藏起来肯定用得着

Shader学习应该如何切入?

喵的Unity游戏开发之路 - 从入门到精通的学习线路和全教程


声明:发布此文是出于传递更多知识以供交流学习之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与我们联系,我们将及时更正、删除,谢谢。

作者:江陵野少

原文:https://zhuanlan.zhihu.com/p/259182212


More:【微信公众号】 u3dnotes

fc4787f9dd39437d9cd452fbecd3940c.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值