面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月29日 22:35

Solidity 智能合约有哪些常见安全漏洞?如何防止?

最致命的 5 类漏洞:重入攻击(Reentrancy)——用 Checks-Effects-Interactions 模式或 ReentrancyGuard;整数溢出——Solidity 0.8+ 内置检查,0.7 及以下用 SafeMath;权限控制缺失——关键函数加 onlyOwner / onlyRole,用 OpenZeppelin 的 AccessControl;闪电贷操纵价格——用 TWAP 而非现货价格,加交易延迟;前端运行(MEV)——用 commit-reveal 方案或私有内存池。核心原则:所有外部调用都是不安全的,所有用户输入都是恶意的。追问重入攻击为什么最难防?因为 transfer / call 会把控制权交给对方合约,对方可以回调你的函数,而你的状态还没更新。Checks-Effects-Interactions 模式强制先改状态再转账,ReentrancyGuard 用锁变量硬性阻止递归进入。两者都用最稳。0.8 之后真的不需要 SafeMath 了吗?算术运算溢出会自动 revert,是的。但类型转换溢出不检查——uint256 i = type(uint256).max; uint8 j = uint8(i) 会静默截断。unchecked 块内的运算也不检查,只在 gas 优化场景使用且确保不会溢出。如何防止闪电贷攻击?闪电贷让攻击者在单笔交易内借到巨量资金操纵价格后归还。防御:用 Uniswap V3 TWAP(时间加权平均价格)取代现货价格;限制单笔交易的滑点范围;加 block.timestamp 延迟阻止同区块操作。delegatecall 有什么安全隐患?delegatecall 在调用者上下文执行被调用者的代码——意味着被调用合约可以修改调用者的存储布局。如果 slot 对不上(存储碰撞),可能覆盖 owner 地址。代理合约模式必须严格对齐存储布局,用 OpenZeppelin 的透明代理或 UUPS 避免手动管理。审计工具能替代人工审计吗?不能。Slither / Mythril / Foundry Fuzz 能找到已知的模式型漏洞,但业务逻辑漏洞(如价格计算公式错误、奖励分配不公平)只能人工审查。工具 + 人工审计 + 测试网演练三者缺一不可。
服务端阅读 05月29日 22:35

Solidity 中如何处理时间锁(Timelock)机制?

时间锁就是给合约操作加一个延迟:提案创建后必须等待指定时间(如 48 小时)才能执行,期间可以取消。核心实现:mapping(bytes32 => uint256) public queuedTimestamp,queue() 记录时间戳,execute() 检查 block.timestamp >= queuedTimestamp[id] + delay。OpenZeppelin 的 TimelockController 是生产级实现,支持多角色( proposer / executor / admin)和最小延迟保障。追问Timelock 和 multisig 哪个更安全?不互斥,通常组合使用。Multisig 防止单点私钥风险,Timelock 防止即时作恶——即使 multisig 签了名,社区也有时间审查和反应。Uniswap、Compound 的治理都是 multisig + timelock 双层。如何防止 Timelock 被绕过?关键:delay 和 minDelay 只能通过 Timelock 自身的提案修改(self-governance),不能有外部 admin 直接改延迟。OpenZeppelin 的 TimelockController 默认就是这样——admin 角色也必须走提案流程。什么操作必须加时间锁?代币增发(mint)、升级代理合约(upgrade)、修改费率、提取资金——凡是影响用户资产的操作都该加。只读操作和紧急暂停(pause)通常不加,因为暂停是保护性操作。Timelock 的 gas 消耗如何?queue 和 execute 各约 5-8 万 gas,主要是 SSTORE 和权限检查。批量操作(batch / scheduleBatch)可以省一些,因为共享一次权限检查。如何实现可取消的时间锁?加 cancel(bytes32 id) 函数,只有 proposer 角色可以调用,删除 queuedTimestamp[id]。执行时如果找不到时间戳就 revert。争议操作被社区反对时,proposer 可以主动取消,避免硬分叉。
服务端阅读 05月29日 22:35

WebGL 缓冲区(Buffer)是什么?VBO 和 VAO 有什么区别?

