Unable to show the exact vertex color using bufferGeometry

I am using bufferGeometry to draw a grid of cells with different colors in a React application. When I set the colors using bufferAttributes, the cells are displayed in a dull light color instead of the specified RGB color.
Could someone help with this?
I have posted the code and attached image where the green/pink color appears different to the expected color.

import ReactDOM from 'react-dom';
import React from 'react';
import {
    Canvas
} from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import * as THREE from 'three';

const positionsArray = new Float32Array([
    2.0, 2.0, 3.0, // bottom left
    3.0, 2.0, 3.0, // bottom right
    3.0, 3.0, 3.0, // top right

    3.0, 3.0, 3.0, // top right?
    2.0, 3.0, 3.0, // top left?
    2.0, 2.0, 3.0, // bottom left

    3.0, 3.0, 4.0, // bottom left
    4.0, 3.0, 4.0, // bottom right
    4.0, 4.0, 4.0, // top right

    4.0, 4.0, 4.0, // top right?
    3.0, 4.0, 4.0, // top left?
    3.0, 3.0, 4.0 // bottom left

]);

const colors = new Float32Array([

    /*
        I used this tool to convert the RGB colors
        to their normalised versions:
        https://doc.instantreality.org/tools/color_calculator/
    */

    // Unable to show the exact hexcode GREEN color shown with
    // buffer attribute #D524A2
    0.8, 1, 0.6,
    0.8, 1, 0.6,
    0.8, 1, 0.6,

    0.8, 1, 0.6,
    0.8, 1, 0.6,
    0.8, 1, 0.6,

    // Unable to show the exact hexcode PINK color shown with
    // buffer attribute #D524A2
    0.835, 0.141, 0.635,
    0.835, 0.141, 0.635,
    0.835, 0.141, 0.635,

    0.835, 0.141, 0.635,
    0.835, 0.141, 0.635,
    0.835, 0.141, 0.635
]);

const normalArray = new Float32Array([
    0, 0, 1,
    0, 0, 1,
    0, 0, 1,

    0, 0, 1,
    0, 0, 1,
    0, 0, 1,
]);

const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3);
const Example : React.FunctionComponent = () => (
    <div style={{ height: '100vh', backgroundColor: 'grey' }}>
        <Canvas
            dpr={Math.min(window.devicePixelRatio, 2)}
            camera={{
                fov: 45,
                position: [1, 1, 18],
                near: 0.1,
                far: 2000
            }}
        >
            <pointLight position={[0, 0, 100]} color="green" intensity={0.9} />
            <axesHelper args={[10]} />
            <OrbitControls dampingFactor={0.05} />
            <mesh>
                <bufferGeometry attach="geometry" attributes={{ position: positionsAttribute }}>
                    {/* <bufferGeometry> */}
                    <bufferAttribute
                        attachObject={['attributes', 'position']}
                        array={positionsArray}
                        itemSize={3}
                        count={positionsArray.length / 3}
                    />
                    <bufferAttribute
                        attachObject={['attributes', 'normal']}
                        array={normalArray}
                        itemSize={3}
                        count={normalArray.length / 3}
                    />
                    <bufferAttribute
                        attachObject={['attributes', 'color']}
                        array={colors}
                        itemSize={3}
                        count={colors.length / 3}
                        normalized
                    />
                </bufferGeometry>
                  
                <meshBasicMaterial side={THREE.DoubleSide} vertexColors lightMapIntensity={0} toneMapped={false} />
            </mesh>
        </Canvas>
    </div>
);

ReactDOM.render(<Example />, document.getElementById('react-page-content'));

I think you don’t need to normalize the value since you’re feeding it with floating color value already. Maybe try without normalizing:

<bufferAttribute
    attachObject={['attributes', 'color']}
    array={colors}
    itemSize={3}
    count={colors.length / 3}
    false
/>

Or try feeding your color array with Uint8, like:

new Uint8Array([255, 255, 255…])

Thanks, I have tried your solution. But, it makes no difference to the output. The rendered colors are still not correct.
Here is the sandbox for reference: zen-glade-8gv4db - CodeSandbox

The expected and actual colors clearly don’t match. (attached images for comparison)
Screenshot 2022-02-15 at 22.47.05
Screenshot 2022-02-15 at 22.46.41

Ahh I see. I think then it might be a color space issue. Try:

<Canvas linear={true}>

Vertex colors are expected to be given in Linear-sRGB color space. By comparison, most other colors in R3F are given in sRGB, and R3F handles the sRGB → Linear-sRGB conversion automatically. For vertex colors you could manually convert before writing to the positions array:

const color = new Color();
for (let i = 0; i < count; i++) {
  color.r = array[i * 3];
  color.g = array[i * 3 + 1];
  color.b = array[i * 3 + 2];

  color.convertSRGBToLinear();

  array[i * 3] = color.r;
  array[i * 3 + 1] = color.g;
  array[i * 3 + 2] = color.b;
}

Note that colors in CSS (excluding some very new CSS features) are always sRGB as well, if you are trying to match colors elsewhere on the page.

3 Likes

Amazing!. Thanks for letting know donmccurdy

Appreciate your answer @avseoul. Setting linear prop in Canvas is a simple fix to make it work with current rgb values for color attributes.

@donmccurdy thanks for the explanation in detail! Didn’t know CSS color value is sRGB. @karthik-selvam my bad I think my answer was a little irresponsible and more prone to error later in your project, might cause other issues