LogoLogo
  • Home
  • Projects
  • About
  • Contact

Photoshop Blend Modes for Unity3D

Devon O. · June 21, 2018 · Shaders, Unity3D · 0 comments
19

So here’s a small thing that may help some folks out. Ages ago, while researching how Photoshop blend modes work behind the scenes, I stumbled across this fantastic demo on Shadertoy and have kept it bookmarked since. Recently, I wanted to do some overlay blending in Unity, so went back to the Shadertoy example and did a quick port to the .cginc file below:

PhotoshopBlendModes.cginc
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#ifndef PHOTOSHOP_BLENDMODES_INCLUDED
#define PHOTOSHOP_BLENDMODES_INCLUDED
 
//
// Ported from https://www.shadertoy.com/view/XdS3RW
//
// Original License:
//
// Creative Commons CC0 1.0 Universal (CC-0)
//
// 25 of the layer blending modes from Photoshop.
//
// The ones I couldn't figure out are from Nvidia's advanced blend equations extension spec -
// http://www.opengl.org/registry/specs/NV/blend_equation_advanced.txt
//
// ~bj.2013
//
 
// Helpers
 
const fixed3 l = fixed3(0.3, 0.59, 0.11);
 
/** @private */
float pinLight(float s, float d)
{
    return (2.0*s - 1.0 > d) ? 2.0*s - 1.0 : (s < 0.5 * d) ? 2.0*s : d;
}
 
/** @private */
float vividLight(float s, float d)
{
    return (s < 0.5) ? 1.0 - (1.0 - d) / (2.0 * s) : d / (2.0 * (1.0 - s));
}
 
/** @private */
float hardLight(float s, float d)
{
    return (s < 0.5) ? 2.0*s*d : 1.0 - 2.0*(1.0 - s)*(1.0 - d);
}
 
/** @private */
float softLight(float s, float d)
{
    return (s < 0.5) ? d - (1.0 - 2.0*s)*d*(1.0 - d)
                : (d < 0.25) ? d + (2.0*s - 1.0)*d*((16.0*d - 12.0)*d + 3.0)
                : d + (2.0*s - 1.0) * (sqrt(d) - d);
}
 
/** @private */
float overlay( float s, float d )
{
    return (d < 0.5) ? 2.0*s*d : 1.0 - 2.0*(1.0 - s)*(1.0 - d);
}
 
