LogoLogo
  • Home
  • Projects
  • About
  • Contact

Spritesheet Scene Transitions in Unity3D

Devon O. · September 16, 2017 · Shaders, Unity3D · 0 comments
38

Over the past couple weeks I’ve been working on  a number of transition effects for use with the Starling Framework. One of my favorites involved using a Starling MovieClip of a greyscale sprite sheet animation to hide or reveal a DisplayObject instance. For example, you could take a black and white frame sequence of something burning or a spreading ink splotch and use it to transition an image in or out. After completing the Starling work, I got to wondering if I could do the same thing in Unity3D with the same spritesheet and texture atlas assets. Of course with a little finagling it turns out I could so I thought I’d share this little tip for anyone looking to do something similar.

What It Is

Before going any further it would probably help to present a visual idea of what the heck I’m talking about. Click here for a WebGL example of the type of effect this technique can create. Just to make the example scene more interesting, I used a fantastic Blacksmith’s Forge interior from 3DForge available for free on the Unity Asset Store. So what’s happening? Essentially, there is a spritesheet animation of a spreading blob of ink being used to mix the main scene texture with a specified color (in this case just black) inside an image effect shader during Unity’s OnRenderImage event.

How It Is

Of course the first thing you’ll need to create a spritesheet transition is a spritesheet and texture atlas .xml file. I created mine with this cool little ink splotch video on YouTube and a bit of fiddling with Adobe Photoshop and Animate. You can make yours however you’d like, but you’re welcome to use what I made so long as it isn’t for commercial purposes as I have no rights to the original video posted by Gerson Domiciano Coelho.

 

Ink Reveal Spritesheet

Ink Reveal Spritesheet Texture Atlas
XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="ink_reveal.png">
    <!-- Created with Adobe Animate version 15.1.1.13 -->
    <!-- http://www.adobe.com/products/animate.html -->
    <SubTexture name="ink_reveal0000" x="1" y="1" width="256" height="128" pivotX="0" pivotY="0"/>
    <SubTexture name="ink_reveal0001" x="1" y="1" width="256" height="128"/>
    <SubTexture name="ink_reveal0002" x="259" y="1" width="256" height="128"/>
    <SubTexture name="ink_reveal0003" x="517" y="1" width="256" height="128"/>
    <SubTexture name="ink_reveal0004" x="1" y="131" width="256" height="128"/>
    <SubTexture name="ink_reveal0005" x="259" y="131" width="256" height="128"/>
    <SubTexture name="ink_reveal0006" x="517" y="131" width="256" height="128"/>
    <SubTexture name="ink_reveal0007" x="1" y="261" width="256" height="128"/>
    <SubTexture name="ink_reveal0008" x="259" y="261" width="256" height="128"/>
    <SubTexture name="ink_reveal0009" x="517" y="261" width="256" height="128"/>
    <SubTexture name="ink_reveal0010" x="1" y="391" width="256" height="128"/>
    <SubTexture name="ink_reveal0011" x="259" y="391" width="256" height="128"/>
    <SubTexture name="ink_reveal0012" x="517" y="391" width="256" height="128"/>
    <SubTexture name="ink_reveal0013" x="1" y="521" width="256" height="128"/>
    <SubTexture name="ink_reveal0014" x="259" y="521" width="256" height="128"/>
    <SubTexture name="ink_reveal0015" x="517" y="521" width="256" height="128"/>
    <SubTexture name="ink_reveal0016" x="1" y="651" width="256" height="128"/>
    <SubTexture name="ink_reveal0017" x="259" y="651" width="256" height="128"/>
    <SubTexture name="ink_reveal0018" x="517" y="651" width="256" height="128"/>
    <SubTexture name="ink_reveal0019" x="1" y="781" width="256" height="128"/>
    <SubTexture name="ink_reveal0020" x="259" y="781" width="256" height="128"/>
    <SubTexture name="ink_reveal0021" x="517" y="781" width="256" height="128"/>
    <SubTexture name="ink_reveal0022" x="1" y="911" width="256" height="128"/>
    <SubTexture name="ink_reveal0023" x="259" y="911" width="256" height="128"/>
    <SubTexture name="ink_reveal0024" x="517" y="911" width="256" height="128"/>
    <SubTexture name="ink_reveal0025" x="1" y="1041" width="256" height="128"/>
    <SubTexture name="ink_reveal0026" x="259" y="1041" width="256" height="128"/>
    <SubTexture name="ink_reveal0027" x="517" y="1041" width="256" height="128"/>
    <SubTexture name="ink_reveal0028" x="1" y="1171" width="256" height="128"/>
    <SubTexture name="ink_reveal0029" x="259" y="1171" width="256" height="128"/>
    <SubTexture name="ink_reveal0030" x="517" y="1171" width="256" height="128"/>
    <SubTexture name="ink_reveal0031" x="1" y="1301" width="256" height="128"/>
    <SubTexture name="ink_reveal0032" x="259" y="1301" width="256" height="128"/>
    <SubTexture name="ink_reveal0033" x="517" y="1301" width="256" height="128"/>
    <SubTexture name="ink_reveal0034" x="1" y="1431" width="256" height="128"/>
    <SubTexture name="ink_reveal0035" x="259" y="1431" width="256" height="128"/>
    <SubTexture name="ink_reveal0036" x="517" y="1431" width="256" height="128"/>
    <SubTexture name="ink_reveal0037" x="1" y="1561" width="256" height="128"/>
    <SubTexture name="ink_reveal0038" x="259" y="1561" width="256" height="128"/>
    <SubTexture name="ink_reveal0039" x="517" y="1561" width="256" height="128"/>
    <SubTexture name="ink_reveal0040" x="1" y="1691" width="256" height="128"/>
    <SubTexture name="ink_reveal0041" x="259" y="1691" width="256" height="128"/>
    <SubTexture name="ink_reveal0042" x="517" y="1691" width="256" height="128"/>
    <SubTexture name="ink_reveal0043" x="1" y="1821" width="256" height="128"/>
    <SubTexture name="ink_reveal0044" x="259" y="1821" width="256" height="128"/>
    <SubTexture name="ink_reveal0045" x="517" y="1821" width="256" height="128"/>
