AI 生成 3D 场景拆解 03:太阳神庙
Revio ·
学习 Techartist 的太阳神庙,拆解程序化建筑与砂岩地形、TSL 材质、太阳盘光效、建筑开合与祭司骨骼动画、镜头和光照,并将可复用的建模与动作编排方法补进 Skill。

在本站体验太阳神庙 Demo · 原作者 GitHub 仓库
Sun Temple 是一座嵌在砂岩山壁前的风格化神庙。打开场景后,我们可以旋转和缩放视角,也可以切换主镜头、正立面与高处视角。点击底部的 Open Solar Court,两侧柱廊向外移动,中央阶梯抬成平台,太阳盘升起并逐渐发光,一位祭司从门内走出,停在庭院里挥手。再次点击按钮,她先转身返回,建筑随后恢复闭合。
本篇参考学习的 Demo 来自 Techartist(GitHub 用户 iamtechartist),原地址为 github.com/iamtechartist/sun-temple。场景设计和原始实现归原作者,项目采用 MIT 许可证,Copyright (c) 2026 Techartist。本文由 Revio 整理,配图来自本次运行 Demo 的实际截图。
前两篇分别拆解了海湾中的水体和山谷中的铁路。这一篇我们把注意力放在建筑与动作编排上:神庙怎样用简单几何搭出来,砂岩怎样在没有石材贴图的情况下保持统一质感,建筑、光效和人物又怎样配合完成一次开启仪式。原仓库没有说明作者使用了哪些 AI 工具;文末的 Skill 是我们从源码中整理出的复用方法。
技术栈
项目把页面、样式、场景代码和两份人物资产集中在一个 index.html 中,使用原生 JavaScript ES Modules,通过 import map 引入 Three.js r185(0.185.0)。本篇固定分析提交 c247c0fd,以下实现都可以在这个版本的 index.html 中找到。
| 部分 | 实际使用的技术 | 作用 |
|---|---|---|
| 渲染 | WebGPURenderer、PCF 阴影、ACES 色调映射 | 绘制场景与统一输出亮度 |
| 材质 | TSL、MeshStandardNodeMaterial、MeshBasicNodeMaterial | 砂岩、天空、太阳盘与透明光效 |
| 几何 | BufferGeometry、RoundedBoxGeometry、LatheGeometry、CylinderGeometry、TorusGeometry | 山体、建筑、柱头、雕像和太阳盘 |
| 重复部件 | InstancedMesh、共享几何 | 台阶、柱子与装饰的批量绘制 |
| 人物 | FBXLoader、SkinnedMesh、AnimationMixer | 加载角色、播放行走和挥手动作 |
| 动作编排 | 归一化进度、分段缓动、状态机 | 同步建筑、太阳盘与人物 |
| 交互 | OrbitControls、HTML 按钮 | 旋转、缩放、视角切换与庭院开合 |
TSL 用 JavaScript 节点表达材质计算。比如 positionWorld 提供世界坐标,normalWorld 提供表面法线,sin、mix 和 smoothstep 组合成纹理与遮罩,最后交给 Three.js 生成着色器。它仍然是在 GPU 上逐顶点或逐片元计算,只是我们组织公式的方式与直接书写 GLSL 不同。
WebGPURenderer 优先使用 WebGPU,Three.js 在不能创建 WebGPU 后端时具有 WebGL 2 回退路径;项目本身没有提供后端切换按钮,也没有单独写两套材质。本站版本将原 import map 中的 CDN 地址换成本地依赖,保留 r185、原场景逻辑和内嵌 FBX 内容,方便直接访问。
程序化生成
这个项目采用了混合方式。神庙、台阶、山壁、沙地、太阳盘、天空和太阳光效都由代码构造,砂岩颜色与太阳纹理由材质公式计算。祭司则来自仓库附带的 walking.fbx 与 waving.fbx,人物网格、骨骼、贴图和动作不是现场生成的。
两份 FBX 也以 Base64 形式内嵌在 HTML 中。本次逐字节核对,它们分别与仓库中的两个独立文件完全相同。运行时会先读取内嵌数据,解码为 ArrayBuffer,再交给 FBXLoader 解析;只有内嵌内容缺失时,才会尝试备用模型地址。人物的颜色贴图同样嵌在 FBX 内部。
我们用同版本 FBXLoader 解析了这两份资产:每份包含一个蒙皮网格、24 根骨骼和一个材质,几何为 9,215 个三角形。行走片段约 1.03 秒,挥手片段约 4.10 秒。解析时用占位 Texture 跳过图片解码,这些数字用于确认文件结构,真实贴图与画面则另在浏览器中检查。
建筑布局也不是任意随机生成。柱子的位置、门框层数、山体体块和沙丘位置都有明确参数,左右主体通过对称循环构造。固定种子噪声负责岩壁与地表的细节变化。我们可以把它理解为一座由参数搭建、可重复生成的指定场景:修改宽度、间距或材料参数,就能调整同一种建筑语言。
构图
神庙的视觉中心在中央门洞和上方太阳盘。阶梯从画面前方指向入口,两侧柱廊提供横向展开的尺度,背后的山壁把视线收回建筑。人物走出后,她的身高成为尺度参照,让太阳盘和建筑更显高大。

