Inner Glow / Gradient Shader Material

I have this gradient shader material. That creates a gradient from one corner to another.

var material = new THREE.ShaderMaterial({
  uniforms: {
    color1: {
      value: new THREE.Color("red")
    color2: {
      value: new THREE.Color("purple")
    bboxMin: {
      value: geometry.boundingBox.min
    bboxMax: {
      value: geometry.boundingBox.max
  vertexShader: `
    uniform vec3 bboxMin;
    uniform vec3 bboxMax;
    varying vec2 vUv;

    void main() {
      vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
      vUv.x = (position.x - bboxMin.x) / (bboxMax.x - bboxMin.x);
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
  fragmentShader: `
    uniform vec3 color1;
    uniform vec3 color2;
    varying vec2 vUv;
    void main() {
      gl_FragColor = vec4(mix(color1, color2, vUv.x+vUv.y), 1.0);

I would like to modify it so it can create an “inner glow” effect like this image. Making a gradient based off the distance from the edge.

I thought the following would work, getting the minimum distance from the edges.

vUv.y = min(abs(position.y - bboxMin.y), abs(position.y - bboxMax.y));
vUv.x = min(abs(position.x - bboxMin.x), abs(position.x - bboxMax.x));

But I know very little about shaders was very wrong. Would anyone be able to help out with this?

Something like that?

Based on the code from this chapter :slight_smile: The Book of Shaders: Shapes

This article is helpful: Square shaped shaders | thndl, and based on it:


Yeah that’s pretty good. Thank you, I’ll read up on how it all works.

When I started three years ago with three.js, I tried simple shaders.
I did that for a German forum, comments in German.

I changed it slightly and translated the comments. It is an outer glow. :fire:


With some very simple shaders from 2016 both on my side


Thanks, that’s super useful! I’ve just spent a long time realising those striations in the square gradient is an optical illusion :man_facepalming:t3: opengl es - Issue getting gradient square in glsl es 2.0, Gamemaker Studio 2.0 - Stack Overflow

1 Like

I’ve made this example which creates a gradient 10 world units from the edge

But there’s a smoother looking example from the square shape shaders @prisoner849 listed which doesn’t have those striations and recreated here, it seems to use the distance from the center.

The problem is with an irregular rectangle the distance from the edge is not the same. I’ve managed to adjust this based of the shape’s aspect ratio in the first example, but the second example is a little more difficult.

Any ideas on how you would do this? I think I need to somehow transform the color based off direction to the center.

I mean, it’s not just an illusion. The 4 - 8 - 16 colours show that according to the formula the colours change squarely from the centre. So the color in the corners is always brighter if you think in a circle from the center.

That’s why I have the circular formula.

// function definition (circles)
	float circ(float d, float x, float y){
	   return d + sqrt( x * x + y * y );

So you can create almost any form of color change if you have the right formula.

In my addons I use a wide variety of functions to create 3D shapes. Maybe you can get something out of it.

Addon. Produces almost infinite many time-varying geometries with functions

Thanks! I think I’m wrapping my head around it now :slight_smile:

A small variation of my example.

<!DOCTYPE html>

<!-- @author hofk -->
  <title> Shader </title>
  <meta charset="utf-8" />
<body> </body>
<script src="../js/three.min.111.js"></script>

<script id="vertexShader" type="x-shader/x-vertex">

	// GLSL Code (OpenGL Shading Language)
	void main( ) {
		// standard output position is gl_Position:
    	gl_Position = vec4( position, 1.0 ); // position: 3D from three.js, fourth comp. > 1.0 gives cutout	

<script id="fragmentShader" type="x-shader/x-fragment">

    // GLSL Code (OpenGL Shading Language)
	#extension GL_OES_standard_derivatives : enable
    // uniform  -  data for all executed GPU threads uniform, read-only, see below at three.js
	uniform vec2  u_resolution; //  size of the painting area (canvas) in pixels (width, height)
	//uniform vec2  u_mouse;    // mouse position over the painting area in pixels (X, Y)
	//uniform float u_time;  	// time in seconds since start of screen layout
	// function definition 
	float circ(float d, float x, float y){		
		return d + sqrt( x*x*x*x*x*x + y*y*y*y*y*y );
	void main( ) {
	    // standard input of fragment coordinates: gl_FragCoord (predefined)
	    vec2 s = 2.0 * gl_FragCoord.xy / u_resolution.xy - 1.0;   // scaling of the axes:  -1 to +1		
		vec3 color = vec3(circ(-0.25, s.x, s.y), circ(-0.1, s.x, s.y),  circ( 0.6, s.x, s.y) - 0.15 ); // color circles			
		color = clamp(color, 0.002, 0.99); // limitation of colors to a region		
	    // standard output fragment color: gl_FragColor (predefined) 
		gl_FragColor = vec4(color, 1.0);	// parallel output - color with opacity: 1.0


// JavaScript - three.js

// Include GLSL code from shader scripts
vertexShaderCode   =  document.getElementById( 'vertexShader'   ).textContent
fragmentShaderCode =  document.getElementById( 'fragmentShader' ).textContent

init( );
animate( );


function init() {

    scene = new THREE.Scene();
	camera = new THREE.Camera();
    //camera.position.z = 1.0;
	renderer = new THREE.WebGLRenderer();
    //renderer.setPixelRatio( window.devicePixelRatio );
	WIDTH  =  700;
	HEIGHT =  350;
	renderer.setSize(WIDTH,HEIGHT);           // Zeichenflaeche
	renderer.setClearColor( 0xffffff, 1 );
	container = document.createElement('div');
	container.appendChild( renderer.domElement );
	// uniform variables for shader integration
    shaderUniforms = {
          //u_time: 		{ type:  "f", value: 1.0},           // "f" float
          u_resolution: { type: "v2", value: new THREE.Vector2() },		  
          //u_mouse: 		{ type: "v2", value: new THREE.Vector2() }
	shaderUniforms.u_resolution.value.x = WIDTH;    // = renderer.domElement.width;   // give value to shader 
    shaderUniforms.u_resolution.value.y = HEIGHT;   // = renderer.domElement.height;  // give value to shader
	// create material from the shader
    shMaterial = new THREE.ShaderMaterial( {
        uniforms: 		shaderUniforms,
        vertexShader:   vertexShaderCode,
        fragmentShader: fragmentShaderCode 
    } );
	//geometry = new THREE.CircleBufferGeometry( 1.0, 72 );
	geometry = new THREE.PlaneBufferGeometry( 4.0, 2.0 );
    plane = new THREE.Mesh(geometry, shMaterial );
    scene.add( plane );

function animate( ) {

    requestAnimationFrame( animate );	
	//shaderUniforms.u_time.value += 0.01;
    renderer.render( scene, camera );


Thanks! I’ll have a play.