Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Computer Graphics (rasterisation, ray tracing, WebGPU)  Module 7 – Light, shading and transparency

Transparency, blending and alpha compositing

Composite layers with straight and premultiplied alpha and Porter and Duff's over operator, then blend and draw transparent surfaces in the right order.

  • Intermediate
  • 25 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free core, Pro depth: 9 of 25 minutes free
Free: Alpha says how much of a pixel a surface covers · Premultiplied alpha stores the colour already times its alpha · Practise: over, premultiply and unpremultiply. With a pass: 3 in-depth parts and 3 interview answers, about 16 minutes.

What you will learn

  • Combine layers with straight and premultiplied alpha using Porter and Duff's over operator
  • Explain why premultiplied alpha keeps over associative and stops dark fringes at resized edges
  • Configure WebGPU blend states for alpha, premultiplied and additive blending
  • Render transparent geometry in the right order, with the depth test on and depth writes off

Before you start

What you’ll learn · Before you start

What you will learn

  • Combine layers with straight and premultiplied alpha using Porter and Duff's over operator
  • Explain why premultiplied alpha keeps over associative and stops dark fringes at resized edges
  • Configure WebGPU blend states for alpha, premultiplied and additive blending
  • Render transparent geometry in the right order, with the depth test on and depth writes off

Before you start

On this page

A pane of red glass that lets half the light through hangs in front of a blue-grey wall, so its pixel should be half the glass’s red and half the wall’s colour. Alpha compositing is that mix: each layer covers its alpha’s share of the pixel. Porter and Duff’s over operator computes it, and two decisions decide whether a renderer gets it right: whether colours are stored already multiplied by their alpha, and in which order transparent surfaces are drawn.

Every number below comes from programs on this page: three layers composited over a wall and a sprite’s edge after a resize, then the blend states and draw orders of a GPU, worked out on the CPU by the same rules.

Alpha says how much of a pixel a surface covers

A pixel’s alpha is the share of it that a surface covers, from 0, nothing, to 1, all of it. Thomas Porter and Tom Duff’s paper of 1984 on compositing digital images defined how images that carry alpha combine, and its over operator: Porter and Duff's rule for laying a source with alpha over a backdrop: the source takes its alpha's share of the pixel and the backdrop shows through the rest. lays a source over a backdrop. The W3C’s Compositing and Blending specification writes it for colours C and alphas α:

Coαo=Csαs+Cbαb(1−αs),αo=αs+αb(1−αs)C_o\,\alpha_o = C_s\,\alpha_s + C_b\,\alpha_b\,(1 - \alpha_s), \qquad \alpha_o = \alpha_s + \alpha_b\,(1 - \alpha_s)

In words: the source takes its alpha’s share of the pixel, the backdrop shows through the rest, and the result’s coverage grows the same way. Transparency in a renderer comes down to this operator, applied pixel by pixel. Over an opaque wall, red glass of alpha 0.5 gives 0.5 × 0.8 + 0.5 × 0.2 = 0.5 in red. This is alpha compositing: Combining images or surfaces by their alpha, the share of each pixel they cover, with operators such as over, so that partly transparent layers show what lies behind them., and glTF uses it: a material with alphaMode BLEND is combined with the background by over, OPAQUE ignores alpha, and MASK shows each pixel fully or not at all, at a cutoff. Step through three layers laid over the wall:

Interactive · step by step Three layers laid over a wall, one at a time Step through the program's pixel as the glass, the smoke and the label are composited over the wall with "over".
Three layers laid over a wall, one at a time, step 5 of 5. Showing White label, α 0.8, Green smoke, α 0.25, Red glass, α 0.5, Blue-grey wall, opaque, The pixel ((0.880, 0.865, 0.851)). In focus: White label, α 0.8, Green smoke, α 0.25, Red glass, α 0.5.White label, α 0.8Green smoke, α 0.25Red glass, α 0.5Blue-grey wall, opaqueThe pixel(0.880, 0.865, 0.851)

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The wall. The wall is opaque, so the pixel starts as its colour, (0.200, 0.300, 0.450), with alpha 1.
  2. Glass over. The glass covers half the pixel: 0.5 of its red, (0.8, 0.1, 0.1), plus the other 0.5 of what lies behind gives (0.500, 0.200, 0.275).
  3. Smoke over. The smoke covers a quarter: 0.25 of its green plus 0.75 of the pixel so far gives (0.400, 0.325, 0.256).
  4. Label over. The label covers 0.8: 0.8 of white plus 0.2 of the pixel so far gives (0.880, 0.865, 0.851), the colour the program prints.
  5. The order matters. Each step lays one layer over everything below it. Laying the same layers in another order gives another colour, because "over" puts its first operand in front.