WebGL 缓冲区就是 GPU 显存中的一块区域,用来存顶点数据(位置、颜色、法线、UV 等)。VBO(Vertex Buffer Object)是存数据的容器,VAO(Vertex Array Object)是记录"哪个 VBO 绑到哪个 attribute、偏移量多少、步长多少"的配置快照。WebGL 1 没有 VAO(需扩展 OES_vertex_array_object),WebGL 2 原生支持。有了 VAO,切换绘制对象只需 gl.bindVertexArray(vao) 一行,不用重复设置一堆 vertexAttribPointer。追问VAO 具体记录了哪些状态?每个 attribute 的启用状态(enableVertexAttribArray)、绑定的 VBO(vertexAttribPointer 时的 ARRAY_BUFFER)、数据偏移和步长、以及 ELEMENT_ARRAY_BUFFER 的绑定。不记录 ARRAY_BUFFER 本身的绑定——这点容易搞混。为什么 WebGL 1 没有 VAO?OpenGL ES 2.0 规范没包含 VAO,它从 OpenGL ES 3.0 / WebGL 2 才成为标准。WebGL 1 可以用扩展 OES_vertex_array_object,但不是所有设备都支持。项目兼容性要求高的话,自己封装一个 VAO 管理器,内部用数组存 attribute 配置,绑定时批量调用 vertexAttribPointer。EBO(IBO)和 VBO 什么关系?EBO(Element Buffer Object)也叫 IBO,存索引数据,告诉 GPU 按什么顺序读顶点,实现顶点复用(一个正方体 8 个顶点而非 36 个)。EBO 绑定到 ELEMENT_ARRAY_BUFFER,绘制时用 gl.drawElements 而非 gl.drawArrays。EBO 的绑定状态记录在当前 VAO 里。什么场景必须手动管理 Buffer?动态更新的数据(粒子系统、变形动画)需要 gl.bufferData 分配大小后用 gl.bufferSubData 局部更新,避免每帧重新分配显存。静态数据(模型网格)创建一次即可,设 gl.STATIC_DRAW 提示驱动放显存。多个 VBO 怎么组织到同一个 VAO?同一个 VAO 绑定期间,依次 bindBuffer + vertexAttribPointer 注册每个 VBO 到不同的 attribute location。也可以把所有数据交错打包到一个 VBO 里(interleaved),用 stride 和 offset 描述布局,减少 buffer 切换次数。
服务端阅读 05月29日 22:35

WebGL 雾效(Fog)是如何实现的?

WebGL 雾效的本质就是根据片段到相机的距离,在物体颜色和雾颜色之间做插值:finalColor = mix(fogColor, objectColor, fogFactor)。三种计算 fogFactor 的方式:线性雾 clamp((end - dist) / (end - start), 0, 1) 需要指定起止距离;指数雾 exp(-density * dist) 更自然,一个 density 参数搞定;指数平方雾 exp(-(density*dist)²) 过渡更柔和。深度值从视图空间的 -viewPos.z 或 length(viewPos.xyz) 获取,后者基于实际距离而非仅 Z 值,物体旋转时效果更稳定。追问线性雾和指数雾怎么选?线性雾可控性强,适合有明确近远边界的场景(如走廊)。指数雾只需一个 density 参数,远处自然消融,户外场景首选。指数平方雾过渡最柔和,但远处会突然消失,实际项目很少用。雾颜色一定要和背景色一致吗?是的,否则远处的物体会被雾染成另一个颜色,而不是"融入背景"。雾颜色 = 清屏颜色 = 天空盒颜色,三者必须统一。雾效能用来做性能优化吗?可以。远处的物体被雾覆盖后几乎看不见,可以降低远处物体的 LOD 级别甚至不渲染,雾正好遮住裁剪的接缝——这是开放世界游戏的常用技巧。Three.js 里的 Fog 和 FogExp2 有什么区别?THREE.Fog(color, near, far) 是线性雾,THREE.FogExp2(color, density) 是指数雾。设置 scene.fog = new THREE.Fog(...) 后所有材质自动应用,不需要改着色器。自定义 ShaderMaterial 需要手动读取 fogColor/fogDensity/fogFar/fogNear uniform。如何实现高度雾(Height Fog)?标准雾只看距离,高度雾额外考虑世界空间 Y 坐标:低处雾浓、高处雾淡。片段着色器中用 worldPos.y 做第二次混合,两个因子相乘就是最终雾浓度。
服务端阅读 05月29日 22:14

