Creating an Impossible Box with GLSL & THREE.js
Tags: glsl, threejs, javascript
Published: 2021-03-02 18:56:21

One of my favorite types of visualizations are the physically impossible ones. And of those, one of the most fun is a bigger-on-the-inside box!

# The Final Product

If you want to play around with it yourself, you can view it [on my website](https://interior.jessesolomon.dev).

And you can check out the fully commented [GitGub repository](https://github.com/JesseSolomon/interior) for more insight.

![Demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmrkbzcyrzsou411qgot.gif)

# Let's Build It!

If you're not familiar with the basics of how shaders and WebGL works, I highly recommend you check out this [MDN article](https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders) on the topic.

Now let's dive into how I went about building this!

To begin, let's talk about the feature that made this whole thing work `discard`.

The fragment shader has a special keyword that works similarly to `return` in a common programming language. `discard` instructs the GPU to not render the current fragment, allowing whatever is behind it to show through. You can read more about it [here](https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/discard.php).

Using this feature we can turn a boring regular cube, into a super cool transparent cube!

[Source: outer_fragment.glsl](https://github.com/JesseSolomon/interior/blob/main/public/outer_fragment.glsl)
```glsl
// Check if the fragment is far enough along any axis
bool x_edge = abs(worldPosition.x) > 0.4;
bool y_edge = abs(worldPosition.y) > 0.64;
bool z_edge = abs(worldPosition.z) > 0.4;

// Check that the fragment is at the edge of at least two axis'
if (!y_edge && !z_edge) {
	discard;
}

if (!y_edge && !x_edge) {
	discard;
}
```

![Transparent Box](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y759ah95483zizblcl84.png)

Now we just need to find a way to tell which face we're seeing though. This was by far the most difficult part, not because the solution was terribly hard, mostly because I'm not very good at math.

So let's go over how I went about implementing this.

1st, since there's a top and bottom to our box, we don't really need to work in 3D for this. So let's think of our 3D box, as a 2D box:

![Whiteboard Box](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr9u0oz22zqv9mpa6ype.png)

Now, we can take the 3D geometry (red) that's inside of the box, and flatten it to 2D:

![Whiteboard Geometry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e5n8v79r40j4dufj7i81.png)

Next, let's add a camera (blue) and some example fragments we want to render (green):

![Whiteboard Rendering](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mtfh1of70dm2gtviayws.png)

With this setup, we can make a line between our fragments and the camera, and check which face they go through:

![Whiteboard Line Intersection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3izuczfddfmivydst9e.png)

If we apply this method to our box, and give a color to each face we get this fun effect!

[Source: inner_fragment.glsl](https://github.com/JesseSolomon/interior/commit/da56b48db681a0889adc06b8c716a1918109b98e#diff-4555993f6120ca48f815318bfff88f00fa8b2c5ef3bdfd26e5c68bb742219ac0)
```glsl
// Define all the corners of our box
const vec2 corners[4] = vec2[](vec2(0.5, 0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5), vec2(0.5, -0.5));

// Define a line from the fragment's position (A) to the camera (B)
vec2 a = worldPosition.xz;
vec2 b = cameraPosition.xz;

int intersectedFace = -1;

// Iterate over each face
for (int i = 0; i < 4; i++) {
	// Get the second point for our face
	int next = int(mod(float(i + 1), 4.0));

	// Create a line from 2 corners based on the face
	vec2 c = corners[i];
	vec2 d = corners[next];

	// Does line 1 and 2 intersect? If so, assign the intersected face
	if (intersect(a, b, c, d)) {
		intersectedFace = i;
		break;
	}
}

// Color the fragment based on the face
switch (intersectedFace) {
	case -1:
		gl_FragColor = vec4(1, 0, 1, 1);
		break;
	case 0:
		gl_FragColor = vec4(1, 0, 0, 1);
		break;
	case 1:
		gl_FragColor = vec4(0, 1, 0, 1);
		break;
	case 2:
		gl_FragColor = vec4(0, 0, 1, 1);
		break;
	case 3:
		gl_FragColor = vec4(0, 1, 1, 1);
		break;
}
```

![Face Detection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qa1pxdmgorq3l2i9hl2.gif)

From here, we can just assign a face to each object we want inside, and `discard` any fragments that don't pass through the given face.

[Source: inner_fragment.glsl](https://github.com/JesseSolomon/interior/blob/main/public/inner_fragment.glsl)
```glsl
// Define a line from the fragment's position (A) to the camera (B)
vec2 a = worldPosition.xz;
vec2 b = cameraPosition.xz;

// Get the second point to define the face
int next = int(mod(float(face + 1), 4.0));

// Define a line at the given face
vec2 c = corners[face];
vec2 d = corners[next];

// If the defined lines do NOT intersect, then discard the fragment
if (!intersect(a, b, c, d)) {
	discard;
}
```

Then we'll just add some interesting animated objects, a little directional lighting for depth, and we're done!

![Demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmrkbzcyrzsou411qgot.gif)

Thanks for reading! I hope you enjoyed it as much as I had making it!