Skip to content

Commit

Permalink
update site with shader
Browse files Browse the repository at this point in the history
  • Loading branch information
johnchourajr committed Jan 13, 2025
1 parent b8513c0 commit 0c02ac8
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 59 deletions.
67 changes: 66 additions & 1 deletion example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
"next": "14.2.3",
"react": "^18",
"react-dom": "^18",
"react-resizable": "^3.0.5"
"react-resizable": "^3.0.5",
"three": "^0.172.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-resizable": "^3.0.7",
"@types/three": "^0.172.0",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8",
Expand Down
155 changes: 155 additions & 0 deletions example/src/components/GenThreeShader/GenThreeShader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"use client";
import clsx from "clsx";
import { useEffect, useMemo, useRef } from "react";
import * as THREE from "three";

/**
* Configuration object for defining shader properties
* @type ShaderConfig
*/
type ShaderConfig = {
/** Optional vertex shader code. If not provided, a default vertex shader will be used */
vertexShader?: string;
/** Required fragment shader code */
fragmentShader: string;
/** Optional additional uniforms to pass to the shader */
uniforms?: Record<string, THREE.IUniform>;
};

/**
* Props for the GenThreeShader component
* @type GenThreeShaderProps
*/
type GenThreeShaderProps = {
/** Optional CSS class name to apply to the container */
className?: string;
/** Shader configuration object */
shaderConfig: ShaderConfig;
};

const defaultVertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

export const GenThreeShader = ({
className,
shaderConfig,
}: GenThreeShaderProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const sceneRef = useRef<THREE.Scene | null>(null);
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const materialRef = useRef<THREE.ShaderMaterial | null>(null);
const sizeRef = useRef<{ width: number; height: number }>({
width: 0,
height: 0,
});

const baseUniforms = useMemo(
() => ({
resolution: { value: new THREE.Vector2() },
mouse: { value: new THREE.Vector2() },
time: { value: 0 },
noiseScale: { value: 3.0 },
colorSpeed: { value: 1.0 },
kaleidoSegments: { value: 6.0 },
patternScale: { value: 1.0 },
colorIntensity: { value: 0.7 },
motionSpeed: { value: 1.0 },
warpIntensity: { value: 0.5 },
}),
[],
);

useEffect(() => {
if (!containerRef.current) return;

const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const container = containerRef.current;
sceneRef.current = scene;

// Initialize renderer
const renderer = new THREE.WebGLRenderer({ alpha: true });
rendererRef.current = renderer;
container.appendChild(renderer.domElement);

// Setup geometry and material
const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({
uniforms: {
...baseUniforms,
...shaderConfig.uniforms,
},
vertexShader: shaderConfig.vertexShader || defaultVertexShader,
fragmentShader: shaderConfig.fragmentShader,
});

materialRef.current = material;
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// Sizing with ResizeObserver
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries[0];
if (entry) {
sizeRef.current = {
width: entry.contentRect.width,
height: entry.contentRect.height,
};
renderer.setSize(sizeRef.current.width, sizeRef.current.height);
material.uniforms.resolution.value.set(
sizeRef.current.width,
sizeRef.current.height,
);
renderer.render(scene, camera);
}
});

resizeObserver.observe(container);

// Mouse handling
const handleMouseMove = (event: MouseEvent) => {
const bounds = container.getBoundingClientRect();
mouseRef.current = {
x: event.clientX - bounds.left,
y: event.clientY - bounds.top,
};
};

// Animation frame management
let animationFrameId: number;
const startTime = performance.now();

const animate = () => {
material.uniforms.time.value = (performance.now() - startTime) * 0.001;
material.uniforms.mouse.value.set(mouseRef.current.x, mouseRef.current.y);
renderer.render(scene, camera);
animationFrameId = requestAnimationFrame(animate);
};

// Start animation
animate();

return () => {
resizeObserver.disconnect();
cancelAnimationFrame(animationFrameId);
renderer.dispose();
scene.clear();
container.removeChild(renderer.domElement);
container.removeEventListener("mousemove", handleMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- Remove onDownload from deps
}, [shaderConfig, baseUniforms]);

return (
<div
ref={containerRef}
className={clsx("relative w-full h-full", className)}
/>
);
};
70 changes: 70 additions & 0 deletions example/src/components/GenThreeShader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# GenThreeShader Component

A React component that renders customizable WebGL shaders using Three.js.

## Usage

```tsx
import { GenThreeShader } from './GenThreeShader';
import { kaleidoscopeShader } from './shaders/kaleidoscopeShader';

const MyComponent = () => {
const shaderConfig = {
fragmentShader: kaleidoscopeShader,
uniforms: {
patternScale: { value: 1.0 },
kaleidoSegments: { value: 6.0 },
colorIntensity: { value: 0.7 },
},
};

return (
<GenThreeShader
shaderConfig={shaderConfig}
className="w-full h-full"
/>
);
};
```

## Props

- `className?: string` - Optional CSS class name for the container
- `shaderConfig: ShaderConfig` - Configuration object for the shader
- `onDownload: (ref: () => void) => void` - Callback to receive the download function

### ShaderConfig Interface

```typescript
type ShaderConfig = {
vertexShader?: string; // Optional custom vertex shader
fragmentShader: string; // Required fragment shader code
uniforms?: Record<string, THREE.IUniform>; // Optional uniforms
};
```

## Base Uniforms

The component automatically provides these uniforms to all shaders:

- `resolution` - Screen resolution (vec2)
- `mouse` - Mouse position (vec2)
- `time` - Elapsed time (float)
- `noiseScale` - Base noise scale (float)
- `colorSpeed` - Color animation speed (float)

## Example with Custom Uniforms

```tsx
const customConfig = {
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: myCustomShader,
uniforms: {
customValue: { value: 1.0 },
customColor: { value: new THREE.Color(1, 0, 0) },
},
};
2 changes: 2 additions & 0 deletions example/src/components/GenThreeShader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GenThreeShader } from "./GenThreeShader";
export { genShaders } from "./shaders";
8 changes: 8 additions & 0 deletions example/src/components/GenThreeShader/shaders/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ShaderVariant } from "./types";
import { wavesShader } from "./wavesShader";

export const genShaders: Record<ShaderVariant, string> = {
wavesShader,
};

export type { ShaderVariant } from "./types";
1 change: 1 addition & 0 deletions example/src/components/GenThreeShader/shaders/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ShaderVariant = "wavesShader";
Loading

0 comments on commit 0c02ac8

Please sign in to comment.