Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/carelesscourage/moonbow

Vue component for adding GLSL to images. Example site: https://moonbow.netlify.app/
https://github.com/carelesscourage/moonbow

glsl shaders three threejs typescript vue vue3 webgl

Last synced: 2 days ago
JSON representation

Vue component for adding GLSL to images. Example site: https://moonbow.netlify.app/

Awesome Lists containing this project

README

        

![chrome-capture-2023-1-5 (1)](https://user-images.githubusercontent.com/12764398/216837975-c1074633-c5be-41f4-b2ca-2c52806eff94.gif)

# Moonbow :new_moon_with_face:+:rainbow:
Vue img component for adding [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to images :fire::fire::fire:

Test it out yourself. Head over to [Moonbow](https://moonbow.netlify.app/) to get an idea of whats possible and feel out the performance.
Or jump right into a code example with this [StackBlitz](https://stackblitz.com/edit/vitejs-vite-334uch?file=src%2FApp.vue,src%2Fstyle.css).

## :alembic: How it works
> Moonbow leverages [three.js](https://github.com/mrdoob/three.js/) to create a 3D space in webGL. It creates a 3 dimensional plane for each image and sticks it to the size and position of the proxy HTML img element. It re-attatches this plane to the img element on every animation frame to keep it consistent with the layout. But it saves on performance by only attaching the planes that are inside the viewport. It does this by using an intersection observer to check for images in view.

- :kissing_cat: ***Simple*** - Just a simple image component needed. Nothing more
- :muscle: ***Flexible*** - Flexible primitives underneath that let you build your own logic
- :telescope: ***Typesafe*** - Written fully in typescript
- :hammer_and_wrench: ***Maintainable*** - HTML stays descriptive of content letting canvas images flows with the HTML elements
- :man_in_manual_wheelchair: ***Accessible*** - Since canvas elements have their HTML counterparts you dont lose accessibility controls like other approaches would

#### :test_tube: Benefits of this approach
This apprach lets you take advantage of GLSL for your images while keeping the DOM descriptive of your content. Images get created in webGL with a HTML proxy element in the DOM taking up space and flowing with your layout. This is also great for accessibility since it means we can accomplish complex image manipulation without sacrificing on the browser inbuilt accessibility tools.

## :scroll: Resources
Learn how to write GLSL: [Book of Shaders](https://thebookofshaders.com/)

## :package: Installation
```bash
npm install moonbow
```

## :building_construction: Setup
You can inject GLSL as a string but if you want to actually use it you're going to want to store GLSL in files that you can import. In order to do that you need to let Vite know how to handle GLSL files though. Import the [vite-plugin-glsl](https://github.com/UstymUkhman/vite-plugin-glsl) package and register it as a plugin in your vite.config file.

```js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import glsl from 'vite-plugin-glsl'

export default defineConfig({
plugins: [vue(), glsl()],
})
```

## :crystal_ball: Usage
Simple example using default GLSL. Here's a [StackBlitz](https://stackblitz.com/edit/vitejs-vite-334uch?file=src%2FApp.vue,src%2Fstyle.css) if you just want to jump right into it as fast as possible.
```vue

import { Moon } from "moonbow"
import "moonbow/dist/style.css"
const imageURL = "https://images.unsplash.com/photo-1642059893618"

```

Adding your own custom GLSL
```vue

import { Moon } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'

```

Adding custom uniforms and changing uniforms dynamically from JavaScript
```vue

import { watch } from 'vue'
import { Moon, useScroll } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'

let uniforms = {
uVelocity: { value: 0 },
}

const velocity = useScroll()
const uniformControls = {
vertexShader,
fragmentShader,
uniforms,
uniformAction: (material) => {
watch(velocity, (velocity) => {
material.uniforms.uVelocity.value = velocity
})
}
}

```

Most of the time you will probably want more than one image. And these multiple images probably will use the same effect, sometimes maybe even in different files. Setting the same shader object over and over can be cumbersome. So I've provided a function for setting the default shader for all images. Just set this once and never worry about setting it again unless you want to overwrite the defaults on a specific image.
```vue

import { watch } from 'vue'
import { Moon, useScroll, defaultGLSL } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'

let uniforms = {
uVelocity: { value: 0 },
}

const scroll = useScroll()
function uniformAction(material: any) {
watch(scroll, (s) => {
material.uniforms.uVelocity.value = s
})
}

defaultGLSL({
uniforms,
vertexShader,
fragmentShader,
uniformAction
})





```

## :dna: Primitives
Moonbow exposes the underlying primitives developed to make the Moon image component. You can easily use these primitives to build your own solution. Below is the entire code for the Moon component showcasing the way it uses the primities.
```vue

import { ref, onMounted } from "vue"
import { useShader, scene, useImage, syncProxyHTML } from "moonbow";

const props = defineProps({
src: string;
vertexShader?: string;
fragmentShader?: string;
uniforms?: any;
uniformAction?: (material: ShaderType) => void;
})

const imageRef = ref(null)

onMounted(() => {
const src = props.src;
const element = imageRef.value
const scene = sceneRef.value
const shader: MoonbowShader = {
vertexShader: props.vertexShader,
fragmentShader: props.fragmentShader,
uniforms: props.uniforms,
uniformAction: props.uniformAction
}

const material = useShader(element, shader)
const proxy = useImage({scene, element, material})
syncProxyHTML({ proxy, src })
})



```

Postprocessing function to let you apply GLSL to all elements uniformly. Example with plain GLSL without dynamic uniforms
```vue

import { postProcessing } from '../composables/canvas'

import vertexShader from '../shaders/bottomScale/vertex.glsl';
import fragmentShader from '../shaders/bottomScale/fragment.glsl';

const uniforms = {
uExample: { value: 0 },
}

postProcessing({
uniforms,
vertexShader,
fragmentShader,
})

```

Postprocessing function with uniformAction/dynamic uniforms example.
```vue

import { postProcessing } from '../composables/canvas'

import vertexShader from '../shaders/bottomScale/vertex.glsl';
import fragmentShader from '../shaders/bottomScale/fragment.glsl';

const uniforms = {
uExample: { value: 0 },
}

const shader = {
uniforms,
vertexShader,
fragmentShader,
uniformAction: (m) => {
watch(velocity, (velocity) => {
m.uniforms.uVelocity.value = velocity
})
}
}

postProcessing(shader)

```