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

时间:2022-12-31 12:11:39

  之前玩Tencent的仙剑4手游时,杀死boss会看到boss有“消融”的效果,就是身体上有多个洞洞然后往四周扩散直至尸体完全消失,但效果是没有关闭背面剔除的“穿帮”效果,可能也是考虑性能因素。 emmmm,那下面开始详细讲解消融等等噪声相关应用的实现~

  · 消融效果

  噪音,就是“沙沙渣渣渣”的杂乱无章的声音,在图形学中使用噪声是为了把一些随机变量来引入到程序中,如火焰、地形、云朵的模拟等等都要使用随机变量。有人可能会问,直接使用random这种函数不就好了吗?为什么要引入这么多算法生成的特定噪声呢?原因在于生成的随机值太“随机”了,在图形学中称这种噪声为“白噪声”(功率谱密度在整个频域内均匀分布的噪声),通俗理解就是类似小时候电视机上面的雪花噪声,

  小强学渲染之Unity Shader噪声应用 (二维的白噪声纹理)

  噪声的基础来自于随机数,若屏幕上的每个像素点给一个0~1之间的随机数来表示象素点的亮度,就能得到上面的白噪声纹理,很像老式电视机故障时的黑白雪花。这种随机图像并没有太大的作用,因为每个点的随机数都是离散的,相互完全没有关系,能看到这幅图像中的世界是完全没有逻辑,没有能量的。  既然基本随机噪音因离散性而没意义,那就人为让噪音连续起来,这种平滑的连续化处理就是插值,在离散数据中间用函数插值的方法把空隙填满空间就自然连续了。

  简单的线性插值得到的棱角分明的结构,指数/三角函数等插值组合能形成各种插值算法,从而对应多种不同噪声。如Perlin噪声被大量用于云朵、火焰和地形等自然环境的模拟;Simplex噪声在其基础上进行改进,提高效率和效果;而Worley噪声被提出用于模拟一些多孔结构,例如纸张、木纹等。有关噪声算法的详解,可参考 【图形学】谈谈噪声

  不管怎样,对简单随机噪音插值后都能得到一种像霉菌表面的图像,本节的噪声纹理如下图:

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

  开始编码,表现效果就是从不同的区域开始,并向随机方向扩张,最后整个物体都将消失不见。

  原理:使用噪声纹理进行取样,将取样的结果和某个控制消融程度的阈值比较,若小于阈值就用clip函数将对应像素裁剪掉(透明度测试处理),不裁剪掉的像素就和另外两个颜色来进行混合插值处理从而达到烧焦的效果,并且将光照阴影和衰减也按照同样的处理,避免错误的投射阴影。

  完整代码如下:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Shaders/Dissolve"
{
    Properties
    {
        _BurnAmount ("Burn Amount",Range(0.0, 1.0)) = 0.0     //消融程度,0为正常显示,1为物体会完全消融
        _LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1 //模拟烧焦效果的线宽,值越大表示火焰边缘蔓延范围越广
        _MainTex ("Base (RGB)", 2D) = "white" {}             //物体本身的漫反射纹理
        _BumpMap ("Normal Map", 2D) = "bump" {}                 //法线纹理
        _BurnMap("Burn Map", 2D) = "white"{}                 //噪声纹理
        //火焰边缘的两种颜色
        _BurnFirstColor(, , , )
        _BurnSecondColor(, , , )
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        Pass
        {
            Tags { "LightMode"="ForwardBase" }
            //关闭面片剔除, 则模型正面/背面都被渲染,因为消融会裸露模型内部的构造
            Cull Off

            CGPROGRAM

            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag

            fixed _BurnAmount;
            fixed _LineWidth;
            sampler2D _MainTex;
            sampler2D _BumpMap;
            sampler2D _BurnMap;
            fixed4 _BurnFirstColor;
            fixed4 _BurnSecondColor;

            float4 _MainTex_ST;
            float4 _BumpMap_ST;
            float4 _BurnMap_ST;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uvMainTex : TEXCOORD0;
                float2 uvBumpMap : TEXCOORD1;
                float2 uvBurnMap : TEXCOORD2;
                float3 lightDir : TEXCOORD3;
                float3 worldPos : TEXCOORD4;
                SHADOW_COORDS()
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //使用 TRANSFORM_TEX内置宏计算三张纹理的纹理坐标
                o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
                o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
                //将光源从模型空间变换到切线空间
                TANGENT_SPACE_ROTATION;
                  o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                  //阴影
                  o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                  TRANSFER_SHADOW(o);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                //对噪声纹理采样
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
                //返回值范围是0~1,小于消融阈值的像素被剔除
                clip(burn.r - _BurnAmount);

                float3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));

                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(, dot(tangentNormal, tangentLightDir));

                /* smoothstep( float _Min, float _Max, float _X )  实现平滑过渡,让烧焦区域颜色混合渐变
                若_X 比 _Min,小于返回 0; 若_X 比 _Max 大则返回 1; 在范围 [_Min, _Max]内返回介于 0 和 1 之间的值
                smoothstep 函数用于在一段时间范围内逐渐但非线性地增加属性,如“不透明度”(Opacity)从 0 增加到 1。*/
                //混合系数t,值为0不需要混合,像素为正常模型颜色;值为1表示像素位于消融边界处
                 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
                /*插值函数lerp实现颜色渐变过渡  lerp(a, b, w):a与b为floatX或fixedX等同种类型,返回值是对应同种类型
                当w为0时返回a,为1时返回b,0~1之间,以比重w将a b进行线性插值计算。*/
                fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
                burnColor = pow(burnColor, );

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                //Mark! step(a, x):Returns (x >= a) ? 1 : 0
                fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

                );
            }
            ENDCG
        }

        //透明度处理的物体的阴影做同样处理,以免被剔除的区域仍会向其他物体投射阴影从而“穿帮”
        Pass
        {
            Tags { "LightMode" = "ShadowCaster" }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            fixed _BurnAmount;
            sampler2D _BurnMap;
            float4 _BurnMap_ST;

            struct v2f {
                V2F_SHADOW_CASTER;
                float2 uvBurnMap : TEXCOORD1;
            };

            v2f vert(appdata_base v) {
                v2f o;

                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

                o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;

                clip(burn.r - _BurnAmount);

                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

  注意,最后通过step判断函数再对混合系数t做条件处理,因一开始_BurnAmount为0时,t可能为1或不为0,lerp函数运算导致显示了_BurnColor,实际上刚开始_BurnAmount为0时不显示任何消融效果。

  运行游戏,显然有了!

  小强学渲染之Unity Shader噪声应用(_BurnAmount  = 0.3)

  小强学渲染之Unity Shader噪声应用(_BurnAmount = 0.5)

  明显看到关闭了剔除之后,背后的消融也不会穿帮。如果把剔除打开,个人觉得效果差不多且减小性能消耗,

  小强学渲染之Unity Shader噪声应用 (_BurnAmount  = 0.5)

  

  资料链接:

  《Unity Shader 消融效果原理与变体

  《Simplex噪声及其生成原理

  Unity Shader-死亡溶解效果    [UnityShader]溶解与重现效果(差不多)

  一种基于边缘Bloom的溶解shader的实现  (效果挺酷)

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

小强学渲染之Unity Shader噪声应用的更多相关文章

  1. 小强学渲染之Unity Shader编程HelloWorld

    第一个简单的顶点vert/片元frag着色器   1)打开Unity 5.6编辑器,新建一个场景后ctrl+s保存命名为Scene_5.默认创建的场景是包含了一摄像机,一平行光,且场景背景是一天空盒而 ...

  2. 小强学渲染之Unity Shader边缘描边加强

    项目开发遇到一个需求,就是当坦克的准心瞄准敌方(enemy tank 或 item box)时,要让选中的对象的轮廓高亮起来,这实际上是接下来要讲解的实时渲染中轮廓线的渲染应用.实现方式有多种,下面逐 ...

  3. 小强学渲染之OpenGL的CPU管线

    读到这里,应该对OpenGL渲染管线有了初步简单了解.下面着重分析CPU管线,即逻辑控制中心做了什么,这部分还是容易理解的.如下图: 一,将数据加载到显存中. 这是由GPU是访问显存中的数据决定的.因 ...

  4. 小强学渲染之OpenGL渲染管线详析

    什么是OpenGL? OpenGL是一套图形硬件的软件API接口库,它直接和GPU交互,将3D场景渲染绘制到2D屏幕上.总结说,OpenGL的功能是将程序中定义的各种2D或3D模型绘制到帧缓存中,或者 ...

  5. 小强学渲染之OpenGL的GPU管线

    GPU渲染流水线,是硬件真正体现渲染概念的操作过程,也是最终将图元画到2D屏幕上的阶段.GPU管线涵盖了渲染流程的 几何阶段 和 光栅化阶段,但对开发者而言,只有对顶点和片段着色器有可编程控制权,其他 ...

  6. 小强学渲染之OpenGL状态机理解

    状态机是理论上的一种机器,呃这个说法非常非常的抽象.通俗一点理解,状态机描述了一个对象在其生命周期内所经历的各种状态,状态间的转变,发生转变的动因,条件及转变中所执行的活动.或者说,状态机是一种行为, ...

  7. Unity Shader入门精要学习笔记 - 第15章 使用噪声

    转载自 冯乐乐的 <Unity Shader 入门精要> 消融效果 消融效果常见于游戏中的角色死亡.地图烧毁等效果.这这些效果中,消融往往从不同的区域开始,并向看似随机的方向扩张,最后整个 ...

  8. 【Unity Shader】新书封面 — Low Polygon风格的渲染

    写在前面 最近又开心又担心,因为我的书马上就要上市了,开心当然是因为等了这么久终于可以如愿了,担心是因为不少人对它的期待都很大,我第一次写书,能力也有限,不知道能不能让大家满意,让大家也都喜欢上它.不 ...

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

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

随机推荐

  1. python的错误和异常

    python错误和异常 错误 错误分为语法错误和逻辑错误 语法错误 >>> if File "<stdin>", line 1 if ^ Syntax ...

  2. Exercise&colon; Rot13 Reader

    package main import ( "io" "os" "strings" "fmt" ) type rot13 ...

  3. chisel中pviews命令无法使用

    chisel是用Python写的LLDB调试器插件,用来调试iOS应用非常方便,相关下载安装链接如下:https://github.com/facebook/chisel.本人安装之后,在xcode里 ...

  4. 浅谈树链剖分(C&plus;&plus;、算法、树结构)

    关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供) 这是讲解 http://www.cnblogs.com/kuangbin/archive/2013 ...

  5. 简单介绍一下ODI的几个基本概念

    简单介绍一下ODI的几个基本概念   ODI的几个基本概念是本文我们主要要介绍的内容,接下来我们就开始介绍这一过程,一起来看看吧! 什么是资料库 ODI资料库可安装在任何支持ANSIISO89的数据库 ...

  6. &lbrack;转&rsqb;Inspecting Obj-C parameters in gdb

    Since the addition of i386 and x86_64 to the Mac OS’s repertoire several years back, remembering whi ...

  7. 深入理解Linux内核 学习笔记(3)

    第三章 进程 可以看到很多熟悉的结构体 进程状态: 可运行状态(TASK_ RUNNING) 进程要么在CPU上执行,要么准备执行. 可巾断的等待状态(TASK_ INTERRUPTIBLE) 进程被 ...

  8. python自动化开发-&lbrack;第五天&rsqb;-面向过程、模块、包

    今日概要: 1.内置模块 2.协程函数 3.递归 4.面向过程编程与函数编程 5.模块 6.包 7.re正则 一.内置模块 1.匿名函数lambda 定义:匿名函数通常是创建了可以被调用的函数,它返回 ...

  9. 用JS实现判断iframe是否加载完成

    本文出至:新太潮流网络博客 var iframe = document.createElement("iframe"); iframe.src = "blog.iinu. ...

  10. Iframe和Frame中实现cookie跨域的方法(转载)

    在Iframe和Frame中默认是不支持Cookie跨域的,但通过设置P3P协议相关的响应头可以解决这一问题.关于p3p协议: P3P: Platform for Privacy Preference ...