基本原理与实现

主要使用噪声透明度测试,从噪声图中读取某个通道的值,然后使用该值进行透明度测试。

主要代码如下:

fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
clip(cutout - _Threshold);

完整代码点这里


边缘颜色

如果纯粹这样镂空,则效果太朴素了,因此通常要在镂空边缘上弄点颜色来模拟火化、融化等效果。

1. 纯颜色

第一种实现很简单,首先定义_EdgeLength和_EdgeColor两个属性来决定边缘多长范围要显示边缘颜色;然后在代码中找到合适的范围来显示边缘颜色。

主要代码如下:

//Properties
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_EdgeColor("Border Color", Color) = (1,1,1,1)
...
//Fragment
if(cutout - _Threshold < _EdgeLength)
return _EdgeColor;

完整代码点这里

2. 两种颜色混合

第一种纯颜色的效果并不太好,更好的效果是混合两种颜色,来实现一种更加自然的过渡效果。

主要代码如下:

if(cutout - _Threshold < _EdgeLength)
{
float degree = (cutout - _Threshold) / _EdgeLength;
return lerp(_EdgeFirstColor, _EdgeSecondColor, degree);
}

完整代码点这里

3. 边缘颜色混合物体颜色

为了让过渡更加自然,我们可以进一步混合边缘颜色和物体原本的颜色。

主要代码如下:

float degree = saturate((cutout - _Threshold) / _EdgeLength); //需要保证在[0,1]以免后面插值时颜色过亮
fixed4 edgeColor = lerp(_EdgeFirstColor, _EdgeSecondColor, degree); fixed4 col = tex2D(_MainTex, i.uvMainTex); fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);

完整代码点这里

4. 使用渐变纹理

为了让边缘颜色更加丰富,我们可以进而使用渐变纹理:



然后我们就可以利用degree来对这条渐变纹理采样作为我们的边缘颜色:

float degree = saturate((cutout - _Threshold) / _EdgeLength);
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree)); fixed4 col = tex2D(_MainTex, i.uvMainTex); fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);

完整代码点这里


从特定点开始消融



为了从特定点开始消融,我们需要把片元到特定点的距离考虑进clip中。

第一步需要先定义消融开始点,然后求出各个片元到该点的距离(本例子是在模型空间中进行):

//Properties
_StartPoint("Start Point", Vector) = (0, 0, 0, 0) //消融开始点
...
//Vert
//把点都转到模型空间
o.objPos = v.vertex;
o.objStartPos = mul(unity_WorldToObject, _StartPoint);
...
//Fragment
float dist = length(i.objPos.xyz - i.objStartPos.xyz); //求出片元到开始点距离

第二步是求出网格内两点的最大距离,用来对第一步求出的距离进行归一化。这一步需要在C#脚本中进行,思路就是遍历任意两点,然后找出最大距离:

public class Dissolve : MonoBehaviour {
void Start () {
Material mat = GetComponent<MeshRenderer>().material;
mat.SetFloat("_MaxDistance", CalculateMaxDistance());
} float CalculateMaxDistance()
{
float maxDistance = 0;
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
for(int i = 0; i < vertices.Length; i++)
{
Vector3 v1 = vertices[i];
for(int k = 0; k < vertices.Length; k++)
{
if (i == k) continue; Vector3 v2 = vertices[k];
float mag = (v1 - v2).magnitude;
if (maxDistance < mag) maxDistance = mag;
}
} return maxDistance;
}
}

同时Shader里面也要同时定义_MaxDistance来存放最大距离的值:

//Properties
_MaxDistance("Max Distance", Float) = 0
//Pass
float _MaxDistance;

第三步就是归一化距离值

//Fragment
float normalizedDist = saturate(dist / _MaxDistance);

第四步要加入一个_DistanceEffect属性来控制距离值对整个消融的影响程度:

//Properties
_DistanceEffect("Distance Effect", Range(0.0, 1.0)) = 0.5
...
//Pass
float _DistanceEffect;
...
//Fragment
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r * (1 - _DistanceEffect) + normalizedDist * _DistanceEffect;
clip(cutout - _Threshold);

上面已经看到一个合适_DistanceEffect的效果了,下面贴出_DistanceEffect为1的效果图:



这就完成了从特定点开始消融的效果了,不过有一点要注意,消融开始点最好是在网格上面,这样效果会好点。

完整代码点这里

应用:场景切换

利用这个从特定点消融的原理,我们可以实现场景切换。

假设我们要实现如下效果:



因为我们原来的Shader是从中间开始镂空的,和图中从四周开始镂空有点不同,因此我们需要稍微修改一下计算距离的方式:

//Fragment
float normalizedDist = 1 - saturate(dist / _MaxDistance);

这时候我们的Shader就能从四周开始消融了。

第二步就是需要修改计算距离的坐标空间,原来我们是在模型空间下计算的,而现在很明显多个不同的物体会同时受消融值的影响,因此我们改为世界空间下计算距离:

//Vert
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
//Fragment
float dist = length(i.worldPos.xyz - _StartPoint.xyz);

完整代码点这里

为了让Shader应用到场景物体上好看点,我加了点漫反射代码。

第三步为了计算所有场景的物体的顶点到消融开始点的最大距离,我定义了下面这个脚本:

public class DissolveEnvironment : MonoBehaviour {
public Vector3 dissolveStartPoint;
[Range(0, 1)]
public float dissolveThreshold = 0;
[Range(0, 1)]
public float distanceEffect = 0.6f; void Start () {
//计算所有子物体到消融开始点的最大距离
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
float maxDistance = 0;
for(int i = 0; i < meshFilters.Length; i++)
{
float distance = CalculateMaxDistance(meshFilters[i].mesh.vertices);
if (distance > maxDistance)
maxDistance = distance;
}
//传值到Shader
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
for(int i = 0; i < meshRenderers.Length; i++)
{
meshRenderers[i].material.SetVector("_StartPoint", dissolveStartPoint);
meshRenderers[i].material.SetFloat("_MaxDistance", maxDistance);
}
} void Update () {
//传值到Shader,为了方便控制所有子物体Material的值
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < meshRenderers.Length; i++)
{
meshRenderers[i].material.SetFloat("_Threshold", dissolveThreshold);
meshRenderers[i].material.SetFloat("_DistanceEffect", distanceEffect);
}
} //计算给定顶点集到消融开始点的最大距离
float CalculateMaxDistance(Vector3[] vertices)
{
float maxDistance = 0;
for(int i = 0; i < vertices.Length; i++)
{
Vector3 vert = vertices[i];
float distance = (vert - dissolveStartPoint).magnitude;
if (distance > maxDistance)
maxDistance = distance;
}
return maxDistance;
}
}

这个脚本同时还提供了一些值来方便控制所有场景的物体。



像这样把场景的物体放到Environment物体下面,然后把脚本挂到Environment,就能实现如下结果了:

具体的场景文件点这里


从特定方向开始消融



理解了上面的从特定点开始消融,那么理解从特定方向开始消融就很简单了。

下面实现X方向消融的效果。

第一步求出X方向的边界,然后传给Shader:

using UnityEngine;
using System.Collections; public class DissolveDirection : MonoBehaviour { void Start () {
Material mat = GetComponent<Renderer>().material;
float minX, maxX;
CalculateMinMaxX(out minX, out maxX);
mat.SetFloat("_MinBorderX", minX);
mat.SetFloat("_MaxBorderX", maxX);
} void CalculateMinMaxX(out float minX, out float maxX)
{
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
minX = maxX = vertices[0].x;
for(int i = 1; i < vertices.Length; i++)
{
float x = vertices[i].x;
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
}
}
}

第二步定义是从X正方向还是负方向开始消融,然后求出各个片元在X分量上与边界的距离:

//Properties
_Direction("Direction", Int) = 1 //1表示从X正方向开始,其他值则从负方向
_MinBorderX("Min Border X", Float) = -0.5 //从程序传入
_MaxBorderX("Max Border X", Float) = 0.5 //从程序传入
...
//Vert
o.objPosX = v.vertex.x;
...
//Fragment
float range = _MaxBorderX - _MinBorderX;
float border = _MinBorderX;
if(_Direction == 1) //1表示从X正方向开始,其他值则从负方向
border = _MaxBorderX;

完整代码点这里


灰烬飞散效果



主要效果就是上面的从特定方向消融加上灰烬向特定方向飞散。

首先我们需要生成灰烬,我们可以延迟clip的时机:

float edgeCutout = cutout - _Threshold;
clip(edgeCutout + _AshWidth); //延至灰烬宽度处才剔除掉

这样可以在消融边缘上面留下一大片的颜色,而我们需要的是细碎的灰烬,因此我们还需要用白噪声图对这片颜色再进行一次Dissolve:

float degree = saturate(edgeCutout / _EdgeWidth);
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree));
fixed4 finalColor = fixed4(lerp(edgeColor, albedo, degree).rgb, 1);
if(degree < 0.001) //粗略表明这是灰烬部分
{
clip(whiteNoise * _AshDensity + normalizedDist * _DistanceEffect - _Threshold); //灰烬处用白噪声来进行碎片化
finalColor = _AshColor;
}

下一步就是让灰烬能够向特定方向飞散,实际上就是操作顶点,让顶点进行偏移,因此这一步在顶点着色器中进行:

float cutout = GetNormalizedDist(o.worldPos.y);
float3 localFlyDirection = normalize(mul(unity_WorldToObject, _FlyDirection.xyz));
float flyDegree = (_Threshold - cutout)/_EdgeWidth;
float val = max(0, flyDegree * _FlyIntensity);
v.vertex.xyz += localFlyDirection * val;

完整代码点这里


Trifox的镜头遮挡消融

具体原理参考 Unity案例介绍:Trifox里的遮挡处理和溶解着色器(一)