//    rgb<-->hsv functions by Sam Hocevar
//    http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
/** @private */
fixed3 rgb2hsv(fixed3 c)
{
    fixed4 K = fixed4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    fixed4 p = lerp(fixed4(c.bg, K.wz), fixed4(c.gb, K.xy), step(c.b, c.g));
    fixed4 q = lerp(fixed4(p.xyw, c.r), fixed4(c.r, p.yzx), step(p.x, c.r));
    
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return fixed3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
 
/** @private */
fixed3 hsv2rgb(fixed3 c)
{
    fixed4 K = fixed4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    fixed3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * lerp(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
 
// Public API Blend Modes
 
fixed3 ColorBurn(fixed3 s, fixed3 d)
{
    return 1.0 - (1.0 - d) / s;
}
 
fixed3 LinearBurn(fixed3 s, fixed3 d )
{
    return s + d - 1.0;
}
 
fixed3 DarkerColor(fixed3 s, fixed3 d)
{
    return (s.x + s.y + s.z < d.x + d.y + d.z) ? s : d;
}
 
fixed3 Lighten(fixed3 s, fixed3 d)
{
    return max(s, d);
}
 
fixed3 Screen(fixed3 s, fixed3 d)
{
    return s + d - s * d;
}
 
fixed3 ColorDodge(fixed3 s, fixed3 d)
{
    return d / (1.0 - s);
}
 
fixed3 LinearDodge(fixed3 s, fixed3 d)
{
    return s + d;
}
 
fixed3 LighterColor(fixed3 s, fixed3 d)
{
    return (s.x + s.y + s.z > d.x + d.y + d.z) ? s : d;
}
 
fixed3 Overlay(fixed3 s, fixed3 d)
{
    fixed3 c;
    c.x = overlay(s.x, d.x);
    c.y = overlay(s.y, d.y);
    c.z = overlay(s.z, d.z);
    return c;
}
 
fixed3 SoftLight(fixed3 s, fixed3 d)
{
    fixed3 c;
    c.x = softLight(s.x, d.x);
    c.y = softLight(s.y, d.y);
    c.z = softLight(s.z, d.z);
    return c;
}
 
fixed3 HardLight(fixed3 s, fixed3 d)
{
    fixed3 c;
    c.x = hardLight(s.x, d.x);
    c.y = hardLight(s.y, d.y);
    c.z = hardLight(s.z, d.z);
    return c;
}
 
fixed3 VividLight(fixed3 s, fixed3 d)
{
    fixed3 c;
    c.x = vividLight(s.x, d.x);
    c.y = vividLight(s.y, d.y);
    c.z = vividLight(s.z, d.z);
    return c;
}
 
fixed3 LinearLight(fixed3 s, fixed3 d)
{
    return 2.0*s + d - 1.0;
}
 
fixed3 PinLight(fixed3 s, fixed3 d)
{
    fixed3 c;
    c.x = pinLight(s.x, d.x);
    c.y = pinLight(s.y, d.y);
    c.z = pinLight(s.z, d.z);
    return c;
}
 
fixed3 HardMix(fixed3 s, fixed3 d)
{
    return floor(s+d);
}
 
fixed3 Difference(fixed3 s, fixed3 d)
{
    return abs(d-s);
}
 
fixed3 Exclusion(fixed3 s, fixed3 d)
{
    return s + d - 2.0*s*d;
}
 
fixed3 Subtract(fixed3 s, fixed3 d)
{
    return s-d;
}
 
fixed3 Divide(fixed3 s, fixed3 d)
{
    return s/d;
}
 
fixed3 Add(fixed3 s, fixed3 d)
{
    return s+d;
}
 
fixed3 Hue(fixed3 s, fixed3 d)
{
    d = rgb2hsv(d);
    d.x = rgb2hsv(s).x;
    return hsv2rgb(d);
}
 
fixed3 Color(fixed3 s, fixed3 d)
{
    s = rgb2hsv(s);
    s.z = rgb2hsv(d).z;
    return hsv2rgb(s);
}
 
fixed3 Saturation(fixed3 s, fixed3 d)
{
    d = rgb2hsv(d);
    d.y = rgb2hsv(s).y;
    return hsv2rgb(d);
}
 
fixed3 Luminosity(fixed3 s, fixed3 d)
{
    float dLum = dot(d, l);
    float sLum = dot(s, l);
    float lum = sLum - dLum;
    fixed3 c = d + lum;
    float minC = min(min(c.x, c.y), c.z);
    float maxC = max(max(c.x, c.y), c.z);
    if(minC < 0.0) return sLum + ((c - sLum) * sLum) / (sLum - minC);
    else if(maxC > 1.0) return sLum + ((c - sLum) * (1.0 - sLum)) / (maxC - sLum);
    else return c;
}
 
#endif // PHOTOSHOP_BLENDMODES_INCLUDED

Usage should be fairly self explanatory, but here’s a quick Overlay shader example:

Overlay.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
Shader "Blendmodes/Overlay"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _BlendTex ("Blend Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Assets/Path/To/PhotoshopBlendModes.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
 
            sampler2D _BlendTex;
            sampler2D _MainTex;
            float4 _MainTex_ST;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 mainColor = tex2D(_MainTex, i.uv);
                fixed4 blendColor = tex2D(_BlendTex, i.uv);
 
                // perform blend
                mainColor.xyz = Overlay(mainColor.xyz, blendColor.xyz);
 
                return mainColor;
            }
            ENDCG
        }
    }
}

Using these two images (which I just happened to find in my Dropbox directory so I’m claiming ownership) can produce all the effects in the picture at the top of this page (left-to-right top-to-bottom: Add, ColorBurn, ColorDodge, DarkerColor, Difference, Divide, Exclusion, HardLight, HardMix, Lighten, LighterColor, LinearBurn, LinearDodge, LinearLight, Overlay, PinLight, Screen, SoftLight, Subtract, and VividLight).

 

I left off the blends Hue, Color, Saturation, and Luminosity simply because they were a little more expensive and I wasn’t interested at the time. Feel free to add them back in – easy enough to do.

Enjoy.

Edit:

Just for the sake of completion, I’ve added the Hue, Color, Saturation, and Luminosity blend modes. Keep in mind, to simplify usage of these, you may only need to add a _Color property to your shader rather than a _BlendTex like in the example above.

  Facebook   Pinterest   Twitter   Google+
  • Playing Around with the New UndoManager
    May 01, 2010 · 11 comments
    3357
    5
    Read more
  • Adventures in Playbook Land
    April 09, 2011 · 9 comments
    28600
    9
    Read more
  • Magnify – a jQuery Plugin
    December 11, 2011 · 5 comments
    2508
    9
    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
  • 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
  • Luca G on Unity Ripple or Shock Wave Effect
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 © 2017 Devon O. Wolfgang