Adding a shaderKit to an object in my project

I cant seem to add the holographic shader to an asset in my project. Im not that hot on this so I don’t know if its even possible.

Hi Tommy!

I was curious so I went ahead and tried this myself. Here’s the result of creating a Hologram shader and applying it to my 3D model.

import * as ecs from '@8thwall/ecs'

ecs.registerComponent({
  name: 'Hologram Shader',
  schema: {},
  schemaDefaults: {},
  data: {},
  stateMachine: ({world, eid}) => {
    const {THREE} = window as any
    let material = null

    ecs.defineState('default')
      .initial()
      .listen(eid, ecs.events.GLTF_MODEL_LOADED, () => {
        ecs.Material.remove(world, eid)
        const object = world.three.entityToObject.get(eid)

        material = new THREE.ShaderMaterial({
          uniforms: {
            time: {value: 0},
            baseColor: {value: new THREE.Color(0x3ec3ff)},  // hologram tint
            opacity: {value: 0.8},                          // overall opacity
            scanFreq: {value: 160.0},                       // scanline frequency
            scanSpeed: {value: 1.8},                        // scanline speed
            glitchIntensity: {value: 0.008},                // UV wobble
          },
          transparent: true,
          depthWrite: false,
          blending: THREE.AdditiveBlending,
          side: THREE.DoubleSide,
          vertexShader: `
            varying vec2 vUv;
            varying vec3 vWorldPos;
            varying vec3 vWorldNormal;
            varying vec3 vViewDir;

            void main() {
              vUv = uv;
              vec4 worldPos = modelMatrix * vec4(position, 1.0);
              vWorldPos = worldPos.xyz;

              // world-space normal
              vWorldNormal = normalize(mat3(modelMatrix) * normal);

              // view direction in world space (cameraPosition is provided by three.js)
              vViewDir = normalize(cameraPosition - vWorldPos);

              gl_Position = projectionMatrix * viewMatrix * worldPos;
            }
          `,
          fragmentShader: `
            uniform float time;
            uniform vec3  baseColor;
            uniform float opacity;
            uniform float scanFreq;
            uniform float scanSpeed;
            uniform float glitchIntensity;

            varying vec2 vUv;
            varying vec3 vWorldPos;
            varying vec3 vWorldNormal;
            varying vec3 vViewDir;

            // hash-based noise
            float hash(vec2 p) {
              p = vec2(dot(p, vec2(127.1, 311.7)),
                       dot(p, vec2(269.5, 183.3)));
              return fract(sin(p.x + p.y) * 43758.5453123);
            }

            // small pseudo-random flicker
            float flicker(float t) {
              return 0.5 + 0.5 * sin(t * 6.2831 * 0.83 + sin(t * 2.1));
            }

            void main() {
              // Fresnel rim glow
              float fres = pow(1.0 - max(dot(normalize(vWorldNormal), normalize(vViewDir)), 0.0), 2.0);

              // Horizontal scanlines that move upward
              float scan = sin(vWorldPos.y * scanFreq + time * 6.2831 * scanSpeed) * 0.5 + 0.5;
              scan = smoothstep(0.45, 0.55, scan); // thin bright lines

              // Subtle UV glitch/wobble driven by world pos & time
              float n1 = hash(vWorldPos.xz * 3.7 + time);
              float n2 = hash(vWorldPos.zy * 4.1 - time * 0.7);
              vec2 uv = vUv;
              uv.x += (n1 - 0.5) * glitchIntensity;
              uv.y += (n2 - 0.5) * glitchIntensity * 0.6;

              // Channel offset shimmer
              float rShift = (hash(uv * 120.0 + time * 0.8) - 0.5) * 0.01;
              float gShift = (hash(uv * 140.0 - time * 0.6) - 0.5) * 0.01;
              float bShift = (hash(uv * 160.0 + time * 0.9) - 0.5) * 0.01;

              // Base emission color with slight per-channel offsets
              vec3 col = vec3(
                baseColor.r * (0.65 + 0.35 * fres) + rShift,
                baseColor.g * (0.65 + 0.35 * fres) + gShift,
                baseColor.b * (0.85 + 0.55 * fres) + bShift
              );

              // Layer in scanlines & flicker
              float glow = 0.25 + 1.25 * fres + 0.75 * scan;
              glow *= 0.85 + 0.15 * flicker(time + vWorldPos.y * 0.1);

              vec3 finalColor = col * glow;

              // Alpha shaped by fresnel + scanlines, then clipped for a crisp hologram edge
              float a = opacity * clamp(0.15 + 0.85 * (0.6 * fres + 0.4 * scan), 0.0, 1.0);

              // subtle dithering to reduce banding (screen-door style)
              float dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);
              a -= (dither - 0.5) * 0.03;

              if (a < 0.03) discard;

              gl_FragColor = vec4(finalColor, a);
            }
          `,
        })

        object.traverse((node: any) => {
          if (node.isMesh) {
            node.material = material
          }
        })
      })
      .onTick(() => {
        if (material) {
          material.uniforms.time.value = world.time.elapsed * 0.001
        }
      })
  },
})

