5.7.2

Refractor

Create real-time refractive (glass-like) surfaces in your scene.

The cientos package provides an abstraction of the , which creates a Mesh that renders what is behind it with a refractive distortion effect — useful for glass panels, water surfaces, and other transmissive materials. This Mesh extends from Mesh so all the default props can be passed as well.

Usage

<script setup lang="ts">
import { Refractor } from '@tresjs/cientos'
</script>

<template>
  <TresCanvas shadows alpha>
    <TresPerspectiveCamera :position="[0, 2, 8]" />
    <OrbitControls />

    <!-- Place objects behind the refracting plane -->
    <TresMesh :position="[-2, 0, -2]">
      <TresTorusKnotGeometry />
      <TresMeshStandardMaterial color="hotpink" />
    </TresMesh>

    <Suspense>
      <Refractor color="#9ec8d4">
        <TresPlaneGeometry :args="[8, 5]" />
      </Refractor>
    </Suspense>
  </TresCanvas>
</template>

Props

PropDescriptionDefault
colorColor tint blended with the refracted image'#7f7f7f'
textureWidthWidth of the internal render target texture512
textureHeightHeight of the internal render target texture512
clipBiasClip bias for the virtual camera projection0
multisampleNumber of MSAA samples for the render target4
shaderCustom shader object to override the built-in shaderRefractor.RefractorShader
All the props except color are not reactive

Custom refraction effect

You can provide your own shader by passing a full shader object with uniforms, vertexShader, and fragmentShader. The example below adds animated circular ripples emanating from the center of the surface, while preserving the color tint:

<script setup lang="ts">
import { Refractor } from '@tresjs/cientos'
import { useLoop } from '@tresjs/core'
import { shallowRef } from 'vue'

const refractorRef = shallowRef()

const customShader = {
  uniforms: {
    color: { value: null },
    tDiffuse: { value: null },
    textureMatrix: { value: null },
    time: { value: 0 },
  },
  vertexShader: /* glsl */`
    uniform mat4 textureMatrix;
    varying vec4 vUv;
    varying vec2 vPos;
    void main() {
      vUv = textureMatrix * vec4(position, 1.0);
      vPos = position.xy;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }`,
  fragmentShader: /* glsl */`
    uniform vec3 color;
    uniform sampler2D tDiffuse;
    uniform float time;
    varying vec4 vUv;
    varying vec2 vPos;
    float blendOverlay(float base, float blend) {
      return(base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)));
    }
    vec3 blendOverlay(vec3 base, vec3 blend) {
      return vec3(blendOverlay(base.r, blend.r), blendOverlay(base.g, blend.g), blendOverlay(base.b, blend.b));
    }
    void main() {
      float dist = length(vPos);
      float wave = sin(dist * 6.0 - time * 4.0) * 0.02;
      vec2 dir = dist > 0.001 ? normalize(vPos) : vec2(0.0);
      vec4 distortedUv = vUv + vec4(dir * wave, 0.0, 0.0);
      vec4 base = texture2DProj(tDiffuse, distortedUv);
      gl_FragColor = vec4(blendOverlay(base.rgb, color), 1.0);
      #include <tonemapping_fragment>
      #include <colorspace_fragment>
    }`,
}

const { onBeforeRender } = useLoop()
onBeforeRender(({ elapsed }) => {
  const uniforms = refractorRef.value?.instance?.material?.uniforms
  if (uniforms?.time) { uniforms.time.value = elapsed }
})
</script>

<template>
  <Refractor
    ref="refractorRef"
    color="#9ec8d4"
    :shader="customShader"
  >
    <TresPlaneGeometry :args="[8, 5]" />
  </Refractor>
</template>

The Refractor shader uses the following configuration by default:

Default shader

const shader = {
  uniforms: {
    color: {
      value: null
    },
    tDiffuse: {
      value: null
    },
    textureMatrix: {
      value: null
    }
  },
  vertexShader: /* glsl */`
    uniform mat4 textureMatrix;
    varying vec4 vUv;

    void main() {
      vUv = textureMatrix * vec4( position, 1.0 );
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }`,
  fragmentShader: /* glsl */`
    uniform vec3 color;
    uniform sampler2D tDiffuse;
    varying vec4 vUv;

    float blendOverlay( float base, float blend ) {
      return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );
    }

    vec3 blendOverlay( vec3 base, vec3 blend ) {
      return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );
    }

    void main() {
      vec4 base = texture2DProj( tDiffuse, vUv );
      gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );

      #include <tonemapping_fragment>
      #include <colorspace_fragment>
    }`
}