Files
AIeffect/test.html
2026-04-01 17:28:39 +08:00

449 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Shader Preview Builder</title>
<style>
:root {
color-scheme: dark;
--bg: #0c1018;
--card: #141b27;
--line: #253147;
--text: #e8edf7;
--muted: #96a6c4;
--accent: #4fa3ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font: 14px/1.4 "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background: radial-gradient(circle at 20% 10%, #1a2740, var(--bg) 55%);
}
.app {
max-width: 1240px;
margin: 0 auto;
padding: 16px;
display: grid;
gap: 12px;
}
.title {
margin: 0;
font-size: 20px;
}
.desc {
margin: 4px 0 0;
color: var(--muted);
}
.layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 12px;
}
.card {
background: color-mix(in oklab, var(--card) 88%, black 12%);
border: 1px solid var(--line);
border-radius: 12px;
overflow: hidden;
}
.preview-wrap {
position: relative;
}
#glCanvas {
width: 100%;
aspect-ratio: 16 / 9;
display: block;
background: #000;
}
.preview-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-top: 1px solid var(--line);
}
.preview-meta {
color: var(--muted);
font-size: 12px;
}
.panel {
padding: 12px;
display: grid;
gap: 10px;
align-content: start;
}
.row {
display: grid;
gap: 6px;
}
.row > label {
color: var(--muted);
font-size: 12px;
}
input,
select,
textarea,
button {
width: 100%;
border: 1px solid var(--line);
border-radius: 8px;
background: #0f1624;
color: var(--text);
padding: 8px;
font: inherit;
}
textarea {
min-height: 200px;
resize: vertical;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.two {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
button {
cursor: pointer;
background: linear-gradient(90deg, #2d69c7, #2ea4db);
border: none;
font-weight: 600;
}
button.secondary {
background: #1d2739;
border: 1px solid var(--line);
}
@media (max-width: 980px) {
.layout {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="app">
<header>
<h1 class="title">Shader 预览与交互结构</h1>
<p class="desc">仅保留预览和交互,方便快速构建。</p>
</header>
<section class="layout">
<article class="card preview-wrap">
<canvas id="glCanvas" width="1280" height="720"></canvas>
<div class="preview-bar">
<span class="preview-meta" id="fpsText">FPS: --</span>
<div class="two" style="width: 220px">
<button class="secondary" id="toggleBtn" type="button">暂停</button>
<button id="fullscreenBtn" type="button">全屏</button>
</div>
</div>
</article>
<aside class="card panel">
<div class="row">
<label for="template">模板</label>
<select id="template">
<option value="fire">Fire</option>
<option value="ripple">Ripple</option>
<option value="distort">Distort</option>
</select>
</div>
<div class="row">
<label for="speed">速度 <span id="speedVal">1.00</span></label>
<input id="speed" type="range" min="0" max="3" step="0.01" value="1" />
</div>
<div class="row">
<label for="scale">缩放 <span id="scaleVal">4.00</span></label>
<input id="scale" type="range" min="0.5" max="12" step="0.1" value="4" />
</div>
<div class="row">
<label for="intensity">强度 <span id="intensityVal">1.00</span></label>
<input id="intensity" type="range" min="0" max="2" step="0.01" value="1" />
</div>
<div class="row">
<label for="fragCode">Fragment Shader可编辑</label>
<textarea id="fragCode"></textarea>
</div>
<div class="two">
<button type="button" id="applyBtn">应用 Shader</button>
<button type="button" class="secondary" id="resetBtn">重置模板</button>
</div>
</aside>
</section>
</main>
<script>
const canvas = document.getElementById("glCanvas");
const gl = canvas.getContext("webgl");
const speed = document.getElementById("speed");
const scale = document.getElementById("scale");
const intensity = document.getElementById("intensity");
const speedVal = document.getElementById("speedVal");
const scaleVal = document.getElementById("scaleVal");
const intensityVal = document.getElementById("intensityVal");
const fragCode = document.getElementById("fragCode");
const template = document.getElementById("template");
const fpsText = document.getElementById("fpsText");
const toggleBtn = document.getElementById("toggleBtn");
if (!gl) {
alert("当前浏览器不支持 WebGL");
}
const vertexSource = `
attribute vec2 a_pos;
varying vec2 v_uv;
void main() {
v_uv = a_pos * 0.5 + 0.5;
gl_Position = vec4(a_pos, 0.0, 1.0);
}`;
const templates = {
fire: `
precision mediump float;
varying vec2 v_uv;
uniform float u_time;
uniform float u_speed;
uniform float u_scale;
uniform float u_intensity;
float hash21(vec2 p){ p = fract(p*vec2(123.34,456.21)); p += dot(p,p+78.23); return fract(p.x*p.y); }
float noise2d(vec2 p){
vec2 i=floor(p), f=fract(p);
float a=hash21(i), b=hash21(i+vec2(1.,0.)), c=hash21(i+vec2(0.,1.)), d=hash21(i+vec2(1.,1.));
vec2 u=f*f*(3.-2.*f);
return mix(a,b,u.x)+(c-a)*u.y*(1.-u.x)+(d-b)*u.x*u.y;
}
void main(){
vec2 uv=v_uv;
uv.y += u_time*u_speed*0.12;
float n=noise2d(uv*u_scale+vec2(0.,u_time*0.6));
float f=smoothstep(0.2,1.0,n+(1.0-uv.y)*0.8);
vec3 col=mix(vec3(0.85,0.1,0.02),vec3(1.0,0.75,0.08),f);
gl_FragColor=vec4(col, clamp(f*u_intensity,0.0,1.0));
}`,
ripple: `
precision mediump float;
varying vec2 v_uv;
uniform float u_time;
uniform float u_speed;
uniform float u_scale;
uniform float u_intensity;
void main(){
vec2 uv=v_uv-0.5;
float d=length(uv);
float w=sin((d*u_scale-u_time*u_speed*3.0)*6.2831)*0.5+0.5;
vec3 col=mix(vec3(0.05,0.2,0.5),vec3(0.4,0.85,1.0),w);
float a=clamp((1.0-d*1.6)*u_intensity,0.0,1.0);
gl_FragColor=vec4(col,a);
}`,
distort: `
precision mediump float;
varying vec2 v_uv;
uniform float u_time;
uniform float u_speed;
uniform float u_scale;
uniform float u_intensity;
float hash21(vec2 p){ p = fract(p*vec2(123.34,456.21)); p += dot(p,p+78.23); return fract(p.x*p.y); }
float noise2d(vec2 p){
vec2 i=floor(p), f=fract(p);
float a=hash21(i), b=hash21(i+vec2(1.,0.)), c=hash21(i+vec2(0.,1.)), d=hash21(i+vec2(1.,1.));
vec2 u=f*f*(3.-2.*f);
return mix(a,b,u.x)+(c-a)*u.y*(1.-u.x)+(d-b)*u.x*u.y;
}
void main(){
vec2 uv=v_uv;
float off=noise2d(uv*u_scale+u_time*u_speed)-0.5;
uv += vec2(off*0.18,off*0.1);
float shade=noise2d(uv*(u_scale*0.7)+11.3);
vec3 col=mix(vec3(0.05,0.05,0.08),vec3(0.8,0.95,1.0),shade);
gl_FragColor=vec4(col, clamp((0.65+shade*0.35)*u_intensity,0.0,1.0));
}`
};
function createShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(info);
}
return shader;
}
function createProgram(vsSource, fsSource) {
const vs = createShader(gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.deleteShader(vs);
gl.deleteShader(fs);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(info);
}
return program;
}
let program;
let loc = {};
function buildProgram(fragmentSource) {
const next = createProgram(vertexSource, fragmentSource);
if (program) gl.deleteProgram(program);
program = next;
loc = {
a_pos: gl.getAttribLocation(program, "a_pos"),
u_time: gl.getUniformLocation(program, "u_time"),
u_speed: gl.getUniformLocation(program, "u_speed"),
u_scale: gl.getUniformLocation(program, "u_scale"),
u_intensity: gl.getUniformLocation(program, "u_intensity")
};
}
const quad = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
function loadTemplate(name) {
fragCode.value = templates[name];
}
function updateLabels() {
speedVal.textContent = Number(speed.value).toFixed(2);
scaleVal.textContent = Number(scale.value).toFixed(2);
intensityVal.textContent = Number(intensity.value).toFixed(2);
}
template.addEventListener("change", () => {
loadTemplate(template.value);
applyShader();
});
[speed, scale, intensity].forEach((el) => el.addEventListener("input", updateLabels));
function applyShader() {
try {
buildProgram(fragCode.value);
} catch (err) {
alert("Shader 编译失败:\n" + err.message);
}
}
document.getElementById("applyBtn").addEventListener("click", applyShader);
document.getElementById("resetBtn").addEventListener("click", () => {
speed.value = "1";
scale.value = "4";
intensity.value = "1";
template.value = "fire";
loadTemplate("fire");
updateLabels();
applyShader();
});
document.getElementById("fullscreenBtn").addEventListener("click", () => {
if (canvas.requestFullscreen) canvas.requestFullscreen();
});
let playing = true;
toggleBtn.addEventListener("click", () => {
playing = !playing;
toggleBtn.textContent = playing ? "暂停" : "继续";
});
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const rect = canvas.getBoundingClientRect();
const w = Math.max(1, Math.floor(rect.width * dpr));
const h = Math.max(1, Math.floor(rect.height * dpr));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
gl.viewport(0, 0, w, h);
}
}
window.addEventListener("resize", resize);
let start = performance.now();
let prev = start;
let frame = 0;
let acc = 0;
function draw(now) {
resize();
const dt = now - prev;
prev = now;
frame += 1;
acc += dt;
if (acc >= 500) {
fpsText.textContent = `FPS: ${(1000 * frame / acc).toFixed(1)}`;
frame = 0;
acc = 0;
}
if (playing) {
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
gl.enableVertexAttribArray(loc.a_pos);
gl.vertexAttribPointer(loc.a_pos, 2, gl.FLOAT, false, 0, 0);
gl.uniform1f(loc.u_time, (now - start) / 1000);
gl.uniform1f(loc.u_speed, Number(speed.value));
gl.uniform1f(loc.u_scale, Number(scale.value));
gl.uniform1f(loc.u_intensity, Number(intensity.value));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
requestAnimationFrame(draw);
}
loadTemplate("fire");
updateLabels();
applyShader();
requestAnimationFrame(draw);
</script>
</body>
</html>