迷幻角色背景
大家好,我是阿赵。
 之前介绍过了经典的Shader写法,物体顶点坐标在顶点程序转换到裁剪空间,然后在片段程序里面通过模型的UV进行贴图采样,然后把颜色显示在模型上面。
 之前也介绍过经典的顶点程序应用,树木的生长。通过特定的信息,控制顶点的偏移,模拟出树干树枝生长的效果。
 这次带来一个我觉得比较经典的UV坐标控制例子。
 
一、例子说明
在做这个例子之前,先来考虑一个问题。我们采样模型的贴图,是不是必须使用模型的UV信息?
 答案肯定是否的。
 我之前在很多例子里面已经用过MatCap效果,采样MatCap就并不是用模型的UV坐标,而是把物体的法线方向转到观察空间,然后再转换到[0,1]的范围,作为采样MatCap贴图的UV信息,可以看代码回顾一下:
float2 GetMatCapUV(float3 objNormal)
	{
float3 normalWorld = mul(unity_ObjectToWorld, objNormal);
float3 normalView = mul(UNITY_MATRIX_IT_MV, normalWorld);
return normalView.xy*0.5+0.5;
}
在这次这个例子里面,我做了一个使用顶点坐标转屏幕空间坐标的操作,使用这个屏幕坐标作为UV来采样贴图。这样得到的效果,模型上的贴图会一直正面朝着屏幕,不会因为模型的旋转或者摄像机的旋转而导致贴图的方向发生变化。模型的顶点范围,只会影响到贴图显示的范围大小。
 为了让这个例子看起来更有意思,所以我使用了2个视频作为贴图,所以我先会说一下在Unity里面怎样使用AVProVideo插件播放视频。
二、详细做法说明
1、AVProVideo播放视频
1.导入AVProVideo

先购买插件,然后导入项目,会看到有一个AVProVideo文件夹,里面会有Demo,想深入了解的话可以去看看各种demo的应用。我这个例子用的不是很复杂,只是单纯的用来播放视频而已,所以那些360播放、VR播放的功能,就不去管了。
2.创建播放器

导入插件之后,在创建物体对象的地方就可以创建MediaPlayer,在场景里面创建MediaPlayer。
3.指定播放的视频

选择MediaPlayer,可以看到选项。资源路径里面可以指定播放视频的地址。然后可以设置自动打开、自动播放、循环等。
4.渲染到指定的材质球

添加一个ApplyToMaterial的组件,可以把指定的MediaPlayer渲染的视频,以RenderTexture的形式传入指定的材质球。
5.为例子准备的2个视频
(1)背景视频
这个背景是我自己用AE做出来的一个循环背景,如果有人有兴趣,可以提出来,我下次再告诉大家怎样做。
 
(2)角色身上的视频
这个是AVProVideo插件的demo自带的视频,本来是一个可以贴到球体上的循环视频,我觉得效果不错,就直接拿来做例子了。
 
2、制作背景材质
背景其实就是是一个面片,但由于没有拿模型的UV坐标作为采样,而是拿了顶点坐标转屏幕坐标作为UV来采样,所以不管怎样旋转面片,显示的画面都会是正对着我们的
 
 
关键的计算步骤是:
1.顶点程序
o.screen_pos = ComputeScreenPos(o.vertex);
其中ComputeScreenPos函数是UnityCG.cginc内置的,通过这个方法获取到的屏幕坐标的取值范围已经是[0,1],比自己计算方便。
inline float4 ComputeScreenPos (float4 pos)
{
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
    o.zw = pos.zw;
    
    return o;
}
2.片段程序
//用透视除法,将裁剪空间坐标转换到NDC空间坐标
			float2 screenUV = i.screen_pos.xy / (i.screen_pos.w + 0.000001);
			float aspect = _ScreenParams.x / _ScreenParams.y;
			float aspectx = aspect * _scale.x;
			screenUV.x *= aspectx;
			screenUV.x += (1 - aspectx) / 2;
			//判断屏幕UV是否左上角是0,是否需要上下翻转V坐标。
			//由于例子用的是视频插件生成的RenderTexture,没有Repeat,所以不能直接UV坐标乘以-1,改为用1-V坐标
			if(_ProjectionParams.x < 0)
			{
				screenUV.y = 1 - screenUV.y;
			}
			//用屏幕UV采样贴图,得到颜色1
			half4 col = tex2D(_MainTex, screenUV);
