How to Implement ShaderToy in Threejs ShaderMaterial?

I am very new in Threejs as well as in ShaderMaterial. And also zero knowledge in GLSL or C/C++.

Could you please help me to convert the ShaderToy code into Threejs uniforms, vertexShader, fragmentShader?.

Kindly note that I want to make dynamic the iChannel2 I mean this is envMap, bounce time etc. (Possible dynamic fields.).

I will very grateful for your help. Thank you in advance.

99.9% of shader toys (including the one you linked) use raytracing / pathtracing / SDFs for rendering, there’s no geometry involved - it’s just postprocessing on a flat plane.

Having that in mind, most of the time shader toys are not directly portable to three.js (they are portable, but it’s more work than just copy-paste, as most of that shader math assumes raytracing on a plane instead of geometry usage.)

If you’re using react-three-fiber - you can get a similar effect with MeshRefractionMaterial. If you are not using react-three-fiber - you can hijack the material code from here.

If I use this code getting lots of error which are not identified.

My code is the bellow.

import * as THREE from 'three'
import { MeshBVHUniformStruct, shaderStructs, shaderIntersectFunction } from "three-mesh-bvh";
// import { fragmentShader } from './fragmentShader';
// import { vertexShader } from './vertexShader';
// import vertexShader from './vertex.glsl'

const getVersion = () => parseInt(THREE.REVISION.replace(/\D+/g, ''));
const version = /* @__PURE__ */getVersion();

