unity projector相关探讨和使用

projector

用到的组件

Projector组件

可以实现

0 角色选择光圈
1 普通圆点阴影
2 伪动态阴影 http://qiankanglai.me/2016/11/14/unity-projector/index.html
3 光的投影
4 投影仪(根据material的贴图,可以是图片、视频,另一个相机看到的景象)
5 3d或者2d手电筒的效果
参考:http://blog.sina.com.cn/s/blog_471132920101hhsd.html

注意事项:
1.确保Cookie Texture一定要设置为Clamp
2.为了避免projector bleeding, Cookie Texture 开启Border Mipmaps选项, 或者直接禁用Mipmap
unity projector相关探讨和使用_第1张图片
unity projector相关探讨和使用_第2张图片
3.FallOff
不管是orthographic还是不是。
如果不用FallOff, 投影下来的都是最亮即alpha为1的cookie,并且会向frustrum的正反2个方向投影,造成我们并不想看到的“双重投影”的效果(这个情况有的会出现有的不会)。不用fallOff,加到其他通道里也可以。用系统包带的Falloff贴图的话。投影会随着距离变淡。

projector之角色脚下的光圈shader编写

unity projector相关探讨和使用_第3张图片

shader脚本如下,可以加其他的控制变量

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'

Shader "Projector/Circle"
{
    Properties
    {
        _MainTex ("Cookie", 2D) = "white" {}
    }
    SubShader
    {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }

        Pass
        {
            ColorMask RGB
            ZWrite off Cull Off Lighting Off 
            //Blend DstColor One
            //Blend SrcAlpha One            // particle add
            Blend SrcAlpha OneMinusSrcAlpha    // particle blend
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 texc:TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4x4 unity_Projector;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.texc = mul(unity_Projector, v.vertex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                //fixed4 col = tex2D(_MainTex, i.uv);
                float4 c = tex2Dproj(_MainTex, i.texc);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return c;
            }
            ENDCG
        }
    }
}

注意:贴图的采样uv是由unity_Projector 乘 v.vertex实现的,采样的时候用tex2Dproj(_MainTex, i.texc);采样,后者等价于tex2D(_MainTex, i.texc.xy/i.texc.z)。

可以再加上旋转脚本

参考:http://blog.csdn.net/ronintao/article/details/52236210
http://blog.csdn.net/candycat1992/article/details/41254799

projector之普通圆片阴影(blob shadow)

加在角色下面,记得把角色的layer加进projector设置下的“Ignore layer”即可。

伪动态阴影

就是用3d Max或者 Maya或者Unity,先把动画对应的阴影烘焙好, 做成序列帧。然后用projector的材质做对应的帧动画即可。

Dynamic Shadow Projector

参考:http://qiankanglai.me/2016/11/14/unity-projector/index.html
守护的影子是基于Asset Store上的Dynamic Shadow Projector插件修改制作的。

  1. 原理其实非常简单:使用Command Buffer控制相机里绘制Cast Shadow部分的模型到一个Render Texture;对这个Render Texture做一定处理之后(譬如是否打开MipMap,是否模糊等),利用Projector将其投影到Receive Shadow的部分。本质上就是生成一个贴图放影子,然后用tex2DProj画上去。

  2. 与ShadowMap优劣比较
    劣势
    无法实现自阴影,或者说实现代价比较大
    绘制影子物体的时候无法Batch
    优势
    可以很方便的实现模糊、软阴影;@赵忠健在ScreenSpaceShadowMask Blur里也对ShadowMap实现了模糊,但是为了完美嵌入Unity自己的RenderLoop需要折腾下
    可以控制增量更新,譬如产生影子的物体没有变化的时候,影子就完全不需要重绘
    可以很方便的控制投影的范围,这是我觉得最重要的一点
    当然它们也有一些共通的地方,可以很方便的控制Cast Shadow/Receive Shadow的部分。

  3. 需要注意的坑
    主要是使用的时候需要注意(对ShadowMap的使用来说也是类似的):

务必控制Cast Shadow/Receive Shadow的物体个数,保证不需要的物体不会参与绘制!以守护为例,只有几个模型参与了绘制,而且当模型超出范围之后立刻挪出渲染队列;接受影子的只有一个大平面,周围复杂的场景部分压根不参与Receive。

