好长一段时间都没写过博客了,身边一有各种杂七杂八的事就懒散下来了,我要振作(╯‵□′)╯""┻━┻。楼主在写这篇博客的时候室友突然要和几个同班同学去云南玩耍个一星期,搞得楼主差点没心情继续往下写了,我也想和他们一起来场说走就走的旅行ಥ_ಥ,不行,我要振作(╯‵□′)╯""┻━┻!!
实现方法二:投影
可以看到Projector这个组件很好地将光环的图片投影到各个物体上,由于使用的是Unity内部封装好的矩阵与投影采样算法,而且从始至终只需要实例化一个材质,因此这种实现方法适合于大场景中。使用Projector组件的时候需要注意的是这个组件默认会向周围多个方向同时投影,如果不加限制的话投影出来的效果就会比较难以控制,Projector组件的投影是带有穿透效果的,适当地使用Ignore Layers属性能达到更好的效果。
实现方法三:投影(这种方法比较奇葩,楼主摸索了大半天写出来的,感兴趣的同学们看看得了)
Projection1_1.cs:
Projection1_1.shader:
Projection1_1.cs:
扩展:
好吧扯远了,自己学shader也有一段时间了(自学这玩意儿真的很苦逼啊,遇到问题得各种尝试,想不通为何Unity官方对这方面的教程这么少),但是从来没在啥项目中真正去编写一些shader,因此就想提取一下在游戏中使用普通逻辑难以实现的相关技术,然后用shader实现它,相信这也是一种很好地学习方法。我们玩游戏时常常能预览角色的技能范围或者点击一个npc或者玩家的时候能看到角色的光环,就比如最近楼主在玩的天涯明月刀ol:
可以看到这些光环是根据地形来动态地投影形状的,下面我们就来在Unity3D引擎中实现这些效果,楼主使用的是Unity 4.6.4f1版本。
实现方法一:使用阴影来模拟
人物的阴影是游戏引擎根据人物模型的顶点所在位置与光线形成向量,接收阴影的物体计算出这些向量与自己是否有交点,如果有交点,即将颜色渲染成灰黑色。因此我们只需要计算接收光环的物体与光环的y轴向量或光线方向的交点便可以实现。
新建一个场景,这里楼主选用了一个Capsule胶囊体作为主角:
然后往Capsule上挂一个Plane,这个Plane没什么很大的作用,只是为了方便观察光环的半径大小,由于不需要显示出来,我们把Plane上的Mesh Renderer给勾掉,楼主给这个Plane加上了一个“PlayerCircle”,用来待会儿在代码中查找这个Plane的位置:
然后我们在Plane下新建一个空物体GameObject,这个物体到Capsule的世界坐标系距离就作为光环的半径:
接下来我们写一个Projection1.cs脚本,这个脚本向shader中提供主角的位置以及光环的半径信息:
Projection1.cs:
using UnityEngine;
using System.Collections;public class Projection1 : MonoBehaviour {Transform mPlayerCircle;public Material shaderMaterial;// Use this for initializationvoid Awake () {//找到光环或者是主角GameObject circleObj = GameObject.FindGameObjectWithTag ("PlayerCircle");if (!circleObj){return;}mPlayerCircle = circleObj.transform;//计算光环的半径float dis = 0;foreach(Transform son in mPlayerCircle){dis = Vector3.Distance(mPlayerCircle.position,son.position);}//获取物体上的所有材质Material[] objMaterials = renderer.materials;foreach(Material m in objMaterials){//通过材质的名字来查找材质//由于实际运行时有可能会有多个角色会使用这个材质,而材质的参数可能会因角色的不同而不同,因此我们不使用sharedMaterial,而使用拷贝的实例if("Projection1 (Instance)" == m.name){shaderMaterial = m;break;}}//告诉shader主角光环的半径if(shaderMaterial && mPlayerCircle){shaderMaterial.SetFloat("_circleRadius",dis);}}// Update is called once per framevoid Update () {if(mPlayerCircle){if(shaderMaterial){Vector3 pos = mPlayerCircle.position;//告诉shader主角的光环的世界坐标shaderMaterial.SetVector("_circlePos",new Vector4(pos.x,pos.y,pos.z,1));//告诉shader主角光环的两个矩阵:世界坐标系转模型坐标系/模型坐标系转世界坐标系shaderMaterial.SetMatrix("_wTom",mPlayerCircle.renderer.worldToLocalMatrix);shaderMaterial.SetMatrix("_mTow",mPlayerCircle.renderer.localToWorldMatrix);}}}
}
对应的,我们还需要写一个Projection1.shader
Projection1.shader:
Shader "Custom/Projection1" {Properties {_circleRadius("circleRadius",float) = 0_circlePos("circlePos",vector) = (0,0,0,1)_circleColor("circleColor",color) = (1,1,1,1)_width("circleWidth",Range(3,10)) = 0}
SubShader {Pass{Tags {"RenderType" = "Transparent"}Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"float4 _circlePos;float _circleRadius;float4 _LightColor0;float4 _circleColor;float _width;struct v2f{float4 pos:SV_POSITION; //投影空间的顶点float4 vertexPos:TEXCOORD3; //世界空间的顶点float3 litDir:TEXCOORD0; //顶点的光照方向或一个y轴垂直分量float3 disDir:TEXCOORD1; //顶点到光环中心位置的距离float4 uv:TEXCOORD2; //顶点的颜色};v2f vert(appdata_base v){v2f o;//将自己的顶点坐标转换成世界坐标float4 worldPos = mul(_Object2World,v.vertex);o.vertexPos = worldPos;//每个点默认的颜色为_LightColor0;o.uv = _LightColor0;//光线的方向//o.litDir = WorldSpaceLightDir(v.vertex);o.litDir.y = 1;o.litDir.x = 0;o.litDir.z = 0;//该物体到光环的向量o.disDir = (_circlePos-worldPos).xyz;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(v2f o):COLOR{float3 litDir = normalize(o.litDir);float3 disDir = o.disDir;float c = length(disDir);disDir = normalize(disDir);float cosB = dot(disDir,litDir);float sinB = sin(acos(max(0,cosB)));float b = sinB*c;if((b>_circleRadius) || (b<_circleRadius-_circleRadius/_width)){return o.uv*-1;}return _circleColor;}ENDCG}}FallBack "Diffuse"
}
这个shader的工作就是计算物体当前的顶点是否在光环的半径内,如果在的话就把顶点的颜色更换掉就好了,用一些简单的数学知识就能搞定,我们还是看看图最直接,下图中的描述对应着shader中的内容:
对于刚接触shader没多久的同学来说,同学们一定会注意到v2f结构体
struct v2f
{float4 pos:SV_POSITION; //投影空间的顶点float4 vertexPos:TEXCOORD3; //世界空间的顶点float3 litDir:TEXCOORD0; //顶点的光照方向或一个y轴垂直分量float3 disDir:TEXCOORD1; //顶点到光环中心位置的距离float4 uv:TEXCOORD2; //顶点的颜色
};
为何在顶点信息结构体v2f中vertexPos和disDir等变量的语义都是TEXCOORD而不是POSITION?因为POSITION是物体的顶点信息,TEXCOORD是物体的uv坐标信息,它们的单位不一样,TEXCOORD与物体的面的uv坐标有关,单位为像素,而POSITION的范围与建模时建模者设定的物体中心有关,单位比像素大,我们需要的是改变物体的uv坐标的颜色,如果使用POSITION来作为判断,将会发现判断的误差相当大,出现颜色成片断裂的情况。
o.uv = _LightColor0;
return o.uv*-1;
由于场景中的物体常常包含1个或多个材质,这两句话的作用是让被渲染的物体在没有接近光环的时候保持原来材质的颜色。
float cosB = dot(disDir,litDir);
为嘛cosB就等于dot(disDir,litDir)呢?
由于dot函数计算的是两向量的点积,点积的几何意义是衡量两向量的相似程度,dot(disDir,litDir) = |disDir|*|litDir|*cos<disDir,litDir>,又因为disDir和litDir都被标准化为了单位向量,因此模都为1,因此cosB就等于dot(disDir,litDir)啦
回到场景,新建一个Projection1的材质,使用刚刚写好的Projection1.shader, 自己设定好光环的颜色以及光环的粗细 :
将cs文件和材质赋给你想要接收光环的物体上 ,比如我想让马路、蘑菇、岩石都能接收光环:
运行游戏,就可以看到效果了:
把shader的代码改一下,直接根据光环中心到顶点uv的水平距离来判断该顶点是否要被渲染成光环的颜色,让它更简单直观一些:
Shader "Custom/Projection1" {
Properties {_circleRadius("circleRadius",float) = 0_circlePos("circlePos",vector) = (0,0,0,1)_circleColor("circleColor",color) = (1,1,1,1)_width("circleWidth",Range(3,10)) = 0
}
SubShader {Pass{Tags {"RenderType" = "Transparent"}Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"float4 _circlePos;float _circleRadius;float4 _LightColor0;float4 _circleColor;float _width;struct v2f{float4 pos:SV_POSITION; //投影空间的顶点float4 vertexPos:TEXCOORD3; //世界空间的顶点float3 litDir:TEXCOORD0; //顶点的光照方向或一个y轴垂直分量float3 disDir:TEXCOORD1; //顶点到光环中心位置的距离float4 uv:TEXCOORD2; //顶点的颜色};v2f vert(appdata_base v){v2f o;//将自己的顶点坐标转换成世界坐标float4 worldPos = mul(_Object2World,v.vertex);o.vertexPos = worldPos;//每个点默认的颜色为_LightColor0;o.uv = _LightColor0;//光线的方向//o.litDir = WorldSpaceLightDir(v.vertex);o.litDir.y = 1;o.litDir.x = 0;o.litDir.z = 0;//该物体到光环的向量o.disDir = (_circlePos-worldPos).xyz;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(v2f o):COLOR{float2 circlePos = float2(_circlePos.x,_circlePos.z);float2 wPos = float2(o.vertexPos.x,o.vertexPos.z);float2 tempVec3 = circlePos-wPos;//float dis = distance(circlePos,wPos);float dis = length(tempVec3);if((dis>_circleRadius) || (dis<_circleRadius-_circleRadius/_width)){return o.uv*-1;}return _circleColor;}ENDCG}}FallBack "Diffuse"
}
可以看到效果是一样的:
这种方法实现起来比较简单,但是也存在一些限制,比如要是假定角色的光环是一张图片而不是纯色,那么用这种方法将不能实现,因为我们没办法以Plane的空间坐标系来对光环图片进行采样;而且这种方法要求每一个物体都挂上写好的cs脚本与材质,在大场景中使用将会产生比较大的开销。因此这种方法建议在一些比较小的场景中使用。
要想实现投影效果,核心是接收光环的物体需要获取uv坐标信息与对光环图片的采样。Unity中有为我们封装好的投影控件Projector,使用这个控件我们可以轻易实现动态的光环效果。
新建一个场景,同样的新建一个Capsule作为我们的主角:
在Capsule下新建一个空物体,给这个空物体绑上一个Projector组件,这个组件使用自定义的材质Projection2,其他参数比如Near Clip Plane(近剪切平面)、Far Clip Plane(远剪切平面)、Field Of View(投影的视角范围)、Aspect Ratio(剪切平面的宽高比)等根据实际场景情况来调节:
接下来我们就来写材质Projection2要用到的Projection2.shader。
Projection2.shader:
Shader "Custom/Projection2" {
Properties {_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {Pass{Tags {"RenderType"="Transparent"}LOD 200ZWrite offBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;float4x4 _Projector;struct v2f{float4 pos:SV_POSITION;float4 texc:TEXCOORD0;};v2f vert(appdata_base v){v2f o;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);//将顶点变换到矩阵空间o.texc = mul(_Projector,v.vertex);return o;}float4 frag(v2f o):COLOR{//对光环图片进行投影采样float4 c = tex2Dproj(_MainTex,o.texc);//限制投影方向c = c*step(0,o.texc.w);return c;}ENDCG}}FallBack "Diffuse"
}
可以看到这个shader的内容很简单,我们来看看比较关键的几句代码:
o.texc = mul(_Projector,v.vertex);
float4 c = tex2Dproj(_MainTex,o.texc);
我们结合图片来看这两句话的意思,大致的思想便是通过_Projector来判定物体的某一个顶点是否处于投影组件的剪切平面内,通过tex2Dproj来获得对光环图片的采样颜色
Blend SrcAlpha OneMinusSrcAlpha
Blend表示颜色的混合 ,SrcAlpha则是混合的标识,这个标识告诉shader可以混合原图颜色通道中的Alpha值,OneMinusSrcAlpha则是混合的具体算法,告诉shader使用(1-source color)的方法来混合。因此我们可以实现材质的透明度的效果,除此之外我们还可以使用Blend DstColor One等混合来实现透明度的效果。
shader写好后,新建一个Projection2材质,使用这个shader,然后使用的光环图片是这张:
由于光环的图片并不透明,我们还需要设置一下图片的属性,勾选Alpha from Grayscale和Alpha Is Transparency,将纯黑的颜色值给过滤(或者事先就在ps中准备好一张透明的png图片),同时把Wrap Mode改为Clamp:
然后看看运行效果:
在方法二中,我们通过使用_Projector矩阵就能计算出物体在投影剪切平面下的对应顶点坐标和uv坐标,那么是不是只要我们自己写出一个矩阵来代替_Projector就可以了呢?我尝试着找到_Projector的相关内容,但是找了好久都没有找到,但是注意到Unity的摄像机也是带有投影的各种属性(如进剪切平面),而且摄像机中确实也开放了他的矩阵属性:Camera.projectionMatrix,那么我们就利用这个矩阵来进行探索。
新建一个场景,与方法一中一样,新建一个Capsule、一个Plane、一个用来计算光环半径的空物体GameObject。
新建一个摄像机,作为Capsule的子物体,将这个摄像机的视角向下,适当调整Clipping Planes里的属性,让摄像机的远剪切平面大致与地面齐平就可以了(不设置也没问题):
接下来,与方法一一样,我们写个Projection1_1.cs脚本:
using UnityEngine;
using System.Collections;public class Projection1_1 : MonoBehaviour {Transform mPlayerCircle;public Material shaderMaterial;public Camera circleCamera;// Use this for initializationvoid Awake () {GameObject circleObj = GameObject.FindGameObjectWithTag ("PlayerCircle");if (!circleObj) {return;}mPlayerCircle = circleObj.transform;float dis = 0;foreach(Transform son in mPlayerCircle){dis = Vector3.Distance(mPlayerCircle.position,son.position);}Material[] objMaterials = renderer.materials;foreach(Material m in objMaterials){if("Projection1_1 (Instance)" == m.name){shaderMaterial = m;break;}}//告诉shader主角光环的半径if(shaderMaterial && mPlayerCircle){shaderMaterial.SetFloat("_circleRadius",dis);//获取指定摄像机的投影矩阵if(circleCamera){Matrix4x4 cameraPro = circleCamera.projectionMatrix;shaderMaterial.SetMatrix("_cameraProMatrix",cameraPro);}}}// Update is called once per framevoid Update () {if(mPlayerCircle){if(shaderMaterial){Vector3 pos = mPlayerCircle.position;//告诉shader主角的光环的世界坐标shaderMaterial.SetVector("_circlePos",new Vector4(pos.x,pos.y,pos.z,1));}}}
}
然后写一个Projection1_1.shader:
Shader "Custom/Projection1_1" {
Properties {_circleRadius("circleRadius",float) = 0_circlePos("circlePos",vector) = (0,0,0,1)_MainTex("MainTex",2D) = "white"{}
}
SubShader {Pass{Tags {"RenderType" = "Transparent"}Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"float4 _circlePos;float _circleRadius;float4x4 _cameraProMatrix; //指定摄像机的投影矩阵float4 _LightColor0;sampler2D _MainTex;struct v2f{float4 pos:SV_POSITION;float4 vertexPos:TEXCOORD3;float4 uv:TEXCOORD2;float4 tex:TEXCOORD4;};v2f vert(appdata_base v){v2f o;//将自己的顶点坐标转换成世界坐标float4 worldPos = mul(_Object2World,v.vertex);o.vertexPos = worldPos;float4x4 proj = _cameraProMatrix;o.tex = mul(proj,v.vertex);o.uv = _LightColor0;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(v2f o):COLOR{float2 circlePos = float2(_circlePos.x,_circlePos.z);float2 wPos = float2(o.vertexPos.x,o.vertexPos.z);float2 tempVec3 = circlePos-wPos;float dis = length(tempVec3);if((dis>_circleRadius)){return o.uv*-1;}return tex2Dproj(_MainTex,o.tex);}ENDCG}}FallBack "Diffuse"
}
同样的,我们给接收光环的物体绑定上脚本和材质,为了方便观察效果,先使用一些比较显眼的图片作为_MainTex,比如楼主这里使用了无冬ol的一张壁纸:
运行效果如下:
可以发现这完全不是我们想要的效果,这说明仅仅使用Camera.projectionMatrix来计算是不够的,一定还缺少了其他的矩阵。仔细思考一下,可以发现Camera.projectionMatrix是用来将物体投影到相机屏幕空间的矩阵,不像_Projector是投射到每一个物体表面的矩阵,那么我们就可以猜测只要正确处理渲染到相机屏幕的过程,就可以获取正确的投影uv颜色值,而渲染到相机屏幕的过程正是UNITY_MATRIX_MVP。UNITY_MATRIX_MVP即Model空间变换矩阵*View空间变换矩阵*Projection空间变换矩阵,其中的Model对应被渲染物体的_Object2World矩阵;View对应UNITY_MATRIX_V,放在每一个相机中看就是每一个相机自己空间矩阵;Projection对应UNITY_MATRIX_P,放在每一个相机中看就是每一个相机自己的投影矩阵。因此,接下来我们尝试获取摄像机屏幕对应的uv颜色值。
MVP中的M、P我们都可以获取,剩下的V就是Camera.worldToCameraMatrix,于是我们来修改Projection1_1.cs与Projection1_1.shader:
using UnityEngine;
using System.Collections;public class Projection1_1 : MonoBehaviour {Transform mPlayerCircle;public Material shaderMaterial;public Camera circleCamera;// Use this for initializationvoid Awake () {GameObject circleObj = GameObject.FindGameObjectWithTag ("PlayerCircle");if (!circleObj) {return;}mPlayerCircle = circleObj.transform;float dis = 0;foreach(Transform son in mPlayerCircle){dis = Vector3.Distance(mPlayerCircle.position,son.position);}Material[] objMaterials = renderer.materials;foreach(Material m in objMaterials){if("Projection1_1 (Instance)" == m.name){shaderMaterial = m;break;}}//告诉shader主角光环的半径if(shaderMaterial && mPlayerCircle){shaderMaterial.SetFloat("_circleRadius",dis);//获取指定摄像机的投影矩阵if(circleCamera){Matrix4x4 cameraPro = circleCamera.projectionMatrix;shaderMaterial.SetMatrix("_cameraProMatrix",cameraPro);Matrix4x4 cameraPro1 = circleCamera.worldToCameraMatrix;shaderMaterial.SetMatrix("_cameraLocalMatrix",cameraPro1);}}}// Update is called once per framevoid Update () {if(mPlayerCircle){if(shaderMaterial){Vector3 pos = mPlayerCircle.position;//告诉shader主角的光环的世界坐标shaderMaterial.SetVector("_circlePos",new Vector4(pos.x,pos.y,pos.z,1));}}}
}
Projection1_1.shader:
Shader "Custom/Projection1_1" {
Properties {_circleRadius("circleRadius",float) = 0_circlePos("circlePos",vector) = (0,0,0,1)_MainTex("MainTex",2D) = "white"{}
}
SubShader {Pass{Tags {"RenderType" = "Transparent"}Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"float4 _circlePos;float _circleRadius;float4x4 _cameraProMatrix; //指定摄像机的投影矩阵float4x4 _cameraLocalMatrix; //指定摄像机的模型空间矩阵float4 _LightColor0;sampler2D _MainTex;struct v2f{float4 pos:SV_POSITION;float4 vertexPos:TEXCOORD3;float4 uv:TEXCOORD2;float4 tex:TEXCOORD4;};v2f vert(appdata_base v){v2f o;//将自己的顶点坐标转换成世界坐标float4 worldPos = mul(_Object2World,v.vertex);o.vertexPos = worldPos;float4x4 proj = _cameraProMatrix;//设置矩阵的级联proj = mul(proj,_cameraLocalMatrix);proj = mul(proj,_Object2World);o.tex = mul(proj,v.vertex);o.uv = _LightColor0;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(v2f o):COLOR{float2 circlePos = float2(_circlePos.x,_circlePos.z);float2 wPos = float2(o.vertexPos.x,o.vertexPos.z);float2 tempVec3 = circlePos-wPos;float dis = length(tempVec3);if((dis>_circleRadius)){return o.uv*-1;}return tex2Dproj(_MainTex,o.tex);}ENDCG}}FallBack "Diffuse"
}
运行效果如下:
可以看到uv的颜色能正确地按照图片来了,但是还有许多问题。由于只要在光环范围内,渲染过程就会一直重复,无论我们把图片的Warp Mode设置成repeat或是clamp都会有这种情况的发生:
上面这图片向我们展示了一个问题就是角色在平移的过程中,uv的像素并不会跟着一起移动;角色旋转的过程中,uv的像素同样不会跟着一起旋转,这个过程就像我们平移一个相机一样,相机的视野在变化,但是相机视野内的物体不会自动跟着相机一起平移。我们再修改一下cs与shader脚本,需要解决三个问题:缩放、平移和旋转。
我们可以设置一个缩放矩阵来控制uv的缩放,再设置一个旋转矩阵来控制uv的旋转,楼主的线性代数不是很好,我们去网上找一下矩阵的公式:D
矩阵公式有了以后,我们就可以往cs和shader中添加代码了
在cs文件中,我们主要添加这几句:
//设置缩放矩阵Matrix4x4 scaleMatrix = Matrix4x4.identity;scaleMatrix.m00 = xScaleValue;scaleMatrix.m11 = yScaleValue;scaleMatrix.m22 = zScaleValue;shaderMaterial.SetMatrix("_scaleMatrix",scaleMatrix);rotationAngle = mPlayerCircle.eulerAngles.y;//设置旋转矩阵Matrix4x4 rotateMatrix = Matrix4x4.identity;rotateMatrix.m00 = Mathf.Cos(rotationAngle);rotateMatrix.m01 = Mathf.Sin(rotationAngle);rotateMatrix.m10 = -Mathf.Sin(rotationAngle);rotateMatrix.m11 = Mathf.Cos(rotationAngle);shaderMaterial.SetMatrix("_rotateMatrix",rotateMatrix);
在shader中,我们主要添加这几句:
proj = mul(proj,_scaleMatrix);
o.tex = mul(_rotateMatrix,o.tex);//平移uv坐标o.tex.x += _xOffset;o.tex.y += _yOffset;
然后在编辑的时候,又遇到了问题:
大家可以在这个gif图片上看到缩放没有什么大问题,主要是旋转,我们可以很明显的看到圆心在正前方的不远处,这种情况下只能平移uv去找到圆心,那么当角色旋转的时候误差就会比较大了,楼主尝试了许多方法想通过换算某些比例来自动找到圆心(比如重新计算texture2D的纹理等),但是发现都有误差,于是这个旋转的效果只能将就这样了(看看就好)。。
最后我们根据角色的移动来计算uv偏移的比例:
//设置平移变量float xOffset = (mNowPos.x - mPrePos.x) / tXScaleValue;float yOffset = (mNowPos.z - mPrePos.z) / tYScaleValue;tXSumValue += xOffset;tYSumValue += yOffset;shaderMaterial.SetFloat ("_xOffset", tXSumValue);shaderMaterial.SetFloat ("_yOffset", tYSumValue);
最终代码如下:
Projection1_1.cs:
using UnityEngine;
using System.Collections;public class Projection1_1 : MonoBehaviour {Transform mPlayerCircle;public Material shaderMaterial;public Camera circleCamera;public float xScaleValue; //缩放uv.x的比例public float yScaleValue; //缩放uv.y的比例public float zScaleValue; //缩放uv.z的比例public float rotationAngle; //旋转的角度public float tXScaleValue; //平移uv.x的比例public float tYScaleValue; //平移uv.y的比例public float tXSumValue; //平移uv.x的总值public float tYSumValue; //平移uv.y的总值Vector3 mPrePos; //上一帧的位置Vector3 mNowPos; //这一帧的位置// Use this for initializationvoid Awake () {GameObject circleObj = GameObject.FindGameObjectWithTag ("PlayerCircle");if (!circleObj) {return;}mPlayerCircle = circleObj.transform;float dis = 0;foreach(Transform son in mPlayerCircle){dis = Vector3.Distance(mPlayerCircle.position,son.position);}Material[] objMaterials = renderer.materials;foreach(Material m in objMaterials){if("Projection1_1 (Instance)" == m.name){shaderMaterial = m;break;}}//告诉shader主角光环的半径if(shaderMaterial && mPlayerCircle){shaderMaterial.SetFloat("_circleRadius",dis);//获取指定摄像机的投影矩阵if(circleCamera){Matrix4x4 cameraPro = circleCamera.projectionMatrix;shaderMaterial.SetMatrix("_cameraProMatrix",cameraPro);Matrix4x4 cameraPro1 = circleCamera.worldToCameraMatrix;shaderMaterial.SetMatrix("_cameraLocalMatrix",cameraPro1);}}tXSumValue = 5.98f;tYSumValue = 1.57f;mNowPos = mPlayerCircle.position;mPrePos = mPlayerCircle.position;}// Update is called once per framevoid Update () {//if (0 == Time.frameCount % 10){if (mPlayerCircle) {mNowPos = mPlayerCircle.position;if (shaderMaterial) {Vector3 pos = mPlayerCircle.position;//告诉shader主角的光环的世界坐标shaderMaterial.SetVector ("_circlePos", new Vector4 (pos.x, pos.y, pos.z, 1));//设置缩放矩阵Matrix4x4 scaleMatrix = Matrix4x4.identity;scaleMatrix.m00 = xScaleValue;scaleMatrix.m11 = yScaleValue;scaleMatrix.m22 = zScaleValue;shaderMaterial.SetMatrix ("_scaleMatrix", scaleMatrix);//rotationAngle = mPlayerCircle.eulerAngles.y;//设置旋转矩阵Matrix4x4 rotateMatrix = Matrix4x4.identity;rotateMatrix.m00 = Mathf.Cos (rotationAngle);rotateMatrix.m01 = Mathf.Sin (rotationAngle);rotateMatrix.m10 = -Mathf.Sin (rotationAngle);rotateMatrix.m11 = Mathf.Cos (rotationAngle);shaderMaterial.SetMatrix ("_rotateMatrix", rotateMatrix);//设置平移变量float xOffset = (mNowPos.x - mPrePos.x) / tXScaleValue;float yOffset = (mNowPos.z - mPrePos.z) / tYScaleValue;tXSumValue += xOffset;tYSumValue += yOffset;shaderMaterial.SetFloat ("_xOffset", tXSumValue);shaderMaterial.SetFloat ("_yOffset", tYSumValue);}mPrePos = mNowPos;}}}
}
Projection1_1.shader:
Shader "Custom/Projection1_1" {
Properties {_circleRadius("circleRadius",float) = 0_circlePos("circlePos",vector) = (0,0,0,1)_MainTex("MainTex",2D) = "white"{}_xOffset("xOffset",float) = 0_yOffset("yOffset",float) = 0
}
SubShader {Pass{Tags {"RenderType" = "Transparent"}Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"float4 _circlePos;float _circleRadius;float4x4 _cameraProMatrix; //指定摄像机的投影矩阵float4x4 _cameraLocalMatrix; //指定摄像机的模型空间矩阵float4x4 _scaleMatrix; //缩放图像的矩阵float4x4 _rotateMatrix; //旋转图像的矩阵float _xOffset;float _yOffset;float4 _LightColor0;sampler2D _MainTex;struct v2f{float4 pos:SV_POSITION;float4 vertexPos:TEXCOORD3;float4 uv:TEXCOORD2;float4 tex:TEXCOORD4;};v2f vert(appdata_base v){v2f o;//将自己的顶点坐标转换成世界坐标float4 worldPos = mul(_Object2World,v.vertex);o.vertexPos = worldPos;float4x4 proj = _cameraProMatrix;//设置矩阵的级联proj = mul(proj,_cameraLocalMatrix);proj = mul(proj,_Object2World);proj = mul(proj,_scaleMatrix);o.tex = mul(proj,v.vertex);o.tex = mul(_rotateMatrix,o.tex);//平移uv坐标o.tex.x += _xOffset;o.tex.y += _yOffset;o.uv = _LightColor0;o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(v2f o):COLOR{float2 circlePos = float2(_circlePos.x,_circlePos.z);float2 wPos = float2(o.vertexPos.x,o.vertexPos.z);float2 tempVec3 = circlePos-wPos;float dis = length(tempVec3);if((dis>_circleRadius)){return o.uv*-1;}return tex2Dproj(_MainTex,o.tex);}ENDCG}}FallBack "Diffuse"
}
与方法一一样,我们把脚本和材质挂到接收光环的平面上,比如草地、房屋等等:
运行效果:
可以看到这种方法实现起来要处理的问题比较多,要达到很好的效果还得优化许多细节,就当一种另类的思路吧= =。
在用Projector组件实现了光环的投影后,我曾经尝试着再寻找其他办法,在方法一中我们新建了一个Plane,有一种办法是给Plane写一个shader,在这个shader中即时地改变Plane的顶点坐标,让这些顶点坐标与接收光环的物体顶点坐标一致,比如角色走到山坡上,就让Plane的顶点坐标变得和这个山坡一样倾斜,就像附在山坡上的一块皮一样,那么问题来了,我要如何才能获取此时此刻山坡的顶点信息呢,楼主尝试了很久只找到了一个办法,就是在光环周围按照一定的密度设置射线点,这些射线点垂直于xz平面发射射线,获取这些射线返回的顶点信息,但是依然会有许多问题,比如开销大、顶点高度会不平滑等等。。
我们在编辑器中新建一个3d物体的时候,只要勾选Mesh Renderer中的Cast Shadow属性便可以让物体投射出阴影,那我们能不能直接修改这些内置好的阴影信息,让它们变成光环纹理的颜色呢,楼主测试了许多遍,发现这些内置的阴影只允许更改uv的坐标和Alpha值,但是uv的颜色是不允许更改的。。于是这种方法是否可行我也没继续深究下去了,有兴趣的同学可以研究研究,要修改这些内置的阴影,可以使用TRANSFER_SHADOW_CASTER以及SHADOW_CASTER_FRAGMENT等宏。
这篇文章就到这里啦,楼主正在努力研究其他好玩的东西~ 由于水平有限,本篇文章讲述的观点有误还请大家轻喷,顺便帮忙指正,共同进步,么么哒:D 。