Wiki
Clone wikiCore / EllipseShader
Ellipse Shader
Introduction
'Ellipse' is an example shader provided in Codea's 'Filters' Shaders Pack.
Vertex shader
The vertex shader simply passes the 'attribute' variable texCoord
on to the accompanying fragment shader as 'varying' variable vTexCoord
. However, the value of texCoord
is first converted from ranging between (0.0, 0.0)
to (1.0, 1.0)
to ranging between (-1.0, -1.0)
and (1.0, 1.0)
:
void main()
{
...
vTexCoord = vec2(texCoord.x * 2.0 - 1.0, texCoord.y * 2.0 - 1.0);
...
gl_Position = modelViewProjection * position;
}
As texCoord
is of type vec2
, and the same operations are applied to each of its components, this transformation could be rewritten more succinctly as:
vTexCoord = texCoord * 2.0 - 1.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
In Cartesian co-ordinates, the region of an ellipse centred on the origin and aligned with the axes can be expressed by the relationship:
c <= 1.0
where:
c = x * x / (r.x * r.x) + y * y / (r.y * r.y)
and where r.x
and r.y
are where the circumference of the ellipse intersects the x-axis and y-axis, respectively. A variable of type vec2
can be used to store these two values.
The fragment shader depends on four such vec2
'radii' of reducing amount: radius
, RadiusAA
, innerRadius
and innerRadiusAA
:
uniform highp vec2 radius;
main()
{
highp vec2 RadiusAA = vec2(radius.x - 4.0, radius.y - 4.0);
...
highp vec2 innerRadius = vec2(radius.x - strokeWidth * 2.0,
radius.y - strokeWidth * 2.0);
highp vec2 innerRadiusAA = vec2(radius.x - strokeWidth * 2.0 - 4.0,
radius.y - strokeWidth * 2.0 - 4.0);
...
}
That is:
vec2 radius; //= Half the dimensions of the rectangular mesh
vec2 RadiusAA = radius - 4.0;
vec2 innerRadius = radius - strokeWidth * 2.0;
vec2 innerRadiusAA = radius - strokeWidth * 2.0 - 4.0;
Four values are calculated based on these vec2
'radii': c
, cAA
, cInner
and cInnerAA
:
vec2 scaledPointSq = vec2(
(vTexCoord.x * radius.x) * (vTexCoord.x * radius.x),
(vTexCoord.y * radius.y) * (vTexCoord.y * radius.y));
float c = (scaledPointSq.x / (radius.x * radius.x)) +
(scaledPointSq.y / (radius.y * radius.y));
float cAA = (scaledPointSq.x / (RadiusAA.x * RadiusAA.x)) +
(scaledPointSq.y / (RadiusAA.y * RadiusAA.y));
float cInner = (scaledPointSq.x / (innerRadius.x * innerRadius.x)) +
(scaledPointSq.y / (innerRadius.y * innerRadius.y));
float cInnerAA = (scaledPointSq.x / (innerRadiusAA.x * innerRadiusAA.x)) +
(scaledPointSq.y / (innerRadiusAA.y * innerRadiusAA.y));
The colour of the fragment (gl_FragColor
) is calculated based on these ratios and using the built-in function smoothstep()
(see Section 8.3 'Common Functions' GLSL ES specification). The logic is clarified below by introducing variables mixInner
and maxOuter
:
float mixInner = smoothstep(cInner / cInnerAA, 1.0, cInner);
lowp vec4 fragCol = mix(fillColor, strokeColor, mixInner);
float mixOuter = smoothstep(c / cAA, 1.0, c);
gl_FragColor = mix(fragCol, vec4(0, 0, 0, 0), mixOuter);
For example, if cInner
is less than or equal to cInner / cInnerAA
, then fragCol
is set to fillColor
. That condition will be true if cInnerAA
is less than or equal to 1.0
. If cInner
is greater than or equal to 1.0
, then fragCol
is set to strokeColor
.
Similarly, if c
is less than or equal to c / cAA
, then gl_FragColor
is set to fragCol
. That condition will be true if cAA
is less than or equal to 1.0
. If c
is greater than or equal to 1.0
, then gl_FragColor
is set to vec4(0, 0, 0, 0)
(transparent black).
Example of use
The code below is a simple example of the use of the shader:
supportedOrientations(LANDSCAPE_ANY)
function setup()
parameter.integer("w", 1, 200, 100)
parameter.integer("h", 1, 200, 100)
parameter.integer("widthOfStroke", 1, 30, 10)
parameter.color("strokeColor", 255)
parameter.color("fillColor", 255, 0, 0)
end
function draw()
background(0)
strokeWidth(widthOfStroke)
stroke(strokeColor)
fill(fillColor)
ellipse(WIDTH / 4, HEIGHT / 2, w, h)
myEllipse(WIDTH * 3/4, HEIGHT / 2, w, h)
end
function myEllipse(x, y, w, h)
local myMesh = mesh()
myMesh:addRect(x, y, w, h)
myMesh.shader = shader("Patterns:Ellipse")
myMesh.shader.radius = vec2(w / 2, h / 2)
myMesh.shader.strokeWidth = strokeWidth() / 2
myMesh.shader.strokeColor = color(stroke())
myMesh.shader.fillColor = color(fill())
myMesh:draw()
end
Updated