某些机器的显卡对于tex2DProj支持的有问题,不过目前只看到一个红米有…在这个设备上ShadowMap也是有问题的,所以目前还是处于没辙的状态。

  1. Fast Shadow Receiver
    当然,还有进一步优化的方法。我在另一个项目里直接使用了低模作为Geometry Proxy,降低Cast Shadow的消耗;另外有个插件Fast Shadow Receiver可以显著降低Receive Shadow消耗:
    原理也非常简单粗暴:直接生成一个Mesh来接受影子,对肯定接受不到的部分就不参与绘制。这个插件牛的地方在于它同时实现了Plane级别的接受和Mesh级别的,因为它直接实现了一个树形结构来保存Mesh信息,按需生成(目瞪口呆脸…)。譬如原来是一个复杂的场景接收影子的话就需要重绘整个模型,现在只需要绘制一小部分的网格就可以了~

光的投影

基本是就把黑的blob cookie换成 中间是白色的cookie即可。

投影仪(根据Meterial的贴图,可以是图片,视频,另一个相机看到的景象)

另一个相机绘制到RenderTexture,然后projector的材质使用RenderTexture即可

3D或者2D手电筒的效果

就是projector的方向和范围设置和手电一致。把电筒的光颜色投出去即可。

projector的消融shader

使用方法

  1. 新建场景,在场景中创建一个plane,layer设为AA,再创建一个sphere,layer设为BBB;
  2. 再在场景中场景一个空物体GameObject,为它添加Projector组件。
  3. Projector的Ignore Layer属性可以设置哪些layer不接受投影:假如不想让plane接收阴影,则选择AA;同样的,如果想要sphere不接受投影,可以把BBB也选上。
  4. 在material中填入material,将相应的材质进行投影。
  5. 可以设置projector的属性或旋转GameObject以将物体投射到不同的地方。
    unity projector相关探讨和使用_第4张图片

消融特效配套的shader及脚本及其参数

unity projector相关探讨和使用_第5张图片

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

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
// Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'

Shader "TGame/Projector/Projector_AlphaBlended" 
{
    Properties 
    {
        _BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
        _MainAlpha ("Main Alpha", Range(0.0, 1.0)) = 1.0
        _LineWidth("Burn Line Width", Range(0.0, 0.7)) = 0.1
        _TransparentWidth("Transparent Width", Range(0.0, 1.0)) = 0.1

        _MainColor ("Main Color", Color) = (1,1,1,1)
        _ShadowTex ("Cookie", 2D) = "" {}
        _FalloffTex ("FallOff", 2D) = "" {}
        _BurnTex ("Burn Noise, the r value which is smaller is first disappeared, the a value decides where disappeared", 2D) = "" {}

        _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)
        _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
        _Multipiler ("Multipiler",Range(0, 10)) = 1

        _XOffset("Horizontal Offset", float) = 0.01
        _YOffset("Vertical Offset", float) = 0.01
    }

    Subshader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
        Pass {
            Cull Off Lighting Off ZWrite Off
            ColorMask RGB

            Offset -1, -1
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"

            struct a2v {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
                fixed4 color : COLOR;
            };

            struct v2f {
                float4 uvShadow : TEXCOORD0;
                float4 uvFalloff : TEXCOORD1;
                float4 uvBurn : TEXCOORD2;
                fixed4 color : COLOR;
                UNITY_FOG_COORDS(3)
                float4 pos : SV_POSITION;
            };

            float4x4 unity_Projector;
            float4x4 unity_ProjectorClip;

            fixed _MainAlpha;
            fixed _BurnAmount;
            fixed _LineWidth;
            fixed _TransparentWidth;

            fixed _XOffset;
            fixed _YOffset;

            fixed4 _BurnFirstColor;
            fixed4 _BurnSecondColor;
            fixed _Multipiler;

            fixed4 _MainColor;
            sampler2D _ShadowTex;
            sampler2D _FalloffTex;
            sampler2D _BurnTex;
            float4 _BurnTex_ST;

            v2f vert (a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                o.uvShadow = mul (unity_Projector, v.vertex);
                o.uvFalloff = mul (unity_ProjectorClip, v.vertex);
                o.uvBurn = mul (unity_Projector, v.vertex);
                o.color = v.color;
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed2 offset = fixed2(_XOffset, _YOffset);
                fixed4 burnColor = tex2Dproj (_BurnTex, UNITY_PROJ_COORD(i.uvBurn));

                clip(burnColor.r - _BurnAmount);
                fixed4 uvShadow = UNITY_PROJ_COORD(i.uvShadow);
                fixed2 uv = uvShadow.xy / uvShadow.w;
                uv += offset;

                fixed4 texS = tex2D(_ShadowTex, uv);
                texS.rgb *= _MainColor.rgb;

                fixed t = 1 - smoothstep(0.0, _LineWidth, burnColor.r - _BurnAmount);
                fixed3 color = lerp(_BurnFirstColor, _BurnSecondColor, t);
                color = pow(color, 5);
                color *= burnColor.a * burnColor.a;

                fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
                fixed4 res = 2.0 * i.color * texS  * texF.a * _Multipiler;
                //return fixed4(texF.a, texF.a, texF.a, 1.0);
                //fixed4 res = 2.0 * i.color * texS  * _Multipiler;

                UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(0,0,0,0));
                fixed3 finalColor = lerp(res.rgb, color, t * step(0.0001, _BurnAmount));

                fixed t1 = smoothstep(0.0, _TransparentWidth, burnColor.r - _BurnAmount);
                fixed t2 = lerp(0, 1, t1);

                fixed finalAlpha = lerp(1, t2, t2 * step(0.0001, _BurnAmount));
                return fixed4(finalColor.rgb, burnColor.a * texS.a * finalAlpha * _MainAlpha);
            }
            ENDCG
        }
    }
}

