Unity creates rotating beam
Hello everyone, my name is A Zhao.
This is an effect that you may have seen in many games. A beam of brushed light is emitted upward from under the feet of portals, magic circles, characters, etc., and then rotates continuously.
This time let’s do this effect in the Unity engine.
1. Prepare materials
The materials that need to be prepared are very simple.
The first one is a cylindrical mesh model with the upper and lower caps removed and then the UVs flattened.
The second one is a noise map
2. Production process
1. Control the shape
Since the prepared output is a cylindrical grid, the actual display effect is a fan-like shape. So it needs to be achieved by controlling the vertices. The principle is very simple. The V coordinate of UV changes from 0 to 1 from the bottom to the top of the model, so just multiply it by the V coordinate of UV along the normal direction, and then multiply it by a control value and add it to the vertex coordinate. , the bottom will remain unchanged, and the width will increase as you go up.
float3 vertexValue = ( v.normal * _normalScale * v.uv.y );
v.vertex.xyz += vertexValue;
o.vertex = UnityObjectToClipPos(v.vertex);
The effect is as shown below
2. Brushed effect
First assign the noise map to the grid model to get this effect:
Then set the number of tilings
You get the brushed effect.
Here, add an HDR color overlay to the inherent color, then input the brushed effect as the Alpha channel, and set Transparent rendering to get this effect:
3. Transparent gradient effect
The above effect is too strong and requires some control over its transparency. First look at the actual range of the V coordinate of the UV coordinate:
As mentioned before, the V coordinate changes from 0 to 1 from the bottom to the top of the model. Next, you can do some processing with this value.
1. Upper and lower edge control
The first thing to control is the upper and lower edges. Now the edges are too hard. I use two SmoothSteps, corresponding to the top and bottom respectively, to make the edges soft:
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 );
Multiplying the soft upper and lower edges with the original brushed Alpha value, we get this effect:
2. Left and right edge control
The above effect is very close to the effect we want, but it is still a little bit worse. The left and right edges are also very hard, so we use the world normal direction and the observation direction to do dot multiplication. Finally, we add a SmoothStep to give a soft gradient to the left and right edges. .
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 ));
The calculation result is the gradual darkening of the left and right sides like this:
3. Overlay mask
Multiply the above three SmoothStep results to get such a mask range:
Then multiplied by the brushed Alpha value, we get this effect:
4. Solving the occlusion problem
There is a problem with translucent rendering. From certain angles, an error will appear:
Here I make another copy of the grid model:
Then the two mesh models use different CullMode
Then the two mesh models are displayed together, and the correct effect is obtained:
3. Shader source code
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
}
}
}