【Unity Shader】实现粒子根据到摄像机的距离渐变

53 篇文章 1 订阅
16 篇文章 1 订阅

0x00需求

特效同学需要一个能随着距离摄像机距离变化,而颜色逐渐变淡的需求。

0x01分析需求

把美术同学的需求转化成程序需求便是:透明度随着距离越来越小。那么问题的关键就变成了如何计算距离,而计算到摄像机的距离,首先想到的是通过计算两个点之间的距离来计算。

		// 方法1.摄像机的世界坐标 - 转换到世界空间的顶点坐标
		o.distance = length(_WorldSpaceCameraPos - mul(unity_ObjectToWorld, v.vertex).xyz);

我在本地的测试,使用一个Capsule,改变它与Camera的距离来测试,一切正常。


可以看到随着Capsule到摄像机的距离越来越近,透明度逐渐变小,越来越透明。
然而交付给美术同学后,美术同学反馈,这个材质应用在粒子特效上,有的粒子看起来正常,有的看起来透明度没有明显变化。

0x02分析原因

这个需求看似很简单,问题的关键就在于计算顶点的距离,我第一反应是计算距离的方法有问题,于是使用矩阵计算,替换了上面与Camera坐标求解的方法:

		// 方法2.从模型空间到观察空间
		// Camera的参数(Position, Target, Up)决定了view matrix。模型在World Space里的位置,经过view matrix的变换,就变成了在View Space里的位置。
		o.distance = length(mul(UNITY_MATRIX_MV, v.vertex).xyz);

经过测试,表现依旧。在我的测试环境下一切正常,而在美术同学使用Particle System时就会出现,部分粒子正常,部分粒子不正常的现象。
事出反常必有妖!
那么问题来了,
妖在哪里???
看代码实在看不出问题,我就打开了一个粒子发射器,使之对准摄像机,我从摄像机的位置出发,盯着满屏幕的粒子发呆。


A few minutes later!

看出来了吗?

没有?

没有继续看。

A few days later!

看出来了吗?

没有?

没有继续看。

A thousand years later!

一千多年以后,我忽然觉得屏幕边缘的粒子确实有渐隐的效果,而屏幕中间的粒子却没有。
这是为什么?难道中间的粒子有什么奇怪的地方?

确实很奇怪!

如果把摄像机的位置当成一个点C,那么中间的粒子P1到点C的距离,与边缘的粒子P2到点C的距离是不同的!此时若用两点间的距离来映射到透明度,那么就会出现中间的粒子透明了,而边缘的粒子却没有变透明。

0x03解决方案

那么如何让不同点到摄像机的距离不一样,却能通过某种函数映射到同一个值呢?
那只能凭借直觉了 = =
没错,是直觉。因为问题不是出现在数学计算上,而是出现在需求的分析上。
其实本文一开始的需求分析就有问题,只是直接将美术同学表述的需求转化成了程序需求,而希望粒子在某一位置透明度变淡,这一位置,其实是一个平面。有感觉的同学肯定已经想到了,我们需要计算的距离是点到面的距离,而不是点到点的距离,那么是哪个面呢?从摄像机看出去,第一面必然是近裁剪面了。问题得证!

0x04效果图&源代码

最后是代码:

Shader "game/particle/Alpha_FadeByDistance"
{
	Properties
	{
		_MainTex ("主帖图", 2D) = "white" {}
		[Gamma][HDR]_MainColor ("主颜色", Color) = (1,1,1,1)
        _Threshold ("渐隐阈值", Float) = 5
	}

	CGINCLUDE
	#include "UnityCG.cginc"

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

	struct v2f
	{
		float2 uv : TEXCOORD0;
		float4 vertex : SV_POSITION;
		float distance : TEXCOORD1;
		float4 vertexColor : COLOR;
	};

	uniform sampler2D _MainTex;
	uniform float4 _MainTex_ST;
	uniform float4 _MainColor;
	uniform float _Threshold;
	
	v2f vert (appdata v)
	{
		v2f o;
		o.vertex = UnityObjectToClipPos(v.vertex);
		o.uv = TRANSFORM_TEX(v.uv, _MainTex);
		o.vertexColor = v.vertexColor;
		// 方法1.摄像机的世界坐标 - 转换到世界空间的顶点坐标
		// o.distance = length(_WorldSpaceCameraPos - mul(unity_ObjectToWorld, v.vertex).xyz);
		// 方法2.从模型空间到观察空间
		// Camera的参数(Position, Target, Up)决定了view matrix。模型在World Space里的位置,经过view matrix的变换,就变成了在View Space里的位置。
		// o.distance = length(mul(UNITY_MATRIX_MV, v.vertex).xyz);
		// 方法3.将模型空间下的顶点坐标转换到观察空间,然后计算顶点z值与近裁剪面的距离
		o.distance = -UnityObjectToViewPos(v.vertex).z - _ProjectionParams.y;
		return o;
	}
	
	fixed4 frag (v2f i) : SV_Target
	{
		fixed4 col = tex2D(_MainTex, i.uv);
		// 将距离映射为alpha[0, 1],为了在粒子系统中控制颜色的变化,需要*vertexColor
		col.a *= clamp(i.distance / _Threshold, 0.0, 1.0) * _MainColor.a * i.vertexColor.a;
		col.rgb *= _MainColor.rgb * i.vertexColor.rgb;
		return col;
	}
	ENDCG
	SubShader
	{
		Tags { "RenderType"="Transparent" "Queue" = "Transparent+200" }
		Blend SrcAlpha OneMinusSrcAlpha
		Cull Off
		ZWrite Off					// 需要关闭深度写入,否则会有纹理的半透明切边
		
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			ENDCG
		}
	}
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值