1.前言
参加腾讯2018游戏岗校招结果出师未捷身先死,连面试机会都没有(-_-||),想想笔试自己三道编程题0个ac也就释怀了233,忙着实习实在没精力复习算法题,精力有限啊...
吐槽完毕回归主题
咱最近在玩wy的神都夜行录,这款手游画面还是挺不错的,就是肝疼(万恶的wy策划)。
刷本的时候看见了如下图这个消融效果,就想着怎么用unity shader来实现,毕竟消融效果在游戏中是非常常见的。
2.实现思路
1.使用clip函数对片元进行裁剪来实现模型的消融
clip(value):当value值小于0时,裁减掉该片元,否则保留。
2.使用下面的噪声图来实现伪随机消融,这样能使效果看起来更加自然。关于什么是噪声,可以去看乐乐大师姐(在下小迷弟一枚)写的关于噪声的博文-【图形学】谈谈噪声
3.关于颜色侵蚀过渡的实现
从上面的效果图中可以看见模型在消融时,有明显的色彩侵蚀过程,这种色彩变化为消融提供了很好的过渡效果,为了实现这种效果,我们可以设置一个侵蚀颜色的阈值,根据这个阈值来判断和返回侵蚀过程中的色彩变化。
//控制侵蚀程度
float _Erode;
//控制侵蚀的颜色阈值
float _ErodeThreshold;
4.光照
直接使用Lambert光照模型,光照的衰减交给unity内置宏来处理
5.关于展示用的模型
手残党的福音,给大家推荐一款小巧的免费体素建模软件-MagicaVoxel, 上手简单,不好用不要钱~
来给大家展示一下我的作品:
怎么样,是不是神还原,哈哈哈哈
3.代码
下面是完整的shader代码,关键部分加了注释:
Shader "Unlit/Melt"
{
Properties{
_MainTex("Base(rgb)", 2D) = "white"{}
_NoiseMap("NoiseMap", 2D) = "white"{}
_StartColor("StarColor", Color) = (0,0,0,0)
_EndColor("EndColor", Color) = (0,0,0,0)
_MeltThreshold("MeltThreshold", Range(0, 1)) = 0
_Erode("Erode", Range(0.0, 1.0)) = 0.98
_ErodeThreshold("ErodeThreshold", Range(0.0, 1.0)) = 0.71
}
SubShader{
CGINCLUDE
#include "Lighting.cginc"
#include "UnityCG.cginc"
#include "AutoLight.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseMap;
//消融边缘起始颜色
fixed4 _StartColor;
//最终颜色
fixed4 _EndColor;
//消融阈值
float _MeltThreshold;
//控制侵蚀程度
float _Erode;
//控制侵蚀颜色阈值
float _ErodeThreshold;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
SHADOW_COORDS(3)
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target{
//使用噪声图采样
fixed3 melt = tex2D(_NoiseMap, i.uv).rgb;
//采样阈值与设定阈值比较,小于设定的阈值就裁剪掉该片元
clip(melt.r - _MeltThreshold);
//光照计算部分,使用兰伯特漫反射光照模型
//纹理采样得到反射率
fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
//世界法线
fixed3 worldNormal = normalize(i.worldNormal);
//入射光
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
//计算环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, -worldLightDir));
//最终光照
fixed3 lightColor = diffuse * atten + ambient;
//侵蚀计算部分
float result = _MeltThreshold / melt.r;
if(result > _Erode){
//如果结果大于消融颜色的阈值,则返回消融结束部分的颜色,否则返回初始颜色
if(result > _ErodeThreshold) {
return _EndColor;
}
return _StartColor;
}
//直接返回光照后颜色
return fixed4(lightColor, 1);
}
ENDCG
Pass{
Tags{ "RenderType" = "Opaque"}
Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}
为了看见整个模型的消融过程,我选择关闭了剔除
Cull Off //关闭遮挡剔除
创建一个脚本来控制消融的程度,实现自动播放的效果
Melt.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Melts : MonoBehaviour {
public Material material;
[Range(0.01f, 1.0f)]
public float meltSpeed = 0.2f;
private float meltThreshold = 0.0f;
void Start(){
material.SetFloat("_MeltThreshold", 0);
}
void Update(){
//使用时间控制消融阈值
meltThreshold = Mathf.Repeat(Time.time * meltSpeed, 6.0f);
material.SetFloat("_MeltThreshold", meltThreshold);
}
}
4.实现效果
来看看实现效果:
emmm,好像颜色有什么地方不对劲....
截图与游戏效果图对比看看
通过对比可以发现,游戏截图中的色彩明显亮于我所使用的颜色。
这五毛钱特效可没脸拿出去见人啊 QAQ
5.修改后的效果
想起从乐乐学姐那里偷师学来的bloom效果,翻出之前写的代码加上去试试看效果咋样
!!! 瞬间感觉高大上了,并且有一种金属感,效果的还原度也很高
关于bloom效果
bloom效果属于图像处理的一种,这种图像处理技术在渲染中常被称为后处理,由脚本和shader共同实现。
bloom的原理简单的来说就是对图像中亮度较高的区域进行高斯模糊处理,由于篇幅的原因我这里不贴出具体代码,对bloom感兴趣的可以看看我的github,我把该效果的源码放在了GitHub上。
链接:我的GitHub