从正立面看,构图依赖中轴对称与重复门框;主镜头略偏侧面,则能看见台基的厚度、台阶的斜坡和柱子的前后关系。高处视角用于查看庭院、山体和平台的连接。它们是为同一座建筑选出的三种观察方式。
造型中有纸莎草式柱头、安卡符号和太阳盘等古埃及意象。这里适合学习其比例和层次组织,源码并没有声明这是某一座历史神庙的精确复原。
神庙
台基与墙体
建筑首先用台基、平台、塔门、上层楼廊和屋顶这些大体块确定轮廓。makeFrustum 用上下两层共八个顶点生成截头体:上下面尺寸不同,侧面因此带有收分,塔门和基础就不会像直接堆叠的直立方盒。
常规石块由 addBlock 创建。它复用一份单位 RoundedBoxGeometry,再通过位置、旋转和缩放得到梁、墙、石框与栏杆。共用基础几何可以降低重复创建网格数据的成本,小倒角也让石块边缘更容易接住光。
不过单位几何经过非等比缩放,倒角宽度也会随轴向改变。如果我们要复用到近距离建筑展示,就需要按部件尺寸生成倒角,或显式控制实际倒角半径;这份 Demo 主要依靠整体构图和光照,采用了更简单的处理。
基础的楔形几何还为前方斜面留出了顶点控制,中央升降区域后面会直接修改其中两个顶点。这里的建模表示方式与动画需求从一开始就是对应的。
柱子与檐口
左右柱廊各有五根柱子。柱脚、柱身和环状束带主要用 CylinderGeometry,柱身有轻微锥度。复杂一点的柱头采用 LatheGeometry:先写一条从下向上的半径轮廓,再绕竖直轴旋转。
轮廓点:(r_i, y_i)
旋转表面:P(i, θ) = (r_i × sin θ, y_i, r_i × cos θ)
r_i 决定这一高度的宽窄,y_i 决定高度。轮廓向外展开再收束,就能形成花苞或纸莎草束状的柱头;底部收窄、中部鼓起,也能得到瓶身、基座和装饰雕像。项目柱头使用 20 个旋转分段,门口的简化雕像使用 18 个分段。
檐口采用另一种方式。makeProfileXGeometry 在 yz 平面写出有台阶与外挑的截面,复制到左右两端,再用侧面连接,最后用 ShapeUtils.triangulateShape 封住端面。也就是“固定轮廓 + 沿 x 方向拉长”。这样可以一次生成有层次的长檐口,无须给每个截面台阶单独加一排方块。
这些重复结构按几何和材质组合成 InstancedMesh。柱廊展开时移动的是整组柱廊,柱头、梁、檐口和装饰仍保持彼此的相对位置。
门洞与装饰
中央入口共有五层逐渐缩小的门框,沿深度方向排列。每层由左右石柱和顶部横梁组成,门洞自然留在中间,因此不需要先建实体墙,再用布尔运算挖洞。重复框架带来明确的透视节奏,入口看起来比一个平面上的黑矩形更深。
最深处放置暗色面板,地面也使用压暗的材质。它负责遮住场景末端并提示室内空间;这里没有完整建模整座神庙的内部房间。我们在正面构图中需要的是可信的入口深度,五层门框与暗部已经足以支撑这一观察范围。
塔门浮雕也采用层次组合:先放深色底板,再加浅色边框、圆盘和几组竖向符号。安卡装饰由环、竖杆和横杆组成。它们是固定图形的几何拼装,没有象形文字生成器,也没有浮雕贴图或通用雕刻算法。
台阶
前方阶梯分为左右固定区与中央活动区。34 排台阶中,左右一共 68 个实例保持原来的阶梯形态,中央 34 个实例参与抬升。关闭时每排有自己的高度;开启时,它们的中心逐步汇到同一个高度。
y_step(i, p) = lerp(y_closed(i), 12.47, p)
y_foundation_front(p) = lerp(0.35, 12.72, p)
p 是经过分段缓动的台阶进度。中央台阶厚度为 0.56,所以完全开启时顶面约为 12.75;每排深度为 1.86,略大于排间距,抬平后相邻台阶有覆盖,组成连续平台。地基前方的两个顶点同步抬高,避免台阶下方出现空隙。
每帧先更新实例矩阵,再标记 instanceMatrix.needsUpdate;基础网格顶点改变后则重新计算法线。整个过程中都沿用原网格与材质,没有在某个时刻突然切换成另一套平台模型。

