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 中保留的是我们已经理解的建模与编排方法。下一次换成遗迹、机械庭院或舞台装置时,可以调整对象与参数,继续沿用这些空间和时间关系;原作者的场景设计、代码与资产仍需保留原有出处。

AI 生成 3D 场景拆解 02:山间铁路

学习 Techartist 的山间铁路,拆解铁路曲线、地形与桥隧、列车建模与停车、建筑植被、河流瀑布、雨夜光照和镜头,并将共享路径与生成方法补进可复用 Skill。

AI 生成 3D 场景拆解 01:Tideglass Cove

学习 Techartist 的 Tideglass Cove,按地形、岩石、植物、房车、水体、动物、光照和镜头拆解程序化实现与关键公式,并将共享空间规则、建模方法和动效经验整理为可直接调用的 Skill。