drawTwoSided
← All Butter documentationParameters
drawTwoSided(drawItem: Function)
drawItemA function that describes how to draw an item that has transparency
Drawing with transparency is one of the hardest things to encompass in a 3D system. In order for blending to work correctly on semi-transparent objects, they need to be drawn from the back to the front. You may be used to this concept, called the "painter's algorithm," in 2D mode, but it can be foreign when working in 3D.
The p5.transparency addon creates methods to make this easier to do. In Butter, this addon is loaded by default, so these methods are always available. The primary tool it provides is the drawTransparenct function. Instead of manually figuring out how to draw your items in a back-to-front order (which is extra hard when you consider a 3D rotation effect might be applied to your component!), it orders and draws your items for you.
However, sometimes single items have back and front faces, so drawTransparent is not sufficient for drawing something like a semitransparent box() or sphere(). For this, it also provides drawToSided. Here's how to use it:
- Translate to where you want to draw your item
- Call
drawTwoSided(function() { ... }), passing it a function that will draw your item
Then, it waits until all your items have been queued up to be drawn, it sorts them, and then it executes them in back-to-front order. Two-sided items will get drawn twice: once with only the back-facing faces, and then again with only the front-facing faces.
Here is an example that makes use of it:
let logo
let bubble
async function setup() {
logo = await loadImage('')
createCanvas(400, 400, WEBGL)
bubble = baseMaterialShader().modify(() => {
let t = uniformFloat(
() => millis() * 0.001
)
getObjectInputs((inputs) => {
inputs.position.y +=
0.05 * sin(inputs.position.x * 5 + t)
return inputs
})
getPixelInputs((inputs) => {
let c = mix(
normalize([
0.5*sin(inputs.normal.y * 3) + 0.5,
0.5*sin(inputs.normal.z * 4) + 0.5,
0.5*sin(inputs.normal.x * 5) + 0.5,
]),
[1, 0, 1],
0.3
)
let a = (1 - abs(inputs.normal.z)) * 0.75
inputs.color = [c, a]
inputs.ambientMaterial = c
return inputs
})
})
}
function draw() {
clear()
noStroke()
orbitControl()
push()
scale(
min(width/logo.width, height/logo.height) *
0.5
)
imageMode(CENTER)
image(logo, 0, 0)
pop()
// Make sure the back and front faces of
// the sphere don't occlude each other
drawTwoSided(() => {
push()
shader(bubble)
specularMaterial(255)
shininess(100)
ambientLight(255)
directionalLight(
255, 255, 255,
-1, 1, 1
)
directionalLight(
255, 255, 255,
0.5, 0.5, -0.5
)
sphere(width * 0.4)
pop()
})
}

If you were to remove the drawTwoSided call, it would look like this. Note how some of the triangles on the sphere clip through while others don't.
let logo
let bubble
async function setup() {
logo = await loadImage('')
createCanvas(400, 400, WEBGL)
bubble = baseMaterialShader().modify(() => {
let t = uniformFloat(
() => millis() * 0.001
)
getObjectInputs((inputs) => {
inputs.position.y +=
0.05 * sin(inputs.position.x * 5 + t)
return inputs
})
getPixelInputs((inputs) => {
let c = mix(
normalize([
0.5*sin(inputs.normal.y * 3) + 0.5,
0.5*sin(inputs.normal.z * 4) + 0.5,
0.5*sin(inputs.normal.x * 5) + 0.5,
]),
[1, 0, 1],
0.3
)
let a = (1 - abs(inputs.normal.z)) * 0.75
inputs.color = [c, a]
inputs.ambientMaterial = c
return inputs
})
})
}
function draw() {
clear()
noStroke()
orbitControl()
push()
scale(
min(width/logo.width, height/logo.height) *
0.5
)
imageMode(CENTER)
image(logo, 0, 0)
pop()
push()
shader(bubble)
specularMaterial(255)
shininess(100)
ambientLight(255)
directionalLight(
255, 255, 255,
-1, 1, 1
)
directionalLight(
255, 255, 255,
0.5, 0.5, -0.5
)
sphere(width * 0.4)
pop()
}

Here are some usage tips:
- Try not to modify your variables within your
drawTwoSidedfunction. Since the order they get called will depend on your viewpoint, it may lead to unexpected results. - If you share a resource like a framebuffer that you need to redraw per item, do the redrawing inside the
drawTwoSidedfunction. Otherwise, the item may not get drawn right after the resource has been updated, and by the time it does get drawn, the resource may have been rewritten already. Updating the resource inside the function ensures that each item will have the right content available to draw with, no matter the order.
Also see drawTransparent for single items that do not have internal front and back sides.