export const shaderMaterial = ({ envMap, color, ior }) => {

  const material = new THREE.ShaderMaterial({
    uniforms: {
      envMap: null,
      bounces: 3,
      ior: 2.4,
      correctMips: true,
      aberrationStrength: 0.01,
      fresnel: 0,
      bvh: /* @__PURE__ */ new MeshBVHUniformStruct(),
      color: /* @__PURE__ */ new THREE.Color('white'),
      resolution: /* @__PURE__ */ new THREE.Vector2(),
      viewMatrixInverse: /* @__PURE__ */ new THREE.Matrix4(),
      projectionMatrixInverse: /* @__PURE__ */ new THREE.Matrix4(),
    },
    vertexShader: `
    uniform mat4 viewMatrixInverse;
  
    varying vec3 vWorldPosition;  
    varying vec3 vNormal;
    varying mat4 vModelMatrixInverse;
  
    #ifdef USE_INSTANCING_COLOR
      varying vec3 vInstanceColor;
    #endif
  
    void main() {        
      vec4 transformedNormal = vec4(normal, 0.0);
      vec4 transformedPosition = vec4(position, 1.0);
      #ifdef USE_INSTANCING
        transformedNormal = instanceMatrix * transformedNormal;
        transformedPosition = instanceMatrix * transformedPosition;
      #endif
  
      #ifdef USE_INSTANCING
        vModelMatrixInverse = inverse(modelMatrix * instanceMatrix);
      #else
        vModelMatrixInverse = inverse(modelMatrix);
      #endif
  
      #ifdef USE_INSTANCING_COLOR
        vInstanceColor = instanceColor.rgb;
      #endif
  
      vWorldPosition = (modelMatrix * transformedPosition).xyz;
      vNormal = normalize((viewMatrixInverse * vec4(normalMatrix * transformedNormal.xyz, 0.0)).xyz);
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * transformedPosition;
    }`,
    fragmentShader: `
    #define ENVMAP_TYPE_CUBE_UV
    precision highp isampler2D;
    precision highp usampler2D;
    varying vec3 vWorldPosition;
    varying vec3 vNormal;
    varying mat4 vModelMatrixInverse;
  
    #ifdef USE_INSTANCING_COLOR
      varying vec3 vInstanceColor;
    #endif
      
    #ifdef ENVMAP_TYPE_CUBEM
      uniform samplerCube envMap;
    #else
      uniform sampler2D envMap;
    #endif
      
    uniform float bounces;
    ${shaderStructs}
    ${shaderIntersectFunction}
    uniform BVH bvh;
    uniform float ior;
    uniform bool correctMips;
    uniform vec2 resolution;
    uniform float fresnel;
    uniform mat4 modelMatrix;
    uniform mat4 projectionMatrixInverse;
    uniform mat4 viewMatrixInverse;
    uniform float aberrationStrength;
    uniform vec3 color;
    
    float fresnelFunc(vec3 viewDirection, vec3 worldNormal) {
      return pow( 1.0 + dot( viewDirection, worldNormal), 10.0 );
    }
      
    vec3 totalInternalReflection(vec3 ro, vec3 rd, vec3 normal, float ior, mat4 modelMatrixInverse) {
      vec3 rayOrigin = ro;
      vec3 rayDirection = rd;
      rayDirection = refract(rayDirection, normal, 1.0 / ior);
      rayOrigin = vWorldPosition + rayDirection * 0.001;
      rayOrigin = (modelMatrixInverse * vec4(rayOrigin, 1.0)).xyz;
      rayDirection = normalize((modelMatrixInverse * vec4(rayDirection, 0.0)).xyz);
      for(float i = 0.0; i < bounces; i++) {
        uvec4 faceIndices = uvec4( 0u );
        vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
        vec3 barycoord = vec3( 0.0 );
        float side = 1.0;
        float dist = 0.0;
        bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
        vec3 hitPos = rayOrigin + rayDirection * max(dist - 0.001, 0.0);      
        vec3 tempDir = refract(rayDirection, faceNormal, ior);
        if (length(tempDir) != 0.0) {
          rayDirection = tempDir;
          break;
        }
        rayDirection = reflect(rayDirection, faceNormal);
        rayOrigin = hitPos + rayDirection * 0.01;
      }
      rayDirection = normalize((modelMatrix * vec4(rayDirection, 0.0)).xyz);
      return rayDirection;
    }
      
    #include <common>
    #include <cube_uv_reflection_fragment>
      
    #ifdef ENVMAP_TYPE_CUBEM
      vec4 textureGradient(samplerCube envMap, vec3 rayDirection, vec3 directionCamPerfect) {
        return textureGrad(envMap, rayDirection, dFdx(correctMips ? directionCamPerfect: rayDirection), dFdy(correctMips ? directionCamPerfect: rayDirection));
      }
    #else
      vec4 textureGradient(sampler2D envMap, vec3 rayDirection, vec3 directionCamPerfect) {
        vec2 uvv = equirectUv( rayDirection );
        vec2 smoothUv = equirectUv( directionCamPerfect );
        return textureGrad(envMap, uvv, dFdx(correctMips ? smoothUv : uvv), dFdy(correctMips ? smoothUv : uvv));
      }
    #endif
    
    void main() {
      vec2 uv = gl_FragCoord.xy / resolution;
      vec3 directionCamPerfect = (projectionMatrixInverse * vec4(uv * 2.0 - 1.0, 0.0, 1.0)).xyz;
      directionCamPerfect = (viewMatrixInverse * vec4(directionCamPerfect, 0.0)).xyz;
      directionCamPerfect = normalize(directionCamPerfect);
      vec3 normal = vNormal;
      vec3 rayOrigin = cameraPosition;
      vec3 rayDirection = normalize(vWorldPosition - cameraPosition);
      vec3 finalColor;
      #ifdef CHROMATIC_ABERRATIONS
        vec3 rayDirectionG = totalInternalReflection(rayOrigin, rayDirection, normal, max(ior, 1.0), vModelMatrixInverse);
        #ifdef FAST_CHROMA 
          vec3 rayDirectionR = normalize(rayDirectionG + 1.0 * vec3(aberrationStrength / 2.0));
          vec3 rayDirectionB = normalize(rayDirectionG - 1.0 * vec3(aberrationStrength / 2.0));
        #else
          vec3 rayDirectionR = totalInternalReflection(rayOrigin, rayDirection, normal, max(ior * (1.0 - aberrationStrength), 1.0), vModelMatrixInverse);
          vec3 rayDirectionB = totalInternalReflection(rayOrigin, rayDirection, normal, max(ior * (1.0 + aberrationStrength), 1.0), vModelMatrixInverse);
        #endif
        float finalColorR = textureGradient(envMap, rayDirectionR, directionCamPerfect).r;
        float finalColorG = textureGradient(envMap, rayDirectionG, directionCamPerfect).g;
        float finalColorB = textureGradient(envMap, rayDirectionB, directionCamPerfect).b;
        finalColor = vec3(finalColorR, finalColorG, finalColorB);
      #else
        rayDirection = totalInternalReflection(rayOrigin, rayDirection, normal, max(ior, 1.0), vModelMatrixInverse);
        finalColor = textureGradient(envMap, rayDirection, directionCamPerfect).rgb;    
      #endif
  
      finalColor *= color;
      #ifdef USE_INSTANCING_COLOR
        finalColor *= vInstanceColor;
      #endif
  
      vec3 viewDirection = normalize(vWorldPosition - cameraPosition);
      float nFresnel = fresnelFunc(viewDirection, normal) * fresnel;
      gl_FragColor = vec4(mix(finalColor, vec3(1.0), nFresnel), 1.0);      
      #include <tonemapping_fragment>
      #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
    }`,
    // transparent: true,
  });
  return material;
}