WebGL Cubemap 立方体贴图是什么?有哪些应用场景?

Cubemap 是 6 张正方形图片拼成的纹理盒子,用 3D 方向向量采样——GPU 根据向量哪个分量绝对值最大决定落在哪个面上,再换算成 2D 坐标取色。核心用途:天空盒、环境反射(reflect)、环境折射(refract)、菲涅尔效果。6 张图必须同尺寸且为 2 的幂次方,采样前务必设 CLAMP_TO_EDGE 防接缝。追问天空盒为什么必须去掉视图矩阵的平移分量?天空盒模拟无限远的环境,如果跟着相机平移,走两步就穿帮了。只保留旋转:mat4 rotOnly = mat4(mat3(viewMatrix))。reflect 和 refract 的区别?reflect(I, N) 计算反射方向——入射光弹回来,用于镜面/金属。refract(I, N, eta) 计算折射方向——光穿过透明介质弯折,用于玻璃/水。真实材质两者同时存在,用菲涅尔公式混合:正面看折射为主,侧面看反射为主。动态环境映射性能开销大怎么办?每帧渲染 6 个面代价太高。常用优化:降低分辨率(64×64 够了,反射本身就模糊)、降低更新频率(每 5-10 帧更新一次)、只给关键物体开动态反射。静态场景用预过滤环境贴图(Prefiltered Env Map),运行时零计算。Cubemap 接缝怎么处理?99% 是忘了设 CLAMP_TO_EDGE。设了还有缝,检查 6 张图边缘像素是否连续——很多在线生成工具会在接缝处偏移 1 像素。+Y 面图片经常上下颠倒,用 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) 翻转。Cubemap 和 Equirectangular(经纬度贴图)怎么选?Cubemap 6 张图,GPU 采样效率高,PBR 管线原生支持。Equirectangular 一张图,存储方便但两极有拉伸畸变,采样需要三角函数计算,性能差。实际工作流:用 Equirectangular 存储/传输,运行时转换为 Cubemap 使用。
服务端阅读 05月29日 01:55

Canvas 动画怎么实现?如何优化性能?

Canvas 动画本质是逐帧重绘:每帧调用 requestAnimationFrame 回调,在其中更新状态、清除画布、重绘所有元素,浏览器按刷新率(通常 60fps)将帧提交到屏幕。性能优化的核心思路是减少每帧的计算量和绘制量:用 OffscreenCanvas + Worker 将计算和预渲染移到后台线程;用对象池复用粒子避免 GC 压力;用脏矩形只重绘变化区域;分层 Canvas 将静态背景和动态前景分离;批量绘制减少状态切换。追问requestAnimationFrame 和 setInterval 做动画有什么本质区别?OffscreenCanvas 在 Worker 中如何与主线程 Canvas 同步?对象池模式怎么实现?复用对象时要注意清理哪些字段?什么是脏矩形优化?在什么场景下效果明显?分层 Canvas 的代价是什么?层太多反而更慢吗?写段代码// 离屏预渲染 + requestAnimationFrameconst offscreen = new OffscreenCanvas(60, 60)const offCtx = offscreen.getContext('2d')offCtx.fillStyle = 'coral'offCtx.beginPath()offCtx.arc(30, 30, 25, 0, Math.PI * 2)offCtx.fill()let x = 0function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.drawImage(offscreen, x, 100) x = (x + 2) % canvas.width requestAnimationFrame(animate)}animate()
服务端阅读 05月29日 01:55

Canvas 元素是什么?和 SVG 有什么区别?

Canvas 是 HTML5 的 <canvas> 元素,提供一块可编程的像素级绘图表面。通过 JS 获取 2D 渲染上下文(getContext('2d'))或 WebGL 上下文来绘制图形。核心用途:数据可视化、2D 游戏、图像处理、动画、视频特效。与 SVG 的关键区别:Canvas 是栅格模式(像素绘制,适合高频更新和大量对象),SVG 是矢量模式(DOM 节点,适合交互和缩放)。Canvas 不保留绘制对象,每帧是完整重绘。追问Canvas 2D 和 WebGL 上下文有什么区别?什么场景需要用 WebGL?Canvas 重绘时旧内容去哪了?为什么说 Canvas 是"立即模式"渲染?Canvas 如何实现像素级操作?getImageData 和 putImageData 的性能如何?Canvas 的 width/height 属性和 CSS 宽高不一致时会怎样?什么情况下应该选 SVG 而不是 Canvas?写段代码const canvas = document.querySelector('canvas')canvas.width = 400 // 设置分辨率canvas.height = 300const ctx = canvas.getContext('2d')ctx.fillStyle = '#4a90d9'ctx.fillRect(10, 10, 100, 80) // 绘制矩形ctx.font = '16px sans-serif'ctx.fillText('Hello Canvas', 120, 50) // 绘制文字
服务端阅读 05月29日 01:55

