Apply gradient shader to a torus

Hi!
I would like to color a torus red on the left half and blue on the right half surely with a transition.
Best check my next post below as I made a mockup of what I want to accomplish.

↓↓↓

The following may not be exactly what you had in mind but it does run in the p5.js WebEditor. The errors came from the shader.vert file, so I used new files from here: https://github.com/aferriss/p5jsShaderExamples/tree/gh-pages/6_3d/6-1_rectangle and they allowed it to run without error.

let redGreen;

function preload() {
  redGreen = loadShader("shader.vert", "shader.frag");
}

function setup() {
  createCanvas(800, 600, WEBGL);
  noStroke();
}

function draw() {
  background(0);
  shader(redGreen);
  rotateX(frameCount * 0.01);
  rotateY(frameCount * 0.01);
  rotateZ(PI/2);
  torus(180, 40, 100, 12);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

shader.frag

precision mediump float;

varying vec2 vTexCoord;

void main() {
  // draw the texcoords to the screen
  gl_FragColor = vec4(vTexCoord.x, vTexCoord.y, 0.0 ,1.0);
}

shader.vert

// Get the position attribute of the geometry
attribute vec3 aPosition;

// Get the texture coordinate attribute from the geometry
attribute vec2 aTexCoord;

// When we use 3d geometry, we need to also use some builtin variables that p5 provides
// Most 3d engines will provide these variables for you. They are 4x4 matrices that define
// the camera position / rotation, and the geometry position / rotation / scale
// There are actually 3 matrices, but two of them have already been combined into a single one
// This pre combination is an optimization trick so that the vertex shader doesn't have to do as much work

// uProjectionMatrix is used to convert the 3d world coordinates into screen coordinates
uniform mat4 uProjectionMatrix;

// uModelViewMatrix is a combination of the model matrix and the view matrix
// The model matrix defines the object position / rotation / scale
// Multiplying uModelMatrix * vec4(aPosition, 1.0) would move the object into it's world position

// The view matrix defines attributes about the camera, such as focal length and camera position
// Multiplying uModelViewMatrix * vec4(aPosition, 1.0) would move the object into its world position in front of the camera
uniform mat4 uModelViewMatrix;


varying vec2 vTexCoord;

void main() {

  // copy the position data into a vec4, using 1.0 as the w component
  vec4 positionVec4 = vec4(aPosition, 1.0);

  // Move our vertex positions into screen space
  // The order of multiplication is always projection * view * model * position
  // In this case model and view have been combined so we just do projection * modelView * position
  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;

  // Send the texture coordinates to the fragment shader
  vTexCoord = aTexCoord;
}

1 Like

@svan thank you!!
It seems I copy pasted something wrong. Yes, I had the shader from that gitthub page loaded too. The problem I still have is that with the torus the gradient transition has a sharp break at one side like if the torus had a start and a end position:
How it looks:
Bildschirmfoto 2023-01-08 um 22.11.12

I am looking for a result like this from red to blue. I mocked it up in photoshop that is why the transition of the gradient looks not so smooth:
Bildschirmfoto 2023-01-08 um 21.44.18

Just for reference the code:

sketch:

let redGreen;

function preload() {
  redGreen = loadShader("shader.vert", "shader.frag");
}

function setup() {
  createCanvas(400, 400, WEBGL);
  noStroke();
}

function draw() {
  background(0);
  shader(redGreen);
  //rotateX(frameCount * 0.01);
  //rotateY(frameCount * 0.01);
  //rotateZ(PI/2);
  translate(200-width/2,200-height/2);
  torus(50, 20, 100, 12);
  
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

shader.vert


attribute vec3 aPosition;


attribute vec2 aTexCoord;


uniform mat4 uProjectionMatrix;


uniform mat4 uModelViewMatrix;


varying vec2 vTexCoord;

void main() {


  vec4 positionVec4 = vec4(aPosition, 1.0);


  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;


  vTexCoord = aTexCoord;
}

shader.frag

precision mediump float;

varying vec2 vTexCoord;

void main() {
  // draw the texcoords to the screen
  gl_FragColor = vec4(vTexCoord.x, vTexCoord.y, 0.0 ,1.0);
}

help would be very much appreciated. Also for more online resources I could read up on. Thanks!

Anyone can help here? :slight_smile:

Since this seems like a shader question and not everyone is familiar with glsl around here, I’d recommend asking on the shader zone discord server. Hope this helps

1 Like

Instead of passing a uv pair for the texture coordinates, you only need a single float value based on the vertex y-coordinate. In your fragment shader, just have

varying float high;

void main() {
  gl_Fragcolor = vec4( high, 0., 1.-high, 1. );
}

Then in your vertex shader, you have to compute high from your aPosition.y. Scale and offset it so it ranges from 0 to 1.

1 Like

I played around with this a bit. It looks like p5 defines the torus model coordinates to scale from -1 to 1 from the middle of the big ring with the smaller ring extending from there. So if you had a torus( 100, 20 ) as an example, in your vertex shader, your aPosition.y would range from -1.2 to 1.2. For that case, I would have high = aPosition.y / 2.4 + 0.5; in the vertex shader.

1 Like

thank you @scudly for playing around with this. yes there is something about how p5 calculates the torus that is not ideal for shaders it seems. But boy do I know nothing about shaders and 3d :slight_smile:

I will try to test your ideas although I don´t know exactly know where to put the lines of your code. I will try to read the shader introduction again and hope it helps to understand your suggestions better. Thanks!

My vertex shader is

uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
attribute vec3 aPosition;
varying float high;

void main() {
  high = aPosition.y / 2.4 + 0.5;
  gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
}

where the 2.4 number depends on the scaling of torus you create. Specifically, the ratio of the two radii.

Thanks, when I paste the above in the vertex shader and the following int o the shader.frag:

precision mediump float;

varying float high;

void main() {
  gl_Fragcolor = vec4( high, 0., 1.-high, 1. );
}

I get this error:
:cherry_blossom: p5.js says: [sketch.js, line 19] An error with message “Failed to execute ‘useProgram’ on ‘WebGLRenderingContext’: parameter 1 is not of type ‘WebGLProgram’.” occurred inside the p5js library when torus was called. If not stated otherwise, it might be an issue with the arguments passed to torus. (reference | p5.js)

​Do I need to change somethin in the stech file too?

Preview

Here’s my full code which runs without errors on my linux chrome:

let redGreen;

function setup() {
  createCanvas(400, 400, WEBGL);
  redGreen = createShader( vs, fs );
  noStroke();
}

function draw() {
  background(0);
  shader(redGreen);
  torus( 100, 20, 100, 12);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}


let vs = `
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
attribute vec3 aPosition;
varying float high;

void main() {
  vec4 positionVec4 = vec4(aPosition, 1.0);
  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
  high = aPosition.y / 2.4 + 0.5;
}
`;


let fs = `
precision mediump float;
varying float high;

void main() {
  gl_FragColor = vec4( high, 0., 1.-high, 1.0 );
}
`;
3 Likes

running here too! That´s awesome. Thank you! Will dig into this now and try to understand. The result is spot on!