Woah you did it?! This is exactly what I want to do, and you did it that easily. Hats off!
So I create a new component and then copy this in, then add the component to the object but it doesn’t seem to work? Am I doing this wrong?

Are you putting the component on the model itself? I can take a look if you land your changes.

Yeah so I created a component and named it Hologram-Shader then pasted this
// This is a component file. You can use this file to define a custom component for your project.

// This component will appear as a custom component in the editor.

import * as ecs from ‘@8thwall/ecs’ // This is how you access the ecs library.

ecs.registerComponent({

name: ‘Hologram-Shader’,

schema: {},

schemaDefaults: {},

data: {},

stateMachine: ({world, eid}) => {

const {THREE} = window as any

let material = null

ecs.defineState(‘default’)

.initial()

.listen(eid, ecs.events.GLTF_MODEL_LOADED, () => {

ecs.Material.remove(world, eid)

const object = world.three.entityToObject.get(eid)

material = new THREE.ShaderMaterial({

      uniforms: {

        time: {value: 0},

        baseColor: {value: new THREE.Color(0x3ec3ff)},  *// hologram tint*

        opacity: {value: 0.8},                          *// overall opacity*

        scanFreq: {value: 160.0},                       *// scanline frequency*

        scanSpeed: {value: 1.8},                        *// scanline speed*

        glitchIntensity: {value: 0.008},                *// UV wobble*

      },

      transparent: true,

      depthWrite: false,

      blending: THREE.AdditiveBlending,

      side: THREE.DoubleSide,

      vertexShader: \`

        varying vec2 vUv;

        varying vec3 vWorldPos;

        varying vec3 vWorldNormal;

        varying vec3 vViewDir;



        void main() {

          vUv = uv;

          vec4 worldPos = modelMatrix \* vec4(position, 1.0);

          vWorldPos = worldPos.xyz;



          // world-space normal

          vWorldNormal = normalize(mat3(modelMatrix) \* normal);



          // view direction in world space (cameraPosition is provided by three.js)

          vViewDir = normalize(cameraPosition - vWorldPos);



          gl_Position = projectionMatrix \* viewMatrix \* worldPos;

        }

      \`,

      fragmentShader: \`

        uniform float time;

        uniform vec3  baseColor;

        uniform float opacity;

        uniform float scanFreq;

        uniform float scanSpeed;

        uniform float glitchIntensity;



        varying vec2 vUv;

        varying vec3 vWorldPos;

        varying vec3 vWorldNormal;

        varying vec3 vViewDir;



        // hash-based noise

        float hash(vec2 p) {

          p = vec2(dot(p, vec2(127.1, 311.7)),

                   dot(p, vec2(269.5, 183.3)));

          return fract(sin(p.x + p.y) \* 43758.5453123);

        }



        // small pseudo-random flicker

        float flicker(float t) {

          return 0.5 + 0.5 \* sin(t \* 6.2831 \* 0.83 + sin(t \* 2.1));

        }



        void main() {

          // Fresnel rim glow

          float fres = pow(1.0 - max(dot(normalize(vWorldNormal), normalize(vViewDir)), 0.0), 2.0);



          // Horizontal scanlines that move upward

          float scan = sin(vWorldPos.y \* scanFreq + time \* 6.2831 \* scanSpeed) \* 0.5 + 0.5;

          scan = smoothstep(0.45, 0.55, scan); // thin bright lines



          // Subtle UV glitch/wobble driven by world pos & time

          float n1 = hash(vWorldPos.xz \* 3.7 + time);

          float n2 = hash(vWorldPos.zy \* 4.1 - time \* 0.7);

          vec2 uv = vUv;

          uv.x += (n1 - 0.5) \* glitchIntensity;

          uv.y += (n2 - 0.5) \* glitchIntensity \* 0.6;



          // Channel offset shimmer

          float rShift = (hash(uv \* 120.0 + time \* 0.8) - 0.5) \* 0.01;

          float gShift = (hash(uv \* 140.0 - time \* 0.6) - 0.5) \* 0.01;

          float bShift = (hash(uv \* 160.0 + time \* 0.9) - 0.5) \* 0.01;



          // Base emission color with slight per-channel offsets

          vec3 col = vec3(

            baseColor.r \* (0.65 + 0.35 \* fres) + rShift,

            baseColor.g \* (0.65 + 0.35 \* fres) + gShift,

            baseColor.b \* (0.85 + 0.55 \* fres) + bShift

          );



          // Layer in scanlines & flicker

          float glow = 0.25 + 1.25 \* fres + 0.75 \* scan;

          glow \*= 0.85 + 0.15 \* flicker(time + vWorldPos.y \* 0.1);



          vec3 finalColor = col \* glow;



          // Alpha shaped by fresnel + scanlines, then clipped for a crisp hologram edge

          float a = opacity \* clamp(0.15 + 0.85 \* (0.6 \* fres + 0.4 \* scan), 0.0, 1.0);



          // subtle dithering to reduce banding (screen-door style)

          float dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) \* 43758.5453);

          a -= (dither - 0.5) \* 0.03;



          if (a < 0.03) discard;



          gl_FragColor = vec4(finalColor, a);

        }

      \`,

    })

object.traverse((node: any) => {

if (node.isMesh) {

node.material = material

      }

    })

  })

.onTick(() => {

if (material) {

material.uniforms.time.value = world.time.elapsed * 0.001

    }

  })

},

})

Then I added the component to the object

// This is a component file. You can use this file to define a custom component for your project.

// This component will appear as a custom component in the editor.

import * as ecs from ‘@8thwall/ecs’ // This is how you access the ecs library.

ecs.registerComponent({

name: ‘Hologram-Shader’,

schema: {},

schemaDefaults: {},

data: {},

stateMachine: ({world, eid}) => {

const {THREE} = window as any

let material = null

ecs.defineState(‘default’)

.initial()

.listen(eid, ecs.events.GLTF_MODEL_LOADED, () => {

ecs.Material.remove(world, eid)

const object = world.three.entityToObject.get(eid)

material = new THREE.ShaderMaterial({

      uniforms: {

        time: {value: 0},

        baseColor: {value: new THREE.Color(0x3ec3ff)},  *// hologram tint*

        opacity: {value: 0.8},                          *// overall opacity*

        scanFreq: {value: 160.0},                       *// scanline frequency*

        scanSpeed: {value: 1.8},                        *// scanline speed*

        glitchIntensity: {value: 0.008},                *// UV wobble*

      },

      transparent: true,

      depthWrite: false,

      blending: THREE.AdditiveBlending,

      side: THREE.DoubleSide,

      vertexShader: \`

        varying vec2 vUv;

        varying vec3 vWorldPos;

        varying vec3 vWorldNormal;

        varying vec3 vViewDir;



        void main() {

          vUv = uv;

          vec4 worldPos = modelMatrix \* vec4(position, 1.0);

          vWorldPos = worldPos.xyz;



          // world-space normal

          vWorldNormal = normalize(mat3(modelMatrix) \* normal);



          // view direction in world space (cameraPosition is provided by three.js)

          vViewDir = normalize(cameraPosition - vWorldPos);



          gl_Position = projectionMatrix \* viewMatrix \* worldPos;

        }

      \`,

      fragmentShader: \`

        uniform float time;

        uniform vec3  baseColor;

        uniform float opacity;

        uniform float scanFreq;

        uniform float scanSpeed;

        uniform float glitchIntensity;



        varying vec2 vUv;

        varying vec3 vWorldPos;

        varying vec3 vWorldNormal;

        varying vec3 vViewDir;



        // hash-based noise

        float hash(vec2 p) {

          p = vec2(dot(p, vec2(127.1, 311.7)),

                   dot(p, vec2(269.5, 183.3)));

          return fract(sin(p.x + p.y) \* 43758.5453123);

        }



        // small pseudo-random flicker

        float flicker(float t) {

          return 0.5 + 0.5 \* sin(t \* 6.2831 \* 0.83 + sin(t \* 2.1));

        }



        void main() {

          // Fresnel rim glow

          float fres = pow(1.0 - max(dot(normalize(vWorldNormal), normalize(vViewDir)), 0.0), 2.0);



          // Horizontal scanlines that move upward

          float scan = sin(vWorldPos.y \* scanFreq + time \* 6.2831 \* scanSpeed) \* 0.5 + 0.5;

          scan = smoothstep(0.45, 0.55, scan); // thin bright lines



          // Subtle UV glitch/wobble driven by world pos & time

          float n1 = hash(vWorldPos.xz \* 3.7 + time);

          float n2 = hash(vWorldPos.zy \* 4.1 - time \* 0.7);

          vec2 uv = vUv;

          uv.x += (n1 - 0.5) \* glitchIntensity;

          uv.y += (n2 - 0.5) \* glitchIntensity \* 0.6;



          // Channel offset shimmer

          float rShift = (hash(uv \* 120.0 + time \* 0.8) - 0.5) \* 0.01;

          float gShift = (hash(uv \* 140.0 - time \* 0.6) - 0.5) \* 0.01;

          float bShift = (hash(uv \* 160.0 + time \* 0.9) - 0.5) \* 0.01;



          // Base emission color with slight per-channel offsets

          vec3 col = vec3(

            baseColor.r \* (0.65 + 0.35 \* fres) + rShift,

            baseColor.g \* (0.65 + 0.35 \* fres) + gShift,

            baseColor.b \* (0.85 + 0.55 \* fres) + bShift

          );



          // Layer in scanlines & flicker

          float glow = 0.25 + 1.25 \* fres + 0.75 \* scan;

          glow \*= 0.85 + 0.15 \* flicker(time + vWorldPos.y \* 0.1);



          vec3 finalColor = col \* glow;



          // Alpha shaped by fresnel + scanlines, then clipped for a crisp hologram edge

          float a = opacity \* clamp(0.15 + 0.85 \* (0.6 \* fres + 0.4 \* scan), 0.0, 1.0);



          // subtle dithering to reduce banding (screen-door style)

          float dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) \* 43758.5453);

          a -= (dither - 0.5) \* 0.03;



          if (a < 0.03) discard;



          gl_FragColor = vec4(finalColor, a);

        }

      \`,

    })

object.traverse((node: any) => {

if (node.isMesh) {

node.material = material

      }

    })

  })

.onTick(() => {

if (material) {

material.uniforms.time.value = world.time.elapsed * 0.001

    }

  })

},

})

I shouldve put it into a block quote, apologies

I have checked and can’t see any reason why it wont do anything though

Hmm, I think I have an idea. Let me try it.

Looks like the Shader didn’t work with scaled SkinnedMeshes. I’ve gone ahead and updated it and landed the change in your LIZO project. :slight_smile:

Thanks for this George, I synced but it said it failed and when I add the component it doesnt work so I doent know if something happened in the sync. Im really sorry about ahhhhh

I’ve already attached it for you, looks like it’s working correctly on my end. Are you seeing something else?

Ah, no thats just because I had change the colour and opacity before I started working on it to try and get the effect. Ill change it back…
Basically the hologram component isnt changing it, just the green tint and opacity.

Ah no, wait are we working on the same… Ill record mine wait there

So I think the issue is that I cant seem to get my simulator to work. It just wont load. I have tried to restart my browser and computer several times and I am using chrome but no joy. I am right in thinking that the shader would only become clear in the simulator once it has been found while i am testing it?

That’s correct, the shader would only become visible once the component is mounted at runtime so you wouldn’t see it in action in the viewport.