Unity制作旋转光束
大家好,我是阿赵。
这是一个在很多游戏里面可能都看到过的效果,在传送门、魔法阵、角色等脚底下往上散发出一束拉丝形状的光,然后在不停的旋转。
这次来在Unity引擎里面做一下这种效果。
一、准备材料
需要准备的素材很简单。
第一个是一个圆柱形的网格模型,删除了上下盖,然后展平UV。
第二个是一张噪声贴图
二、制作过程
1、控制形状
由于准备的输出是一个圆柱网格,但实际显示的效果是一个类似于扇形的形状。所以需要通过控制顶点来实现。原理很简单,UV的V坐标,是从模型的底部到顶部从0到1变化,所以只要沿着法线方向,乘以UV的V坐标,然后再乘以一个控制值,加到顶点坐标上,就能做到底部不变,越往上宽度越大了。
float3 vertexValue = ( v.normal * _normalScale * v.uv.y );
v.vertex.xyz += vertexValue;
o.vertex = UnityObjectToClipPos(v.vertex);
效果入下图
2、拉丝效果
先把噪声图赋给网格模型,得到这样的效果:
然后设置一下平铺次数
就得到了拉丝的效果了。
这里给固有色添加一个HDR的颜色叠加,然后把这个拉丝效果作为Alpha通道输入,设置Transparent透明渲染,就能得到这样的效果:
3、透明渐变效果
上面的效果太强烈,需要对它的透明度做一定的控制。先看看UV坐标的V坐标的实际范围:
之前提到过,V坐标是从模型底部到顶部从0到1变化。接下来就可以通过这个值做一些处理。
1.上下边缘控制
首先控制的是上下边缘,现在边缘太硬,我把它用两个SmoothStep,分别对应顶部和底部,让边缘变得柔和:
float tempOneMinueVal = ( 1.0 - i.uv.y );
float smoothstepResultV1 = smoothstep( _vMin , _vMax , ( tempOneMinueVal - _vOffset ));
float smoothstepResultV2 = smoothstep( _vMin2 , _vMax2 , i.uv.y);
float clampResult = clamp( min( min( smoothstepResultV1 , tempOneMinueVal ) , smoothstepResultV2 ) , 0.0 , 1.0 );
把这个上下边缘柔和的结果和原来的拉丝Alpha值相乘,得到了这个效果:
2.左右边缘控制
上面的效果已经很接近我们想要的效果了,但还差一点,左右边缘也很硬,所以用世界法线方向和观察方向做点乘,最后还是加一个SmoothStep,让左右边缘有个柔和渐变。
float3 worldNormal = i.worldNormal.xyz;
float3 worldViewDir = UnityWorldSpaceViewDir(i.worldPos);
worldViewDir = normalize(worldViewDir);
float dotResult = dot( worldNormal , worldViewDir );
float smoothstepResultEdge = smoothstep( _edgeMin , _edgeMax , abs( dotResult ));
计算结果是这样的左右两边渐变的变暗:
3.叠加遮罩
把上面的3个SmoothStep结果相乘,就得到了这样一个遮罩范围:
然后和拉丝的Alpha值相乘,得到了这样的效果:
4、遮挡问题解决
这里有一个半透明渲染的问题,在某些角度看,会出现错误显示:
这里我再复制一份网格模型:
然后两个网格模型使用不同的CullMode
然后两个网格模型一起显示,就得到了正确的效果:
三、Shader源码
Shader "azhao/LightColumn"
{
Properties
{
[HDR]_emissCol("emissCol", Color) = (0,0,0,0)
_emissScale("emissScale", Float) = 1
_noiseTex("noiseTex", 2D) = "white" {}
_flowSpeed("flowSpeed", Vector) = (0,0,0,0)
_vOffset("vOffset", Float) = 0
_edgeMin("edgeMin", Range( 0 , 1)) = 0
_edgeMax("edgeMax", Range( 0 , 1)) = 1
_vMin("vMin", Range( 0 , 1)) = 0
_vMax("vMax", Range( 0 , 1)) = 1
_normalScale("normalScale", Range(-2,2)) = 1
_vMin2("vMin2", Range( 0 , 1)) = 0
_vMax2("vMax2", Range( 0 , 1)) = 1
[Enum(UnityEngine.Rendering.CullMode)]_CullMode("CullMode", Float) = 2
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
CGINCLUDE
#pragma target 3.0
ENDCG
Blend SrcAlpha One, SrcAlpha One
AlphaToMask Off
Cull [_CullMode]
ColorMask RGBA
ZWrite On
ZTest LEqual
Offset 0 , 0
Pass
{
Name "Unlit"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
//only defining to not throw compilation error over Unity 5.5
#define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)
#endif
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
uniform float _CullMode;
uniform float _normalScale;
uniform float4 _emissCol;
uniform float _emissScale;
uniform sampler2D _noiseTex;
SamplerState sampler_noiseTex;
uniform float2 _flowSpeed;
uniform float4 _noiseTex_ST;
uniform float _vMin;
uniform float _vMax;
uniform float _vOffset;
uniform float _vMin2;
uniform float _vMax2;
uniform float _edgeMin;
uniform float _edgeMax;
v2f vert ( appdata v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = worldNormal;
o.uv.xy = v.uv.xy;
float3 vertexValue = ( v.normal * _normalScale * v.uv.y );
v.vertex.xyz += vertexValue;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
half4 frag (v2f i ) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
float2 uv_noiseTex = i.uv.xy * _noiseTex_ST.xy + _noiseTex_ST.zw;
float2 panner6 = ( 1.0 * _Time.y * _flowSpeed + uv_noiseTex);
float tempOneMinueVal = ( 1.0 - i.uv.y );
float smoothstepResultV1 = smoothstep( _vMin , _vMax , ( tempOneMinueVal - _vOffset ));
float smoothstepResultV2 = smoothstep( _vMin2 , _vMax2 , i.uv.y);
float clampResult = clamp( min( min( smoothstepResultV1 , tempOneMinueVal ) , smoothstepResultV2 ) , 0.0 , 1.0 );
float3 worldNormal = i.worldNormal.xyz;
float3 worldViewDir = UnityWorldSpaceViewDir(i.worldPos);
worldViewDir = normalize(worldViewDir);
float dotResult = dot( worldNormal , worldViewDir );
float smoothstepResultEdge = smoothstep( _edgeMin , _edgeMax , abs( dotResult ));
half4 finalColor = (float4((( _emissCol * _emissScale )).rgb , ( tex2D( _noiseTex, panner6 ).r * clampResult * smoothstepResultEdge )));
return finalColor;
}
ENDCG
}
}
}