</TextureAtlas>

 

If you’re playing along and don’t have a Unity project set up, create one now. Just to keep things neat, add the ink_reveal.png to a Textures directory inside your Unity project Assets directory and the ink_reveal.xml to a StreamingAssets directory inside your Unity project Assets directory (as you may have guessed, we’ll be streaming that XML in a little bit later) PROTIP: set the encoding of the .xml file to ascii or 8bit – when loading text in a WebGL project, Unity tends to choke on UTF-8.

The trickiest part of this technique is the image effect shader, so let’s get that out of the way. In a Shaders directory in your Unity project, create a new Image Effect Shader and name it “SpriteSheetTransition”. In the properties section of the shader, in addition to the _MainTex property, we also want to add a _TransitionTex property which is another 2D, a _Region property which is a Vector, and a _Color property which is a, you guessed it, Color.  Also add these properties to the SubShader section of the shader (down below the sampler2D _MainTex; line).  In the same place, also declare a float4 property named _TransitionTex_TexelSize. Now we can get down to brass tax and write the fragment shader.

What we want to do is convert the uv coordinates passed from the vertex shader to a specific region so when we sample the transition texture (i.e. the ink_reveal.png file) we only sample the specific frame we want (i.e. a specific rectangle as described by the SubTexture elements in the texture atlas .xml file). There’s a very simple formula used to convert numerical ranges from one to another that looks like this:

NewValue = (((OldValue-OldMin) * (NewMax-NewMin)) / (OldMax-OldMin)) + NewMin

Now in uv land, where we range from 0-1, OldMax-OldMin is always 1. Likewise OldValue-OldMin will always be OldValue. So that standard formula can be simplified to something like:

transitionUV = oldUV * rangeValues + minimumValues

It’s easy enough to get our range and minimum values from our XML region values by multiplying by the texel size (_TransitionTex_TexelSize.xy is texel width and texel height. Check out the Unity docs for more details). The final thing we’ll want to do is flip the v coordinate (i.e. subtract from 1) to range from bottom to top rather than top to bottom.