地形
沙地
沙地是宽 300、深 255、细分 100×86 的网格。基础高度上叠加两组方向不同的正弦起伏和四层 value noise,形成低矮、连续的沙面。
value noise 的做法是先在整数网格上生成散列值,再对相邻四个角插值。源码的散列核心可以写为:
hash(x,z,seed) = fract(sin(127.1x + 311.7z + 74.7seed) × 43758.5453123)
f(t) = t²(3 - 2t)
noise = 四个角的 hash,按 f(fract(x))、f(fract(z)) 做双线性插值
fract 取小数部分,使输出落在 0 到 1。直接插值会在网格边界留下明显的斜率变化,三次平滑函数让过渡更柔和。fbm 再叠加四个尺度:初始振幅 0.56,每层乘 0.47,频率每层乘 2.02。大尺度决定缓慢起伏,小尺度补细节。
建筑附近还需要更平整的基础,所以作者使用一个四次幂指数遮罩降低地表波动:
M(x,z) = exp(-(x/74)^4 - ((z-10)/82)^4)
h(x,z) = -1.35 + (wind(x,z) + noise(x,z)) × (1 - 0.76M)
遮罩中心在神庙附近,向外逐渐减弱。它让建筑区域的起伏降到原来的约 24%,而不是硬切成一块完全平坦的矩形。这个高度场用于沙地本身,项目没有另外提供通用的建筑落脚采样接口。
局部堆沙由六个单独的网格补上。每个网格把平面位置映射成归一化坐标 u、v,用椭圆范围控制沙堆轮廓:
falloff = max(0, 1 - u² - v²)
crest = 0.86 + 0.22 × exp(-((u + skew × v) × 1.8)²)
h = -0.78 + falloff^1.6 × height × crest
height 控制沙堆高度,skew 让隆起的脊线偏斜,falloff 让边缘回落。椭圆外的网格没有被裁掉,而是保留在基底高度。地面的大尺度起伏与局部沙堆分开,便于把积沙放在台基前和山壁旁。
山壁
山壁由四块实体状网格组合:背部大山体、左右斜放的侧翼,以及向上叠起的顶部岩块。每块都生成前后两层顶点、各自的面片和周边封口。它们有厚度与侧面,可以经受当前相机范围内的侧向观察。
顶边由低频 fbm 改变高度,正面则叠加三种细节:噪声产生不规则起伏,随高度变化的正弦条带表现岩层,窄而高的峰值形成横向岩架。
shelf(y) = max(0, sin(0.41y + 0.73seed))^13 × 1.45
13 次幂把正弦的宽峰压成较窄的凸起,岩层因此能在几何上形成突出边缘。两条竖向沟槽则用负高斯函数向内凹陷:
gully(x,y) = -depth × exp(-((x - center(y))/width)²)
沟槽中心随高度缓慢摆动,宽度控制凹槽范围,深度控制切入岩面的距离。作者为每块岩体设置不同的固定种子、尺寸与偏移,使它们有共同的砂岩语言,又不完全重复。

