Eric Ryan
Eric Ryan

Reputation: 11

Having a problem in porting shader from shadertoy to react vite app

I want to port shader from shadertoy to my React-Vite app using Three.js and @react-three/fiber. Exactly this shader; https://www.shadertoy.com/view/DlySDD I surfed Google for a long time, but I couldn't find an exact answer. There are some posts on porting shadertoy shader on Stack Overflow, but their example shader is a bit different. As I was trying to port it, I got a problem. I think I don't get an idea of BufferShader. Below I attached my code snippet. Please check and give me an idea on what could be wrong.

import { Fragment, useRef, useMemo } from "react";
import * as THREE from "three";
import { Canvas, useFrame, useLoader, useThree } from "@react-three/fiber";

const vertexShader = `
    varying vec2 vUv;
    
    void main() {
      vUv = uv;

      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

const Buffer_A_Frag = `
    uniform vec2 iResolution;

    void mainImage(out vec4 fragColor, in vec2 fragCoord)
    {
        // Normalized pixel coordinates (from 0 to 1)
        vec2 uv = fragCoord/iResolution.xy;

        // Time varying pixel color
        vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));

        // Output to screen
        fragColor = vec4(col, 1.0);
    }

    void main() {
      vec4 fragColor;
      mainImage(fragColor, gl_FragCoord.xy);
      gl_FragColor = fragColor;
    }
`;

const fragmentShader = `
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)

void mainImage(out vec4 color, vec2 coord)
{
    //Resolution for scaling
    vec3 res = iResolution, 
    //Compute ray direction ranging from -1 to 1 (aspect corrected, with +z forward)
    dir = vec3(res.xy-coord*2.,res.x) / res.x,
    //Sample position
    pos;
    
    //Use RG channels of last frame for the GB (brightened by /.7)
    //This produces a simple chromatic aberration effect!
    color = vec4(0, texture(iChannel0,coord/res.xy)/.7);
    
    //Loop 100 times
    for(float i = 0.; i<1e2; i++)
        //Compute sample point along ray direction plus an arbitrary offset
        //Starts at random point between 0 and 0.1 and increments 0.1 each step
        pos = dir*(texture(iChannel2,coord/1e3).r+i)*.1 + 9.,
        //Rotate about the y-axis
        pos.xz *= mat2(cos(iTime*.1+asin(vec4(0,1,-1,0)))),
        //Add sample point, decreasing intensity with each interation (down to 0)
        color.r += (1e2-i) / 1e5 /
        //Attenuate from the absolute distance to the noise + gyroid shape
        abs(texture(iChannel1,pos*.1).r + dot(sin(pos),cos(pos.yzx)));
}

void main() {
    vec4 fragColor;
    mainImage(fragColor, gl_FragCoord.xy);
    gl_FragColor = fragColor;
}
`;

const OceanShaderMesh = () => {
  const materialRef = useRef(null);
  const { size, gl } = useThree();
  gl.setPixelRatio(1);

  useFrame(({ clock }) => {
    if (materialRef.current) {
      const material = materialRef.current;
      material.uniforms.iTime.value = clock.getElapsedTime();
      material.uniforms.iResolution.value.set(size.width, size.height);
      gl.setSize(size.width, size.height);
    }
  });

  const uniforms = useMemo(() => {
    return {
      iTime: { value: 0 },
      iResolution: { value: new THREE.Vector2(size.width, size.height) },
      iChannel0: {
        value: Buffer_A_Frag,
      },
      iChannel1: { value: useLoader(THREE.TextureLoader, "buffer1.png") },
      iChannel2: {
        value: useLoader(THREE.TextureLoader, "noise.png"),
      },
    };
  }, []);

  return (
    <mesh scale={[size.width / size.height, 1, 1]}>
      <planeGeometry args={[size.width, size.height]} />
      <shaderMaterial
        ref={materialRef}
        uniforms={uniforms}
        vertexShader={vertexShader}
        fragmentShader={fragmentShader}
      />
    </mesh>
  );
};

const Background = () => (
  <Fragment>
    <Canvas
      orthographic
      camera={{ zoom: 100 }}
      style={{
        width: "100vw",
        height: "100vh",
        backgroundColor: "rgba(0, 0, 0, 0.5)",
      }}
    >
      <OceanShaderMesh />
    </Canvas>
  </Fragment>
);

export default Background;

I made that React component which includes Shader, but it has errors in compiling.

Here is an error log: enter image description here

And I put FragmentShader into the iChannel0 of uniform, but I think it's not correct and even stupid.

Any idea?

Upvotes: 0

Views: 82

Answers (1)

Nephelococcygia
Nephelococcygia

Reputation: 1131

If you read your error message, you can see that the texture function is not defined.

This is because ShaderToy is by default in OpenGL ES latest version. And for three.js WebGL shaders are by default in GLSL1.

There are little differences in naming of default functions. texture in older versions would be texture2D. Not sure if there is a well documented "migration" page somewhere but you could compare from this example page that compares version 100 and 330 (but note that three.js uses version 100 and 300).

Alternatively, if you want to use equivalent OpenGL ES more recent versions with three.js, you can set material version to THREE.GLSL3 (Material's constants end of page)

Upvotes: 0

Related Questions