完整代码点这里 我这里的实现是简化版。


项目代码

项目代码在Github上,点这里查看


参考

《Unity Shader 入门精要》

Tutorial - Burning Edges Dissolve Shader in Unity

A Burning Paper Shader

Unity案例介绍:Trifox里的遮挡处理和溶解着色器(一)

《Trifox》中的遮挡处理和溶解着色器技术(下)

Unity Shader - 消融效果原理与变体的更多相关文章

  1. Unity Shader 景深效果

    效果 原理: 开启摄像机的深度模式,将深度保存到一张名为_CameraDepthTexture(Unity5.0之后才有)内置的纹理中. 如果深度在焦点范围内就用原图,否则就用模糊图. Shader: ...

  2. Unity Shader 玻璃效果

    一个玻璃效果主要分为两个部分,一部分是折射效果的计算,另一部分则是反射.下面分类进行讨论: 折射: 1.利用Grass Pass对当前屏幕的渲染图像进行采样 2.得到法线贴图对折射的影响 3.对采集的 ...

  3. Unity Shader 广告牌效果

    广告牌效果指的是,一个二维平面的法线方向始终与视线(摄像机的观察方向)相同.广泛运用于渲染烟雾,云朵,闪光等. 它的本质在于构建旋转矩阵,此时我们可以选择三个基向量来构建此矩阵. 指向→的方向(X轴) ...

  4. 小强学渲染之Unity Shader噪声应用

    之前玩Tencent的仙剑4手游时,杀死boss会看到boss有“消融”的效果,就是身体上有多个洞洞然后往四周扩散直至尸体完全消失,但效果是没有关闭背面剔除的“穿帮”效果,可能也是考虑性能因素. em ...

  5. 【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题 [Unity Shader学习笔记](三) -- ...

  6. 【Unity Shader】(十) ------ UV动画原理及简易实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

  7. Unity Shader入门精要学习笔记 - 第12章 屏幕后处理效果

    建立一个基本的屏幕后处理脚本系统 屏幕后处理,顾名思义,通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效.使用这种技术,可以为游戏画面添加更多艺术效果,例如景深. ...

  8. 【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的实现

    [Unity Shader](三) ---------------- 光照模型原理及漫反射和高光反射的实现 [Unity Shader](四) ------ 纹理之法线纹理.单张纹理及遮罩纹理的实现 ...

  9. 【Unity Shader】(九) ------ 高级纹理之渲染纹理及镜子与玻璃效果的实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

随机推荐

  1. 201521123008《Java程序设计》第1周学习总结

    本周学习总结 了解了JAVA:jdk:jre:jvm等 C语音与JAVA的部分区别: C语言全面向过程,java面向对象: C语言的代码不能跨平台,java的代码可以跨平台: C语言有指针,java没 ...

  2. 201521123088《Java程序设计》第13周学习总结

    1.本周学习总结 2.书面作业 1. 网络基础1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? ping cec. ...

  3. Java课程设计—学生成绩管理系统(201521123005 杨雪莹)

    一.团队课程设计博客链接 学生成绩管理系统 二.个人负责模块或任务说明 学生成绩录入 显示所有学生信息 显示各科平均成绩 显示学生成绩(按降序排序) 三.自己的代码提交记录截图 四.自己负责模块或任务 ...

  4. 201521123055 《Java程序设计》第9周学习总结

    1. 本章学习总结 2. 书面作业 Q.1常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 1.3 什么样的 ...

  5. Linux的诞生史

    Linux的诞生史 目录 Multics计划--开始 自由的产物-BSD GUN计划的产生 导火索MINIX Linux的诞生 Linux的标志物 Linux的现状 Multics计划--开始. 这是 ...

  6. Linux文件管理_1

    在Linux中,全部都是文件,所以文件管理在Linux上格外重要,在我们学习文件管理前,我们先学习几个关于文件的命令,之后才能更好的学习文件管理. 目录 pwd命令 cd命令 列出文件内容ls 查看文 ...

  7. Maven第三篇【Maven术语、pom.xml介绍】

    maven术语 在我们上一篇中已经知道了在Intellij idea下是如何使用Maven的了,创建出来的目录结构是这样子的: 上面的目录结构就是Maven所谓的"约定",我们使用 ...

  8. ActiveMQ_Windows版本的安装部署

    1, 保证电脑上安装了jdk6以上版本的java,并配置了好环境变量 : 2, 官方下载地址:http://activemq.apache.org/download-archives.html ,这里 ...

  9. eclipse Maven新建一个项目并使用

      安装参考这篇博文eclipse配置maven + 创建maven项目(三)  打开pom.xml 试着添加MySQL的JDBC驱动 添加如下配置, <dependency> <g ...

  10. Java 使用Axis实现WebService实例

    在上一篇WebService实例中,基于jdk1.6以上的javax.jws 发布webservice接口.这篇博文则主要用eclipse/myeclipse 使用axis插件进行发布和调用WebSe ...