Wiki
Clone wikiCore / ElectricityShader
Electricity Shader
Introduction
'Electricity' is an example shader provided in Codea's 'Effects' Shaders Pack. The source of the fragment shader is one in the GLSL Sandbox gallery at glsl.heroku.com posted by Trisomie21.
The shader outputs a horizontal neon purple arcing spark against the background of a faint dark grey square grid.
Vertex shader
The vertex shader manipulates the 'attribute' variable texCoord
before passing it on to the accompanying fragment shader as 'varying' variable vTexCoord
:
void main()
{
vTexCoord = vec2(texCoord.x * 2.0 - 1.0,
texCoord.y * 2.0 - 1.0);
...
gl_Position = modelViewProjection * position;
}
The effect of the manipulation is to map values of texCoord
in the square region defined by the opposite corners (0, 0) and (1, 1) onto values in the square region defined by (-1, -1) and (1, 1) (with the centre at (0, 0)).
gl_Position
is a variable that is intended for outputting the vertex position in homogenous co-ordinates (that is, as a vec4
value). All vertex shaders must write a value into that variable. (See Section 7.1 'Vertex Shader Special Variables' of the GLSL ES specification.) Here, the modelViewProjection
4x4 matrix is applied to the vertex's position
. modelViewProjection
is a 'uniform' mat4
variable supplied automatically by Codea when the shader is used with a mesh. It is the current model matrix * view matrix * projection matrix. position
is a vec4
'attribute' variable, also supplied automatically by Codea from the mesh.
Fragment shader
The fragment shader makes use of two 'uniform' variables: resolution
and time
. The latter allows for the animation of the result of the shader.
The fragment shader sets gl_FragColor
to the output of the Wave()
function (with an opacity of 1.0
). In input to the Wave()
function is the vTexCoord
scaled by the ratio of the first and second components of the 'uniform' variable resolution
:
void main(void)
{
gl_FragColor = vec4(Wave(vTexCoord * (resolution.y / resolution.x)), 1.0);
}
The Wave()
function is complex. It takes as its input a vec2
value representing a position, and scales that value by s = 30.0
. The function can be re-written more explicitly as:
vec3 Wave(vec2 p)
{
float s = 30.0;
p = p * s;
// Logic for the arcing spark
float x = floor(p.x);
vec2 p0 = vec2(x - 0.5, Amp(x - 0.5, s));
vec2 p1 = vec2(x + 0.5, Amp(x + 0.5, s));
vec2 p3 = vec2(x + 1.5, Amp(x + 1.5, s));
float d1 = segment(p, p0, p1);
float d2 = segment(p, p1, p2);
float d = min(d1, d2) - 0.2;
float a1 = clamp(max(d, 0.0) * s, 0.0, 1.0);
float a0 = clamp(max(abs(d) - 0.05, 0.0) * s, 0.0, 4.0);
// Logic for the background square grid
vec3 pFrac = mod(p, vec2(1.0, 1.0))
vec3 pGrid = abs(pFrac - vec2(0.5, 0.5)) - 0.01;
float b = clamp(min(pGrid.x, pGrid.y) * resolution.x / s, 0.0, 1.0);
vec3 colBackgroundGrid = vec3((1.0 - b) * 0.2 + 0.2) * 0.2;
vec3 colSpark = vec3(a0 * 0.6, a0 * 0.3, a0);
// Mix the spark and the background
return vec3(mix(colBackgroundGrid, colSpark,(1.0 - a1)));
}
The Wave()
function makes use of two other helper functions: Amp(x, s)
and segment(P, P0, P1)
.
Amp(x, s)
Amp()
scales down the value of its first parameter, x
, by its second parameter s
, clamps the result in the range -0.5
to +0.5
(using the built-in GLSL ES function clamp()
- see Section 8.3 'Common Functions' of the GLSL ES specification), and translates the result to the range 0.0
to 1.0
. The result is stored in variable f
.
The result in f
is later transformed to a quadratic factor that is scaled up by s
: f * (1.0 - f) * s
. This factor is 0.0
when f
is 0.0
or 1.0
and reaches a maximum of 0.25 * s
when f
is 0.5
.
The scaling factor is applied to:
fract(sin(x + time * 0.00005) * 1e5)) - 0.5
This expression yields a value between -0.5
and 0.5
based on the digits after the fifth decimal place in the result of the sin()
function. The expression is used as a pseudo-random number generator.
Amp()
can be re-written more explicitly as:
float Amp(float x, float s)
{
float f1 = clamp(x / s, -0.5, 0.5) + 0.5;
float f2 = f1 * (1.0 - f1) * s;
float rand = fract(sin(x + time * 0.00005) * 1e5)) - 0.5;
return rand * f2;
}
segment(P, P0, P1)
segment()
calculates the intersection between the line passing through points P0
and P1
and the perpendicular line passing through point P
. The intersection is clamped to the line segment from point P0
to point P1
. The function then returns the distance between point P
and that point.
float segment(vec2 P, vec2 P0, vec2 P1)
{
vec2 v = P1 - P0;
vec2 w = P - P0;
float b = dot(w,v) / dot(v,v);
v *= clamp(b, 0.0, 1.0);
return length(w - v);
}
Example of use
The code below is a simple example of the use of the shader:
function setup()
local dim = math.min(WIDTH, HEIGHT)
parameter.integer("resX", 10, dim, 322)
parameter.integer("resY", 10, dim, 322)
myMesh = mesh()
myMesh:addRect(WIDTH / 2, HEIGHT / 2, dim, dim)
myMesh.shader = shader("Effects:Electricity")
end
function draw()
background(0)
myMesh.shader.time = ElapasedTime
myMesh.shader.resolution = vec2(resX, resY)
myMesh:draw()
end
Updated