I haven’t posted anything about my current project, mainly because I mostly worked on internal systems. But recently I needed a bit of a break and implemented some visual FX. The first I want to show here is a ‘simple’ explosion.
The base effect consists of a Shuriken particle system, making use of a custom shader to control color and transparency. This shader is based on Florian Smolka’s shader linked to in this blog post. It uses 2D gradients to remap the color of a sprite into a different color space. Each horizontal pixel-line in the texture defines a color gradient that is selected according to each individual particle’s age. I modified the shader to only use a single gradient texture with an alpha channel instead of two textures and added a multiplier for HDR output. The sprite itself is animated (via a spritesheet) to further enhance the animation. I added a shockwave effect with a second shader that uses the GrabPass function and some maths to distort the already rendered image (sourcecode at the end of this post). It is not physically correct, and has some issues (most notably it distorts objects in front of the shockwave, since it is rendered in the transparent queue), but it is sufficient for this effect. It takes both the vertex normals and an optional normal map into account to calculate the distortion, and the strength is controlled further via a fresnel effect, so distortion is strongest at surfaces perpendicular to the camera’s viewing direction. The shockwave, as well as a point light, is animated via a script and a custom curve.
And since Unity updated its particle system with line renderers and noise, I decided to quickly try creating some kind of electrical/emp explosion effect. Totally unrelated to the above technique (save for the shockwave), but it looks quite neat for the effort. The line renderer and noise functions are really good.
Now obviously have to create a nuclear mushroom cloud. No explosion VFX set can be complete without one.
Shader "Custom/Distortion" { Properties { _MainColor ("Color", Color) = (1,1,1,1) _Normal ("Normal Map", 2D) = "normal" {} _IOR("Refraction", Range(-5, 5)) = 1 _NormalMapStrength("Normal Map Strength)", Range(0, 1)) = 1 _FresnelStrength("Fresnel Strength)", Range(0, 1)) = 1 } SubShader { Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } GrabPass {} Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 viewDir : TEXCOORD2; float4 grabUv : TEXCOORD3; float3 viewNormal : TEXCOORD4; }; uniform sampler2D _GrabTexture; uniform half4 _MainColor; uniform sampler2D _Normal; uniform float4 _Normal_ST; uniform half _IOR; uniform half _NormalMapStrength; uniform half _FresnelStrength; v2f vert (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); OUT.uv = IN.uv; float3 worldPos = mul(unity_ObjectToWorld, IN.vertex).xyz; OUT.viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); OUT.normal = UnityObjectToWorldNormal(IN.normal); OUT.grabUv = ComputeGrabScreenPos(OUT.position); OUT.viewNormal = UnityObjectToClipPos(IN.normal); return OUT; } //TODO fix distortion of geometry in front of object fixed4 frag (v2f IN) : SV_Target { IN.normal = normalize(IN.normal); IN.viewDir = normalize(IN.viewDir); IN.viewNormal = normalize(IN.viewNormal); fixed3 normalMap = normalize(UnpackNormal(tex2D(_Normal, _Normal_ST * IN.uv))) * _NormalMapStrength; float3 normProj = IN.normal - dot(IN.normal, IN.viewDir) * IN.viewDir; float fresnel = pow(length(cross(IN.normal, IN.viewDir)),4) * _FresnelStrength; IN.grabUv.x += _IOR * IN.viewNormal.x * fresnel; IN.grabUv.y -= _IOR * IN.viewNormal.y * fresnel; IN.grabUv.xy += _IOR * normalMap; fixed4 refraction = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(IN.grabUv)); return _MainColor * refraction; } ENDCG } } }
Comments