这里没有进行侵蚀模拟,也没有把神庙从山体里做布尔切割。视觉上的“岩壁神庙”由建筑靠在山壁前、岩体包围两侧、色彩与纹理相互协调形成。真正改变轮廓的是山壁几何,微小颗粒则交给下面的材质处理。
砂岩材质
makeSandstone 给神庙、岩壁和沙地提供同一套材质计算,不同对象换用不同的色板、尺度与凹陷参数。基础色是暖棕,混入赭石、浅沙色和深褐色,粗糙度限制在 0.72 到 0.98,金属度为 0。
纹理从世界坐标 positionWorld 出发,因此相邻石块可以读到连续的岩层,不必先制作 UV。低频正弦控制大片色差,中频条带表现沉积层,散列颗粒形成少量深色斑点。条带中的一项是:
B = 0.5 + 0.5 × sin(0.43y + 1.8sin(0.018x) + 0.7sin(0.021z))
主变化沿 y 方向,小幅 x、z 扰动让条带不至于完全笔直。这里的 x,y,z 已经乘过该材质的尺度参数。不同表面使用相同公式,所以颜色相近的建筑与山体能保持连贯。
材质还读取法线。朝上的表面多混一点浅色,朝向太阳的表面增加暖色与亮度,凹陷区域根据显式 recess 参数压暗。这些是作者写进材质的调色规则,之后仍由标准材质接受实际光照。凹陷参数不是屏幕空间 AO,也没有通过射线计算遮挡。
颗粒散列 fract(sin(dot(p,k)) × c) 与沙地的平滑 value noise 不同,它没有网格插值,更适合制造细碎色差。它与细条带也参与粗糙度计算,让表面各处对光的响应略有差别。
这一套砂岩没有法线贴图、凹凸贴图或材质位移。细节改变的是颜色与粗糙度,真正的岩架、沟槽和倒角仍由几何承担。我们复用时应该先保证大轮廓,再增加材质层次。
世界坐标纹理也有一个需要留意的结果:柱廊移动时,石块会经过世界中的纹理场,条带不会完全固定在石块表面。如果需要长期观察运动部件,可对它们改用局部坐标,并明确保存纹理尺度。
太阳盘
几何结构
太阳盘本身是一件有厚度的石雕。主体是半径 7.3、厚度 1.18 的圆柱,外围有 TorusGeometry 做出的圆环,中间有凸出的圆柱轴心,周围再布置 12 根放射状石条。整组太阳盘沿石质导轨从高度 66.7 升到 79,同时绕正面的轴旋转。
圆柱默认沿局部 y 轴延伸,作者把它绕 x 轴旋转了 π/2,使圆盘正面朝向神庙前方。因此圆柱材质里的径向距离用局部 x、z 计算,而透明光晕面片使用局部 x、y。我们提取着色器时需要保留这个坐标区别,否则圆形遮罩会变成错误的条带。
表面纹理
太阳盘从砂岩状态过渡到燃烧状态,使用 solarIgnition 这个 0 到 1 的 uniform 控制。盘面、圆环、轴心和石条使用同一类材质,只改变发光强度、中心遮罩与时间偏移。
火焰纹理由几组随时间变化的正弦函数组合。下面是对应源码的标量形式,坐标来自材质局部位置,t 是共享时间加上部件的相位偏移:
A = 0.5 + 0.5sin(1.34x + 0.72z + 2.15t)
B = 0.5 + 0.5sin(-0.63x + 1.91z - 1.47t + 2.4A)
C = 0.5 + 0.5sin(2.72x + 2.1sin(1.18z - 0.92t))
F = clamp(0.46A + 0.38B + 0.30C, 0, 1)^3.2
A、B 的方向和速度不同,B 又受 A 扰动,C 增加局部交错。3.2 次幂压低中间亮度,保留少量强亮区,所以盘面上能看到游动的亮丝,而不是整块均匀发橙。
颜色从暗红、红到橙色混合,黄色只占有限比例,中心区域额外加亮。ignition 同时控制砂岩色与火焰色的混合、发光值和粗糙度:
color = mix(dormantStone, fireColor, ignition)
emissive = fireColor × ignition × glowStrength
× (0.945 + 0.055sin(2.4t)) × (0.42 + 0.48F)
roughness = mix(0.82, 0.38, ignition)
材质从粗糙石面变成更亮、更平滑的发光表面,脉冲幅度只有 5.5%,整体亮度不会剧烈跳动。这些计算是对火焰外观的表达,没有流体网格、温度场或燃烧模拟。
光晕与火舌