Checkpoint

Check: Alpha as coverage and the over operator

  1. Question 1 of 2 Green smoke (0.1, 0.7, 0.2) with alpha 0.25 lies over an opaque wall (0.2, 0.3, 0.45). What is the green channel of the result?

    Type a number.

    A hint

    Over an opaque backdrop: αs × source + (1 − αs) × backdrop.

    Show the answer: Question 1 of 2

    Answer: 0.4 (anything from 0.3995 to 0.4005 counts)

    0.25 × 0.7 + 0.75 × 0.3 = 0.175 + 0.225 = 0.400. The smoke takes a quarter of the pixel and the wall shows through the other three quarters.

  2. Question 2 of 2 A layer of alpha 0.5 is laid over a backdrop of alpha 0.4. What is the alpha of the result?

    Type a number.

    A hint

    αo = αs + αb (1 − αs).

    Show the answer: Question 2 of 2

    Answer: 0.7 (anything from 0.6995 to 0.7005 counts)

    0.5 + 0.4 × (1 − 0.5) = 0.5 + 0.2 = 0.7: the layer covers half the pixel, and the backdrop covers 0.4 of the half that is left.

Premultiplied alpha stores the colour already times its alpha

Store each colour already multiplied by its alpha, c = αC, and over becomes one multiply-add in every channel, alpha included:

co=cs+cb(1−αs)c_o = c_s + c_b\,(1 - \alpha_s)

That is premultiplied alpha: Storing a pixel's colour already multiplied by its alpha, so that half-transparent red is (0.5, 0, 0, 0.5). It makes over one multiply-add and keeps filtering correct., against the straight alpha: Storing a pixel's colour and its alpha separately, without multiplying the colour by the alpha; also called unassociated or non-premultiplied alpha. of the formula above. The program composites the three layers both ways, merges them before they meet the wall, and halves a sprite’s edge:

Three layers, a merge and a resized edge, straight and premultiplied JavaScript · layers/porter_duff.mjs
// Porter and Duff's "over" on three invented layers above an opaque wall, with straight and with premultiplied
// alpha, then a fringe that only straight alpha makes when a sprite is resized. Colours are linear RGB.
const fmt = (c) => `(${c.map((x) => x.toFixed(3)).join(', ')})`;
// Straight alpha: [r, g, b, a] with the colour not multiplied by a.
const overStraight = (s, d) => {
  const a = s[3] + d[3] * (1 - s[3]);
  if (a === 0) return [0, 0, 0, 0];
  return [0, 1, 2].map((k) => (s[k] * s[3] + d[k] * d[3] * (1 - s[3])) / a).concat(a);
};
// Premultiplied alpha: the colour is already times a, so "over" is one multiply-add per channel.
const premultiply = (c) => [c[0] * c[3], c[1] * c[3], c[2] * c[3], c[3]];
const overPremultiplied = (s, d) => s.map((x, k) => x + d[k] * (1 - s[3]));

const wall = [0.2, 0.3, 0.45, 1];
const glass = [0.8, 0.1, 0.1, 0.5]; // red glass, half the light through
const smoke = [0.1, 0.7, 0.2, 0.25]; // green smoke
const label = [1, 1, 1, 0.8]; // white text

let straight = wall;
let premultiplied = premultiply(wall);
for (const [name, layer] of [['glass', glass], ['smoke', smoke], ['label', label]]) {
  straight = overStraight(layer, straight);
  premultiplied = overPremultiplied(premultiply(layer), premultiplied);
  console.log(`after the ${name.padEnd(5)}  straight ${fmt(straight)}  premultiplied ${fmt(premultiplied)}`);
}

