Writing Custom Shaders

From GiderosMobile

Parent: Shaders

Shader Language

Shaders can be written in the Shader language for the platform you are coding for: GLSL, HLSL or MSL.

The Shader API allows the creation of Shader objects from within Lua.

Gideros will search the assets for a file with the supplied name, automatically adding the extension relevant for the target platform:

  • .glsl for OpenGL
  • .cso or .hlsl for DirectX
  • .msl for Metal

GLSL comes in two flavours: the regular language developed for desktop platforms, and the GLSL for an embedded system, aka OpenGL ES 3.0. The latter is often more strict and needs additional precision modifiers. Gideros helps you to deal with those differences by removing precision modifiers if run on a desktop platform and setting the appropriate language version. That way you only need to write an OpenGL ES 3.0 compliant shader.

Each shader processes one or more stream of data, which we will call attributes as per OpenGL semantics, and further produce one or more stream of (processed) data.

Each shader also has access to one or more constants called uniforms. Those constants are settable at will before invoking the shader program, so that they are in fact only constants from the shader program point of view, and because their values can’t be changed in the middle of the processing of a stream of data.

If you decide to write directly for the platforms then start with the HLSL version which is in many ways more restrictive than the GLSL. It is far easier to port HLSL code to GLSL than the opposite way.

If you are a beginner, it is better to start with a working shader, from an example or from this documentation, and then modify it to suit your needs.

Example: A Blur Shader

The code below defines a Blur shader consisting of:

  • vShaderBlur.{glsl/hlsl} vertex shader
  • fShaderBlur.{glsl/hlsl} fragment shader

Here the shader files are in the assets root directory.

No special flag is given.

GLSL Version

GLSL Vertex Shader: vShaderBlur.glsl

attribute highp vec3 vVertex;
attribute mediump vec2 vTexCoord;
uniform highp mat4 vMatrix;
varying mediump vec2 fTexCoord;

void main() {
	vec4 vertex = vec4(vVertex, 1.0);
	gl_Position = vMatrix*vertex;
	fTexCoord = vTexCoord;
}

GLSL Fragment Shader: fShaderBlur.glsl

uniform lowp vec4 fColor;
uniform lowp sampler2D fTexture;
uniform int fRad;
uniform mediump vec4 fTexelSize;
varying mediump vec2 fTexCoord;

void main() {
	lowp vec4 frag = vec4(0, 0, 0, 0);
	int ext = 2 * fRad + 1; // fRad will be set in Lua
	mediump vec2 tc = fTexCoord - fTexelSize.xy * fRad;
	for(int v=0; v<ext; v++)
	{
		frag = frag + texture2D(fTexture, tc);
		tc += fTexelSize.xy;
	}
	frag = frag / ext;
	if(frag.a == 0.0) discard;
	gl_FragColor = frag;
}

HLSL Version

HLSL Vertex Shader: vShaderBlur.hlsl

struct VOut
{
	float4 position : SV_POSITION;
	float2 texcoord : TEXCOORD;
};

cbuffer cbv : register(b0)
{
	float4x4 vMatrix;
};

VOut VShader(float4 position : vVertex, float2 texcoord : vTexCoord)
{
	VOut output;

	position.w = 1.0f;

	output.position = mul(vMatrix, position);
	output.texcoord = texcoord;

	return output;
}

HLSL Fragment Shader: fShaderBlur.hlsl

Texture2D myTexture : register(t0);
SamplerState samLinear : register(s0);

cbuffer cbp : register(b1)
{
	float4 fColor;
	float4 fTexelSize;
	int fRad; // fRad will be set in Lua
};

float4 PShader(float4 position : SV_POSITION, float2 texcoord : TEXCOORD) : SV_TARGET
{
	float4 frag=float4(0,0,0,0);
	int ext=(2*fRad+1);
	float2 tc=texcoord-fTexelSize.xy*fRad;
	[unroll(21)]
	for (int v=0;v<ext;v++)
	{
		frag=frag+myTexture.Sample(samLinear, tc)/ext;
		tc+=fTexelSize.xy;
	}
	if (frag.a == 0.0) discard;
	return frag;
}

Lua Code

local shaderblur = Shader.new("vShaderBlur", "fShaderBlur", 0, -- shaders files without extension
	{
	{name="vMatrix", type=Shader.CMATRIX, sys=Shader.SYS_WVP, vertex=true},
	{name="fColor", type=Shader.CFLOAT4, sys=Shader.SYS_COLOR, vertex=false },
	{name="fTexture", type=Shader.CTEXTURE, vertex=false},
	{name="fTexelSize", type=Shader.CFLOAT4, vertex=false},
	{name="fRad", type=Shader.CINT, vertex=false},
	},
	{
	{name="vVertex", type=Shader.DFLOAT, mult=3, slot=0, offset=0},
	{name="vColor", type=Shader.DUBYTE, mult=4, slot=1, offset=0},
	{name="vTexCoord", type=Shader.DFLOAT, mult=2, slot=2, offset=0},
	}
)

Five uniforms are defined, named vMatrix, fColor, fTexture, fTexelSize and fRad. Only the first one is associated to the vertex program (you can check that in the respective programs seen earlier).

Furthermore, we ask Gideros to take responsibility of setting vMatrix to the Matrix/View/Projection matrix (SYS_WVP), and fColor to the fixed color of the shape being rendered (SYS_COLOR).

fTexture is actually a texture (Shader.CTEXTURE) and declared appropriately in GLSL or HLSL.

fTexelSize and fRad have not specific meaning and will be set by the Lua code.

The three standard attributes are also defined.

Changing Uniforms/Constants

In order to change the value of a uniform from Lua, you use the setConstant function, it takes four arguments:

  • the uniform name
  • the type of data to set: one of the Shader.Cxxx constants
  • the number of elements of the given type to set (number)
  • the actual data to set, either as a table or as multiple arguments
application:setBackgroundColor(0x333333)

local bitmap = Bitmap.new(Texture.new("gfx/test.png"))
bitmap:setPosition(32*1, 32*4)
local texw, texh = bitmap:getWidth(), bitmap:getHeight()
stage:addChild(bitmap)

shaderblur:setConstant("fRad", Shader.CINT, 1, 6) -- 6 = Initial blur level
shaderblur:setConstant("fTexelSize", Shader.CFLOAT4, 1, {1/texw, 1/texh, 0, 0} ) --Initial texel size

This will change the value of a Uniform for the whole Shader.

You can also change the value of a Uniform on a sprite to sprite basis using Sprite:setShaderConstant:

bitmap:setShaderConstant("fRad", Shader.CINT, 1, 6) -- 6 = Initial blur level
bitmap:setShaderConstant("fTexelSize", Shader.CFLOAT4, 1, {1/texw, 1/texh, 0, 0} ) --Initial texel size

Apply to a Sprite

The Sprite API has a new call to deal with that: Sprite:setShader(shader) tells Gideros to use the specified shader for rendering the sprite. Setting back the shader to nil actually revert to the default shader.

bitmap:setShader(shaderblur)

Custom Shader Examples

Custom Shader Examples

Further Reading

OpenGL Shading Language: https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language
Common compatibility pitfalls: https://www.opengl.org/wiki/GLSL_:_common_mistakes
The Book of Shaders: https://thebookofshaders.com/
ShaderToy: https://www.shadertoy.com/
GH: https://github.com/mattdesl/lwjgl-basics/wiki/Shaders
Inigo Quilez: https://iquilezles.org/