太阳盘周围的光晕来自两张透明平面,尺寸分别为 36 和 31。平面材质先按到中心的距离 r 定义一个圆环:
outside = smoothstep(6.55, 7.34, r)
falloff = 1 - smoothstep(7.5, 15.8, r)
alpha ∝ ignition² × outside × falloff
outside 压掉盘面内部,falloff 让外缘逐渐透明,两个遮罩相乘得到环形范围。再加入随时间移动的径向波纹、斜向扰动和小幅闪烁,光环就有了层次。透明度使用 ignition²,点火初期更克制,后期才明显扩散。
外侧还有 22 片放射状火舌。每片仅用五个顶点、三个三角形形成窄长轮廓,按照圆周分布,并根据固定正弦值给出不同长度。它们复用三份材质,材质沿局部 y 方向控制根部到尖端的衰减,再用时间项改变亮度,看起来像能量向外流动。
两层光晕与火舌采用 AdditiveBlending、双面渲染,并关闭深度写入。加法混合让重叠区域更亮,透明外缘不会留下实体方框;关闭深度写入则避免透明面提前挡住后续光效。作者还为三组效果设置了 12、13、14 的 renderOrder,保持绘制次序。它们仍然接受深度测试,可以被前方实体遮挡。
光晕和火舌分别以很小的角速度转动,有的方向相反,让几层细节不完全同步。它们固定在太阳盘所在的平面,并没有每帧转向相机。因此这套效果与当前以建筑正面为主的观察范围是配合的;若扩展为自由绕行,需要补上侧面体积或重新设计光效。
环境照明
太阳盘前方还放了一盏橙色 PointLight,跟随盘组移动。它的强度由点火进度控制,再叠加两组小幅周期变化:
I = ignition × (155 + 18sin(2.65t) + 7sin(5.7t))
发光材质让盘面自身变亮,透明面片扩大屏幕上的亮轮廓,点光源则照亮附近建筑。三者各负责一个部分,不能相互替代。
项目没有使用 Bloom 后处理,也没有体积光步进。我们看到的柔亮边缘是已经建好的光效几何在相机中叠加出来的。复用这套做法时,可以把主体、外轮廓与环境照明分别调节,比一开始就把发光值拉得很高更容易控制画面。
建筑动画

