shaderz 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +208 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/package.json +51 -0
- package/shaders/CosmicNebulaShader.tsx +265 -0
- package/shaders/GlassTwistShader.tsx +169 -0
- package/shaders/GlossyRibbonShader.tsx +164 -0
- package/shaders/GradientWavesShader.tsx +277 -0
- package/shaders/LiquidOrangeShader.tsx +246 -0
- package/shaders/NeonFluidShader.tsx +259 -0
- package/shaders/OceanWavesShader.tsx +229 -0
- package/shaders/PlasmaShader.tsx +174 -0
- package/shaders/SilkFlowShader.tsx +281 -0
- package/shaders/VideoBackground.tsx +37 -0
- package/videos/glossy-film.mp4 +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Shaderz CLI
|
|
2
|
+
|
|
3
|
+
Add beautiful WebGL shaders to your React/Next.js project with a simple CLI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install shaderz
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use with npx (no installation required):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx shaderz add
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Interactive Installation
|
|
20
|
+
|
|
21
|
+
Install shaders interactively by selecting from a list:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx shaderz add
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This will:
|
|
28
|
+
1. Show a checkbox list of all available shaders
|
|
29
|
+
2. Let you select multiple shaders with Space
|
|
30
|
+
3. Install selected shaders to `components/shaders/`
|
|
31
|
+
4. Copy video files to `public/videos/` (for video shaders)
|
|
32
|
+
5. Check and remind you to install required dependencies
|
|
33
|
+
6. Show usage examples
|
|
34
|
+
|
|
35
|
+
## Available Shaders
|
|
36
|
+
|
|
37
|
+
**WebGL Shaders:**
|
|
38
|
+
- `liquid-orange` - Flowing liquid shader with warm orange tones
|
|
39
|
+
- `ocean-waves` - Dynamic ocean waves shader
|
|
40
|
+
- `neon-fluid` - Vibrant neon fluid shader
|
|
41
|
+
- `gradient-waves` - Smooth gradient waves shader
|
|
42
|
+
- `cosmic-nebula` - Space-themed nebula shader
|
|
43
|
+
- `glossy-ribbon` - Glossy ribbon flow shader
|
|
44
|
+
- `silk-flow` - Smooth silk flow shader
|
|
45
|
+
- `glass-twist` - Glass twist effect shader
|
|
46
|
+
- `plasma` - Classic plasma shader
|
|
47
|
+
|
|
48
|
+
**Video Background:**
|
|
49
|
+
- `glossy-film` - MP4 video background (copies video to public/videos/)
|
|
50
|
+
|
|
51
|
+
## Usage in Your Project
|
|
52
|
+
|
|
53
|
+
After installation:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import LiquidOrangeShader from '@/components/shaders/LiquidOrangeShader';
|
|
57
|
+
|
|
58
|
+
function App() {
|
|
59
|
+
return (
|
|
60
|
+
<div className="relative w-full h-screen">
|
|
61
|
+
<LiquidOrangeShader />
|
|
62
|
+
{/* Your content */}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Video Background
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import VideoBackground from '@/components/shaders/VideoBackground';
|
|
72
|
+
|
|
73
|
+
function App() {
|
|
74
|
+
return (
|
|
75
|
+
<div className="relative w-full h-screen">
|
|
76
|
+
<VideoBackground src="/videos/glossy-film.mp4" />
|
|
77
|
+
{/* Your content */}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
- React 18+ or 19+
|
|
86
|
+
- No additional dependencies required (WebGL is built into modern browsers)
|
|
87
|
+
- Video shader requires the video file to be in public/videos/ (automatically handled by CLI)
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT © harsh and shubham
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
var import_prompts = __toESM(require("prompts"));
|
|
29
|
+
var import_chalk = __toESM(require("chalk"));
|
|
30
|
+
var import_ora = __toESM(require("ora"));
|
|
31
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
32
|
+
var import_path = __toESM(require("path"));
|
|
33
|
+
var program = new import_commander.Command();
|
|
34
|
+
var SHADERS = [
|
|
35
|
+
{
|
|
36
|
+
name: "liquid-orange",
|
|
37
|
+
title: "Liquid Orange",
|
|
38
|
+
description: "Flowing liquid shader with warm orange tones",
|
|
39
|
+
file: "LiquidOrangeShader"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "ocean-waves",
|
|
43
|
+
title: "Ocean Waves",
|
|
44
|
+
description: "Dynamic ocean waves shader",
|
|
45
|
+
file: "OceanWavesShader"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "neon-fluid",
|
|
49
|
+
title: "Neon Fluid",
|
|
50
|
+
description: "Vibrant neon fluid shader",
|
|
51
|
+
file: "NeonFluidShader"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "gradient-waves",
|
|
55
|
+
title: "Gradient Waves",
|
|
56
|
+
description: "Smooth gradient waves shader",
|
|
57
|
+
file: "GradientWavesShader"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "cosmic-nebula",
|
|
61
|
+
title: "Cosmic Nebula",
|
|
62
|
+
description: "Space-themed nebula shader",
|
|
63
|
+
file: "CosmicNebulaShader"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "glossy-ribbon",
|
|
67
|
+
title: "Glossy Ribbon",
|
|
68
|
+
description: "Glossy ribbon flow shader",
|
|
69
|
+
file: "GlossyRibbonShader"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "silk-flow",
|
|
73
|
+
title: "Silk Flow",
|
|
74
|
+
description: "Smooth silk flow shader",
|
|
75
|
+
file: "SilkFlowShader"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "glass-twist",
|
|
79
|
+
title: "Glass Twist",
|
|
80
|
+
description: "Glass twist effect shader",
|
|
81
|
+
file: "GlassTwistShader"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "plasma",
|
|
85
|
+
title: "Plasma",
|
|
86
|
+
description: "Classic plasma shader",
|
|
87
|
+
file: "PlasmaShader"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "glossy-film",
|
|
91
|
+
title: "Glossy Film (Video)",
|
|
92
|
+
description: "MP4 video background shader",
|
|
93
|
+
file: "VideoBackground",
|
|
94
|
+
isVideo: true,
|
|
95
|
+
videoFile: "glossy-film.mp4"
|
|
96
|
+
}
|
|
97
|
+
];
|
|
98
|
+
async function addShaders() {
|
|
99
|
+
console.log(import_chalk.default.bold.cyan("\n\u2728 Welcome to Shaderz!\n"));
|
|
100
|
+
const response = await (0, import_prompts.default)({
|
|
101
|
+
type: "multiselect",
|
|
102
|
+
name: "shaders",
|
|
103
|
+
message: "Select shaders to add to your project:",
|
|
104
|
+
choices: SHADERS.map((shader) => ({
|
|
105
|
+
title: `${shader.title} - ${import_chalk.default.gray(shader.description)}`,
|
|
106
|
+
value: shader.name,
|
|
107
|
+
selected: false
|
|
108
|
+
})),
|
|
109
|
+
hint: "- Space to select. Return to submit"
|
|
110
|
+
});
|
|
111
|
+
if (!response.shaders || response.shaders.length === 0) {
|
|
112
|
+
console.log(import_chalk.default.yellow("No shaders selected. Exiting."));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
const targetDir = process.cwd();
|
|
116
|
+
const possiblePaths = [
|
|
117
|
+
"src/components",
|
|
118
|
+
"app/components",
|
|
119
|
+
"components"
|
|
120
|
+
];
|
|
121
|
+
let componentsBase = "";
|
|
122
|
+
for (const possiblePath of possiblePaths) {
|
|
123
|
+
if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, possiblePath))) {
|
|
124
|
+
componentsBase = possiblePath;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!componentsBase) {
|
|
129
|
+
if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, "src"))) {
|
|
130
|
+
componentsBase = "src/components";
|
|
131
|
+
} else if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, "app"))) {
|
|
132
|
+
componentsBase = "app/components";
|
|
133
|
+
} else {
|
|
134
|
+
componentsBase = "components";
|
|
135
|
+
}
|
|
136
|
+
console.log(import_chalk.default.yellow(`
|
|
137
|
+
No components directory found. Will create: ${componentsBase}/shaders/`));
|
|
138
|
+
const { createDir } = await (0, import_prompts.default)({
|
|
139
|
+
type: "confirm",
|
|
140
|
+
name: "createDir",
|
|
141
|
+
message: "Continue?",
|
|
142
|
+
initial: true
|
|
143
|
+
});
|
|
144
|
+
if (!createDir) {
|
|
145
|
+
console.log(import_chalk.default.red("Installation cancelled."));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const componentsDir = import_path.default.join(targetDir, componentsBase, "shaders");
|
|
150
|
+
const publicVideosDir = import_path.default.join(targetDir, "public", "videos");
|
|
151
|
+
const useTypeScript = import_fs_extra.default.existsSync(import_path.default.join(targetDir, "tsconfig.json"));
|
|
152
|
+
const fileExtension = useTypeScript ? ".tsx" : ".jsx";
|
|
153
|
+
await import_fs_extra.default.ensureDir(componentsDir);
|
|
154
|
+
const spinner = (0, import_ora.default)("Installing shaders...").start();
|
|
155
|
+
try {
|
|
156
|
+
for (const shaderName of response.shaders) {
|
|
157
|
+
const shader = SHADERS.find((s) => s.name === shaderName);
|
|
158
|
+
if (!shader) continue;
|
|
159
|
+
if (shader.isVideo && shader.videoFile) {
|
|
160
|
+
await import_fs_extra.default.ensureDir(publicVideosDir);
|
|
161
|
+
const sourceVideo = import_path.default.join(__dirname, "..", "videos", shader.videoFile);
|
|
162
|
+
const targetVideo = import_path.default.join(publicVideosDir, shader.videoFile);
|
|
163
|
+
await import_fs_extra.default.copy(sourceVideo, targetVideo);
|
|
164
|
+
spinner.succeed(`Added ${import_chalk.default.green(shader.title)} (video copied to public/videos/)`);
|
|
165
|
+
spinner.start();
|
|
166
|
+
}
|
|
167
|
+
const sourceFile = import_path.default.join(__dirname, "..", "shaders", `${shader.file}.tsx`);
|
|
168
|
+
const targetFile = import_path.default.join(componentsDir, `${shader.file}${fileExtension}`);
|
|
169
|
+
await import_fs_extra.default.copy(sourceFile, targetFile);
|
|
170
|
+
spinner.succeed(`Added ${import_chalk.default.green(shader.title)}`);
|
|
171
|
+
spinner.start();
|
|
172
|
+
}
|
|
173
|
+
spinner.stop();
|
|
174
|
+
console.log(import_chalk.default.bold.green("\n\u2705 Shaders installed successfully!\n"));
|
|
175
|
+
console.log(import_chalk.default.gray("Location:"), import_chalk.default.cyan(`${componentsBase}/shaders/
|
|
176
|
+
`));
|
|
177
|
+
console.log(import_chalk.default.bold("Usage:"));
|
|
178
|
+
response.shaders.forEach((shaderName) => {
|
|
179
|
+
const shader = SHADERS.find((s) => s.name === shaderName);
|
|
180
|
+
if (shader) {
|
|
181
|
+
console.log(import_chalk.default.gray(` import ${shader.file} from '@/${componentsBase}/shaders/${shader.file}';`));
|
|
182
|
+
if (shader.isVideo) {
|
|
183
|
+
console.log(import_chalk.default.gray(` // Video file: /videos/${shader.videoFile}`));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
console.log("");
|
|
188
|
+
const packageJsonPath = import_path.default.join(targetDir, "package.json");
|
|
189
|
+
if (import_fs_extra.default.existsSync(packageJsonPath)) {
|
|
190
|
+
const packageJson = await import_fs_extra.default.readJson(packageJsonPath);
|
|
191
|
+
const hasThree = packageJson.dependencies?.three || packageJson.devDependencies?.three;
|
|
192
|
+
if (!hasThree) {
|
|
193
|
+
console.log(import_chalk.default.yellow("\u26A0\uFE0F Required dependency not found!\n"));
|
|
194
|
+
console.log(import_chalk.default.gray("Install it with:\n"));
|
|
195
|
+
console.log(import_chalk.default.cyan(" npm install three @types/three"));
|
|
196
|
+
console.log(import_chalk.default.gray(" or"));
|
|
197
|
+
console.log(import_chalk.default.cyan(" pnpm add three @types/three\n"));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
spinner.fail("Failed to install shaders");
|
|
202
|
+
console.error(import_chalk.default.red(error));
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
program.name("shaderz").description("CLI to add beautiful WebGL shaders to your project").version("1.0.0");
|
|
207
|
+
program.command("add").description("Add shaders to your project").action(addShaders);
|
|
208
|
+
program.parse();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
var index_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(index_exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shaderz",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to add beautiful WebGL shaders to your React/Next.js project",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shaderz": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"shaders",
|
|
12
|
+
"videos"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/cli.ts src/index.ts --format cjs --dts --shims",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"shaderz",
|
|
20
|
+
"shader",
|
|
21
|
+
"webgl",
|
|
22
|
+
"cli",
|
|
23
|
+
"components",
|
|
24
|
+
"react",
|
|
25
|
+
"nextjs"
|
|
26
|
+
],
|
|
27
|
+
"author": "harsh and shubham",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^12.0.0",
|
|
31
|
+
"prompts": "^2.4.2",
|
|
32
|
+
"chalk": "^4.1.2",
|
|
33
|
+
"ora": "^5.4.1",
|
|
34
|
+
"fs-extra": "^11.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/prompts": "^2.4.9",
|
|
38
|
+
"@types/fs-extra": "^11.0.4",
|
|
39
|
+
"@types/node": "^22.10.2",
|
|
40
|
+
"tsup": "^8.0.0",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/harsh-and-shubham/shaderz"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/harsh-and-shubham/shaderz/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://shaderz.vercel.app"
|
|
51
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
const CosmicNebulaShader: React.FC = () => {
|
|
5
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
6
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
7
|
+
const animationRef = useRef<number | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const canvas = canvasRef.current;
|
|
11
|
+
const container = containerRef.current;
|
|
12
|
+
if (!canvas || !container) return;
|
|
13
|
+
|
|
14
|
+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
15
|
+
if (!gl) return;
|
|
16
|
+
|
|
17
|
+
const resizeCanvas = () => {
|
|
18
|
+
const rect = container.getBoundingClientRect();
|
|
19
|
+
canvas.width = rect.width;
|
|
20
|
+
canvas.height = rect.height;
|
|
21
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
resizeCanvas();
|
|
25
|
+
window.addEventListener('resize', resizeCanvas);
|
|
26
|
+
|
|
27
|
+
const vertexShaderSource = `
|
|
28
|
+
attribute vec2 position;
|
|
29
|
+
void main() {
|
|
30
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const fragmentShaderSource = `
|
|
35
|
+
precision highp float;
|
|
36
|
+
uniform vec2 resolution;
|
|
37
|
+
uniform float time;
|
|
38
|
+
|
|
39
|
+
// Hash for pseudo-random values
|
|
40
|
+
float hash(vec3 p) {
|
|
41
|
+
p = fract(p * 0.3183099 + 0.1);
|
|
42
|
+
p *= 17.0;
|
|
43
|
+
return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3D noise
|
|
47
|
+
float noise(vec3 x) {
|
|
48
|
+
vec3 p = floor(x);
|
|
49
|
+
vec3 f = fract(x);
|
|
50
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
51
|
+
|
|
52
|
+
return mix(
|
|
53
|
+
mix(mix(hash(p), hash(p + vec3(1,0,0)), f.x),
|
|
54
|
+
mix(hash(p + vec3(0,1,0)), hash(p + vec3(1,1,0)), f.x), f.y),
|
|
55
|
+
mix(mix(hash(p + vec3(0,0,1)), hash(p + vec3(1,0,1)), f.x),
|
|
56
|
+
mix(hash(p + vec3(0,1,1)), hash(p + vec3(1,1,1)), f.x), f.y),
|
|
57
|
+
f.z);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fractal Brownian Motion for nebula clouds
|
|
61
|
+
float fbm(vec3 p) {
|
|
62
|
+
float value = 0.0;
|
|
63
|
+
float amplitude = 0.5;
|
|
64
|
+
float frequency = 1.0;
|
|
65
|
+
|
|
66
|
+
for(int i = 0; i < 6; i++) {
|
|
67
|
+
value += amplitude * noise(p * frequency);
|
|
68
|
+
frequency *= 2.1;
|
|
69
|
+
amplitude *= 0.45;
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Domain warping for nebula distortion
|
|
75
|
+
vec3 domainWarp(vec3 p, float time) {
|
|
76
|
+
vec3 q = vec3(
|
|
77
|
+
fbm(p + vec3(0.0, 0.0, time * 0.1)),
|
|
78
|
+
fbm(p + vec3(5.2, 1.3, time * 0.15)),
|
|
79
|
+
fbm(p + vec3(1.7, 9.2, time * 0.08))
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return p + q * 0.5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Stars
|
|
86
|
+
float stars(vec2 p, float count) {
|
|
87
|
+
vec2 pos = floor(p * count);
|
|
88
|
+
float star = hash(vec3(pos, 1.0));
|
|
89
|
+
|
|
90
|
+
if(star > 0.98) {
|
|
91
|
+
vec2 center = (pos + 0.5) / count;
|
|
92
|
+
float dist = length(p - center);
|
|
93
|
+
float brightness = hash(vec3(pos, 2.0));
|
|
94
|
+
float twinkle = sin(time * 2.0 + brightness * 10.0) * 0.5 + 0.5;
|
|
95
|
+
return (1.0 - smoothstep(0.0, 0.002, dist)) * brightness * twinkle;
|
|
96
|
+
}
|
|
97
|
+
return 0.0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void main() {
|
|
101
|
+
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
|
102
|
+
vec2 p = (uv - 0.5) * 2.0;
|
|
103
|
+
p.x *= resolution.x / resolution.y;
|
|
104
|
+
|
|
105
|
+
float t = time * 0.15;
|
|
106
|
+
|
|
107
|
+
// 3D position for nebula
|
|
108
|
+
vec3 pos = vec3(p * 1.5, t);
|
|
109
|
+
|
|
110
|
+
// Domain warp for swirling nebula
|
|
111
|
+
vec3 warped = domainWarp(pos, t);
|
|
112
|
+
|
|
113
|
+
// Layered nebula clouds
|
|
114
|
+
float nebula1 = fbm(warped * 2.0);
|
|
115
|
+
float nebula2 = fbm(warped * 3.0 + vec3(2.0, 1.0, 0.0));
|
|
116
|
+
float nebula3 = fbm(warped * 4.0 - vec3(1.0, 2.0, 0.0));
|
|
117
|
+
|
|
118
|
+
// Combine nebula layers
|
|
119
|
+
float nebulaDensity = nebula1 * 0.6 + nebula2 * 0.3 + nebula3 * 0.1;
|
|
120
|
+
nebulaDensity = pow(nebulaDensity, 1.5);
|
|
121
|
+
|
|
122
|
+
// Cosmic color palette
|
|
123
|
+
vec3 deepPurple = vec3(0.15, 0.05, 0.3);
|
|
124
|
+
vec3 magenta = vec3(0.8, 0.1, 0.6);
|
|
125
|
+
vec3 pink = vec3(1.0, 0.3, 0.7);
|
|
126
|
+
vec3 cyan = vec3(0.2, 0.7, 1.0);
|
|
127
|
+
vec3 violet = vec3(0.5, 0.2, 0.9);
|
|
128
|
+
|
|
129
|
+
// Color mixing based on density and position
|
|
130
|
+
vec3 nebulaColor = mix(deepPurple, magenta, nebulaDensity);
|
|
131
|
+
nebulaColor = mix(nebulaColor, pink, nebula2 * 0.7);
|
|
132
|
+
nebulaColor = mix(nebulaColor, violet, nebula3 * 0.5);
|
|
133
|
+
|
|
134
|
+
// Add cyan highlights in dense areas
|
|
135
|
+
float highlights = smoothstep(0.6, 0.9, nebulaDensity);
|
|
136
|
+
nebulaColor = mix(nebulaColor, cyan, highlights * 0.4);
|
|
137
|
+
|
|
138
|
+
// Glow effect
|
|
139
|
+
float glow = pow(nebulaDensity, 0.8) * 1.5;
|
|
140
|
+
nebulaColor *= glow;
|
|
141
|
+
|
|
142
|
+
// Add stars
|
|
143
|
+
float starField = stars(uv, 200.0);
|
|
144
|
+
starField += stars(uv * 1.5, 300.0) * 0.7;
|
|
145
|
+
starField += stars(uv * 2.0, 400.0) * 0.5;
|
|
146
|
+
|
|
147
|
+
vec3 starColor = vec3(1.0, 0.95, 0.9) * starField;
|
|
148
|
+
|
|
149
|
+
// Combine nebula and stars
|
|
150
|
+
vec3 finalColor = nebulaColor + starColor;
|
|
151
|
+
|
|
152
|
+
// Add subtle color shift animation
|
|
153
|
+
float colorShift = sin(t * 0.5 + length(p)) * 0.5 + 0.5;
|
|
154
|
+
finalColor += vec3(0.1, 0.0, 0.15) * colorShift * nebulaDensity * 0.3;
|
|
155
|
+
|
|
156
|
+
// Center bright spot
|
|
157
|
+
float centerGlow = 1.0 - length(p * 0.5);
|
|
158
|
+
centerGlow = pow(centerGlow, 3.0) * 0.15;
|
|
159
|
+
finalColor += vec3(0.6, 0.2, 0.8) * centerGlow;
|
|
160
|
+
|
|
161
|
+
// Vignette
|
|
162
|
+
float vignette = 1.0 - length(uv - 0.5) * 0.7;
|
|
163
|
+
finalColor *= vignette;
|
|
164
|
+
|
|
165
|
+
gl_FragColor = vec4(finalColor, 1.0);
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
const createShader = (gl: WebGLRenderingContext, type: number, source: string) => {
|
|
170
|
+
const shader = gl.createShader(type);
|
|
171
|
+
if (!shader) return null;
|
|
172
|
+
|
|
173
|
+
gl.shaderSource(shader, source);
|
|
174
|
+
gl.compileShader(shader);
|
|
175
|
+
|
|
176
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
177
|
+
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
|
|
178
|
+
gl.deleteShader(shader);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return shader;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
|
|
186
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
|
|
187
|
+
|
|
188
|
+
if (!vertexShader || !fragmentShader) return;
|
|
189
|
+
|
|
190
|
+
const program = gl.createProgram();
|
|
191
|
+
if (!program) return;
|
|
192
|
+
|
|
193
|
+
gl.attachShader(program, vertexShader);
|
|
194
|
+
gl.attachShader(program, fragmentShader);
|
|
195
|
+
gl.linkProgram(program);
|
|
196
|
+
|
|
197
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
198
|
+
console.error('Program link error:', gl.getProgramInfoLog(program));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const positionBuffer = gl.createBuffer();
|
|
203
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
204
|
+
gl.bufferData(
|
|
205
|
+
gl.ARRAY_BUFFER,
|
|
206
|
+
new Float32Array([
|
|
207
|
+
-1, -1,
|
|
208
|
+
1, -1,
|
|
209
|
+
-1, 1,
|
|
210
|
+
1, 1,
|
|
211
|
+
]),
|
|
212
|
+
gl.STATIC_DRAW
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const positionLocation = gl.getAttribLocation(program, 'position');
|
|
216
|
+
const resolutionLocation = gl.getUniformLocation(program, 'resolution');
|
|
217
|
+
const timeLocation = gl.getUniformLocation(program, 'time');
|
|
218
|
+
|
|
219
|
+
let startTime = Date.now();
|
|
220
|
+
|
|
221
|
+
const render = () => {
|
|
222
|
+
if (!gl || !canvas) return;
|
|
223
|
+
|
|
224
|
+
gl.clearColor(0, 0, 0, 1);
|
|
225
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
226
|
+
|
|
227
|
+
gl.useProgram(program);
|
|
228
|
+
|
|
229
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
230
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
231
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
232
|
+
|
|
233
|
+
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
|
|
234
|
+
gl.uniform1f(timeLocation, (Date.now() - startTime) / 1000);
|
|
235
|
+
|
|
236
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
237
|
+
|
|
238
|
+
animationRef.current = requestAnimationFrame(render);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
render();
|
|
242
|
+
|
|
243
|
+
return () => {
|
|
244
|
+
window.removeEventListener('resize', resizeCanvas);
|
|
245
|
+
if (animationRef.current) {
|
|
246
|
+
cancelAnimationFrame(animationRef.current);
|
|
247
|
+
}
|
|
248
|
+
gl.deleteProgram(program);
|
|
249
|
+
gl.deleteShader(vertexShader);
|
|
250
|
+
gl.deleteShader(fragmentShader);
|
|
251
|
+
gl.deleteBuffer(positionBuffer);
|
|
252
|
+
};
|
|
253
|
+
}, []);
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<div ref={containerRef} className="absolute inset-0 w-full h-full bg-black overflow-hidden">
|
|
257
|
+
<canvas
|
|
258
|
+
ref={canvasRef}
|
|
259
|
+
className="absolute top-0 left-0 w-full h-full"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export default CosmicNebulaShader;
|