Canvas 路径绘制和图形变换怎么用?

路径绘制分三步:定义路径、闭合路径、渲染路径。先用 beginPath() 清除旧路径,moveTo() 定起点,再用 lineTo()/arc()/bezierCurveTo() 等绘制子路径,最后 closePath() 闭合并用 fill() 或 stroke() 渲染。图形变换通过坐标系变换实现:translate(x,y) 平移原点、rotate(angle) 绕原点旋转、scale(sx,sy) 缩放,transform(a,b,c,d,e,f) 直接设置变换矩阵。变换是累积的,用 save()/restore() 管理状态栈,避免污染后续绘制。追问beginPath() 和 closePath() 的区别是什么?不调用 closePath() 会怎样?save()/restore() 保存了哪些状态?变换矩阵和路径都会保存吗?transform() 和 setTransform() 有什么区别?多次 translate() 后坐标系如何变化?如何回到初始状态?如何用贝塞尔曲线绘制平滑曲线?控制点怎么选?写段代码ctx.save()ctx.translate(150, 150) // 移动原点到画布中心ctx.rotate(Math.PI / 6) // 旋转30度ctx.fillStyle = 'tomato'ctx.fillRect(-40, -40, 80, 80) // 以新原点为中心绘制ctx.restore() // 恢复原始坐标系
服务端阅读 05月29日 01:55

Nuxt.js 的路由系统和 Vue Router 有什么不同?

核心区别:Vue Router 需要手动在 router.ts 中声明路由表(path/component 映射);Nuxt 基于文件系统自动生成路由——pages/ 目录的文件路径即路由路径。例如 pages/user/[id].vue 自动映射为 /user/:id,pages/user/index.vue 映射为 /user。Nuxt 还内置了 layouts/ 布局系统、middleware/ 路由中间件(替代 Vue Router 的 beforeEnter/全局守卫),以及 NuxtPage 和 NuxtLayout 组件处理嵌套路由渲染。底层仍使用 Vue Router,但零配置。追问Nuxt 的嵌套路由怎么定义?和 Vue Router 的 children 有什么对应关系?路由中间件的执行顺序是什么?全局、布局、页面级中间件谁先执行?动态路由 [id].vue 在 SSG 模式下需要额外配置吗?如何在 Nuxt 中自定义 Vue Router 的 scrollBehavior?Nuxt 3 的 typedPages 实验特性是做什么的?写段代码<!-- pages/user/[id].vue --><script setup>definePageMeta({ middleware: 'auth', layout: 'dashboard'})const route = useRoute()const id = route.params.id</script>
服务端阅读 05月29日 01:55

Nuxt.js 中如何管理状态?useState 和 Pinia 怎么选?

Nuxt 3 推荐两种方案:内置的 useState() composable 和 Pinia(通过 @pinia/nuxt 模块集成)。useState() 适合跨组件共享简单响应式状态,天然 SSR 安全——服务端和客户端状态自动同步,无需额外处理水合。Pinia 适合复杂状态管理,支持 actions、getters、插件生态,且同样对 SSR 友好。Vuex 是 Nuxt 2 时代的默认方案,Nuxt 3 已不再内置。追问useState() 和 ref() 在 SSR 场景下有什么区别?为什么不能用 ref() 共享状态?Pinia store 在 Nuxt 3 中如何避免跨请求状态污染?useState() 的底层实现原理是什么?它是怎么保证 SSR 水合一致的?什么场景下 useState() 不够用,必须上 Pinia?Nuxt 3 的 useState() 可以在普通 JS 文件中使用吗?还是必须在 setup 中?写段代码// composables/useCounter.js - useState 方案export const useCounter = () => useState('counter', () => 0)// stores/user.js - Pinia 方案export const useUserStore = defineStore('user', () => { const user = ref(null) const login = async (creds) => { user.value = await $fetch('/api/login', creds) } return { user, login }})
服务端阅读 05月29日 01:55

