实现了类似光线追踪的效果,用之前的车辆算法,自创的3d渲染算法,100物体时跑到了240帧

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, user-scalable=no">

<title>3D 几何渲染系统(无限数量+参数实时生效+光线追踪)</title>

<style>

body { margin: 0; background: #000; display: flex; flex-direction: column; align-items: center; font-family: Arial; overflow: hidden; }

#canvas { background: #111; border: 1px solid #333; }

.controls { position: fixed; top: 10px; right: 10px; color: #fff; display: flex; flex-direction: column; gap: 10px; padding: 10px; background: rgba(0,0,0,0.7); border-radius: 8px; }

input { width: 80px; padding: 5px; border: none; border-radius: 4px; }

button { padding: 6px 12px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }

button:hover { background: #45a049; }

#fps { position: fixed; top: 10px; left: 10px; color: #00ff00; font-size: 16px; font-weight: bold; }

#materialTips { position: fixed; top: 40px; left: 10px; color: #ff6666; font-size: 14px; background: rgba(0,0,0,0.8); padding: 5px; border-radius: 4px; }

</style>

</head>

<body>

<div id="fps">FPS: 0</div>

<div id="materialTips">1=反光 2=镜子 3=透光 4=折射 | 亮度系数: <span id="brightnessVal">1.0</span></div>

<canvas id="canvas"></canvas>

<div class="controls">

<label>物体数量: <input type="number" id="objCount" value="8" min="1" max="999" step="1"></label>

<label>缩放系数: <input type="number" id="scale" value="1.0" min="0.1" max="5.0" step="0.1"></label>

<label>物体间距: <input type="number" id="gap" value="3.0" min="0.5" max="20.0" step="0.5"></label>

<label>物体尺寸: <input type="number" id="objSize" value="0.7" min="0.1" max="3.0" step="0.1"></label>

<label>全局亮度: <input type="number" id="brightness" value="1.0" min="0.1" max="2.0" step="0.1"></label>

<button id="startBtn">启动/刷新渲染</button>

</div>

<script>

function F(x, k) { return 1 + Math.pow(x, k) + x; }

function integral(eq, a, b, d=0.1, K=5) {

const g = new Function('x', `return ${eq};`);

let s = [[a, b]], r = 0.0;

while (s.length > 0) {

let n = [];

for (let [x0, x1] of s) {

const dx = x1 - x0;

if (dx < d) {

const m = (x0 + x1)/2;

const m_scaled = m/(2*Math.PI);

const f_values = Array.from({length:K}, (_,k) => F(m_scaled, k+1));

let W = 0.0, V = 0.0;

const g_m = g(m);

for (let k = 0; k < K; k++) {

const w = 1/(f_values[k] + 1e-12);

W += w; V += w * g_m;

}

r += dx * (W ? V/W : g_m);

continue;

}

const u = g(x0), v = g(x1), mid = (x0+x1)/2, mv = g(mid);

if (Math.abs(u-v) < 1e-8 && Math.abs(mv-u) < 1e-8) {

r += dx * mv; continue;

}

const h = dx/10;

for (let i = 0; i < 10; i++) n.push([x0+i*h, x0+(i+1)*h]);

}

s = n;

}

return r;

}

function c(seg) { let cnt = 0; for (let i=0;i<seg.length;i++) for(let j=i+1;j<seg.length;j++) if(seg[i]>seg[j]) cnt++; return cnt; }

function w(arr, wa) {

let a = [...arr], n = a.length;

if (n <=1) return a;

const exp_wa = Array.from({length:n}, (_,i) => Math.pow(wa, i+1));

for (let i=0;i<n;i++) {

let swapped = false;

for (let j=0;j<n-i-1;j++) {

const v1 = a[j]*(1 + wa + exp_wa[j]);

const v2 = a[j+1]*(1 + wa + exp_wa[j+1]);

if (Math.abs(v1-v2) <1e-8) {

const eq = `{a\[j\]}\*x - {a[j+1]}*x`;

const r = integral(eq, 0, 1);

if (r>0) { [a[j],a[j+1]] = [a[j+1],a[j]]; swapped=true; }

} else if (v1>v2) { [a[j],a[j+1]] = [a[j+1],a[j]]; swapped=true; }

}

if (!swapped) break;

}

return a;

}

function sortAngle(arr) {

let n = arr.length;

if (n<=1) return [...arr];

let segs = [], i=0;

while (i < n) {

let l=1;

while (i+l <n && arr[i+l]>=arr[i+l-1]) l++;

const s = arr.slice(i, i+l);

const r = l>1 ? c(s)/(l-1) : 0;

segs.push({s, r});

i += l;

}

let n_segs = [];

for (let {s, r} of segs) {

if (r>0 && s.length>1) {

const avg = s.reduce((a,b)=>a+b)/s.length;

const wa_sum = Array.from({length:20}, (_,k) => 1/(F(avg, k+1)+1e-12)).reduce((a,b)=>a+b);

n_segs.push({s:w(s, wa_sum), r: c(w(s, wa_sum))/(s.length-1)});

} else n_segs.push({s, r});

}

return n_segs.flatMap(item => item.s);

}

const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');

let [W, H] = [window.innerWidth, window.innerHeight];

canvas.width = W; canvas.height = H;

let objects = [];

let isRendering = false;

let rotateX = 0, rotateY = 0;

let lastTime = 0, frameCount = 0, fps = 0;

let globalBrightness = 1.0;

// 全局参数(实时更新)

let params = {

count: 8,

scale: 1.0,

gap: 3.0,

size: 0.7

};

// 材质类型定义

const MATERIAL_TYPES = {

REFLECTIVE: 1, // 反光

MIRROR: 2, // 镜子

TRANSLUCENT:3, // 透光

REFRACTIVE:4 // 折射

};

// 生成均匀分布位置(适配任意数量,避免超出视角)

function getUniformPos(index, count, gap) {

const gridSize = Math.ceil(Math.sqrt(count));

const normX = (index % gridSize) / gridSize - 0.5;

const normY = Math.floor(index / gridSize) / gridSize - 0.5;

const x = normX * gridSize * gap;

const y = normY * gridSize * gap;

const z = Math.random()*gap*2 + 3;

return {x, y, z};

}

// 计算高光:基于物体位置和方向,生成局部高光区域

function calcHighlight(obj, faceVertices, lightDir) {

const center = faceVertices.reduce((acc, v) => ({

x: acc.x + v.x / faceVertices.length,

y: acc.y + v.y / faceVertices.length

}), {x:0, y:0});

const highlightRadius = obj.size * 15 * params.scale;

const highlightIntensity = obj.material === MATERIAL_TYPES.MIRROR ? 0.8 :

obj.material === MATERIAL_TYPES.REFLECTIVE ? 0.5 : 0;

if (highlightIntensity <= 0) return 0;

// 基于旋转角度(转弯幅度)计算高光衰减

const rotateFactor = Math.abs(rotateX) + Math.abs(rotateY);

const attenuation = 1 / (1 + rotateFactor * 0.1);

return highlightIntensity * attenuation * globalBrightness;

}

// HSL转RGB(用于调整饱和度和亮度)

function hslToRgb(h, s, l) {

s = Math.max(0, Math.min(1, s));

l = Math.max(0, Math.min(1, l));

let r, g, b;

if (s === 0) r = g = b = l;

else {

const hue2rgb = (p, q, t) => {

if (t < 0) t += 1;

if (t > 1) t -= 1;

if (t < 1/6) return p + (q - p) * 6 * t;

if (t < 1/2) return q;

if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;

return p;

};

const q = l < 0.5 ? l * (1 + s) : l + s - l * s;

const p = 2 * l - q;

r = hue2rgb(p, q, h + 1/3);

g = hue2rgb(p, q, h);

b = hue2rgb(p, q, h - 1/3);

}

return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };

}

// 完整几何体类(新增材质、高光、明暗饱和度联动)

class SolidGeometry {

constructor(id, pos, type, size) {

this.id = id;

this.pos = pos;

this.type = type;

this.size = size;

this.baseHue = (id*30) % 360;

this.material = Math.floor(Math.random() * 4) + 1; // 随机材质

this.color = `hsl(${this.baseHue}, 70%, 50%)`;

this.vertices = this.generateVertices();

this.faces = this.generateFaces();

this.edges = this.generateEdges();

}

generateVertices() {

const s = this.size;

if (this.type === 'cube') {

return [

-s,-s,-s\], \[s,-s,-s\], \[s,s,-s\], \[-s,s,-s\], \[-s,-s,s\], \[s,-s,s\], \[s,s,s\], \[-s,s,s

];

} else {

const vertexCount = Math.floor(Math.random()*6)+4;

const vertices = [];

for (let i=0; i<vertexCount; i++) {

const angle = (i/vertexCount)*Math.PI*2;

const x = Math.cos(angle)*s;

const y = Math.sin(angle)*s;

const z = (Math.random()-0.5)*s*2;

vertices.push([x, y, z]);

}

return vertices;

}

}

generateFaces() {

if (this.type === 'cube') {

return [[0,1,2,3], [4,5,6,7], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7]];

} else {

const faces = [];

for (let i=1; i<this.vertices.length-1; i++) {

faces.push([0, i, i+1]);

}

return faces;

}

}

generateEdges() {

if (this.type === 'cube') {

return [[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]];

} else {

const edges = [];

for (let i=0; i<this.vertices.length; i++) {

edges.push([i, (i+1)%this.vertices.length]);

}

for (let i=1; i<this.vertices.length-1; i++) {

edges.push([0, i]);

}

return edges;

}

}

projectVertex(v) {

let [x, y, z] = v;

x += this.pos.x; y += this.pos.y; z += this.pos.z;

x *= params.scale; y *= params.scale; z *= params.scale;

const cosX = Math.cos(rotateX), sinX = Math.sin(rotateX);

const cosY = Math.cos(rotateY), sinY = Math.sin(rotateY);

let ny = y*cosX - z*sinX;

let nz = y*sinX + z*cosX;

let nx = x*cosY + nz*sinY;

nz = -x*sinY + nz*cosY;

const fov = 250;

const projX = (nx * fov) / (nz + 20) + W/2;

const projY = (ny * fov) / (nz + 20) + H/2;

return {x: projX, y: projY, z: nz};

}

render() {

const projected = this.vertices.map(v => this.projectVertex(v));

// 旋转幅度(转弯幅度):越大越暗

const rotateAmplitude = Math.abs(rotateX) + Math.abs(rotateY);

const brightnessFactor = 1 / (1 + rotateAmplitude * 0.2) * globalBrightness;

// 饱和度系数:旋转幅度越大,饱和度越低

const saturationFactor = 1 / (1 + rotateAmplitude * 0.15);

this.faces.forEach((face, faceIdx) => {

const faceVertices = face.map(idx => projected[idx]);

// 计算面的高光强度

const lightDir = {x: Math.cos(rotateY), y: Math.sin(rotateX)};

const highlight = calcHighlight(this, faceVertices, lightDir);

// 根据材质和旋转幅度调整颜色

let h = this.baseHue / 360;

let s = 0.7 * saturationFactor;

let l = 0.5 * brightnessFactor;

// 材质特殊效果

if (this.material === MATERIAL_TYPES.MIRROR) {

s = 0.9; l = l * 1.2 + highlight; // 镜子高饱和高亮度

} else if (this.material === MATERIAL_TYPES.TRANSLUCENT) {

l = l * 0.8 + highlight * 0.3; // 透光半透明

} else if (this.material === MATERIAL_TYPES.REFRACTIVE) {

s = s * 0.8; l = l * 1.1; // 折射轻微去饱和

} else if (this.material === MATERIAL_TYPES.REFLECTIVE) {

l = l + highlight; // 反光叠加高光

}

const rgb = hslToRgb(h, s, l);

const faceColor = `rgb({rgb.r}, {rgb.g}, ${rgb.b})`;

// 绘制面:添加高光渐变(局部区域高光)

const gradient = ctx.createRadialGradient(

faceVertices[0].x, faceVertices[0].y, 0,

faceVertices[0].x, faceVertices[0].y, this.size * 20 * params.scale

);

gradient.addColorStop(0, `rgba(255,255,255,${highlight})`);

gradient.addColorStop(1, faceColor);

ctx.fillStyle = this.material === MATERIAL_TYPES.REFLECTIVE || this.material === MATERIAL_TYPES.MIRROR ? gradient : faceColor;

ctx.beginPath();

ctx.moveTo(faceVertices[0].x, faceVertices[0].y);

faceVertices.forEach(v => ctx.lineTo(v.x, v.y));

ctx.closePath();

ctx.fill();

});

// 绘制边

ctx.strokeStyle = '#ffffff';

ctx.lineWidth = 1.5;

this.edges.forEach(edge => {

const v1 = projected[edge[0]];

const v2 = projected[edge[1]];

ctx.beginPath();

ctx.moveTo(v1.x, v1.y);

ctx.lineTo(v2.x, v2.y);

ctx.stroke();

});

// 绘制材质标记(红色数字)

const center = projected.reduce((acc, v) => ({

x: acc.x + v.x / projected.length,

y: acc.y + v.y / projected.length

}), {x:0, y:0});

ctx.fillStyle = '#ff0000';

ctx.font = 'bold 16px Arial';

ctx.textAlign = 'center';

ctx.fillText(this.material.toString(), center.x, center.y);

}

}

// 光线采样优化

function optimizeRays() {

const angles = Array.from({length:100}, () => Math.random()*Math.PI*2);

return sortAngle(angles).slice(0, 20);

}

// 重建物体列表(参数修改后调用)

function rebuildObjects() {

objects = [];

for (let i=0; i<params.count; i++) {

const pos = getUniformPos(i, params.count, params.gap);

const type = Math.random() > 0.5 ? 'cube' : 'poly';

objects.push(new SolidGeometry(i, pos, type, params.size));

}

}

// 动画循环

function animate(currentTime) {

if (!isRendering) return;

requestAnimationFrame(animate);

frameCount++;

if (currentTime - lastTime >= 1000) {

fps = frameCount;

frameCount = 0;

lastTime = currentTime;

document.getElementById('fps').textContent = `FPS: ${fps}`;

}

ctx.clearRect(0, 0, W, H);

optimizeRays();

// 按物体 z 轴排序(整体遮挡)

objects.sort((a,b) => {

const aZ = a.pos.z * params.scale;

const bZ = b.pos.z * params.scale;

return aZ - bZ;

});

objects.forEach(obj => obj.render());

}

// 初始化控制(参数实时监听+更新)

function initControls() {

document.getElementById('objCount').addEventListener('input', (e) => params.count = parseInt(e.target.value));

document.getElementById('scale').addEventListener('input', (e) => params.scale = parseFloat(e.target.value));

document.getElementById('gap').addEventListener('input', (e) => params.gap = parseFloat(e.target.value));

document.getElementById('objSize').addEventListener('input', (e) => params.size = parseFloat(e.target.value));

document.getElementById('brightness').addEventListener('input', (e) => {

globalBrightness = parseFloat(e.target.value);

document.getElementById('brightnessVal').textContent = globalBrightness.toFixed(1);

});

// 鼠标/触屏旋转

let isDragging = false;

let lastX, lastY;

canvas.addEventListener('mousedown', (e) => { isDragging = true; [lastX, lastY] = [e.clientX, e.clientY]; });

canvas.addEventListener('mousemove', (e) => {

if (!isDragging) return;

const dx = e.clientX - lastX;

const dy = e.clientY - lastY;

rotateY += dx * 0.01;

rotateX += dy * 0.01;

lastX, lastY\] = \[e.clientX, e.clientY\]; }); canvas.addEventListener('mouseup', () =\> isDragging = false); canvas.addEventListener('touchstart', (e) =\> { isDragging = true; \[lastX, lastY\] = \[e.touches\[0\].clientX, e.touches\[0\].clientY\]; }); canvas.addEventListener('touchmove', (e) =\> { if (!isDragging) return; const dx = e.touches\[0\].clientX - lastX; const dy = e.touches\[0\].clientY - lastY; rotateY += dx \* 0.01; rotateX += dy \* 0.01; \[lastX, lastY\] = \[e.touches\[0\].clientX, e.touches\[0\].clientY\]; }); canvas.addEventListener('touchend', () =\> isDragging = false); // 启动/刷新按钮 document.getElementById('startBtn').addEventListener('click', () =\> { isRendering = true; rebuildObjects(); requestAnimationFrame(animate); }); } // 窗口自适应 window.addEventListener('resize', () =\> { \[W, H\] = \[window.innerWidth, window.innerHeight\]; canvas.width = W; canvas.height = H; }); initControls(); \ \ \

相关推荐
我是伪码农2 小时前
Tab选项卡
css·html·css3
一个没有感情的程序猿2 小时前
前端实现交互式3D人体肌肉解剖图:基于 Three.js + React Three Fiber 的完整方案
前端·javascript·3d
苏州知芯传感2 小时前
柔性抓取的“慧眼”:MEMS 3D视觉如何让机器人精准识别无序堆叠的复杂钣金件?
算法·3d·机器人·mems
OranTech3 小时前
练习01-走进Web世界
html
软件开发技术深度爱好者3 小时前
数学公式生成器HTML版
前端·html
数智前线4 小时前
火山引擎智能3D视频启动商业化,计划落地直播应用
3d·音视频·火山引擎
Cv打怪升级4 小时前
3D-Front数据集 json说明
3d·json
Lan.W16 小时前
element UI + vue2 + html实现堆叠条形图 - 横向分段器
前端·ui·html
Y_3_717 小时前
3D 圣诞树网页代码
3d