shader

  1. BurnAmount: 噪声贴图中小于该值的区域先消失,通过脚本控制逐渐变化;
  2. BurnLineWidth:控制消融时边缘的宽度,边缘处有两种颜色,下面BurnFirst Color为消融处里面的颜色,Burn Second Color为消融处外边缘的颜色;如果不需要边缘颜色,将该值设为0即可;
  3. MainColor:设置投影颜色的主颜色(需要a通道,不显示的地方a通道和颜色都设置为黑色,与噪声贴图比例一致,否则会出错);
  4. Cookie:投影区域主贴图,控制颜色,和MainColor相乘决定最终颜色;
  5. FallOff:不设置,具体也不知道什么作用;
  6. Burn Noise:噪声贴图,越黑的地方越先被消融;
  7. Burn First Color:燃烧区域内边缘的颜色;
  8. Burn Second Color:燃烧区域外边缘的颜色;
  9. Multiplier: 加强颜色,和前面的颜色相乘,使颜色更明显;

脚本

  1. Material:将要投影的material拖进来即可;
  2. Burn Speed:控制消融的速度,从而控制shader中的_BurnAmount属性,使其从0变到1,实现消融的效果。
    浮点数曲线
using UnityEngine;
using System.Collections;


public enum ProjectorShaderProperties
{
    _MainAlpha,
    _BurnAmount,
    _XOffset,
    _YOffset
};


public class ProjectorShaderFloatCurve : MonoBehaviour {

    public ProjectorShaderProperties m_projectorFloatProperty = ProjectorShaderProperties._BurnAmount;
    public AnimationCurve m_floatCurve = AnimationCurve.EaseInOut(0, -1, 1, 1);
    public float m_graphTimeMultiplier = 6;
    public float m_graphIntensityMultiplier = 1;
    public bool m_isLoop = false;
    public bool m_useSharedMaterial = true;

    private bool m_canUpdate;
    private float m_startTime;
    private Material m_mat;
    private float m_startFloat;
    private int m_propertyID;
    private string m_shaderProperty;
    private bool m_isInitialized;

    private void Awake()
    {
        var rend = GetComponent();
        if (rend == null)
        {
            var projector = GetComponent();
            if (projector != null)
            {
                if (!m_useSharedMaterial)
                {
                    if (!projector.material.name.EndsWith("(Instance)"))
                        projector.material = new Material(projector.material) { name = projector.material.name + " (Instance)" };
                    m_mat = projector.material;
                }
                else
                {
                    m_mat = projector.material;
                }
            }
        }
        else
        {
            if (!m_useSharedMaterial) m_mat = rend.material;
            else m_mat = rend.sharedMaterial;
        }

        m_shaderProperty = m_projectorFloatProperty.ToString();
        if (m_mat.HasProperty(m_shaderProperty)) m_propertyID = Shader.PropertyToID(m_shaderProperty);
        m_startFloat = m_mat.GetFloat(m_propertyID);
        var eval = m_floatCurve.Evaluate(0) * m_graphIntensityMultiplier;
        m_mat.SetFloat(m_propertyID, eval);
        m_isInitialized = true;
    }