// Grouping: the three layers merged first, then laid over the wall.
const merged = overPremultiplied(premultiply(label), overPremultiplied(premultiply(smoke), premultiply(glass)));
console.log(`\nlayers merged first, alpha ${merged[3].toFixed(3)}: over the wall ${fmt(overPremultiplied(merged, premultiply(wall)))}`);
// The same grouping with the opaque-backdrop formula, s a + d (1 - a), which ignores the lower layer's alpha.
const naive = (s, d) => [0, 1, 2].map((k) => s[k] * s[3] + d[k] * (1 - s[3])).concat(s[3] + d[3] * (1 - s[3]));
const naiveMerged = naive(label, naive(smoke, glass));
console.log(`merged with s a + d (1 - a), then over the wall: ${fmt(naive(naiveMerged, wall))}`);

// A sprite edge: an opaque white texel beside a transparent one that straight alpha stores as black, (0, 0, 0, 0).
// Halving the sprite averages the two texels; the result is drawn over a white page.
const page = [1, 1, 1, 1];
const avg = (p, q) => p.map((x, k) => (x + q[k]) / 2);
const s = avg([1, 1, 1, 1], [0, 0, 0, 0]); // the same numbers in both encodings, read two ways
const toCode = (l) => Math.round((l <= 0.0031308 ? 12.92 * l : 1.055 * l ** (1 / 2.4) - 0.055) * 255);
const asStraight = overStraight(s, page)[0];
const asPremultiplied = overPremultiplied(s, page)[0];
console.log(`\nhalved edge texel ${fmt(s)} over a white page:`);
console.log(`  read as straight alpha:      ${asStraight.toFixed(3)}, code ${toCode(asStraight)}`);
console.log(`  read as premultiplied alpha: ${asPremultiplied.toFixed(3)}, code ${toCode(asPremultiplied)}`);

Output

after the glass  straight (0.500, 0.200, 0.275, 1.000)  premultiplied (0.500, 0.200, 0.275, 1.000)
after the smoke  straight (0.400, 0.325, 0.256, 1.000)  premultiplied (0.400, 0.325, 0.256, 1.000)
after the label  straight (0.880, 0.865, 0.851, 1.000)  premultiplied (0.880, 0.865, 0.851, 1.000)

layers merged first, alpha 0.925: over the wall (0.880, 0.865, 0.851, 1.000)
merged with s a + d (1 - a), then over the wall: (0.871, 0.809, 0.797, 1.000)

halved edge texel (0.500, 0.500, 0.500, 0.500) over a white page:
  read as straight alpha:      0.750, code 225
  read as premultiplied alpha: 1.000, code 255

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node porter_duff.mjs

Both ways give (0.880, 0.865, 0.851). Premultiplied over is associative: the three layers merged first, alpha 0.925, give the same pixel, while merging straight colours with the opaque-backdrop formula gives (0.871, 0.809, 0.797). Filtering is the second gain. Halving a sprite averages an opaque white texel with a transparent one, (0, 0, 0, 0): read as straight alpha, the average is grey at half coverage and shows 0.750 over a white page, code 225, a dark fringe; read as premultiplied, it is white at half coverage, 1.000, code 255.

Swap two layers

Press Edit and put the smoke before the glass in the loop’s list. The pixel ends at (0.898, 0.850, 0.849) instead of (0.880, 0.865, 0.851): over puts the layer laid last in front.

The two ways of storing a colour with its alpha compare like this:

Straight alpha Premultiplied alpha
A pixel stores the colour and its alpha the colour times alpha, and alpha
Over needs αs and αb in the colour terms cs + cb (1 − αs) in every channel
Merging layers first needs a division by the new alpha the same as one at a time
Resizing and filtering dark fringes at transparent edges correct
Getting the colour back stored as it is divide by alpha, none at alpha 0

