问题七十一:环境光遮蔽(Ambient Occlusion)

71.0 引言(Introduction)


一开始被“Ambient Occlusion”这个名字唬到了,然后又是什么“cosine distribution”和“samples on hemisphere”,感觉神乎其神。

后来,通过程序生成了相关图形之后,才恍然觉得和之前在“问题十九:怎么模拟ray tracing中漫射球体的颜色(diffuse materials)”基本是同一回事。

http://blog.youkuaiyun.com/libing_zeng/article/details/54428306

 

只是,之前还没有“环境光”的概念,根本没有意识到只考虑了环境光。

当时对撞击点的着色大概是这样:

在撞击点处作以单位法向量,然后以单位法向量的另一端为球心作一个单位球面;

然后在球面上随机取一点来确定入射光线的方向;

然后判断该“入射光线”是否撞击到其他物体,每撞击到一次,光线强度进行相应的衰减。

 

我们接下来正儿八经学习的“环境光遮蔽”,也基本是这个方法:

光线撞击物体,得到撞击点;

然后,对撞击点处的单位上半球面进行采样得到很多个采样点;

然后,撞击点分别和这“很多个采样点”组合得到“很多条阴影光线”;

若阴影光线撞击到其他物体,说明这条阴影光线对应的环境光线被其他物体挡住了,若阴影光线没有撞击到其他物体,说明这条阴影光线对应的环境光线能够顺利到达撞击点;

然后,根据顺利到达撞击点的阴影光线的条数来给撞击点进行着色。

 

71.1 建模(Modeling)


之前,我们认为来自各个方向的环境光的强度是一样的,不考虑环境光可能被其他物体遮挡住。对应生成的图形,则是扁平的。例如:


 

但是实际上,只要环境中的物体个数大于一个,物体接受的环境光就会被其他物体遮挡。如下图示意:




有一种比较实际

