During our stream with @bruno_simon, we implemented a tube shaped procedural scene that required augmenting GLSL's atan() function a little bit so that we could play with our uv's on a radial plane.

Using two coordinate parameters, commonly x and y, the GLSL function atan() allows us to access an angle for each pixel position.

From there, Bruno constructed a larger function around it, adding a few calculations in order to create a near perfect tubural form that creates a really satisfying sense of depth.

With one caveat — there's a visible edge demarcating the start and end of the radial shape.

ilithya found a solution for this by adding a mirroring effect to Bruno's function. By applying GLSL's abs() function on the angle, she accessed half the radial circle and fliiped it, hidding that pesky line in the process.

Teamwork makes the dream work!

/* 
* RADIAL UV
* - authored by @bruno_simon -
* (uv) transforms uv
* returns => radial uv
*/

vec2 getRadialUv(vec2 uv) {
	float angle = atan(uv.x, -uv.y);
	angle = abs(angle);
	vec2 radialUv = vec2(0.0);
	radialUv.x = angle / (PI * 2.0) + 0.5;
	radialUv.y = 1.0 - pow(1.0 - length(uv), 4.0);	
	return radialUv;
}
<!--
 * #curiouslyminded
 * 
 * Follow:
 * https://www.twitch.tv/curiouslyminded
 * https://www.youtube.com/curiouslyminded
-->

<div id="example"></div> <script id="vertex" type="x-shader/x-vertex"> void main() { gl_Position = vec4(position, 1.0); } </script> <script id="fragment" type="x-shader/x-vertex"> precision mediump float; #define PI 3.14159265359 uniform vec2 u_resolution; uniform float u_time;

// RADIAL UV // authored by @bruno_simon // (uv) transform uv to radial uv vec2 getRadialUv(vec2 uv) { float angle = atan(uv.x, -uv.y); angle = abs(angle);

vec2 radialUv = vec2(0.0); radialUv.x = angle / (PI * 2.0) + 0.5; radialUv.y = 1.0 - pow(1.0 - length(uv), 4.0);

return radialUv; }

void main() { vec2 uv = (gl_FragCoord.xy - u_resolution * 0.5) / u_resolution.yy; vec3 color = vec3(0.0); vec2 radialUv = getRadialUv(uv); color = vec3(radialUv, 0.5); gl_FragColor = vec4(color, 1.0); } </script>

* {
user-select: none;
}

body {
height: 100vh;
background-color: black;
margin: 0;
padding: 0;
overflow: hidden;
position: relative;
}
/* 
 * #curiouslyminded
 *
 * This JS is an adapted template from the https://thebookofshaders.com/
 */

let camera, scene, renderer, clock;
let uniforms;

function init() {
	const container = document.getElementById("example");

	clock = new THREE.Clock();
	camera = new THREE.Camera();
	camera.position.z = 1;

	scene = new THREE.Scene();

	const geometry = new THREE.PlaneBufferGeometry(2, 2);

	uniforms = {
		u_time: { type: "f", value: 1.0 },
		u_resolution: { type: "v2", value: new THREE.Vector2() },
	};

	const material = new THREE.ShaderMaterial({
		uniforms,
		vertexShader: document.getElementById("vertex").textContent,
		fragmentShader: document.getElementById("fragment").textContent
	});

	const mesh = new THREE.Mesh(geometry, material);
	scene.add(mesh);

	renderer = new THREE.WebGLRenderer();
	renderer.setPixelRatio(window.devicePixelRatio);

	container.appendChild(renderer.domElement);

	onWindowResize();
	window.addEventListener("resize", onWindowResize);
}

function onWindowResize() {
	renderer.setSize(window.innerWidth, window.innerHeight);
	uniforms.u_resolution.value.x = renderer.domElement.width;
	uniforms.u_resolution.value.y = renderer.domElement.height;
}

function render() {
	uniforms.u_time.value = clock.getElapsedTime();
	renderer.render(scene, camera);
}

function animate() {
	render();
	requestAnimationFrame(animate);
}

init();
animate();