A WebGPU canvas takes the same choice: its alphaMode is “opaque” by default, and “premultiplied” reads colours so, where 100 % red at 50 % alpha is [0.5, 0, 0, 0.5]. On a GPU, the same arithmetic is set as a blend state. Alpha blending mixes straight colours by their alpha. Premultiplied blending adds the source to the destination times one minus its alpha. Additive blending adds light, for fire and glows. Transparent surfaces are drawn after the opaque ones, sorted from back to front, usually with depth writes off. Surfaces that cross need Blending transparent surfaces in the right order at each pixel separately, instead of sorting whole objects, so that surfaces which cross each other also come out right., which resolves the order per pixel.

Make Background Transparent Cut the background out of a photo and look at the edge pixels it leaves: their partial alpha is the coverage this lesson composites.

Checkpoint

Check: Straight and premultiplied alpha

  1. Question 1 of 2 The red glass is (0.8, 0.1, 0.1) with alpha 0.5. What is its red channel premultiplied?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 0.4 (anything from 0.3995 to 0.4005 counts)

    Premultiplied alpha stores the colour times its alpha: 0.8 × 0.5 = 0.4, so the glass is stored as (0.4, 0.05, 0.05, 0.5).

  2. Question 2 of 2 Halving a sprite with straight alpha leaves a grey fringe of 0.750 over a white page around its white shape. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Averaging straight colours lets the invisible black of the transparent texel count fully, so the edge turns grey

    An average of straight colours weights each texel's colour equally, whatever its coverage, so a transparent black texel darkens its neighbour. Premultiplied colours carry their coverage, so the average is white at half coverage and shows 1.000.

Practise: over, premultiply and unpremultiply

The exercise writes the three operations this lesson uses. Its sample tests lay the glass, the smoke and the label over the wall, then check that merging them first changes nothing.

Exercise · Easy · JavaScript

Premultiply colours and composite them with over

Write three functions in alpha.mjs. Colours are [r, g, b, a] arrays in linear light.

  • premultiply(c) multiplies the colour channels by the alpha.
  • unpremultiply(c) divides them by it again; a pixel of alpha 0 has no colour to recover and returns [0, 0, 0, 0].
  • over(src, dst) is Porter and Duff's "over" for premultiplied colours: in each of the four channels, src + dst × (1 − src's alpha), so the result's alpha is src's alpha + dst's alpha × (1 − src's alpha).

The sample tests lay the lesson's glass, smoke and label over its wall, then merge the three layers first and check that the grouping changes nothing. The starter's over uses the formula for straight colours over an opaque backdrop, and its unpremultiply divides by zero.

Starter code · alpha.mjs

/** [r, g, b, a] with straight colour to premultiplied: the colour times its alpha. */
export function premultiply(c) {
  return [c[0] * c[3], c[1] * c[3], c[2] * c[3], c[3]];
}

/** Premultiplied back to straight: the colour divided by its alpha; a fully transparent pixel is [0, 0, 0, 0]. */
export function unpremultiply(c) {
  // Divides by zero for a fully transparent pixel.
  return [c[0] / c[3], c[1] / c[3], c[2] / c[3], c[3]];
}

/** Porter and Duff's "over" for premultiplied colours: src + dst x (1 - src alpha), in every channel. */
export function over(src, dst) {
  // The opaque-backdrop formula for straight colours: wrong for premultiplied ones, and for alpha.
  const a = src[3];
  return [src[0] * a + dst[0] * (1 - a), src[1] * a + dst[1] * (1 - a), src[2] * a + dst[2] * (1 - a), 1];
}
The sample tests · alpha.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { premultiply, unpremultiply, over } from './alpha.mjs';

const close = (got, want, what) => {
  for (let k = 0; k < 4; k++) assert.ok(Math.abs(got[k] - want[k]) <= 1e-9, `${what}: channel ${k} should be ${want[k]}, got ${got[k]}`);
};
const WALL = [0.2, 0.3, 0.45, 1];
const GLASS = [0.8, 0.1, 0.1, 0.5];
const SMOKE = [0.1, 0.7, 0.2, 0.25];
const LABEL = [1, 1, 1, 0.8];

test('premultiply and back, including a fully transparent pixel', () => {
  close(premultiply(GLASS), [0.4, 0.05, 0.05, 0.5], 'red glass at alpha 0.5');
  close(unpremultiply([0.4, 0.05, 0.05, 0.5]), GLASS, 'and back again');
  close(unpremultiply([0, 0, 0, 0]), [0, 0, 0, 0], 'alpha 0 has no colour to recover');
  close(premultiply(WALL), WALL, 'an opaque colour does not change');
});

