| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <title>3D Mandelbrot WebGL Renderer</title>
- <style>
- body { margin: 0; overflow: hidden; background: #000; }
- canvas { display: block; width: 100vw; height: 100vh; }
- #instruction {
- position: absolute;
- top: 10px; left: 10px;
- color: rgba(255,255,255,0.8);
- background: rgba(0,0,0,0.6);
- padding: 15px;
- border-radius: 8px;
- font-family: sans-serif;
- pointer-events: none;
- user-select: none;
- }
- </style>
- </head>
- <body>
- <div id="instruction">
- <h3>复数分形探索器 (WebGL)</h3>
- <p>• 滚轮:无极缩放 (Zoom)</p>
- <p>• 拖拽:移动视野 (Pan)</p>
- <p>• 算法:Distance Estimation (DEM) + Phong Lighting</p>
- <p style="font-size:12px; color:#aaa;">对应你上传的图2风格</p>
- </div>
- <canvas id="glcanvas"></canvas>
- <script id="vs" type="x-shader/x-vertex">
- attribute vec2 position;
- void main() {
- gl_Position = vec4(position, 0.0, 1.0);
- }
- </script>
- <script id="fs" type="x-shader/x-fragment">
- precision highp float;
- uniform vec2 u_resolution;
- uniform vec2 u_center;
- uniform float u_zoom;
- uniform float u_time;
- // 复数乘法
- vec2 complexMul(vec2 a, vec2 b) {
- return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
- }
- // HSV转RGB辅助函数
- vec3 hsv2rgb(vec3 c) {
- vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
- vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
- return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
- }
- void main() {
- // 1. 坐标归一化
- vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;
-
- // 2. 应用缩放和位移 (映射到复数平面 C)
- vec2 c = u_center + uv * (4.0 / u_zoom);
-
- // Z用于迭代,DZ用于距离估算(导数)
- vec2 z = vec2(0.0);
- vec2 dz = vec2(0.0);
-
- float iter = 0.0;
- const float max_iter = 500.0; // 最大迭代次数
- float escapeRadius = 1024.0; // 逃逸半径
-
- // 3. 核心迭代循环 Z = Z^2 + C
- // 同时计算导数 dZ = 2*Z*dZ + 1 (用于计算距离)
- float trap = 1e20; // 轨道陷阱变量(用于图1那种球体效果,这里简化做纹理)
- for (float i = 0.0; i < max_iter; i++) {
- // 导数迭代: dz = 2*z*dz + 1
- dz = 2.0 * complexMul(z, dz) + vec2(1.0, 0.0);
-
- // 主函数迭代: z = z^2 + c
- z = complexMul(z, z) + c;
- // 记录最小距离作为纹理(类似Orbit Trap)
- trap = min(trap, abs(z.x * z.y));
- // 检查逃逸
- if (dot(z, z) > escapeRadius) {
- iter = i;
- break;
- }
- iter = i;
- }
- // 4. 距离估算 (Distance Estimation)
- // d = 0.5 * |z| * log(|z|) / |dz|
- float r = length(z);
- float dist = 0.5 * r * log(r) / length(dz);
-
- // 5. 渲染逻辑
- vec3 color = vec3(0.0);
- if (iter < max_iter) {
- // --- 外部着色 (External) ---
-
- // 平滑迭代数 (消除颜色断层)
- float smoothIter = iter - log2(log2(dot(z,z))) + 4.0;
-
- // 模拟3D光照 (基于距离场的法线计算)
- // 我们不通过差分计算法线,而是直接用距离函数衰减来模拟光晕
- float edge = clamp(pow(dist, 0.2) * 1.5, 0.0, 1.0);
-
- // 基础色调:蓝色 -> 奶油色 -> 深紫 (模仿图2)
- float t = smoothIter * 0.05 - u_time * 0.02;
- vec3 col1 = vec3(0.1, 0.2, 0.5); // 深蓝
- vec3 col2 = vec3(0.95, 0.8, 0.6); // 奶油/金
- vec3 col3 = vec3(0.4, 0.0, 0.4); // 紫
-
- vec3 baseColor = 0.5 + 0.5 * cos(6.28318 * (vec3(1.0) * t + vec3(0.0, 0.33, 0.67)));
-
- // 混合颜色
- color = baseColor * edge;
-
- // 添加类似图2的"山脊"高光
- // 如果距离非常小(靠近集合边界),则调亮
- float ridge = 1.0 / (1.0 + 100.0 * dist);
- color += vec3(1.0, 0.9, 0.8) * ridge;
- } else {
- // --- 内部着色 (Internal / Mandelbrot Set) ---
- color = vec3(0.0); // 黑色核心
- }
- gl_FragColor = vec4(color, 1.0);
- }
- </script>
- <script>
- // WebGL 初始化样板代码
- const canvas = document.getElementById('glcanvas');
- const gl = canvas.getContext('webgl');
- if (!gl) { alert("您的浏览器不支持WebGL"); }
- // 编译着色器
- function createShader(gl, type, source) {
- const shader = gl.createShader(type);
- gl.shaderSource(shader, source);
- gl.compileShader(shader);
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
- console.error(gl.getShaderInfoLog(shader));
- gl.deleteShader(shader);
- return null;
- }
- return shader;
- }
- const vsSource = document.getElementById('vs').text;
- const fsSource = document.getElementById('fs').text;
- const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
- const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
- const program = gl.createProgram();
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.linkProgram(program);
- gl.useProgram(program);
- // 设置矩形顶点 (覆盖全屏)
- const positionBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
- -1, -1, 1, -1, -1, 1,
- -1, 1, 1, -1, 1, 1,
- ]), gl.STATIC_DRAW);
- const positionLocation = gl.getAttribLocation(program, "position");
- gl.enableVertexAttribArray(positionLocation);
- gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
- // Uniforms 变量位置
- const uResolution = gl.getUniformLocation(program, "u_resolution");
- const uCenter = gl.getUniformLocation(program, "u_center");
- const uZoom = gl.getUniformLocation(program, "u_zoom");
- const uTime = gl.getUniformLocation(program, "u_time");
- // 交互状态
- let state = {
- x: -0.745, // 经典的“海马谷”位置
- y: 0.1,
- zoom: 1.0,
- targetZoom: 1.0,
- dragging: false,
- lastX: 0,
- lastY: 0
- };
- function resize() {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- gl.viewport(0, 0, canvas.width, canvas.height);
- }
- window.addEventListener('resize', resize);
- resize();
- // 交互逻辑
- canvas.addEventListener('mousedown', e => {
- state.dragging = true;
- state.lastX = e.clientX;
- state.lastY = e.clientY;
- });
- window.addEventListener('mouseup', () => state.dragging = false);
- window.addEventListener('mousemove', e => {
- if (state.dragging) {
- const dx = e.clientX - state.lastX;
- const dy = e.clientY - state.lastY;
- state.lastX = e.clientX;
- state.lastY = e.clientY;
-
- // 移动速度随缩放层级调整
- const moveSpeed = 4.0 / (state.zoom * canvas.height);
- state.x -= dx * moveSpeed;
- state.y += dy * moveSpeed; // WebGL Y轴通常相反,但在复数平面视情况而定
- }
- });
- canvas.addEventListener('wheel', e => {
- e.preventDefault();
- const zoomSpeed = 1.05;
- if (e.deltaY < 0) state.targetZoom *= zoomSpeed;
- else state.targetZoom /= zoomSpeed;
- }, { passive: false });
- // 渲染循环
- function render(time) {
- time *= 0.001;
-
- // 简单的平滑缩放
- state.zoom += (state.targetZoom - state.zoom) * 0.1;
- gl.uniform2f(uResolution, canvas.width, canvas.height);
- gl.uniform2f(uCenter, state.x, state.y);
- gl.uniform1f(uZoom, state.zoom);
- gl.uniform1f(uTime, time);
- gl.drawArrays(gl.TRIANGLES, 0, 6);
- requestAnimationFrame(render);
- }
- requestAnimationFrame(render);
- </script>
- </body>
- </html>
|