Nuxt.js 的 SSG 和 SSR 有什么区别?怎么选?

SSG 在构建时(nuxt generate)遍历所有路由,执行数据获取逻辑,生成完整静态 HTML 文件,部署到任意静态服务器即可,请求零服务端开销;SSR 在每次请求时由 Node 服务动态渲染 HTML,内容实时但需要持续运行服务器。Nuxt 3 通过 routeRules 支持混合模式:同一个应用中,博客页 SSG、商品页 ISR(swr 缓存)、后台 SPA,按路由粒度选择渲染策略,不再需要二选一。追问ISR(Incremental Static Regeneration)在 Nuxt 3 里怎么实现?和 Next.js 的 ISR 有什么区别?SSG 构建时动态路由太多怎么办?如何按需生成?混合模式下 SSR 和 SSG 页面的 payload 传递机制有区别吗?SSG 页面的客户端数据怎么保持更新?Nitro 的预设(preset)对 SSG/SSR 部署有什么影响?写段代码// nuxt.config.ts - 混合渲染策略export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, // SSG '/blog/**': { swr: 3600 }, // ISR 1小时 '/dashboard/**': { ssr: false }, // SPA '/api/**': { cors: true } // API路由 }, nitro: { preset: 'cloudflare-pages' }})
服务端阅读 05月29日 01:54

Nuxt.js 的模块和插件有什么区别?

模块(Module)在 Nuxt 初始化阶段执行,能修改构建配置、注册插件和中间件,是 Nuxt 核心扩展机制,通过 nuxt.config 的 modules 数组注册。插件(Plugin)在 Vue 实例化前后执行,用于注册全局组件、指令或注入实例属性,放在 plugins/ 目录自动注册。关键区别:模块可以注册插件,反过来不行;模块可发布为 npm 包(如 @nuxtjs/axios),插件通常项目内使用。追问模块的执行时机为什么比插件早?如果插件需要修改 Nuxt 配置怎么办?modules 数组中的顺序会影响什么?多个模块冲突如何处理?Nuxt 3 中模块和插件的注册方式有什么变化?如何编写一个可发布的 Nuxt 模块?需要导出什么?插件的 mode 属性(client/server)在 Nuxt 3 中被什么替代了?写段代码// nuxt.config.js - 注册模块和插件export default { modules: ['@pinia/nuxt', '@nuxtjs/i18n'], plugins: ['~/plugins/vue-gtag.client.js']}// plugins/vue-gtag.client.jsexport default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use(VueGtag, { config: { id: 'G-XXX' } })})
服务端阅读 05月29日 01:54

Nuxt.js 应用性能优化有哪些关键手段?

Nuxt 性能优化分三层:构建层利用 Nitro 做 payload 提取,将 SSR 数据抽到独立 JSON 避免重复内联;渲染层通过 routeRules 按Route规则混合 SSR/SSG/SPA,静态页面用 prerender 避免运行时渲染开销;资源层用 @nuxt/image 自动生成响应式 srcset 和 WebP 格式,LazyHydrate 延迟水合非首屏组件。此外 Nuxt 3 基于 Vite 的按需编译和 tree-shaking 本身就比 Nuxt 2 的 Webpack 产物更小。追问payload extraction 具体做了什么?对 TTFB 有多大影响?@nuxt/image 的 ipx 服务是做什么的?和直接用 CDN 图片处理有什么区别?Nuxt 3 的 LazyHydrate 组件怎么用?和 Vue 的 Suspense 有什么关系?Nitro 的缓存策略(cachedFunctions)怎么配置?静态资源加 hash 缓存后,怎么处理版本更新问题?写段代码// nuxt.config.ts - 性能优化配置export default defineNuxtConfig({ modules: ['@nuxt/image'], routeRules: { '/blog/**': { swr: 3600 }, // ISR: 1小时缓存 '/admin/**': { ssr: false }, // SPA: 跳过SSR '/': { prerender: true } // SSG: 构建时生成 }, experimental: { payloadExtraction: true }})
服务端阅读 05月29日 01:54