Once we get that transitionUV coordinate, we can sample our transition texture there. Then we add up the rgb values of the sample and divide by 3 to get the average color (keeping in mind, this is a greyscale image). This average is what we can use for a mix amount. To reverse the effect (say for a transition out rather than a transition in), just subtract this value from 1 before mixing. In any case, now that we have our mix amount, we can just sample the main texture at the original uv coordinates and mix (lerp, that is) that color with our _Color property the amount to mix. The final shader looks something like this:

SpriteSheetTransition.shader
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
*    Copyright (c) 2017 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
Shader "Transitions/SpriteSheetTransition"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TransitionTex ("Transition Texture", 2D) = "white" {}
        _Region ("Region", Vector) = (1,1,1,1)
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
            
            sampler2D _MainTex;
            sampler2D _TransitionTex;
            float4 _Region;
            float4 _TransitionTex_TexelSize;
            float4 _Color;
 
            fixed4 frag (v2f i) : SV_Target
            {
                float tw = _TransitionTex_TexelSize.x;
                float th = _TransitionTex_TexelSize.y;
 
                float2 minValues = float2(0,0);
                float2 rangeValues = float2(0,0);
 
                minValues.x = _Region.x * tw;
                float maxX = (_Region.x+_Region.z) * tw;
                rangeValues.x = maxX - minValues.x;
 
                minValues.y = _Region.y * th;
                float maxY = (_Region.y+_Region.w) * th;
                rangeValues.y = maxY - minValues.y;
 
                float2 xUV = i.uv * rangeValues + minValues;
                xUV.y = 1-xUV.y;
 
                float4 transColor = tex2D(_TransitionTex, xUV);
                float mixAmount = (transColor.x+transColor.y+transColor.z)/3;
 
                // To invert for a transition out effect subtract mix amount from 1, like below
                //mixAmount = 1-mixAmount;
 
                float4 col = tex2D(_MainTex, i.uv);
              
                return lerp(col, _Color, mixAmount);
            }
            ENDCG
        }
    }
}

That is really the heat of the meat as far as the effect goes, but we may as well finish up. The next thing we’ll need is a material that uses the transition shader. In a materials directory in the Unity project, create a new material named TransitionMaterial. In the Inspector, with the new material selected, set the shader to the SpriteSheetTransition.shader we just created. Also in the inspector, set the Transition Texture to the ink_reveal.png you added to the Textures directory and the Color to, well, whatever you want (black to match the effect I created).

After that, we’ll need a deserialized xml object to hold our texture atlas xml. Really this will just be a collection of objects mapped to those SubTexture elements in the xml atlas. In a Scripts directory, add a new script called RegionContainer (note that this doesn’t extend MonoBehaviour). This will both be our deserialized xml as well as a couple static methods to actually do the deserialization and return a RegionContainer instance. Something like this:

RegionContiner.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
*    Copyright (c) 2017 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
using System.Xml;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.IO;
 
[XmlRoot("TextureAtlas")]
public class RegionContainer
{
    [XmlElement("SubTexture")]
    public List<Region> Regions = new List<Region>();
 
    /// <summary>
    /// Deserialize xml locally from file stream
    /// </summary>
    public static RegionContainer Stream(string path)
    {
        var serializer = new XmlSerializer(typeof(RegionContainer));
        using(FileStream stream = new FileStream(path, FileMode.Open))
        {
            return serializer.Deserialize(stream) as RegionContainer;
        }
    }
 
    /// <summary>
    /// Deserialize xml from loaded string
    /// </summary>
    public static RegionContainer Load(string text)
    {
        var serializer = new XmlSerializer(typeof(RegionContainer));
        return serializer.Deserialize(new StringReader(text)) as RegionContainer;
    }
}
 
public class Region
{
    [XmlAttribute("name")]
    public string Name{ get; set; }
 
    [XmlAttribute("x")]
    public float X { get; set; }
 
    [XmlAttribute("y")]
    public float Y { get; set; }
 