Errors are the bellow.

THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false

Program Info Log: Fragment shader is not compiled.


FRAGMENT

ERROR: 0:554: 'CUBEUV_MAX_MIP' : undeclared identifier
ERROR: 0:555: 'CUBEUV_TEXEL_WIDTH' : undeclared identifier
ERROR: 0:556: 'CUBEUV_TEXEL_HEIGHT' : undeclared identifier
ERROR: 0:593: 'CUBEUV_MAX_MIP' : undeclared identifier


  549: 			uv.y += faceSize;
  550: 			face -= 3.0;
  551: 		}
  552: 		uv.x += face * faceSize;
  553: 		uv.x += filterInt * 3.0 * cubeUV_minTileSize;
> 554: 		uv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );
  555: 		uv.x *= CUBEUV_TEXEL_WIDTH;
  556: 		uv.y *= CUBEUV_TEXEL_HEIGHT;
  557: 		#ifdef texture2DGradEXT
  558: 			return texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;
  559: 		#else
  560: 			return texture2D( envMap, uv ).rgb;

As per my observation the ‘shaderIntersectFunction’ not available in three-mesh-bvh. Even also used in @react-three/drei. I am unable to find how it’s working in @react-three/drei.

Yes, as per this explanation - unfortunately it may not be super trivial to copy that material out of drei, as it’s not encapsulated within a single file and relies on the react-three ecosystem (which could also be a good motivation to port it to vanilla-drei, if you have some spare time to do it - removing react and external dependencies to make it self-sustainable.)

I’d personally just try to understand the logic of the shader and write it as a vanilla ShaderMaterial, copy-pasting it may in fact be the more complex way of recreating the material in vanilla.

You are missing glue code drei/src/core/MeshRefractionMaterial.tsx at 639fca1f16f74b16714097ea66f2fcb7b1e02604 · pmndrs/drei · GitHub

An envmap can be a samplercube or sampler2d in glsl and the shader material needs different defines for each. It also needs access to the renderloop to update the view matrices.

As for vanilla-drei the problem is not so much that it’s not a single file, vanilla has no way to make something re-usable, and a class can’t either. This is one of the more complex cases, that shader needs more bindings than a regular one, we didn’t yet find a good enough class abstraction. There is a vanilla project GitHub - N8python/diamonds but you will struggle harder to port that than the drei component.

You can use three with react. It’s still just threejs, but you get the ability to have self contained components, and an eco system.

:man_facepalming:

GitHub - N8python/diamonds is the vanilla version of the diamond shader.

i am not saying it can’t exist in vanilla. i am saying that a class doesn’t have the means to make something self-contained, therefore complicated things are shared as apps, like the repo above.

joe armstrong, creator of erlang progamming language, wrote about re-usability and oop in the 80s.

The problem with OOP is the implicit environment it carries around. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

You do realize that you’re comparing core language features to a library or framework right? Please stop comparing apples to oranges.

We all know you’re a react enthousiast, but bashing the core language and steer people away from actually learning the language and the paradigms that come with it is very demeaning and demotivating for everyone here. It has been said countless times now, specially directed to you, yet still spout this meaningless and wrong statements. Just stop already.

what core. it’s three + react, the same threejs, with optional sharing abilities.

i worked on that diamond shader together with n8. i gave @pallab_kole all the hints he needs to make it work in vanilla and explained what’s missing. i responded to @mjurczyk describing why it won’t be easy to port or why n8 vanilla diamonds has never been made into a vanilla-drei re-usable, which btw i started and help maintain.

not sure what even prompted you, you certainly didn’t contribute or help him get it working.