Nuxt 3 有哪些数据获取方式?useFetch 和 useAsyncData 怎么选?

Nuxt 3 提供两个核心组合式函数:useFetch 和 useAsyncData。useFetch 是 useAsyncData + $fetch 的语法糖,99% 场景用 useFetch 即可;useAsyncData 适合需要自定义获取逻辑(如依赖多个 API 聚合数据)的场景。两者都在 SSR 时于服务端执行,结果序列化到 payload 中供客户端 hydration 复用,避免重复请求。底层 HTTP 客户端是 ofetch($fetch),支持服务端直接调用,无跨域问题。追问useFetch 的 key 是干什么用的?不写会怎样?SSR 时数据怎么从服务端传到客户端?hydration mismatch 怎么避免?$fetch 和 useFetch 的区别是什么?能不能在组件里直接用 $fetch?server/api 里的接口怎么和 useFetch 配合?如何给 useFetch 加请求缓存和去重?写段代码// 自定义 key + transformconst { data, pending } = await useFetch( `/api/user/${route.params.id}`, { key: `user-${route.params.id}`, transform: (res) => res.data, getCachedData(key, nuxt) { return nuxt.payload.data[key] } })
服务端阅读 05月29日 01:54

Nuxt.js 和普通 Vue 应用有什么区别?

Nuxt.js 是基于 Vue 的全栈框架,核心区别在于:普通 Vue 是纯客户端渲染 SPA,需要手动配置 Vue Router、Pinia、Vite 等生态;Nuxt 内置了 SSR/SSG/SPA 三种渲染模式、基于 pages/ 目录的文件路由、组件和组合式函数的自动导入、server/api 目录的 BaaS 风格服务端路由,以及 Nitro 服务引擎。普通 Vue 应用首屏为空壳 HTML,SEO 差;Nuxt 默认 SSR 可输出完整 HTML,对 SEO 和首屏性能更友好。追问Nuxt 3 的自动导入是怎么实现的?和手动 import 有什么性能差异?什么场景下应该选 SPA 模式而非 SSR?Nuxt 的 server/api 目录和传统后端 API 有什么区别?Nuxt 3 为什么用 Nitro 替代了 Nuxt 2 的服务端架构?如果项目从 Vue CLI 迁移到 Nuxt,最大的改造成本在哪?写段代码// nuxt.config.ts - 三种渲染模式切换export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, // SSG '/api/**': { cors: true }, // 服务端路由 '/dashboard': { ssr: false } // SPA }})
服务端阅读 05月29日 01:40

什么是区块链技术?请解释其核心特点和去中心化原理

区块链是一种分布式账本技术,通过密码学将数据区块按时间顺序链式连接,实现去中心化存储与验证。核心特点:去中心化(无单点控制,全网节点共持账本)、不可篡改(每个区块头包含前一区块哈希,篡改任一区块需重算后续所有区块哈希)、透明性(交易公开可验证)、安全性(共识机制防恶意攻击)。去中心化原理:传统系统依赖中心服务器,区块链中所有节点对等存储完整账本,通过共识算法(PoW/PoS/PBFT)对新区块达成一致。公有链任何人可参与,联盟链需授权,私有链由单一组织控制。追问哈希指针如何保证区块不可篡改?如果攻击者控制了51%算力会发生什么?PoW、PoS、PBFT 三种共识机制分别适用于什么场景?它们的吞吐量和最终确认时间有何差异?默克尔树在区块链中起什么作用?轻节点如何利用默克尔证明验证交易?公有链、联盟链、私有链在去中心化程度上有什么区别?各自适合什么业务?区块链的"不可能三角"指什么?为什么无法同时满足去中心化、安全性和可扩展性?写段代码// 简化版区块链:哈希链与工作量证明class Block { constructor(data, prevHash) { this.data = data; this.prevHash = prevHash; this.nonce = 0; this.hash = this.mine(); } mine() { let h; do { this.nonce++; h = sha256(this.prevHash + this.data + this.nonce); } while (!h.startsWith('0000')); // 难度:前4位为0 return h; }}class Chain { constructor() { this.blocks = [new Block('genesis', '0')]; } add(data) { this.blocks.push(new Block(data, this.blocks.at(-1).hash)); } isValid() { return this.blocks.every((b,i) => i===0 || b.prevHash === this.blocks[i-1].hash); }}
服务端阅读 05月29日 01:40