开启顺序
建筑动作由一个总进度 transformation.value 控制,范围是 0 到 1。点击按钮只改变目标值,每帧按 delta / 6.5 推进或退回,所以建筑开启的设计时长为 6.5 秒。各部件从总进度里取自己的时间区间。
| 部分 | 总进度区间 | 开启时的约定时间 | 动作 |
|---|---|---|---|
| 柱廊 | 0~0.92 | 0~5.98 秒 | 左右整组各外移 16 |
| 中央阶梯 | 0.015~0.87 | 0.10~5.66 秒 | 抬平台阶与前方基础 |
| 塔门 | 0.03~0.90 | 0.20~5.85 秒 | 左右各外移 5.5 |
| 太阳盘 | 0.08~0.94 | 0.52~6.11 秒 | 上升 12.3 并旋转 |
| 点火 | 0.62~0.985 | 4.03~6.40 秒 | 渐变材质、光晕与点光源 |
| 祭司出场 | 总进度达到 0.72 | 约 4.68 秒开始 | 从门内走向庭院 |
时间来自进度乘以 6.5 的换算,假设动画时间正常推进。祭司走出还需要 6 秒,所以从点击开启到她站定约为 10.68 秒,不能把建筑完成的 6.5 秒当作整段演出的时长。
这些阶段有重叠:柱廊还在移动,太阳盘已经升起;盘面开始点火时,人物准备出场。于是我们看到一段连续动作,而不是六个互不相关的开关。
缓动
heavySegment 先把总进度映射到部件的局部进度,再使用五次平滑函数:
u = clamp((value - start) / (end - start), 0, 1)
E(u) = 6u^5 - 15u^4 + 10u^3
E 在两端的一阶、二阶导数都为零,因此位移能平稳起步、平稳停止。对较重的石块来说,这种变化比线性插值更符合我们对缓慢启动与收住动作的预期。
最后 22% 的进度还叠加一个很小的衰减振荡:
q = (u - 0.78) / 0.22
E += 0.012 × sin(2πq) × (1-q)² // 仅在 0.78 < u < 1 时加入
它让部件在终点附近轻微回摆,幅度随 q 接近 1 而消失。这是作者设计的动作曲线,没有质量、阻尼或弹簧的物理求解。
打开过程中即使再次点击关闭,各部件仍从当前 value 计算位姿,可以沿原来的形变关系退回。关闭时如果人物已走出,情况则更复杂,需要下面的角色状态机先完成返回。
返回与中断
每帧更新总进度前,源码检查:目标是否为关闭,祭司是否就绪,以及她的路径进度是否大于零。只要人物还在外面,建筑进度就暂时保持不动。她回到门内并隐藏后,建筑才开始收拢。
这个检查解决了平台、人物和建筑之间的依赖。它不需要给每块石头安排碰撞体,也能让这段预设动作保持合理的先后关系。我们实际运行了开启、完整关闭,以及返回刚开始时再次开启:按钮切换正常,重新开启后人物与平台继续保持在当前演出中,没有重载场景。
太阳盘在总进度超过 0.9 时,还会积累一小段持续旋转。这个角度保存在 discSpin 中,关闭时没有清零;共享时间 solarClock 也一直前进。因此关闭再打开并不是把每个细节都复原到同一帧,材质流动和累计转角可以继续延续。
祭司
模型与骨骼动画
人物由 walking FBX 的根对象进入场景,缩放为 0.052,并放在中央入口。waving FBX 提供挥手 AnimationClip,但第二个角色根对象没有加入场景。两个片段交给同一个 AnimationMixer,绑定到正在显示的角色上。
这依赖两份资产中的骨骼命名与动画绑定兼容。源码没有执行一套通用重定向算法,也没有自动修复不同骨架的比例。如果换人物,我们需要先检查动作轨道能否找到对应骨骼,并核对朝向、单位和站立高度。
行走和挥手都设置为无限循环,播放速度分别是 1.14 和 0.94。走到目标位置时,挥手动作重置并用约 0.72 秒淡入,行走动作淡出;开始返回时,再切回行走。AnimationAction 的权重过渡让两段姿态有一段混合,不会在切换时直接跳帧。
载入时作者还把角色贴图设为 sRGB,启用投射和接收阴影,并关闭网格的视锥剔除,防止骨骼动作超出静态包围盒时角色突然消失。角色材质被统一设为不透明并写入深度;如果我们替换成带透明发片的角色,这项统一设置需要重新评估。
两份 FBX 都触发了“顶点超过四个蒙皮权重”的加载器警告。r185 的 FBXLoader 选择其中较大的四项,角色网格再归一化权重。原页面暂时过滤了这条警告,它只是隐藏提示,并没有增加蒙皮影响数量。复用时应该检查实际变形效果,必要时在导出阶段整理权重。
路径与转身
骨骼动画负责腿、手臂和身体姿态,人物在庭院里的整体位置则由代码控制。路径进度 s 从 0 增到 1,根对象沿中央直线移动:
z(s) = lerp(-36, 1.5, s)
d = clamp((s - 0.12) / 0.46, 0, 1)
y(s) = lerp(16.2, 13.53, d²(3-2d))
她从较高的门内位置走到庭院,竖直方向用平滑函数下降。这里没有 Root Motion、导航网格、逐脚 IK 或实时地面射线检测。角色位置与当前平台是作者按这条演出路线协调好的。
出门用 6 秒,返回用 5.6 秒。源码没有根据脚步长度反推整体速度,因此替换动作或修改距离后可能出现滑步。更稳妥的复用方式是同时调整路径长度、动作速度与行程时间,再观察脚是否贴着平台移动。
角色状态包括隐藏、走出、挥手、转向门内、走回与重新转向外侧。关闭时先留出约 0.68 秒转身,再往回走;返回途中重新开启,则给约 0.42 秒转向,然后从当前路径进度继续走出。
转身首先把角度差归到最短方向,再按时间进行指数平滑:
α = 1 - exp(-λ × delta)
heading_next = heading + shortestAngle(target - heading) × α
这样不会因为从 359° 转到 1° 而绕行几乎一整圈。按 delta 计算也让不同帧率下的转向速度更接近一致。到达入口后角色隐藏,两个动作暂停,建筑便可以继续关闭。
镜头
项目使用 41° 视野的透视相机,近裁剪面为 0.2,远裁剪面为 560。三个预设如下,坐标与场景共用同一单位:
| 镜头 | 相机位置 | 观察目标 |
|---|---|---|
| Hero | (84, 38, 152) | (0, 31, -27) |
| Façade | (0, 39, 156) | (0, 33, -35) |
| Elevated | (102, 98, 128) | (0, 31, -24) |
切换时直接复制位置和目标,没有平滑飞行或自动运镜。Reset 返回 Hero 视角,不会关闭庭院或重置动画。拖动相机时,预设按钮的选中状态被清除,表示我们已经离开固定镜头。
OrbitControls 禁止平移,缩放距离限制在 44 到 200;方位角限制在 -1.12 到 1.12 弧度,约为正面左右各 64°,俯仰范围也避免相机钻到地下。这个范围保住了入口层次、暗面板和太阳盘平面光效所依赖的观察角度。
窄屏处理主要针对按钮、安全区和 FPS 的位置。相机只更新 aspect,并没有按建筑包围盒重新计算距离。本次 390×844 预览中按钮可用,建筑两侧会被裁切,中央入口仍能看到。如果我们要把它做成面向手机的完整场景,可按主体包围盒分别计算横向和竖向的取景距离,并在缩放上限内给出窄屏镜头。
光照与天空
主光是偏暖的 DirectionalLight,强度为 4.1,从神庙左前上方照射,产生柱子、檐口与山壁的大块阴影。半球光用偏冷天空色和暖棕地面色提供整体明暗,低强度 AmbientLight 与另一盏冷色方向光抬起背光面的细节。
主光使用 4096×4096 阴影图,相机范围明确包住建筑与部分周边山体,配合 bias 和 normalBias 减少阴影自交伪影。阴影框越大,同样分辨率下每个区域分到的像素越少;这里是围绕主场景调出的尺寸,复用时应重新检查覆盖范围。
天空是一个半径 360、从内部观看的球体。材质根据视线高度混合暖色地平线和偏蓝灰的高空,再用与太阳方向的点积生成亮区:
glow = max(dot(skyDirection, sunDirection), 0)^54
core = max(dot(skyDirection, sunDirection), 0)^420
54 次幂形成较宽的暖色晕圈,420 次幂缩成更小的亮核。天空亮区与砂岩材质的朝向调色共用 SUN_VECTOR。实际方向光另用灯的位置与目标计算方向,两者都指向左前上方,但并不精确同向;如果我们需要严格一致,应由同一参数生成天空、材质与灯光方向。场景没有载入 HDR 环境图,也没有昼夜、天气或雾的切换。
风格化主要来自这些具体选择:暖砂岩大色块、低饱和的冷色阴影、明确的直射光、克制的石面颗粒、几何化装饰,以及中央太阳盘的橙红光效。ACESFilmicToneMapping 配合 1.2 的曝光,把普通石面和强亮光效放进同一画面;色调映射负责最终亮度压缩,不会自动替我们生成额外的光晕。
性能与加载
项目复用了基础石块几何,并把同类台阶、柱子与装饰放进实例网格,减少重复对象的绘制调用。动态中央台阶更新 34 个实例矩阵,也比每帧重新创建 34 份几何更合适。像素比上限为 1.6,控制高密度屏幕上的渲染像素量。
太阳光效的几何成本比较小:两张平面、22 片三角形火舌和少量石雕。透明区域依然会产生叠加绘制成本,但它没有额外的 Bloom 模糊链或离屏反射 pass。人物则保留完整蒙皮网格,主光阴影图也较大,这些都需要结合实际设备再评估。
加载方面,原 index.html 约 16.7 MB,其中主要是内嵌 Base64 的两份 FBX。Base64 把二进制转成文字,会增加未压缩体积;运行时还要解码字节、解析模型和图片。虽然挥手文件的网格没有加入场景,解析它时仍会创建第二套模型与材质。这种单文件分发很直接,如果继续产品化,可考虑保留一份角色网格,再单独保存兼容动作,改为外部二进制资产并按需加载。
还有两处可以继续优化:中央台阶与地基在静止时也会被更新,基础法线反复重算;主光的阴影没有按建筑动作是否变化单独控制刷新。复用到静态占比较大的展示场景时,可以给变形与阴影增加更新标记,再确认人物与光效的动态阴影需求。
更新循环把 delta 限制在最多 0.05 秒,避免切回页面时一次跳过太多动画。但它没有补上被截断的时间,也没有固定步长子步,所以低于 20 FPS 时演出可能比实际时间更慢。页面上的 FPS 是短时间窗口内的帧数统计,不能据此承诺所有设备的表现。本次浏览器运行和窄屏检查已完成,真实手机设备与强制 WebGL 2 后端的画面尚未单独测试。
学习总结
这座神庙最值得我们带走的,是几何、材质和行为之间明确的对应关系。轮廓决定用截头体、旋转曲面还是挤出;台阶的实例矩阵与基础顶点一起响应同一个进度;发光表面、透明轮廓和环境光分别负责各自的画面效果;角色先返回,平台才能关闭。每个模块都不复杂,组合时的规则让它们成为完整场景。
我们把可复用内容补进了 procedural-3d-scene Skill。入口保存通用工作步骤,太阳神庙的参数、公式和近似放在独立案例中,前两篇的海湾与铁路案例继续保留。
| 本篇方法 | 在 Skill 中复用的内容 | 使用时需要检查 |
|---|---|---|
| 对称神庙与重复门框 | 主体轮廓、二维截面、旋转曲面与实例化 | 门洞净空、部件连接和倒角比例 |
| 地表平整遮罩与岩壁沟槽 | 大形状、局部遮罩、分层噪声与封口 | 建筑基础、边界和实际几何法线 |
| 同一套砂岩节点材质 | 大色差、结构纹理、颗粒与粗糙度分层 | 世界坐标与运动部件局部坐标 |
| 太阳盘光效 | 主体发光、透明轮廓、环境照明分开控制 | 透明排序、深度和侧面观察 |
| 开启演出 | 总进度映射到各模块区间,加入缓动与轻微回摆 | 开启、中断、反向与终点位姿 |
| 人物与平台同步 | 状态机、返回守卫、路径位移与动作混合 | 骨架兼容、滑步和脚下支撑 |
| 单文件人物资产 | 记录导入资产、动画和许可证边界 | 包体、重复资源与真实来源 |
下载本篇更新后的完整 Skill · 查看 Skill 入口 · 查看太阳神庙案例
安装后,我们可以把场景目标、资产限制与验收状态一起交给 AI,而不是只要求“做一个类似神庙的场景”。例如:
使用 $procedural-3d-scene,阅读 Sun Temple 案例。
制作一个可交互的沙漠遗迹:中轴对称,两组柱廊可以展开,
中央阶梯抬成平台,上方能量盘在动作后半段逐渐点亮。
建筑、地形与材质由代码生成,人物允许使用我提供的骨骼资产,
请明确记录角色网格、动作与贴图的来源和许可证。
先用灰盒确认关闭、完全开启和窄屏三种构图,再添加装饰。
用一个总进度组织建筑动作,用人物状态机处理走出与返回。
关闭时必须等人物回到入口,中途重新开启应从当前位置继续。
能量盘分别实现表面发光、透明轮廓与附近照明,
不要在没有对应实现时把它描述成 Bloom 或体积光。
交付参数表、可运行场景、来源清单和实际截图。
检查完整开合、开启途中关闭、返回途中开启、镜头切换与窄屏;
检查动作过渡、平台支撑、透明排序和静止状态的更新成本。
Skill 中保留的是我们已经理解的建模与编排方法。下一次换成遗迹、机械庭院或舞台装置时,可以调整对象与参数,继续沿用这些空间和时间关系;原作者的场景设计、代码与资产仍需保留原有出处。