    [XmlAttribute("width")]
    public float Width { get; set; }
 
    [XmlAttribute("height")]
    public float Height { get; set; }
}

Finally we’ll need a component (i.e. a script) that actually uses that material to create the post process effect. This component will be responsible for getting our regions (deserializing the .xml file, that is), applying the effect by blitting the main scene material with our TransitionMaterial and updating the regions of the sprite sheet used in our shader. When the effect is complete, the component should just kill itself and be done.

So, back in the Scripts directory, create a new C# script named TransitionPostProcessor that looks something like this:

TransitionPostProcessor.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
*    Copyright (c) 2017 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
 
public class TransitionPostProcessor : MonoBehaviour
{
    public Material TransitionMaterial;
    public float FPS;
 
    private List<Region> Regions;
    private int RegionIndex;
    private float Accum;
    private Vector4 CurrentRegion;
 
    // Awake
    void Awake()
    {
        this.RegionIndex = 0;
        this.Accum = 0f;
        this.CurrentRegion = new Vector4();
    }
 
    // Start
    void Start()
    {
        StartCoroutine("GetRegions");
    }
 
    // On Render Image
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(this.Regions==null)
            return;
 
        Graphics.Blit(source, destination, this.TransitionMaterial);
        this.Accum+=Time.deltaTime;
        if(this.Accum>=1f/FPS)
        {
            // reset the time accumulator
            this.Accum=0f;
            if (++this.RegionIndex==this.Regions.Count)
            {
                // If we've reached the end of the sprite sheet animation, remove this component from the Camera and exit
                Destroy(this);
                return;
            }
            UpdateMaterial();
        }
    }
 
    // Get list of regions from texture atlas xml
    private IEnumerator GetRegions()
    {
        string path = Path.Combine(Application.streamingAssetsPath, "ink_reveal.xml");
        if(path.Contains("://"))
        {
            // If in browser, load xml file with WWW
            // see https://docs.unity3d.com/ScriptReference/Application-streamingAssetsPath.html
            var www = new WWW(path);
            yield return www;
            this.Regions = RegionContainer.Load(www.text).Regions;
            UpdateMaterial();
        }
        else
        {
            // Otherwise stream from local file
            this.Regions = RegionContainer.Stream(path).Regions;
            UpdateMaterial();
        }
    }
 
    /// <summary>
    /// Update material with current texture atlas region
    /// </summary>
    private void UpdateMaterial()
    {
        Region r = this.Regions[this.RegionIndex];
        this.CurrentRegion.Set(r.X, r.Y, r.Width, r.Height);
        this.TransitionMaterial.SetVector("_Region", this.CurrentRegion);
    }
}

To finally tie everything together, we’ll need one last puzzle piece – a simple script that adds the above component to the main camera. In the unity scene, create an empty GameObject named TransitionController and in the Scripts directory create a new C# script with the same name. The script itself is very simple and really just intended as a demo (I’m sure you’d never want to just keep adding a transition on mouse click. I hope.). This script then just adds the transition component to the camera on awake – and again every time you click on the scene.

TransitionController.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
*    Copyright (c) 2017 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
using UnityEngine;
 
public class TransitionController : MonoBehaviour
{
 
    public Material TransitionMaterial;
 
    private Camera MainCamera;
 
    // Awake
    void Awake()
    {
        this.MainCamera = Camera.main;
        AddTransitionToCamera();
    }
 
    // Update
    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            AddTransitionToCamera();
        }
    }
 
    /// <summary>
    /// Adds transition effect to the main camera
    /// </summary>
    private void AddTransitionToCamera()
    {
        TransitionPostProcessor tpp = MainCamera.gameObject.GetComponent<TransitionPostProcessor>();
 
        // Don't keep adding the component if users get click happy
        if(tpp!=null)
            Destroy(tpp);
 
        tpp = MainCamera.gameObject.AddComponent<TransitionPostProcessor>();
        tpp.FPS = 60;
        tpp.TransitionMaterial = this.TransitionMaterial;
    }
}