WebGL 中的阴影(Shadow)是如何实现的?

WebGL 阴影的主流实现方式是阴影贴图(Shadow Mapping):先从光源视角渲染场景生成深度图,再从相机视角渲染时将每个片段变换到光源空间,比较其深度与深度图中的值——若片段深度更大则处于阴影中。软阴影通过 PCF(Percentage Closer Filtering)对深度图多次采样取平均实现。两大典型问题:阴影痤疮(shadow acne)由深度精度误差导致,用基于法线-光线夹角的 bias 修复;彼得潘效应(peter panning)由 bias 过大导致阴影脱离物体,改用正面剔除渲染深度图解决。大场景用级联阴影贴图(CSM)按距离分配不同分辨率。追问为什么阴影贴图会产生阴影痤疮?bias 值如何根据表面法线和光线方向动态计算?PCF 软阴影的采样半径越大越模糊,但会带来什么性能问题?Poisson Disk 采样有何优势?级联阴影贴图(CSM)如何划分级联?级联接缝处的接缝问题怎么处理?点光源阴影为什么用立方体贴图?渲染立方体阴影贴图需要几次 draw call?WebGL 1.0 没有深度纹理附件,如何用 RGBA 编码深度值?精度损失如何补偿?写段代码// PCF 软阴影核心逻辑float calcShadow(vec4 lightPos, sampler2D shadowMap, vec3 normal, vec3 lightDir) { vec3 proj = lightPos.xyz / lightPos.w * 0.5 + 0.5; float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); float shadow = 0.0; vec2 texel = 1.0 / vec2(textureSize(shadowMap, 0)); for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) { float d = texture(shadowMap, proj.xy + vec2(x,y)*texel).r; shadow += proj.z - bias > d ? 1.0 : 0.0; } return shadow / 9.0;}
服务端阅读 05月29日 01:39

WebGL 是什么?它与 OpenGL 有什么关系?

WebGL 是基于 OpenGL ES 的浏览器端 3D 图形 API,关系链为 OpenGL → OpenGL ES → WebGL。WebGL 继承了 OpenGL ES 的可编程渲染管线,通过 GLSL 编写顶点着色器和片段着色器控制 GPU 渲染,但运行在浏览器沙箱中,用 JavaScript 调用,且去除了 OpenGL 的固定管线。核心渲染流程:顶点数据经顶点着色器变换 → 图元装配 → 光栅化 → 片段着色器着色 → 帧缓冲输出。WebGL 1.0 基于 OpenGL ES 2.0,WebGL 2.0 基于 OpenGL ES 3.0。追问WebGL 和 Canvas 2D 的本质区别是什么?Canvas 2D 能调用 GPU 吗?顶点缓冲对象(VBO)的作用是什么?为什么不用 CPU 每帧传顶点数据?WebGL 的统一变量(uniform)和属性变量(attribute)分别在什么阶段使用?WebGL 2.0 相比 1.0 新增了哪些关键能力?VAO 和 3D 纹理对开发有什么影响?浏览器如何保证 WebGL 的安全性?哪些 OpenGL ES 特性被有意移除了?写段代码// WebGL 初始化:创建着色器程序并绘制三角形const gl = canvas.getContext('webgl');const vs = gl.createShader(gl.VERTEX_SHADER);gl.shaderSource(vs, 'attribute vec2 a_pos; void main(){ gl_Position=vec4(a_pos,0,1); }');gl.compileShader(vs);const fs = gl.createShader(gl.FRAGMENT_SHADER);gl.shaderSource(fs, 'precision mediump float; void main(){ gl_FragColor=vec4(1,0,0,1); }');gl.compileShader(fs);const prog = gl.createProgram();gl.attachShader(prog, vs); gl.attachShader(prog, fs);gl.linkProgram(prog); gl.useProgram(prog);const buf = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, buf);gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0.5, -0.5,-0.5, 0.5,-0.5]), gl.STATIC_DRAW);const loc = gl.getAttribLocation(prog, 'a_pos');gl.enableVertexAttribArray(loc);gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);gl.drawArrays(gl.TRIANGLES, 0, 3);