feat:新增文件内容

This commit is contained in:
Daniel
2026-04-01 17:28:39 +08:00
commit 0bf1299908
33 changed files with 1703 additions and 0 deletions

448
test.html Normal file
View File

@@ -0,0 +1,448 @@
<!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>