Unity 交互草制作
介绍常见的交互草实现思路[*]通过脚本传递交互物体的世界坐标到Shader中,如果要实现拖影的效果,可以维护数组的方式进行存储前几帧的世界坐标,然后衰减交互强度。
[*]通过后处理的方式,维护一张拖影深度RT,然后采样这张深度RT,转换成法线,以此进行交互。
方法二的效果图
不要在意草为什么这么丑
能够通过效果图看到,移动过的位置,草并没有里面停止,而是有一种被拨动的感觉,并且在逐渐的衰减。这就是为什么不用Line Rendering 或者 Trail Rendering的原因。
实现
场景设置
[*]正交相机
暂时假设 物体是相机的父物体!!!! 后续会为了适配项目修改
后处理获得拖影深度RT
思考如何实现随着时间变换,以前的深度变小,来实现拖影的效果。
第一个想法是冯乐乐入门精要后处理部分教的Pingpong Buffer的方式。
效果图
Shader
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
float4 _CameraDepthTexture_TexelSize;
//上一帧的RT
sampler2D _LastFrame;
//同一像素上一帧和当前帧的世界坐标距离
float3 _PlayerPosOffset;
float _Size;
float _Velocity;
Vert:
o.uv = v.uv;
o.vertex = UnityObjectToClipPos(v.vertex);
//因为当前我们假设的物体和相机一起移动那么 uv采样的时候也需要移动来修正这个差距。
//不让相机会一直觉得物体并没有移动
//不
o.offsetUV = v.uv + _PlayerPosOffset.xz * _Size;
Frag:
//检测平台图形API
#if UNITY_UV_STARTS_AT_TOP
if(_CameraDepthTexture_TexelSize.y<0)
i.uv.y = 1 - i.uv.y;
#endif
//当前深度
float d = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,i.uv ).r);
//上一帧的RT
float4 lastFrame = tex2D(_LastFrame,i.offsetUV );
float3 final = float3(0,0,0);
//R 后续会介绍 具体干什么
final.r = 0;
//G记录 深度衰减
final.g = max(d,tex.g - _Velocity);
Post.cs
//一切常规的 数据初始化,具体看冯乐乐入门精要。 只写我这里特有的
private Vector3 previousPlayerPos;
private Vector3 currentPlayerPos;
public float size;
public float _Velocity;
OnRenderImage:
if (material != null)
{
//Pingpong buffer,两个rt实现循环一次Shader。
//目的:
//第一次blit,初始化深度,这里可以优化,最后我会介绍方法。
//第二次blit,因为第一次的blit后的RT作为了lastFrame,然后混合lastFrame和当前帧的深度,并且lastFrame会衰减,不要直接相加,最好max,防止爆掉。
//多次blit,叠加效果,可以自行选择次数,当然一次blit就是一次DC,根据需要选择。
RenderTexture buffer0 = RenderTexture.GetTemporary(src.width, src.height, 0);
buffer0.filterMode = FilterMode.Bilinear;
material.SetFloat(&#34;_Size&#34;, size);
Shader.SetGlobalFloat(&#34;_Velocity&#34;, _Velocity);
Graphics.Blit(src, buffer0,material,0);
material.SetTexture(&#34;_LastFrame&#34;, buffer0);
currentPlayerPos = camera.transform.position;
Vector3 playerPosOffset = currentPlayerPos - previousPlayerPos;
material.SetVector(&#34;_PlayerPosOffset&#34;, playerPosOffset);
previousPlayerPos = currentPlayerPos;
RenderTexture buffer1 = RenderTexture.GetTemporary(src.width, src.height, 0);
buffer1.filterMode = FilterMode.Bilinear;
Graphics.Blit(buffer0, buffer1, material, 0);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
Graphics.Blit(buffer0, dest, material, 0);
RenderTexture.ReleaseTemporary(buffer0);
}
else
...
为了通过这张RT让我们知道物体离开这个位置多久。我们需要添加时间属性。
添加时间属性。
Post.cs
....
Shader.SetGlobalFloat(&#34;_DeltaTime&#34;, Time.deltaTime);
OnRenderImage:
....
Shader.SetGlobalFloat(&#34;_DeltaTime&#34;, Time.deltaTime);Shader
Frag:
...
float3 final = float3(0,0,0);
//R记录 是否来过这里
final.r = max(d,tex.r);
//G记录 深度衰减
final.g = max(d,tex.g - _DeltaTime * _Velocity);效果图
我们就可以根据RG通道的差值然后除以速度,就可以获得时间啦,是不是很想初中物理呢。
运用到草上
随便做个切片草意思一下,反正我们要交互的效果而已啦。OvO
Post.cs
OnRenderImage:
//近远裁剪平面,_DepthToNormalVector:(相机左下角,宽,高)
Shader.SetGlobalFloat(&#34;_Far&#34;, camera.farClipPlane);
Shader.SetGlobalFloat(&#34;_Near&#34;, camera.nearClipPlane);
GetDepthToNormalVector(camera);
void GetDepthToNormalVector(Camera camera)
{
Vector3 leftBottom = camera.ScreenToWorldPoint(Vector3.zero);
//和上面等价,获得相机世界坐标下的左下角
//leftBottom = camera.transform.position + new Vector3(-camera.orthographicSize, 0, camera.orthographicSize);
float width = (2 * camera.orthographicSize) * camera.aspect;
float height = (2 * camera.orthographicSize) / camera.aspect;
Vector4 depthToNormalVector = new Vector4(leftBottom.x, leftBottom.z, width, height);
Shader.SetGlobalVector(&#34;_DepthToNormalVector&#34;, depthToNormalVector);
}GrassShader
//就是我们生成的RT图
_DepthToNormal(&#34;Depth To Normal&#34;,2D) = &#34;black&#34; {}
Vert:
//核心顶点偏移部分,其他的就没什么可说了
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
//因为是正交相机,直接减左下角,然后归一化,这里需要移动(0,1)才能够正常显示,应该是反转后出现的问题。
float2 DTNcoords = (worldPos.xz - _DepthToNormalVector.xy) / _DepthToNormalVector.zw + float2(0,1);
DTNcoords.y = 1 - DTNcoords.y;
//RG通道
float maxDepth = UNITY_SAMPLE_DEPTH(tex2Dlod(_DepthToNormal,float4(DTNcoords,0,0)).r);
float d = UNITY_SAMPLE_DEPTH(tex2Dlod(_DepthToNormal,float4(DTNcoords,0,0)).g);
float fzDistance = _Far - _Near;
//相邻两个点的深度,如同ddx,ddy一样求得梯度。
float dr = UNITY_SAMPLE_DEPTH(tex2Dlod(_DepthToNormal,float4(DTNcoords + float2(_DepthToNormal_TexelSize.x,0),0,0)).r);
float dt = UNITY_SAMPLE_DEPTH(tex2Dlod(_DepthToNormal,float4(DTNcoords + float2(0,-_DepthToNormal_TexelSize.y),0,0)).r);
//如下图,根据获得的两个方向 叉乘获得法线。
float3 rightDir = float3(_DepthToNormal_TexelSize.x,-(dr - d) * fzDistance, 0);
float3 downDir = float3(0,(dt - d) * fzDistance,_DepthToNormal_TexelSize.y);
o.normal = normalize(cross(rightDir,downDir));
//最后叠加的世界坐标上
//当前点的时间函数
float time = max(0.0001,(maxDepth - d) / (0.005f * _Velocity));
//方向 * 强度 * 时间影响如下下图 因为sin(x) / x 正好符合我时间越久,摇摆强度越小的需求。
worldPos.z +=o.normal.z * _InteractionIntensity* sin(time) / time;
worldPos.x +=o.normal.x * _InteractionIntensity* sin(time) / time;
法线转换的效果图
关于相机跟随的问题
最后选择相机和物体分离,这样的好处在于相机按照一格一格的移动,保证了我们只有在有草地(或者需要交互)的时候才会用到。
优化思路
[*]使用RenderWithShader 来实现虚拟相机渲染,不走相机渲染的路线,这样就会少掉很多DC,可能手机端都能够使用。当然这样我们就需要用一个pass\shader来做深度图。
[*]控制RT的质量,毕竟草交互,用不着那么高精度的贴图。
最后找找22暑假实习,本人大三
QQ:2422538491 (邮箱同)
页:
[1]