test("the lesson's three layers over the wall", () => {
  let pixel = premultiply(WALL);
  pixel = over(premultiply(GLASS), pixel);
  close(pixel, [0.5, 0.2, 0.275, 1], 'after the glass');
  pixel = over(premultiply(SMOKE), pixel);
  pixel = over(premultiply(LABEL), pixel);
  close(pixel, [0.88, 0.865, 0.85125, 1], 'after the label');
});

test('over is associative for premultiplied colours, and keeps alpha', () => {
  const g = premultiply(GLASS);
  const s = premultiply(SMOKE);
  const l = premultiply(LABEL);
  const merged = over(l, over(s, g));
  close(merged, [0.865, 0.8425, 0.8175, 0.925], 'the layers merged first');
  close(over(merged, premultiply(WALL)), over(l, over(s, over(g, premultiply(WALL)))), 'same as one at a time');
  close(over(premultiply([1, 0, 0, 0]), g), g, 'a fully transparent layer changes nothing');
  close(over(premultiply([0, 0, 1, 1]), g), [0, 0, 1, 1], 'an opaque layer hides everything below');
});
A hint

Premultiplied "over" needs no multiplication by the source's alpha, because the source colour already carries it: add the destination, scaled by 1 − src[3], to the source, in all four channels including alpha. In unpremultiply, test for an alpha of 0 before dividing.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Practice that adapts to you

Practise this lesson

7 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 What does a glTF material with alphaMode MASK do with alpha?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: It draws each pixel fully opaque or fully transparent, depending on whether its alpha passes a cutoff

    glTF's three modes: OPAQUE ignores alpha, MASK cuts each pixel on or off at a cutoff, and BLEND composites with over.

  2. Question 2 of 7 Laying the smoke over the glass gives a different pixel from laying the glass over the smoke. Why?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: Over puts its first operand in front, and the layer in front takes its alpha's share before the one behind

    Over is associative but not commutative: A over B ≠ B over A whenever both are partly transparent and differ in colour. That is why transparent surfaces must be drawn in depth order.

  3. Question 3 of 7 Any layer, of any alpha, is laid over an opaque backdrop. What is the alpha of the result?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: 1, because αs + 1 × (1 − αs) = 1

    αo = αs + αb (1 − αs), and with αb = 1 this is 1 for any αs: every pixel of the three-layer stack over the wall has alpha 1.000 in the program.

  4. Question 4 of 7 In premultiplied form, the glass is (0.4, 0.05, 0.05, 0.5) and the wall (0.2, 0.3, 0.45, 1). What is the red channel of glass over wall?

    Type a number.

    A hint

    c_o = c_s + c_b × (1 − α_s).

    Show the answer: Question 4 of 7

    Answer: 0.5 (anything from 0.4995 to 0.5005 counts)

    0.4 + 0.2 × (1 − 0.5) = 0.4 + 0.1 = 0.5, the program's first red value: the same result as straight alpha, with one multiply-add.

  5. Question 5 of 7 What colour does a premultiplied pixel of alpha 0 have once it is unpremultiplied?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: None; the division by alpha is undefined, so code returns (0, 0, 0, 0) by convention

    Premultiplying by alpha 0 makes every channel 0, so the original colour is gone. The exercise of this lesson returns [0, 0, 0, 0] for such a pixel.

  6. Question 6 of 7 A user interface toolkit wants to merge several translucent layers into one image once, and lay it over different backgrounds later. Which storage makes that safe, and why?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: Premultiplied alpha, because over is associative for premultiplied colours, so the merged image gives the same result over any background

    With premultiplied colours, (C over B) over A equals C over (B over A), so a group of layers can be merged once and reused. Straight colours need the merged alpha in a division to get the same result.

  7. Question 7 of 7 How does a WebGPU canvas treat alpha when its configuration does not set alphaMode?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: As "opaque": it reads the colour and ignores the alpha

    GPUCanvasConfiguration's alphaMode is "opaque" by default. With "premultiplied", the canvas reads its colours as already multiplied by their alpha, as in [0.5, 0, 0, 0.5] for half-transparent red.