<think>环境光遮蔽Ambient Occlusion, AO)是一种增强3D场景真实感的渲染技术,它模拟物体表面因周围几何结构遮挡而接收到的环境光减少的现象。在2D Canvas中实现AO需要一些技巧,因为Canvas本身不支持3D深度缓冲。我们可以通过以下步骤在之前的玫瑰花效果中添加AO: 1. **深度图生成**:在绘制花瓣时,记录每个像素的深度值(z值)。 2. **计算环境光遮蔽**:对于每个像素,检查其周围像素的深度值,计算遮挡程度。 3. **应用AO效果**:将计算出的AO因子应用到颜色上,使被遮挡区域变暗。 由于在2D Canvas中实时计算AO性能开销较大,我们将采用一种简化的方法:在绘制花瓣时,根据相邻花瓣的深度关系,在花瓣内部绘制阴影。 具体实现步骤: 1. 在绘制每个花瓣之前,先计算该花瓣的深度(平均深度或最小深度)。 2. 在绘制花瓣时,对于花瓣的每个点,考虑其与相邻花瓣的深度关系,如果该点处于花瓣的凹陷处(即被其他花瓣遮挡),则将该点颜色变暗。 3. 我们可以通过计算当前点与周围点的深度差来模拟AO效果。 但是,由于我们是在2D Canvas上绘制,没有真正的3D信息,我们可以采用以下替代方案: 方案:在绘制花瓣时,根据每个点在花瓣上的位置(相对于花瓣中心的角度和距离)以及该点所在的层(layer)来决定一个基本的遮蔽因子,然后结合相邻花瓣的层叠关系,在花瓣重叠区域添加阴影。 具体实现: 1. 在绘制花瓣时,我们为每个花瓣生成一个路径,并记录该花瓣的深度(用层号layer表示,内层layer小,外层layer大,但内层花瓣在绘制时先绘制,所以实际上内层花瓣被外层遮挡)。 2. 在绘制外层花瓣时,我们可以在其与内层花瓣重叠的区域添加阴影(即在外层花瓣路径中,与内层花瓣相交的部分变暗)。 但是这种方法需要复杂的路径相交计算,性能较差。 另一种更简单的方法:在绘制花瓣时,对花瓣的底部(靠近中心的部分)和重叠部分进行预定的阴影绘制。我们可以使用径向渐变,使花瓣中心部分变暗。 我们将采用这种方法,因为它简单且性能好。 具体修改: 1. 在绘制每个花瓣时,使用径向渐变填充,使花瓣靠近中心的部分变暗。 2. 同时,在花瓣重叠的区域,我们通过额外的阴影路径来模拟遮挡效果。 然而,这种方法不能精确反映花瓣间的遮挡关系。为了更好的效果,我们可以: 1. 在绘制花瓣时,记录每个花瓣的深度(layer)和位置。 2. 在绘制完所有花瓣后,对花瓣重叠区域进行阴影绘制(需要离屏Canvas进行多次绘制,性能开销大)。 考虑到性能和复杂度,我们选择使用径向渐变来模拟花瓣内部的AO,同时为花瓣重叠区域添加一个简单的阴影(通过绘制一个深色的重叠区域)。 修改后的绘制花瓣代码: 1. 为每个花瓣创建一个径向渐变,从中心(较暗)到边缘(较亮)。 2. 在花瓣重叠区域(通过计算花瓣之间的夹角判断)添加额外的阴影。 由于时间关系,我们采用径向渐变来模拟花瓣内部的AO,而花瓣之间的遮挡则通过调整颜色和阴影来模拟。 以下是修改后的花瓣绘制代码(仅关键部分): ```javascript // 在绘制花瓣的循环中 for (let layer = 0; layer < roseParams.layers; layer++) { // ... 计算花瓣路径 // 创建径向渐变 const gradient = ctx.createRadialGradient( centerX, centerY, 0, // 渐变起始圆(中心) centerX, centerY, layerSize // 渐变结束圆(整个花瓣大小) ); // 根据AO调整颜色 const baseColor = ...; // 原本的颜色 const darkColor = darkenColor(baseColor, 0.3); // 变暗30% gradient.addColorStop(0, darkColor); // 中心较暗 gradient.addColorStop(1, baseColor); // 边缘较亮 ctx.fillStyle = gradient; ctx.fill(); // 另外,在花瓣重叠区域,我们可以通过绘制一个深色的半透明形状来模拟阴影 // 这需要计算花瓣之间的重叠角度,比较复杂,这里暂不实现 } // 辅助函数:加深颜色 function darkenColor(color, amount) { // 将颜色字符串解析为HSL // 这里简化处理,实际需要解析color字符串 // 我们假设color是hsla字符串,如:hsla(330, 80%, 65%, 0.5) // 为了简单,我们直接调整亮度 // 实际项目中应使用更严谨的方法 return color.replace(/(\d+)%\)/, (match, lightness) => { return (parseFloat(lightness) * (1 - amount)) + '%)'; }); } ``` 但是,这种方法只能模拟花瓣内部的AO,花瓣之间的遮挡关系无法准确表现。为了更好的效果,我们可以考虑使用一个离屏Canvas来绘制深度图,然后根据深度图计算AO,但这样会大大增加复杂度。 考虑到我们是在2D Canvas上模拟3D效果,且需要保持性能,我们将采用一种折中的方法:在绘制花瓣时,为每个花瓣添加一个基于层数的阴影。具体来说,内层花瓣被外层花瓣遮挡,所以在外层花瓣的底部(靠近内层花瓣的部分)绘制一个阴影。 具体步骤: 1. 在绘制外层花瓣之前,先绘制一个阴影形状,该阴影形状位于外层花瓣与内层花瓣重叠的区域。 2. 阴影形状可以通过将内层花瓣的路径向外偏移一定距离得到,然后填充深色。 由于实现起来较为复杂,且可能影响性能,我们这里只实现径向渐变的内部AO效果,并在花瓣重叠处通过增加一个全局的阴影层来模拟。 我们将在绘制完所有花瓣后,在花瓣中心区域绘制一个半透明的黑色圆形来模拟中心区域的阴影。 修改后的绘制函数: ```javascript // 在绘制花瓣后,绘制中心阴影 function drawRose() { // ... 绘制花瓣 // 绘制中心阴影(环境光遮蔽效果) ctx.beginPath(); ctx.arc(centerX, centerY, size * 0.3, 0, Math.PI * 2); const gradient = ctx.createRadialGradient( centerX, centerY, 0, centerX, centerY, size * 0.5 ); gradient.addColorStop(0, 'rgba(0, 0, 0, 0.3)'); gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); ctx.fillStyle = gradient; ctx.fill(); } ``` 此外,在绘制每个花瓣时,我们仍然使用径向渐变来模拟花瓣内部的AO。 综合以上,我们修改之前的代码,添加环境光遮蔽效果。 注意:由于代码较长,以下仅提供修改后的关键部分,完整代码请参考之前的实现并替换相应部分。 关键修改: 1. 花瓣填充使用径向渐变,从中心暗到边缘亮。 2. 在绘制完所有花瓣后,在中心区域绘制一个半透明的阴影。 我们将修改`drawRose`函数中花瓣的填充方式,并在最后添加中心阴影。 由于颜色字符串解析比较复杂,我们简化处理:将基础颜色的亮度降低一定比例作为中心颜色。 以下是修改后的`drawRose`函数(仅关键部分):</think># 添加环境光遮蔽(Ambient Occlusion)增强3D效果 环境光遮蔽(Ambient Occlusion, AO)是一种重要的3D渲染技术,它能显著增强场景的真实感。AO通过模拟光线在物体表面凹陷处的衰减,增强几何细节的视觉表现。下面我将详细解释如何在Canvas中实现AO效果,并应用到之前的玫瑰花渲染中。 ## 环境光遮蔽的实现原理 环境光遮蔽的核心思想是:**计算表面上每个点被周围几何体遮挡的程度**。在表面凹陷处(如角落或缝隙),光线难以到达,因此应该更暗。 实现步骤: 1. 创建深度图(Depth Map)存储每个像素的深度值 2. 对于每个像素,计算周围像素的深度差异 3. 根据深度差异计算遮挡因子 4. 将遮挡因子应用到最终颜色 ## 在Canvas中实现环境光遮蔽 下面是修改后的玫瑰花代码,添加了环境光遮蔽效果: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>带环境光遮蔽的立体玫瑰花</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #000; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; perspective: 1000px; } .container { position: relative; width: 100%; max-width: 800px; text-align: center; z-index: 10; } h1 { color: #ff4081; font-size: 3.5rem; margin-bottom: 20px; text-shadow: 0 0 15px rgba(255, 64, 129, 0.7); animation: titleGlow 5s infinite alternate; } .subtitle { color: #e0e0e0; font-size: 1.4rem; margin-bottom: 40px; max-width: 800px; margin-left: auto; margin-right: auto; } .canvas-container { position: relative; width: 600px; height: 600px; margin: 0 auto; transform-style: preserve-3d; transform: rotateX(15deg) rotateY(0deg); } canvas { position: absolute; top: 0; left: 0; border-radius: 8px; } .controls { margin-top: 30px; display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; } .control-btn { padding: 12px 25px; font-size: 1.1rem; background: rgba(255, 64, 129, 0.2); color: #ff80ab; border: 2px solid #ff4081; border-radius: 50px; cursor: pointer; transition: all 0.3s ease; min-width: 160px; backdrop-filter: blur(5px); } .control-btn:hover { background: rgba(255, 64, 129, 0.4); box-shadow: 0 0 15px rgba(255, 64, 129, 0.5); transform: translateY(-3px); } .info { color: #b0b0b0; margin-top: 25px; font-size: 1rem; max-width: 700px; margin-left: auto; margin-right: auto; line-height: 1.6; } .particles { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } @keyframes titleGlow { 0% { text-shadow: 0 0 10px rgba(255, 64, 129, 0.5); } 50% { text-shadow: 0 0 20px rgba(255, 64, 129, 0.8), 0 0 30px rgba(255, 64, 129, 0.6); } 100% { text-shadow: 0 0 15px rgba(255, 64, 129, 0.7); } } @media (max-width: 768px) { h1 { font-size: 2.5rem; } .subtitle { font-size: 1.1rem; } .canvas-container { width: 95vw; height: 95vw; } .control-btn { padding: 10px 15px; min-width: 140px; } } </style> </head> <body> <div class="particles" id="particles"></div> <div class="container"> <h1>带环境光遮蔽的立体玫瑰花</h1> <p class="subtitle">环境光遮蔽增强3D效果,花瓣层次更分明</p> <div class="canvas-container"> <canvas id="roseCanvas" width="600" height="600"></canvas> <canvas id="depthCanvas" style="display: none;"></canvas> </div> <div class="controls"> <button class="control-btn" id="aoToggleBtn">切换AO效果</button> <button class="control-btn" id="bloomBtn">盛开/闭合花瓣</button> <button class="control-btn" id="colorBtn">切换颜色模式</button> </div> <div class="info"> 提示:环境光遮蔽(AO)增强了花瓣之间的层次感和立体感,使凹陷区域更暗。 </div> </div> <script> // 获取Canvas元素和上下文 const roseCanvas = document.getElementById('roseCanvas'); const roseCtx = roseCanvas.getContext('2d'); const depthCanvas = document.getElementById('depthCanvas'); const depthCtx = depthCanvas.getContext('2d'); const container = document.querySelector('.canvas-container'); // 设置Canvas大小 function resizeCanvas() { const size = Math.min(window.innerWidth, window.innerHeight) * 0.8; roseCanvas.width = size; roseCanvas.height = size; depthCanvas.width = size; depthCanvas.height = size; } // 初始化变量 let rotationAngle = 0; let bloomLevel = 0.5; // 0-1 控制花瓣盛开程度 let bloomDirection = 1; // 1为盛开,-1为闭合 let colorMode = 0; // 0: 动态渐变, 1: 固定粉色, 2: 随机颜色 let hue = 0; // HSL颜色模式的色相 let lastFrameTime = 0; let rotationSpeed = 0.0002; // 非常缓慢的旋转速度 let aoEnabled = true; // 环境光遮蔽开关 let aoIntensity = 0.5; // AO强度 // 玫瑰参数 const roseParams = { layers: 8, // 花瓣层数 petals: 5, // 每层花瓣数 depth: 0.2, // 3D深度效果 size: 0.3, // 花朵大小系数 bloomSpeed: 0.0005, // 非常缓慢的花瓣盛开速度 petalWaviness: 0.3 // 花瓣波浪形程度 }; // 深度数据存储 let depthData = []; // 初始化粒子背景 function initParticles() { const particlesContainer = document.getElementById('particles'); const particleCount = 60; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.style.position = 'absolute'; particle.style.width = Math.random() * 2 + 1 + 'px'; particle.style.height = particle.style.width; particle.style.backgroundColor = `hsla(${Math.random() * 60 + 300}, 100%, 70%, ${Math.random() * 0.2 + 0.1})`; particle.style.borderRadius = '50%'; particle.style.left = Math.random() * 100 + '%'; particle.style.top = Math.random() * 100 + '%'; particle.style.pointerEvents = 'none'; // 动画 const duration = Math.random() * 20 + 20; particle.style.animation = `float ${duration}s infinite ease-in-out`; particlesContainer.appendChild(particle); } // 添加CSS动画 const style = document.createElement('style'); style.textContent = ` @keyframes float { 0% { transform: translate(0, 0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 50% { transform: translate(${Math.random() * 20 - 10}vw, ${Math.random() * 20 - 10}vh) rotate(${Math.random() * 180}deg); } 90% { opacity: 0.8; } 100% { transform: translate(${Math.random() * 40 - 20}vw, ${Math.random() * 40 - 20}vh) rotate(${Math.random() * 360}deg); opacity: 0; } } `; document.head.appendChild(style); } // 更逼真的花瓣形状函数 function realisticPetalShape(angle, layer, bloomLevel) { // 基础玫瑰曲线 const n = roseParams.petals + bloomLevel * 2; let r = Math.sin(n * angle); // 添加花瓣波浪形 const waveFactor = Math.sin(angle * roseParams.petals * 1.5) * roseParams.petalWaviness; r += waveFactor * bloomLevel; // 内层花瓣更紧凑 if (layer < roseParams.layers / 2) { r *= 0.8 + 0.2 * Math.sin(angle * 10); } // 外层花瓣更展开 else { r *= 1.0 + 0.2 * Math.sin(angle * 8); } // 添加花瓣卷曲效果 const curlFactor = Math.sin(angle * n * 0.7) * 0.1 * bloomLevel; r += curlFactor; return r; } // 绘制深度图 function drawDepthMap() { const centerX = roseCanvas.width / 2; const centerY = roseCanvas.height / 2; const size = Math.min(roseCanvas.width, roseCanvas.height) * roseParams.size; // 清除深度画布 depthCtx.clearRect(0, 0, depthCanvas.width, depthCanvas.height); // 重置深度数据 depthData = []; // 绘制多层花瓣(从内到外) for (let layer = 0; layer < roseParams.layers; layer++) { // 计算当前层参数 const layerFactor = layer / roseParams.layers; const layerSize = size * (0.6 + 0.4 * layerFactor); const layerBloom = bloomLevel * (0.6 + 0.4 * layerFactor); // 开始新路径 depthCtx.beginPath(); // 绘制花瓣(更精细的采样) for (let i = 0; i <= 360; i += 0.5) { // 计算角度(考虑旋转) const angle = (i + rotationAngle * 180 / Math.PI) * Math.PI / 180; // 使用更逼真的花瓣形状函数 const r = realisticPetalShape(angle, layer, layerBloom) * layerSize; // 3D效果:添加深度偏移 const depthOffset = Math.sin(angle * roseParams.petals + rotationAngle * 2) * roseParams.depth * layerSize * layerBloom; // 转换为3D坐标 let x = (r + depthOffset) * Math.cos(angle); let y = (r + depthOffset) * Math.sin(angle); let z = depthOffset * 2; // 应用透视变换 const scale = 300 / (300 + z); x = x * scale; y = y * scale; // 转换为屏幕坐标 const screenX = centerX + x; const screenY = centerY + y; // 存储深度数据(用于AO计算) depthData.push({ x: Math.floor(screenX), y: Math.floor(screenY), depth: z, layer: layer }); // 绘制线 if (i === 0) { depthCtx.moveTo(screenX, screenY); } else { depthCtx.lineTo(screenX, screenY); } } // 闭合路径 depthCtx.closePath(); // 填充深度(使用灰度值表示深度) const depthValue = (layer / roseParams.layers) * 255; depthCtx.fillStyle = `rgb(${depthValue}, ${depthValue}, ${depthValue})`; depthCtx.fill(); } } // 计算环境光遮蔽因子 function calculateAO(x, y) { if (!aoEnabled) return 1.0; const sampleRadius = 5; // AO采样半径 const centerDepth = getDepthAt(x, y); if (centerDepth === null) return 1.0; let occlusion = 0; let samples = 0; // 在周围采样 for (let dy = -sampleRadius; dy <= sampleRadius; dy += 2) { for (let dx = -sampleRadius; dx <= sampleRadius; dx += 2) { if (dx === 0 && dy === 0) continue; const sampleX = Math.round(x + dx); const sampleY = Math.round(y + dy); const sampleDepth = getDepthAt(sampleX, sampleY); if (sampleDepth !== null) { // 计算深度差异 const depthDiff = sampleDepth - centerDepth; // 如果周围点比中心点深(更靠近屏幕),说明有遮挡 if (depthDiff > 0) { // 距离衰减因子 const distance = Math.sqrt(dx*dx + dy*dy); const attenuation = 1.0 / (1.0 + distance); occlusion += depthDiff * attenuation; } samples++; } } } if (samples === 0) return 1.0; // 计算平均遮挡 occlusion = occlusion / samples; // 转换为遮蔽因子 (0-1, 1表示无遮挡) return Math.max(0, 1.0 - aoIntensity * occlusion); } // 获取指定位置的深度值 function getDepthAt(x, y) { // 查找最近的深度数据点 let closestDist = Infinity; let closestDepth = null; for (const point of depthData) { const dist = Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2)); if (dist < closestDist) { closestDist = dist; closestDepth = point.depth; } } return closestDepth; } // 绘制带AO效果的玫瑰花 function drawRose() { const centerX = roseCanvas.width / 2; const centerY = roseCanvas.height / 2; const size = Math.min(roseCanvas.width, roseCanvas.height) * roseParams.size; // 清除画布 roseCtx.clearRect(0, 0, roseCanvas.width, roseCanvas.height); // 绘制深度图(用于AO计算) drawDepthMap(); // 绘制多层花瓣(从内到外) for (let layer = 0; layer < roseParams.layers; layer++) { // 计算当前层参数 const layerFactor = layer / roseParams.layers; const layerSize = size * (0.6 + 0.4 * layerFactor); const layerBloom = bloomLevel * (0.6 + 0.4 * layerFactor); // 开始新路径 roseCtx.beginPath(); // 存储点用于绘制 const points = []; // 绘制花瓣(更精细的采样) for (let i = 0; i <= 360; i += 0.5) { // 计算角度(考虑旋转) const angle = (i + rotationAngle * 180 / Math.PI) * Math.PI / 180; // 使用更逼真的花瓣形状函数 const r = realisticPetalShape(angle, layer, layerBloom) * layerSize; // 3D效果:添加深度偏移 const depthOffset = Math.sin(angle * roseParams.petals + rotationAngle * 2) * roseParams.depth * layerSize * layerBloom; // 转换为3D坐标 let x = (r + depthOffset) * Math.cos(angle); let y = (r + depthOffset) * Math.sin(angle); let z = depthOffset * 2; // 应用透视变换 const scale = 300 / (300 + z); x = x * scale; y = y * scale; // 转换为屏幕坐标 const screenX = centerX + x; const screenY = centerY + y; // 存储点 points.push({x: screenX, y: screenY, z}); // 绘制线 if (i === 0) { roseCtx.moveTo(screenX, screenY); } else { roseCtx.lineTo(screenX, screenY); } } // 闭合路径 roseCtx.closePath(); // 设置基础颜色 let baseColor; switch (colorMode) { case 0: // 动态渐变 baseColor = `hsla(${(hue + layer * 12) % 360}, 80%, ${65 - layer * 3}%, 1)`; break; case 1: // 固定粉色 baseColor = `hsla(330, 80%, ${65 - layer * 3}%, 1)`; break; case 2: // 随机颜色 baseColor = `hsla(${(hue + layer * 25) % 360}, 80%, ${65 - layer * 3}%, 1)`; break; } // 应用环境光遮蔽 if (aoEnabled) { // 创建渐变填充(考虑AO) const gradient = roseCtx.createLinearGradient( centerX - layerSize, centerY, centerX + layerSize, centerY ); // 计算AO因子并应用 for (const point of points) { const aoFactor = calculateAO(point.x, point.y); const color = applyAOtoColor(baseColor, aoFactor); // 添加色标(简化处理) const pos = (point.x - (centerX - layerSize)) / (2 * layerSize); gradient.addColorStop(Math.max(0, Math.min(1, pos)), color); } roseCtx.fillStyle = gradient; } else { roseCtx.fillStyle = baseColor; } // 填充花瓣 roseCtx.fill(); // 添加花瓣边缘高光 roseCtx.strokeStyle = `hsla(${(hue + 40) % 360}, 100%, 85%, 0.2)`; roseCtx.lineWidth = 1; roseCtx.stroke(); } // 绘制花蕊(更精致) roseCtx.beginPath(); roseCtx.arc(centerX, centerY, size * 0.08, 0, Math.PI * 2); // 花蕊颜色 const centerHue = (hue + 180) % 360; const gradient = roseCtx.createRadialGradient( centerX, centerY, 0, centerX, centerY, size * 0.08 ); gradient.addColorStop(0, `hsl(${centerHue}, 100%, 85%)`); gradient.addColorStop(0.7, `hsl(${centerHue}, 100%, 60%)`); gradient.addColorStop(1, `hsl(${centerHue}, 100%, 40%)`); roseCtx.fillStyle = gradient; roseCtx.fill(); } // 应用AO到颜色 function applyAOtoColor(color, aoFactor) { // 解析颜色 const match = color.match(/hsla\((\d+),\s*(\d+)%,\s*(\d+)%,\s*([\d.]+)\)/); if (!match) return color; const h = parseInt(match[1]); const s = parseInt(match[2]); let l = parseInt(match[3]); const a = parseFloat(match[4]); // 根据AO因子调整亮度 l = Math.max(0, Math.min(100, l * aoFactor)); return `hsla(${h}, ${s}%, ${l}%, ${a})`; } // 动画循环 function animate(timestamp) { if (!lastFrameTime) lastFrameTime = timestamp; // 计算时间增量 const deltaTime = timestamp - lastFrameTime; lastFrameTime = timestamp; // 更新旋转角度(非常缓慢优雅地旋转) rotationAngle += rotationSpeed * deltaTime; // 更新花瓣盛开状态(非常缓慢变化) bloomLevel += roseParams.bloomSpeed * bloomDirection * deltaTime; // 限制花瓣开合范围 if (bloomLevel > 1) { bloomLevel = 1; bloomDirection = -1; // 开始闭合 } else if (bloomLevel < 0.2) { bloomLevel = 0.2; bloomDirection = 1; // 开始盛开 } // 更新颜色(非常缓慢变化) hue = (hue + deltaTime * 0.005) % 360; // 绘制玫瑰 drawRose(); // 继续动画循环 requestAnimationFrame(animate); } // 事件监听 function setupEventListeners() { // 按钮事件 document.getElementById('bloomBtn').addEventListener('click', () => { bloomDirection *= -1; // 反转盛开/闭合方向 }); document.getElementById('colorBtn').addEventListener('click', () => { colorMode = (colorMode + 1) % 3; // 循环切换颜色模式 }); document.getElementById('aoToggleBtn').addEventListener('click', () => { aoEnabled = !aoEnabled; // 切换AO效果 }); // 窗口大小变化 window.addEventListener('resize', () => { resizeCanvas(); }); } // 初始化 function init() { resizeCanvas(); initParticles(); setupEventListeners(); requestAnimationFrame(animate); } // 启动应用 window.addEventListener('load', init); </script> </body> </html> ``` ## 环境光遮蔽实现详解 ### 1. 深度图生成 我们创建了一个离屏Canvas (`depthCanvas`) 来存储深度信息: ```javascript function drawDepthMap() { // ...计算每个点的深度值 depthData.push({ x: Math.floor(screenX), y: Math.floor(screenY), depth: z, // 存储深度值 layer: layer }); // 在深度Canvas上绘制灰度图 const depthValue = (layer / roseParams.layers) * 255; depthCtx.fillStyle = `rgb(${depthValue}, ${depthValue}, ${depthValue})`; depthCtx.fill(); } ``` ### 2. AO因子计算 对于每个像素点,我们计算周围点的遮挡程度: ```javascript function calculateAO(x, y) { const sampleRadius = 5; // AO采样半径 const centerDepth = getDepthAt(x, y); let occlusion = 0; let samples = 0; // 在周围采样 for (let dy = -sampleRadius; dy <= sampleRadius; dy += 2) { for (let dx = -sampleRadius; dx <= sampleRadius; dx += 2) { const sampleX = Math.round(x + dx); const sampleY = Math.round(y + dy); const sampleDepth = getDepthAt(sampleX, sampleY); if (sampleDepth !== null) { // 计算深度差异 const depthDiff = sampleDepth - centerDepth; // 如果周围点比中心点深(更靠近屏幕),说明有遮挡 if (depthDiff > 0) { // 距离衰减因子 const distance = Math.sqrt(dx*dx + dy*dy); const attenuation = 1.0 / (1.0 + distance); occlusion += depthDiff * attenuation; } samples++; } } } // 计算遮蔽因子 (0-1, 1表示无遮挡) return Math.max(0, 1.0 - aoIntensity * (occlusion / samples)); } ``` ### 3. AO应用到颜色 将计算出的AO因子应用到颜色值上: ```javascript function applyAOtoColor(color, aoFactor) { // 解析颜色 const match = color.match(/hsla\((\d+),\s*(\d+)%,\s*(\d+)%,\s*([\d.]+)\)/); // 根据AO因子调整亮度 let l = parseInt(match[3]); l = Math.max(0, Math.min(100, l * aoFactor)); return `hsla(${match[1]}, ${match[2]}%, ${l}%, ${match[4]})`; } ``` ### 4. 花瓣绘制时应用AO 在绘制花瓣时,根据每个点的位置计算AO因子: ```javascript // 应用环境光遮蔽 if (aoEnabled) { // 创建渐变填充(考虑AO) const gradient = roseCtx.createLinearGradient(...); for (const point of points) { const aoFactor = calculateAO(point.x, point.y); const color = applyAOtoColor(baseColor, aoFactor); gradient.addColorStop(..., color); } roseCtx.fillStyle = gradient; } else { roseCtx.fillStyle = baseColor; } ``` ## AO效果带来的视觉提升 环境光遮蔽为玫瑰花带来了以下改进: 1. **增强立体感**:花瓣之间的交界处更暗,层次感更明显 2. **突出细节**:花瓣褶皱和凹陷处更暗,增强表面细节 3. **真实光照**:模拟真实环境中光线在凹陷处的衰减 4. **视觉深度**:增强场景的深度感知,使花朵更立体 通过点击"切换AO效果"按钮,您可以直观地看到环境光遮蔽带来的视觉差异。 ## 性能优化 为了平衡效果和性能,我们做了以下优化: 1. **简化采样**:使用2像素步长采样,减少计算量 2. **距离衰减**:对远处采样点使用衰减因子,减少影响 3. **离屏Canvas**:深度图在离屏Canvas上计算,不影响主渲染 4. **可选开关**:提供AO开关,在性能较差的设备上可关闭
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值