Basics, part 10

HLSL and GLSL say the same thing in different words

Unity shaders are written in HLSL, but browsers run GLSL. Let's look at the names that differ, the few rules that bite, and how this site turns one into the other.

  • Part 105 min dose
  • Beginner

Unity shaders are written in HLSL, the High Level Shading Language from Microsoft. Your browser can’t run that, because WebGL speaks GLSL, the OpenGL Shading Language. They’re close cousins, with a few words spelled differently and a few rules that quietly differ. I write every demo here in HLSL and your browser runs it as GLSL, so let’s peek at both sides.

A flower in HLSL and GLSL

Turn on JavaScript to run this demo.

How many petals the flower has.6
How far the flower is turned, in degrees.0
hero.hlsl draws the demo above
Fig. 1

Open the code under the picture and flip between the HLSL and GLSL tabs: lerp becomes mix, frac becomes fract, and mul a plain * with its sides swapped. Then play with Petals and Turn.

Same ideas, different words

Most differences are only names. Here are the ones you’ll meet most:

HLSL (Unity)GLSL (WebGL)What it does
float2, float3, float4vec2, vec3, vec42, 3 or 4 numbers together
lerp(a, b, t)mix(a, b, t)Blends from a to b
frac(x)fract(x)The part after the decimal point
saturate(x)clamp(x, 0.0, 1.0)Keeps x between 0 and 1
atan2(y, x)atan(y, x)The angle of a point
mul(M, v)v * MTurns or stretches v with M
tex2D(t, uv)texture(t, uv)Reads a texture
ddx(v)dFdx(v)How much v changes one pixel across

Everything else you’ve met so far, like sin, dot and smoothstep, is spelled the same. But a few rules differ behind that same look, and those bite.

Matrices fill the other way

The rot2 helper from Sine and cosine builds the matrix below to turn a vector, and each language fills it from a list of numbers in its own order. HLSL goes row by row, so the first two numbers are the top row, while GLSL goes column by column, so they’re the left column.

float2x2 M = float2x2(c, -s,   // top row
                      s,  c);  // bottom row
float2 q = mul(M, v);
mat2 M = mat2(c, -s,   // left column
              s,  c);  // right column
vec2 q = v * M;

This one bends brains, so take it slow. From the same list, GLSL puts -s and s in each other’s corners, and a turning matrix with those two traded turns the other way. Multiplying from the other side, v * M, trades them back. That’s why mul(M, v) becomes v * M.

One matrix read two ways
Drag the dot, or press Tab to reach it and use the arrow keys

Turn on JavaScript to run this demo.

How far the matrix should turn the point, in degrees.60
matrix.hlsl draws the demo above
Fig. 2

Drag the dot and slide Angle. Mint is mul(M, v) and turns anticlockwise, as asked. Pink reads the same four numbers by columns and turns the other way. In the GLSL tab, mint’s line has become v * M and pink’s M * v.

Remainders with a sign

HLSL’s fmod and GLSL’s mod both give the remainder of a division (what is left of xx after taking away whole copies of yy). While xx is positive they agree. Below zero they part ways:

fmod⁡(x,y)=x−y⋅trunc⁡(x/y)mod⁡(x,y)=x−y⋅floor⁡(x/y)\begin{aligned} \operatorname{fmod}(x, y) &= x - y \cdot \operatorname{trunc}(x / y) \\ \operatorname{mod}(x, y) &= x - y \cdot \operatorname{floor}(x / y) \end{aligned}

trunc⁡\operatorname{trunc} cuts the fraction off toward zero, while floor⁡\operatorname{floor} always rounds down. So fmod keeps the sign of xx, and mod takes the sign of yy. A pattern that repeats across zero wants the GLSL way, so in HLSL write x - y * floor(x / y).

Remainders on both sides of zero

Turn on JavaScript to run this demo.

The y in fmod(x, y), which sets how big each repeat is.1.00
Code for the plot above
  • f1(x):fmod(x, _Y)
  • f2(x):x - _Y * floor(x / _Y)
Fig. 3

Right of zero the lines lie on top of each other. Left of zero, mint fmod follows xx below zero, while dashed pink mod keeps its teeth between 0 and yy. Divisor y stretches them.

Three small traps

  • A falling smoothstep. smoothstep(1.0, 0.0, x) falls from 1 to 0. Unity is fine with that, since Microsoft defines smoothstep by a formula that doesn’t mind the order. GLSL calls the answer undefined unless the first edge is below the second, so a browser may draw anything.
  • One number for three. GLSL happily fills vec3(0.5) with three halves. Unity refuses float3(0.5), so write float3(0.5, 0.5, 0.5).
  • Values at the top of the file. In GLSL, float k = 2.0; outside any function is a plain value. Unity takes it for an input the material hands in, like a slider from the Properties block. Nothing hands it in, so it starts at 0. Write static const float k = 2.0; instead.

Precision on phones

Both languages can keep numbers with fewer digits, which saves work on small chips. In GLSL the pixel code must say how precise its floats are. highp is a full 32 bit float, while mediump only has to keep about three digits and is often 16 bits on phones. This site picks highp.

Unity’s version is the half type. A PC runs it as a full float anyway, so you never notice. On phones, and a Quest is a phone chip at heart, it can really be 16 bits. That’s plenty for a color, but time, UVs and world positions run out of digits.

Float and half keeping time

Turn on JavaScript to run this demo.

How long the shader has been running before the demo starts.30
half.hlsl draws the demo above
Fig. 4

Mint keeps time as a float. Pink rounds it like a 16 bit half, so at 30 minutes it can only jump once a second, past 35 minutes every 2 seconds and past 69 every 4. Slide Minutes running to 0 and the hands agree.

How this site does it

You always see and edit HLSL here, and the site turns it into GLSL line for line each time you edit. It swaps the names from the table, flips every mul, and adds what GLSL lacks: saturate, plus fmod and smoothstep with HLSL’s rules. My favourite part is that every line keeps its number, so an error points at your own line. Write something only one side accepts, like float3(0.5), and the site stops you with the line and the fix.

The GLSL tab shows your code exactly as your browser runs it. Copy for Unity goes the other way and wraps the same HLSL in a complete Unity shader file, with a Properties block made from the sliders.

Takeaways

  • Most differences are names: float2 and vec2, lerp and mix, frac and fract.
  • HLSL fills matrices by rows and GLSL by columns, so mul(M, v) becomes v * M.
  • Watch for fmod below zero, falling smoothsteps, float3(0.5), missing static const, and half on phones.
Search the lessons

Type any word from the lessons, like normalize or smoothstep. The start of a word is enough.