Add the TransitionController.cs script to the TransitionController game object in the scene and in the inspector window, set the Transition Material property to the TransitionMaterial we created earlier.

And that should be that. Test the project and see what happens. In theory, the TransitionController will add the TransitionPostProcessor component to the main camera. The processor will, in turn, load or stream the texture atlas .xml file and deserialize it into a list of Region objects. Once that list is created, the processor will blit the transition material and update the region rectangle in the shader according to a specified FPS (well, I didn’t mention that, but have a look through the posted code).  When the last region has been reached, the component will simply remove itself from the camera and the scene is good to go.

It probably seems more complicated than it needs to be but really, this technique can be easily used to produce a pretty much endless number of fairly extravagant scene transitions. And if you already have the assets handy from Starling (or another spritesheet/texture atlas based framework), all the better.

If you have any problems or questions, let me know in the comments.

  Facebook   Pinterest   Twitter   Google+
shaders
  • Playing With a Couple Game Ideas
    September 16, 2012 · 10 comments
    3750
    12
    Read more
  • Multitouch Fluid Dynamics with AIR for Android and RTMFP
    January 19, 2011 · 3 comments
    3274
    8
    Read more
  • Fake Specular Highlights with Shader Graph
    December 27, 2018 · 0 comments
    So, the other day at work we were exploring various methods of adding nice
    6386
    17
    Read more

Leave a Comment! Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Devon O. Wolfgang

AIR | Unity3D | AR/VR

Unity Certified Developer

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development” and “Starling Game Development Essentials”

Reviewer of “The Starling Handbook”

Unity Engineer at Touch Press.

Categories
  • Actionscript (95)
  • AIR (16)
  • Flash (99)
  • Games (7)
  • Liberty (13)
  • Life (53)
  • Shaders (20)
  • Unity3D (21)
Recent Comments
  • MainDepth on Unity Ripple or Shock Wave Effect
  • Devon O. on Unity Ripple or Shock Wave Effect
  • Feral_Pug on Unity Ripple or Shock Wave Effect
  • bavvireal on Unity3D Endless Runner Part I – Curved Worlds
  • Danielius Vargonas on Custom Post Processing with the LWRP
Archives
  • December 2020 (1)
  • December 2019 (1)
  • September 2019 (1)
  • February 2019 (2)
  • December 2018 (1)
  • July 2018 (1)
  • June 2018 (1)
  • May 2018 (2)
  • January 2018 (1)
  • December 2017 (2)
  • October 2017 (1)
  • September 2017 (2)
  • January 2017 (1)
  • July 2016 (1)
  • December 2015 (2)
  • March 2015 (1)
  • September 2014 (1)
  • January 2014 (1)
  • August 2013 (1)
  • July 2013 (1)
  • May 2013 (1)
  • March 2013 (2)
  • December 2012 (1)
  • November 2012 (1)
  • September 2012 (3)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • December 2011 (2)
  • October 2011 (3)
  • September 2011 (1)
  • August 2011 (1)
  • July 2011 (1)
  • May 2011 (2)
  • April 2011 (2)
  • March 2011 (1)
  • February 2011 (1)
  • January 2011 (2)
  • December 2010 (3)
  • October 2010 (5)
  • September 2010 (1)
  • July 2010 (2)
  • May 2010 (5)
  • April 2010 (2)
  • March 2010 (7)
  • February 2010 (5)
  • January 2010 (5)
  • December 2009 (3)
  • November 2009 (1)
  • October 2009 (5)
  • September 2009 (5)
  • August 2009 (1)
  • July 2009 (1)
  • June 2009 (2)
  • May 2009 (6)
  • April 2009 (4)
  • March 2009 (2)
  • February 2009 (4)
  • January 2009 (1)
  • December 2008 (5)
  • November 2008 (2)
  • September 2008 (1)
  • August 2008 (6)
  • July 2008 (6)
  • June 2008 (9)
  • May 2008 (4)
  • April 2008 (3)
  • March 2008 (4)
  • February 2008 (9)
  • January 2008 (7)
  • December 2007 (6)
Copyright © 2021 Devon O. Wolfgang