Change color of shader

Hello, I made this morphic shape and I’d like to change the gradient of It, I’m somehow lost on how to do it. Would anyone give me ideas ?

Here’s my code

import * as THREE from 'three';
import openSimplexNoise from 'https://cdn.skypack.dev/open-simplex-noise';
import {Vector3, Vector4} from "three";
import {vector} from "three/addons/nodes/core/NodeBuilder.js";

/**
 * Scene
 *
 * @type {Scene}
 */
const scene = new THREE.Scene();

/**
 * Camera
 *
 * @type {PerspectiveCamera}
 */
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

/**
 * Renderer
 *
 * @type {WebGLRenderer}
 */
const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('#bg'),
  alpha: true,
  antialias: true
});

init();

function init() {
  // Camera positions
  camera.position.x = 0;
  camera.position.y = 10;
  camera.position.z = 0;
  camera.lookAt(0, 0, 0);

  // Renderer
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.shadowMap.enabled = true;

  // Lighting
  const pointLight = new THREE.PointLight(0xc1c1c1, 1, 100);
  pointLight.position.set(20, 20, 20);
  const ambientLight = new THREE.AmbientLight(0x222222);

  // Helpers
  const gridHelper = new THREE.GridHelper(200, 50);

  scene.add(
    ambientLight,
    pointLight,
    gridHelper
  );
}

// Sphere

let sphereGeometry = new THREE.SphereGeometry(4, 200, 200);
sphereGeometry.positionData = [];

let v3 = new THREE.Vector3();

for (let i = 0; i < sphereGeometry.attributes.position.count; i++) {
  v3.fromBufferAttribute(sphereGeometry.attributes.position, i);
  sphereGeometry.positionData.push(v3.clone());
}

let sphereMesh = new THREE.ShaderMaterial({
  uniforms: {
    // -- THIS IS WHERE COLOR IS HANDLED --
    colorA: { type: 'vec3', value: new Vector3(0.5, 0.5, 0.5) },
  },
  vertexShader: document.getElementById('vertex').textContent,
  fragmentShader: document.getElementById('fragment').textContent,
});

let sphere = new THREE.Mesh(sphereGeometry, sphereMesh);
scene.add(sphere);

let noise = openSimplexNoise.makeNoise4D(Date.now());
let clock = new THREE.Clock();

window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight)
});

// Animate loop

function animate() {
  requestAnimationFrame(animate);

  let t = clock.getElapsedTime() / 1.;

  sphereGeometry.positionData.forEach((p, idx) => {
    let setNoise = noise(p.x, p.y, p.z, t * 1.05);
    v3.copy(p).addScaledVector(p, setNoise);
    sphereGeometry.attributes.position.setXYZ(idx, v3.x, v3.y, v3.z);
  })
  sphereGeometry.computeVertexNormals();
  sphereGeometry.attributes.position.needsUpdate = true;

  renderer.render(scene, camera);
}

animate();

Here’s my fragment

<script id="fragment" type="text/glsl">
    uniform vec3 colorA;

    #define NORMAL

    #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )

        varying vec3 vViewPosition;

    #endif

    #include <packing>
    #include <uv_pars_fragment>
    #include <normal_pars_fragment>
    #include <bumpmap_pars_fragment>
    #include <normalmap_pars_fragment>
    #include <logdepthbuf_pars_fragment>
    #include <clipping_planes_pars_fragment>

    void main() {

        #include <clipping_planes_fragment>
        #include <logdepthbuf_fragment>
        #include <normal_fragment_begin>
        #include <normal_fragment_maps>

        gl_FragColor = vec4( normalize( normal ) * colorA + 0.5, 1.0 );

        #ifdef OPAQUE

            gl_FragColor.a = 1.0;

        #endif

    }
</script>

Here’s my vertex

<script id="vertex" type="text/glsl">
    #define NORMAL

    #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )

        varying vec3 vViewPosition;

    #endif

    #include <common>
    #include <uv_pars_vertex>
    #include <displacementmap_pars_vertex>
    #include <normal_pars_vertex>
    #include <morphtarget_pars_vertex>
    #include <skinning_pars_vertex>
    #include <logdepthbuf_pars_vertex>
    #include <clipping_planes_pars_vertex>

    void main() {

        #include <uv_vertex>

        #include <beginnormal_vertex>
        #include <morphnormal_vertex>
        #include <skinbase_vertex>
        #include <skinnormal_vertex>
        #include <defaultnormal_vertex>
        #include <normal_vertex>

        #include <begin_vertex>
        #include <morphtarget_vertex>
        #include <skinning_vertex>
        #include <displacementmap_vertex>
        #include <project_vertex>
        #include <logdepthbuf_vertex>
        #include <clipping_planes_vertex>

    #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )

        vViewPosition = - mvPosition.xyz;

    #endif

    }
</script>

I’m guessing it is from this line :

gl_FragColor = vec4( normalize( normal ) * colorA + 0.5, 1.0 );

How should I do it to play with RGB values ?

In general, gl_FragColor is a vec4 type variable, it has 4 components - RGBA, each component is a float type value from 0 to1, for example:

float r = 0.5;
float g = 0.0;
float b = 0.25;
float a = 1.0;
gl_FragColor = vec4(r, g, b, a); // an equivalent of 128/0/64/1 or 0x800040FF

or, the same

vec3 rgb = vec3(0.5, 0.0, 0.25);
float a = 1.0;
gl_FragColor = vec4(rgb, a);

in your case normalize(normal) and colorA are vec3 types with 3 floating point components each, they are multiplied component-wise and 0.5 is added to each component of the result:

vec3 nn = normalize(normal);
vec3 clr = colorA;
vec3 rgb = nn * clr + 0.5; // vec3(nn.r * clr.r + 0.5, nn.g * clr.g + 0.5, nn.b * clr.b + 0.5)

It sounds like you are a shader beginner.

Then Shader Beginner (de/en) from the collection might be something for you. :slightly_smiling_face:

@hofk @tfoller Thanks for your help, and this collection is really cool !

I’ll try to do something on my side.

The result I want is the colors to be gray, white, black-ish, is it also a solution to apply some kind of black & white filter / lighting ?

Grey colors simply have the same amount of red green and blue in them, there is no filter.

But there are many ways to calculate that amount from the original red green and blue in the colored image. It’s a messy topic that involves many different standards and particulars of human vision.

I made a fiddle some time ago to compare some of those ways to make black & white image or desaturate (which, as it turns out, is not the same thing):

In your case you could try Photoshop desaturate:

vec3 sRGB = normalize( normal ) * colorA + 0.5;
float bw = (min(sRGB.r, min(sRGB.g, sRGB.b)) + max(sRGB.r, max(sRGB.g, sRGB.b))) * 0.5;
gl_FragColor = vec4(vec3(bw), 1.0);

Isn’t the issue here the inclusion of the “normal” fragment as this would return colors of a standard normal map?

I think that’s what’s currently colorizing the shape, xyz(rgb) components of normals, the question here is how to transform those.