ShadowGun 的学习笔记 - GodRays

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/WPAPA/article/details/51736035

首先先贴一些参考文章,感谢这些人的分享精神。

官方:http://blogs.unity3d.com/2012/03/23/shadowgun-optimizing-for-mobile-sample-level/

一些分享:
http://ravenw.com/blog/2012/03/30/shadowgun-shared-level-shaders-and-skills/

http://blog.csdn.net/candycat1992/article/details/42061701


ShadowGun 的官方 demo 里确实有很多不错的实现思路,极其的适合移动平台来使用,真的是能省就省。效果不用真实,只要能骗过玩家的眼睛就行!

顺便再赞一下人家老外的分享精神,知识要分享,交流,才能够进步!


这里先不讨论真正的 god rays 要怎么弄,因为 ShadowGun 中的例子都是为了移动端所设计的,讲究的就是省!

要理解什么是 god rays ,看一张图你就懂了。(图形学这东西,有时候必须要看实际效果和例子的,不然很可能你都不知道它在说什么~)

如图:
这里写图片描述
图里的这个光,就是 god rays ,教堂里最常见,大家想体验的话可以去教堂看看~

首先我们先来看 god rays ,demo 里的文件是 MADFINGER-god-rays.shader

代码:

Shader "MADFINGER/Transparent/GodRays" {

Properties {
    _MainTex ("Base texture", 2D) = "white" {}
    _FadeOutDistNear ("Near fadeout dist", float) = 10  
    _FadeOutDistFar ("Far fadeout dist", float) = 10000 
    _Multiplier("Multiplier", float) = 1
    _ContractionAmount("Near contraction amount", float) = 5
}


SubShader {


    Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }

    Blend One One
//  Blend One OneMinusSrcColor
    Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }

    LOD 100

    CGINCLUDE   
    #include "UnityCG.cginc"
    sampler2D _MainTex;

    float _FadeOutDistNear;
    float _FadeOutDistFar;
    float _Multiplier;
    float _ContractionAmount;


    struct v2f {
        float4  pos : SV_POSITION;
        float2  uv      : TEXCOORD0;
        fixed4  color   : TEXCOORD1;
    };

    float4 MyLerp(float4 from,float4 to,float t)
    {
        return from + t * (to - from);
    }

    v2f vert (appdata_full v)
    {
        v2f         o;
        float3  viewPos = mul(UNITY_MATRIX_MV,v.vertex);
        float       dist        = length(viewPos);
        float       nfadeout    = saturate(dist / _FadeOutDistNear);
        float       ffadeout    = 1 - saturate(max(dist - _FadeOutDistFar,0) * 0.2);

        ffadeout *= ffadeout;

        nfadeout *= nfadeout;
        nfadeout *= nfadeout;

        nfadeout *= ffadeout;

        float4 vpos = v.vertex;

        vpos.xyz -=   v.normal * saturate(1 - nfadeout) * v.color.a * _ContractionAmount;

        o.uv        = v.texcoord.xy;
        o.pos   = mul(UNITY_MATRIX_MVP, vpos);
        o.color = nfadeout * v.color * _Multiplier;

        return o;
    }
    ENDCG


    Pass {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest       
        fixed4 frag (v2f i) : COLOR
        {           
            return tex2D (_MainTex, i.uv.xy) * i.color;
        }
        ENDCG 
    }   
}


}

ShadowGun 的思路是把它处理成了一种雾效

这里的雾效不是指那种真的全局环境都受影响的大雾,而是一种现象:在视角逐渐接近它的时候,视野逐渐清晰。例如对于体积光,从远处看它可能会感觉很亮,但越接近亮度越小,越能看清后面的物体。这种效果可以很好地让玩家感觉到深度的变化。

如果要使用这种 Shader,就需要在三维建模软件中处理用来显示效果的mesh。

①顶点的透明度用于决定顶点是否可以移动(透明度为0表示不可移动,1为可移动)
②顶点法线决定移动的方向

然后Shader通过计算与观察者的距离来控制雾面的淡入/淡出。
这种技术非常简单,而且可以用于光射线、光锥等各种透明效果。