    private void OnEnable()
    {
        m_startTime = Time.time;
        m_canUpdate = true;
        if (!m_isInitialized)
        {
            var eval = m_floatCurve.Evaluate(0)* m_graphIntensityMultiplier;
            m_mat.SetFloat(m_propertyID, eval);
        }
    }

    private void Update()
    {
        var time = Time.time - m_startTime;
        if (m_canUpdate)
        {
            var eval = m_floatCurve.Evaluate(time / m_graphTimeMultiplier) * m_graphIntensityMultiplier;
            m_mat.SetFloat(m_propertyID, eval);
        }
        if (time >= m_graphTimeMultiplier)
        {
            if (m_isLoop) m_startTime = Time.time;
            else m_canUpdate = false;
        }
    }

    void OnDisable()
    {
        if (m_mat == null)
            return;
        if(m_useSharedMaterial) m_mat.SetFloat(m_propertyID, m_startFloat);
    }

    void OnDestroy()
    {
        if (!m_useSharedMaterial)
        {
            if (m_mat != null)
                DestroyImmediate(m_mat);
            m_mat = null;
        }
    }
}
  1. _mainColor等颜色渐变
using UnityEngine;
using System.Collections;

public enum ProjectorShaderColorProperties
{
    _MainColor
};

public class ProjectorShaderColor : MonoBehaviour {
    public ProjectorShaderColorProperties m_colorProperty = ProjectorShaderColorProperties._MainColor;
    public Gradient m_mainColorGradient = new Gradient()
    {
        colorKeys = new GradientColorKey[]
        {
                new GradientColorKey(new Color(1f, .639f, .482f, 1f), 0f),
                new GradientColorKey(new Color(1f, .725f, .482f, 1f), .10f),
                new GradientColorKey(new Color(1f, .851f, .722f, 1f), .50f),
                new GradientColorKey(new Color(1f, .725f, .482f, 1f), .90f),
                new GradientColorKey(new Color(1f, .639f, .482f, 1f), 1f)
        }
    }
    ;

    public float m_graphTimeMultiplier = 6;
    public bool m_isLoop = false;
    public bool m_useSharedMaterial = false;

    private bool m_canUpdate;
    private float m_startTime;
    private Material m_mat;
    private string m_shaderProperty;
    private int m_propertyID;
    private bool m_isInitialized;
    private Color m_startColor;

    private void Awake()
    {
        var rend = GetComponent();
        if (rend == null)
        {
            var projector = GetComponent();
            if (projector != null)
            {
                if (!m_useSharedMaterial)
                {
                    if (!projector.material.name.EndsWith("(Instance)"))
                        projector.material = new Material(projector.material) { name = projector.material.name + " (Instance)" };
                    m_mat = projector.material;
                }
                else
                {
                    m_mat = projector.material;
                }
            }
        }
        else
        {
            if (!m_useSharedMaterial) m_mat = rend.material;
            else m_mat = rend.sharedMaterial;
        }

        m_shaderProperty = m_colorProperty.ToString();
        if (m_mat.HasProperty(m_shaderProperty)) m_propertyID = Shader.PropertyToID(m_shaderProperty);
        m_startColor = m_mat.GetColor(m_propertyID);
        var eval = m_mainColorGradient.Evaluate(0);
        m_mat.SetColor(m_propertyID, eval);
        m_isInitialized = true;
    }

    private void OnEnable()
    {
        m_startTime = Time.time;
        m_canUpdate = true;
        if (!m_isInitialized)
        {
            var eval = m_mainColorGradient.Evaluate(0);
            m_mat.SetColor(m_propertyID, eval);
        }
    }

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        var time = Time.time - m_startTime;
        if (m_canUpdate)
        {
            var eval = m_mainColorGradient.Evaluate(time / m_graphTimeMultiplier);
            m_mat.SetColor(m_propertyID, eval);
        }
        if (time >= m_graphTimeMultiplier)
        {
            if (m_isLoop) m_startTime = Time.time;
            else m_canUpdate = false;
        }
    }

    void OnDisable()
    {
        if(m_mat == null)
        {
            return;
        }
        m_mat.SetColor(m_propertyID, m_startColor);
    }
}

你可能感兴趣的:(Unity)