Center is off with react-fiber-threejs

Hi,

just trying out threejs for the first time. I need to render this circle into the middle of the screen while maintaining the parent div and canvas size to full inner window size.


const Mesh = () => {

  const meshRef = useRef();

  useFrame((state) => {

    let time = state.clock.getElapsedTime();

    if (meshRef.current){

      meshRef.current.material.uniforms.u_time.value = time + 1;
    }

  });

  const uniforms = useMemo(() => ({
      u_time: { type: "f", value: 0 },
      u_resolution: { type: "v2", value: new THREE.Vector2(window.innerWidth, window.innerHeight)}
    }),
  []);

  return <mesh ref={meshRef}>
    <planeGeometry args={[window.innerWidth, window.innerHeight]} />
    <shaderMaterial 
        fragmentShader={fragmentShader}
        vertexShader={vertexShader}
        uniforms={uniforms}
        side={THREE.DoubleSide}
      />
  </mesh>
}

export default function Home() {

  return (
    <main className="h-screen w-screen bg-black/50">

      <Canvas><Mesh/></Canvas>
    </main>
  );
}

vert.glsl

varying vec2 vUv;
void main() {
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectionPosition = projectionMatrix * viewPosition;
    gl_Position = projectionPosition;
}

frag.glsl

#define GLSLIFY 1
// Common uniforms
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform float u_frame;
#define T u_time
#define R u_resolution

void main() {


        //center origin point
	vec2 uv = ( gl_FragCoord.xy -.5* u_resolution.xy) / u_resolution.y;
    
    //calculate distance from the origin point
    float d = length(uv);
 
    float r = 0.3;

    float col = smoothstep(r,r - 2.0/u_resolution.y ,d) ;

    gl_FragColor = vec4(vec3(col), 1.0);

}

Here I set all divs to 500*500 px and three js resizes the canvas to other values.

canvas scales to the next absolute/relative parent.

and this is a mistake

<planeGeometry args={[window.innerWidth, window.innerHeight]} />

three units !== pixels, you want to do this:

const { width, height } = useThree(state => state.viewport)
return (
  <mesh scale={[width, height, 1]}>
    <planeGeometry />

this scales the plane without re-creating the geometry on size changes. constructor arguments would re-create it every time. viewport corresponds to the size of the threejs canvas in threejs units.

1 Like

it would be way easier btw if you used a shader helper, like drei/shaderMaterial

import { extend } from '@react-three/fiber'
import { shaderMaterial } from '@react-three/drei'

const SomeMaterial = shaderMaterial(
  { u_time: 0, u_resolution: new THREE.Vector2() },
  vertexShader,
  fragmentShader,
)
extend(SomeMaterial)

...
const ref = useRef()
useFrame((state, delta) => {
  ref.current.time = state.clock.getElapsedTime()
})
return (
  <mesh ref={ref}>
    <planeGeometry />
    <someMaterial side={THREE.DoubleSide} />

example