在ShadowGun中,有三个shaders使用了这个技术:GodRays,Blinking GodRays和Blinking GodRays Billboarded。
其中GodRays用于模拟体积光。

这里我们也看到了,主要代码都在vert函数里了,所以它的思路主要就是通过对mesh顶点的移动,来模拟雾效。光说的话不是很深刻,这里我们来看几张图。

显示参数:
这里写图片描述

重点是Near fadeout distFar fadeout dist,就是它们决定了雾效的作用范围。

显示用的mesh:
这里写图片描述

最终产生的mesh:
这里写图片描述

通过这2张图,我们就看出来了,这里把体积光做成了一个现成的mesh片了。


雾效的表现,注意看有辅助线框的部分

远看:
这里写图片描述

近看:
这里写图片描述

看图比代码好理解吧~
当远处时,这个mesh片是完全伸展开的,当拉近时,一侧的mesh顶点会收缩起来


现在我们来看代码:

float3 viewPos = mul(UNITY_MATRIX_MV,v.vertex);
float dist = length(viewPos);
float nfadeout = saturate(dist / _FadeOutDistNear);
float ffadeout = 1 - saturate(max(dist - _FadeOutDistFar,0) * 0.2); 

dist是顶点在View Space中的模,就是在View Space中距离原点的远近,也就是距离视角的远近。

nfadeout是一个范围在(0,1)之间的淡化系数,它用于模拟淡入或淡出效果。和它相关的有两个属性:_FadeOutDistNear和_FadeOutDistFar。玩家由无限远开始接近这个物体的过程中,一开始是远大于_FadeOutDistFar,那么是看不到这个体积光的;然后逐渐接近_FadeOutDistFar后,开始出现淡入效果;如果小于了_FadeOutDistNear,那么就会开始模拟淡出的效果。
可以看出,当dist大于_FadeOutDistNear时,总是返回1,从而不会产生任何影响;而一旦小于_FadeOutDistNear后,就会产生一个线性的衰减。

ffadeout的计算看起来复杂也难懂一点。我们希望ffadeout的结果是,在dist远大于_FadeOutDistFar时返回0;在dist逐渐接近_FadeOutDistFar时,逐渐从0增加到1;在dist小于_FadeOutDistFar时,返回1。从函数图像来看,其实就是个分段函数。上面的写法只是通过max和saturate函数来实现这种分段的目的,其中0.2是模拟了淡入的速率。下面就是这句计算表达式的函数图像:

这里写图片描述

对于一般的射灯模拟,_FadeOutDistNear的值都比较大。在计算完nfadeout和ffadeout后,并没有直接相乘,而是各自进行了指数操作。这里感觉是感性的计算,即希望淡入/淡出的速率更快或者更慢等。

下面是顶点位置的计算。与其相关的语句是:

float4 vpos = v.vertex;  
vpos.xyz -=   v.normal * saturate(1 - nfadeout) * v.color.a * _ContractionAmount; 

这里的目的是为了在淡出时移开(也可以为收缩)顶点。当nfadeout值越接近0时,表明正在淡出,那么顶点就需要朝着其法线方向的反方向进行收缩。其中,顶点的透明通道决定了这个顶点是否可以移动(这是因为,体积光往往有一边是不可以移动的,想象一下从窗户投进来的光,起点用于在和窗户的衔接处是不会动的)。而_ContractionAmount表示收缩的程度。


这里用一张图来进一步说明它们之间的关系(我画的比较烂):

这里写图片描述

图中当我们的眼睛改变与mesh之间的距离的时候,dist的大小就会不断变化。然后我们把nfadeout和ffadeout相乘

nfadeout *= ffadeout;

当dist的值在_FadeOutDistNear和_FadeOutDistFar之间时,nfadeout和ffadeout的值都=1,所以乘积会=1,mesh会保持它本来的尺寸。
当dist大于_FadeOutDistFar或小于_FadeOutDistNear时,nfadeout和ffadeout其中之一的值=1,另一个会<1,所以乘积会<1,我们可以根据这个值对mesh的顶点进行收缩,就产生了我们需要的雾效效果了。


以上就是 ShadowGun 中 GodRays 的实现思路,借鉴了不少别人的文章,大家就凑合看吧。

展开阅读全文

没有更多推荐了,返回首页