这里的重点是用透视除法求NDC空间的坐标。然后用_ScreenParams.x / _ScreenParams.y;求出屏幕的长宽比例,进行偏移,让画面可以在屏幕中间居中。
 不过如果单纯是这样计算出UV然后进行采样,会发现画面是上下颠倒的,所以还要根据_ProjectionParams.x判断屏幕UV是否左上角是0,是否需要上下翻转V坐标。
 最后就可以把计算出来的UV坐标来采样贴图了。
3、角色材质制作
角色的材质是在背景材质上做了加工。
 这里分别用2套UV对角色模型进行了采样,一套是正常的模型UV、另外一套是顶点坐标转屏幕坐标(和背景的计算方式一样)
 两套UV都是采样传进去的视频作为贴图,具体的效果如下:
 
 
接下来要做的事情就非常简单了,我很喜欢用一张噪声图,把它自身的UV平铺数值改成0,0,然后用一个_Time.y来滚动UV坐标,就能采样噪声图的单个像素,做出黑白灰闪动的效果:
float noiseUV = i.uv*_noiseMap_ST.xy + _noiseMap_ST.zw;
noiseUV.x += _Time.y*_speed;
float4 noiseCol = tex2D(_noiseMap, noiseUV);
float noiseVal = step(0.5, noiseCol.r);

 
最后,对黑白灰的效果做step,让它只会出现黑白,然后用来混合刚才两套UV的采样结果,就做出了2种效果之间的闪烁变化了。
 float4 finalCol = col * noiseVal + col2 * (1 - noiseVal);
三、完整Shader
Shader "azhao/CenterImgRole"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white" {}
		_scale("Scale",Vector) = (1,1,1,1)
		_noiseMap("Noise",2D) = "black"{}
		_speed("Speed",float) = 1
	}
		SubShader
		{
			Tags { "RenderType" = "Opaque" }
			LOD 100
			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#include "UnityCG.cginc"
				struct appdata
				{
					float4 vertex : POSITION;
					float2 uv : TEXCOORD0;
				};
				struct v2f
				{
					float4 vertex : SV_POSITION;
					float2 uv : TEXCOORD0;
					float4 screen_pos:TEXCOORD1;
				};
				sampler2D _MainTex;
				float4 _MainTex_ST;
				float4 _scale;
				float _speed;
				sampler2D _noiseMap;
				float4 _noiseMap_ST;
				v2f vert(appdata v)
				{
					v2f o;
					o.vertex = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);
					//获取顶点对应的屏幕空间坐标
					o.screen_pos = ComputeScreenPos(o.vertex);
					return o;
				}
				half4 frag(v2f i) : SV_Target
				{
					//用透视除法,将裁剪空间坐标转换到NDC空间坐标
					float2 screenUV = i.screen_pos.xy / (i.screen_pos.w + 0.000001);
					float aspect = _ScreenParams.x / _ScreenParams.y;
					float aspectx = aspect * _scale.x;
					screenUV.x *= aspectx;
					screenUV.x += (1 - aspectx) / 2;
					//判断屏幕UV是否左上角是0,是否需要上下翻转V坐标。
					//由于例子用的是视频插件生成的RenderTexture,没有Repeat,所以不能直接UV坐标乘以-1,改为用1-V坐标
					if(_ProjectionParams.x < 0)
					{
						screenUV.y = 1 - screenUV.y;
					}
					//用屏幕UV采样贴图,得到颜色1
					half4 col = tex2D(_MainTex, screenUV);
					//用正常模型坐标采样贴图,得到颜色2
					half4 col2 = tex2D(_MainTex, i.uv);
					//用模型坐标采样一张噪声贴图,用于混合颜色1和颜色2
					float noiseUV = i.uv*_noiseMap_ST.xy + _noiseMap_ST.zw;
					noiseUV.x += _Time.y*_speed;
					float4 noiseCol = tex2D(_noiseMap, noiseUV);
					float noiseVal = step(0.5, noiseCol.r);
					float4 finalCol = col * noiseVal + col2 * (1 - noiseVal);
					return finalCol;
				}
				ENDCG
			}
		}
}



