WebGPU blend states, evaluated factor by factor Pro: opens with a Pro, Premium or Ultimate pass

On a GPU, over and its relatives are written as blend states. This part reads the specification’s factors and operations, evaluates three blend states on overlapping sparks in two orders, and shows which ones care about order.

In this part:

  • What a blend state computes
  • Alpha blending depends on the order; additive blending does not

1 runnable example (JavaScript) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Two quiet errors: the canvas's alpha and the sRGB target, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Two mistakes in a blend state draw pictures that look nearly right in a quick test. This part reproduces both on one pixel and gives the setting that fixes each.

In this part:

  • Alpha blended with the colour’s factors makes the canvas see-through
  • An -srgb target blends light; a plain one blends codes

1 runnable example (JavaScript) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Drawing transparent geometry in order, and where sorting fails Pro: opens with a Pro, Premium or Ultimate pass

Blending a surface needs everything behind it already in the pixel, and the depth buffer can throw surfaces away. This part draws three panes in front of a floor six ways, then two panes that cross, where no order works.

In this part:

  • Opaque first, then transparent from back to front
  • Depth writes off: a misplaced pane blends instead of vanishing
  • Crossing surfaces defeat any order of whole objects
  • Order transparent objects from back to front, stably
    • Results of the sample tests

1 runnable example (JavaScript) · 1 exercise with sample tests · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

A warm-up comes first, answered in full, then three problems where transparency goes wrong. The question page of this module holds the first steps and the follow-up titles of all three:

  1. Fresher to mid · Warm-up

    What is premultiplied alpha, and why do compositors and GPUs prefer it to straight alpha?

    A pixel with straight alpha stores its colour and its coverage separately; with premultiplied alpha it stores the colour already multiplied by its alpha, so half-transparent red is (0.5, 0, 0, 0.5). Porter and Duff's over then becomes one multiply-add in every channel, alpha included: co = cs + cb (1 − αs). Two things follow. Over is associative, so layers can be merged once and laid over any backdrop later with the same result. And filtering is correct: averaging texels averages light weighted by coverage, so a resized sprite keeps a white edge white, 1.000 over a white page, where straight alpha turns it into a grey fringe of 0.750. The cost is that a colour at alpha 0 cannot be recovered.

  2. Mid to senior

    A game's leaf sprites, white-green shapes on transparent backgrounds, show dark outlines when the camera zooms out and the textures are minified with mipmaps and linear filtering. Explain the outlines with numbers and fix the asset pipeline and the renderer.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Glowing embers rise behind a large glass window. As the camera moves, the embers sometimes show dimmed behind the glass and sometimes bright as if in front of it. Both are transparent, sorted by their centres. Explain the flicker and design the draw order and blend states.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A WebGPU canvas configured with alphaMode "premultiplied" sits over a web page. Wherever translucent user interface panels are drawn, the page's text shows faintly through areas that should be opaque, and one browser shows odd colours there. Explain both with numbers and fix the pipeline.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Over gives the source its alpha’s share of the pixel and the backdrop the rest, and the result’s alpha grows the same way.
  • Premultiplied alpha stores the colour times its alpha, which makes over one multiply-add, associative, and safe to filter: a halved sprite edge stays 1.000 instead of turning into a 0.750 grey fringe.
  • On a GPU, blend states carry the same arithmetic, and transparent surfaces are drawn after opaque ones, from back to front.

Terms in this lesson

alpha compositing
Combining images or surfaces by their alpha, the share of each pixel they cover, with operators such as over, so that partly transparent layers show what lies behind them.
over operator
Porter and Duff's rule for laying a source with alpha over a backdrop: the source takes its alpha's share of the pixel and the backdrop shows through the rest.
premultiplied alpha
Storing a pixel's colour already multiplied by its alpha, so that half-transparent red is (0.5, 0, 0, 0.5). It makes over one multiply-add and keeps filtering correct.
straight alpha
Storing a pixel's colour and its alpha separately, without multiplying the colour by the alpha; also called unassociated or non-premultiplied alpha.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.