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
3.FallOff
不管是orthographic还是不是。
如果不用FallOff, 投影下来的都是最亮即alpha为1的cookie,并且会向frustrum的正反2个方向投影,造成我们并不想看到的“双重投影”的效果(这个情况有的会出现有的不会)。不用fallOff,加到其他通道里也可以。用系统包带的Falloff贴图的话。投影会随着距离变淡。
// 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
加在角色下面,记得把角色的layer加进projector设置下的“Ignore layer”即可。
就是用3d Max或者 Maya或者Unity,先把动画对应的阴影烘焙好, 做成序列帧。然后用projector的材质做对应的帧动画即可。
参考:http://qiankanglai.me/2016/11/14/unity-projector/index.html
守护的影子是基于Asset Store上的Dynamic Shadow Projector插件修改制作的。
原理其实非常简单:使用Command Buffer控制相机里绘制Cast Shadow部分的模型到一个Render Texture;对这个Render Texture做一定处理之后(譬如是否打开MipMap,是否模糊等),利用Projector将其投影到Receive Shadow的部分。本质上就是生成一个贴图放影子,然后用tex2DProj画上去。
与ShadowMap优劣比较
劣势
无法实现自阴影,或者说实现代价比较大
绘制影子物体的时候无法Batch
优势
可以很方便的实现模糊、软阴影;@赵忠健在ScreenSpaceShadowMask Blur里也对ShadowMap实现了模糊,但是为了完美嵌入Unity自己的RenderLoop需要折腾下
可以控制增量更新,譬如产生影子的物体没有变化的时候,影子就完全不需要重绘
可以很方便的控制投影的范围,这是我觉得最重要的一点
当然它们也有一些共通的地方,可以很方便的控制Cast Shadow/Receive Shadow的部分。
务必控制Cast Shadow/Receive Shadow的物体个数,保证不需要的物体不会参与绘制!以守护为例,只有几个模型参与了绘制,而且当模型超出范围之后立刻挪出渲染队列;接受影子的只有一个大平面,周围复杂的场景部分压根不参与Receive。
某些机器的显卡对于tex2DProj支持的有问题,不过目前只看到一个红米有…在这个设备上ShadowMap也是有问题的,所以目前还是处于没辙的状态。
基本是就把黑的blob cookie换成 中间是白色的cookie即可。
另一个相机绘制到RenderTexture,然后projector的材质使用RenderTexture即可
就是projector的方向和范围设置和手电一致。把电筒的光颜色投出去即可。
// 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
}
}
}
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;
}
}
}
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);
}
}