znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第09章:材质、光照与渲染

在第02章中我们认识了 GeometricElement——所有三维可见元素的基类。它除了定义几何形状(通过 Representation + SolidOperation),还需要材质来赋予表面外观,需要光照来照亮场景。本章从 Material 类的完整属性出发,逐步介绍颜色系统、内置材质、灯光类型,最终落到 glTF 导出与逐顶点修饰的实战应用。

9.1 Material 材质

Material 继承自 Element,因此它拥有 Id(GUID 唯一标识)和 Name,可以在 Model 中作为独立元素存储和引用。核心属性如下:

public class Material : Element
{
    public Color  Color              { get; set; } = Colors.Gray;  // RGBA 颜色 [Required]
    public double SpecularFactor     { get; set; } = 0.1;         // 高光强度 [0, 1]
    public double GlossinessFactor   { get; set; } = 0.1;         // 光泽度 [0, 1]
    public bool   Unlit              { get; set; } = false;       // 无光照(自发光/常量色)
    public string Texture            { get; set; }                 // 纹理图片路径
    public string NormalTexture      { get; set; }                 // 法线贴图路径
    public string EmissiveTexture    { get; set; }                 // 自发光纹理路径
    public double EmissiveFactor     { get; set; }                 // 自发光强度 [0, 1]
    public bool   DoubleSided        { get; set; } = false;       // 双面渲染
    public bool   RepeatTexture      { get; set; } = true;        // 纹理重复/拉伸
    public bool   InterpolateTexture { get; set; } = true;        // 纹理插值(关闭为硬像素)
    public bool   DrawInFront        { get; set; } = false;       // 总在最前渲染
    public EdgeDisplaySettings EdgeDisplaySettings { get; set; }  // 边线显示设置
}

9.1.1 Color:RGBA 颜色

Color 是一个结构体struct),位于 Elements.Geometry 命名空间下。每个分量的取值范围是 [0.0, 1.0](0 为全暗/全透明,1 为最亮/完全不透明):

public struct Color : IEquatable<Color>
{
    public double Red   { get; set; }  // 红色分量 [0, 1]
    public double Green { get; set; }  // 绿色分量 [0, 1]
    public double Blue  { get; set; }  // 蓝色分量 [0, 1]
    public double Alpha { get; set; }  // 透明度分量 [0, 1]
}

创建颜色的几种方式:

// 直接指定 RGBA 浮点值
var c1 = new Color(1.0, 0.0, 0.0, 1.0);      // 红色

// 隐式转换:从 (R, G, B) 元组(Alpha 自动为 1)
Color c2 = (1.0, 0.5, 0.0);                   // 橙色

// 隐式转换:从 (R, G, B, A) 元组
Color c3 = (0.0, 1.0, 0.0, 0.5);              // 半透明绿色

// 隐式转换:从十六进制字符串
Color c4 = "#FF5733";

// 隐式转换:从 X11 颜色名(如 "Goldenrod", "DarkSlateGray" 等)
Color c5 = "SteelBlue";

Color 支持加减乘运算符和线性插值:

var blended = c1.Lerp(c2, 0.3);     // 30% c1 + 70% c2
var darkened = 0.5 * c1;            // 亮度减半
var additive = c1 + c2;             // 加法混合(自动钳位到 [0,1])

9.1.2 高光与光泽:SpecularFactor 与 GlossinessFactor

Elements 的材质使用 Specular-Glossiness 工作流(对应 glTF 已弃用但 Elements 仍在使用的 PBR 变体):

  • SpecularFactor(高光强度):值越大,高光越明显。金属材质可设 0.5~1.0,混凝土/木材设 0.0。
  • GlossinessFactor(光泽度):值越大,高光范围越小(越”亮面”)。1.0 为完美镜面反射效果,0.0 为完全漫反射。

两者的取值范围均为 [0.0, 1.0],构造时超出范围将抛出 ArgumentOutOfRangeException

9.1.3 无光照模式:Unlit

Unlit = true 时,材质不响应场景光照,始终以 Color 常量值渲染。适合分析可视化、标注线、轴指示器等不需要明暗变化的元素。

var unlitRed = new Material("analysis", Colors.Red, unlit: true);

9.1.4 纹理映射:Texture / NormalTexture / EmissiveTexture

三个纹理属性都接受相对文件路径.jpg / .png),构造时会检查文件是否存在——若文件不存在,该属性被静默置为 null

var texturedMaterial = new Material(
    name: "brick-wall",
    color: Colors.White,
    texture: "textures/brick_diffuse.png",
    normalTexture: "textures/brick_normal.png",
    repeatTexture: true
);
  • Texture:基础颜色纹理(漫反射贴图),与 Color 相乘得到最终颜色。
  • NormalTexture:法线贴图,增加表面凹凸细节。
  • EmissiveTexture:自发光纹理,配合 EmissiveFactor 控制发光强度。
  • RepeatTexturetrue 时纹理在 UV 范围外重复;false 时钳制到边缘。
  • InterpolateTexturetrue 时纹理像素间线性插值(平滑);false 时显示硬像素边缘。

9.1.5 双面渲染:DoubleSided

默认情况下,三角面片的法线朝外那一面才可见。当 DoubleSided = true 时,面片的背面也会被渲染。这在以下场景有用:

  • 单层曲面(如幕墙玻璃、薄膜结构)
  • 没有厚度的开口
  • 半透明材质需要从内部看到

9.1.6 边线控制:EdgeDisplaySettings

EdgeDisplaySettings 控制曲线和线段的显示样式(不影响网格/实体渲染):

public class EdgeDisplaySettings
{
    public double LineWidth { get; set; } = 1;  // 线宽
    public EdgeDisplayWidthMode WidthMode { get; set; }  // 屏幕单位 或 世界单位
    public EdgeDisplayDashMode DashMode { get; set; }    // 虚线模式
    public double DashSize { get; set; } = 1;            // 虚线长度
    public double? GapSize { get; set; } = 1;            // 虚线间隔
}

9.1.7 构造器一览

Elements 提供了多个 Material 构造器,按需选用:

// 1. 默认构造:灰色材质
var m1 = new Material();

// 2. 仅命名
var m2 = new Material("my-material");

// 3. 命名 + 颜色 + 可选参数(最常用)
var m3 = new Material("concrete", Colors.Gray, specularFactor: 0.0, glossinessFactor: 0.0);

// 4. 命名 + 颜色 + 纹理(完整参数)
var m4 = new Material(
    name: "glass-panel",
    color: new Color(1, 1, 1, 0.3),
    specularFactor: 1.0,
    glossinessFactor: 1.0,
    texture: null,
    unlit: false,
    doubleSided: true,
    repeatTexture: true
);

// 5. 完整参数构造器(含所有属性)
var m5 = new Material(
    color: Colors.White,
    specularFactor: 0.1,
    glossinessFactor: 0.5,
    unlit: false,
    texture: "wood.png",
    doubleSided: false,
    repeatTexture: true,
    normalTexture: null,
    interpolateTexture: true,
    emissiveTexture: null,
    emissiveFactor: 0.0,
    drawInFront: false
);

9.2 标准颜色预设:Colors 常量类

Colors 静态类提供了 30 种常用颜色预设,值域均为 RGB [0, 1] + Alpha 1.0

属性 R G B 视觉
Colors.Red 1.0 0.0 0.0 纯红
Colors.Green 0.0 1.0 0.0 纯绿
Colors.Blue 0.0 0.0 1.0 纯蓝
Colors.White 1.0 1.0 1.0 纯白
Colors.Black 0.0 0.0 0.0 纯黑
Colors.Gray 0.5 0.5 0.5 中灰
Colors.Darkgray 0.2 0.2 0.2 深灰
Colors.Yellow 1.0 0.9 0.1 黄色
Colors.Orange 1.0 0.5 0.1 橙色
Colors.Cyan 0.3 0.9 0.9 青色
Colors.Purple 0.7 0.1 1.0 紫色
Colors.Brown 0.6 0.4 0.2 棕色
Colors.Pink 1.0 0.3 0.5 粉色

完整清单还包括 AquaBeigeCobaltCoralCrimsonEmeraldGraniteLavenderLimeMagentaMaroonMintNavyOliveSandStoneTeal。快速创建带颜色的材质:

var redMaterial = new Material("red", Colors.Red);
var semiTransparent = new Material("glass", new Color(1, 1, 1, 0.3), specularFactor: 1.0, glossinessFactor: 1.0);

9.3 材质与 GeometricElement 的关联

GeometricElement 上的 Material 属性决定了元素的基础材质。每个建筑元素类型(WallBeamColumnFloor 等)在构造时都可以传入 Material 参数:

// Profile 和 Line 的用法参见第03章
var profile = Polygon.Rectangle(10, 10);

// 创建材质
var concreteMaterial = new Material("concrete", Colors.Gray, specularFactor: 0.0, glossinessFactor: 0.0);

// 将材质绑定到楼板
var floor = new Floor(profile, concreteMaterial, transform: new Transform(0, 0, 3));

9.3.1 RepresentationInstance 中的材质覆盖

当建筑元素使用 RepresentationInstances(如幕墙中的嵌板、门窗实例)时,每个 RepresentationInstance 可以携带自己的 Material覆盖基础元素的材质。换言之:

GeometricElement.Material          →  默认材质(所有表现实例的 fallback)
RepresentationInstance.Material    →  实例级覆盖材质(优先级更高)

这种两级材质体系使得同一个元素定义可以产生不同外观的实例。例如一面标准墙体定义可以生成不同楼层、不同颜色的墙段——无需为每种颜色创建新元素。

9.3.2 元素定义与实例共享

GeometricElement.IsElementDefinition = true 时,该元素本身不在场景中渲染,而是作为模板供其他 ElementInstance 引用。实例通过 CreateInstance(transform, name) 创建:

// 定义模板(不渲染)
var columnDef = new Column(Vector3.Origin, 3.0, Polygon.Rectangle(0.3, 0.3), 
    material: BuiltInMaterials.Steel, isElementDefinition: true);

// 在不同位置创建实例
var instance1 = columnDef.CreateInstance(new Transform(0, 0, 0), "col-1");
var instance2 = columnDef.CreateInstance(new Transform(5, 0, 0), "col-2");
model.AddElements(columnDef, instance1, instance2);

9.4 Light 灯光体系

Light 是所有灯光的抽象基类,继承自 Element。灯光朝向由 Transform-Z 轴 定义(即灯光朝 Transform 局部坐标系的 -Z 方向照射)。

public abstract class Light : Element
{
    public LightType LightType { get; set; }  // 灯光类型枚举
    public double    Intensity { get; set; }  // 光照强度
    public Color     Color     { get; set; }  // 光照颜色
    public Transform Transform { get; set; }  // 位置+方向(朝 -Z)
}

三种具体灯光类型,对应枚举 LightType

9.4.1 DirectionalLight:平行光(太阳光)

平行光模拟无限远处的光源(如太阳),所有光线方向平行。强度以 lux(勒克斯)为单位,默认名称 "Sun"

var sun = new DirectionalLight(
    color: Colors.White,
    transform: new Transform(Vector3.Origin, new Vector3(1, 1, 1)),
    intensity: 100000,        // 晴天日光约 100000 lux
    name: "Sun"
);

要改变光线方向,调整 Transform 的朝向即可。例如从东南方 45 度角照射:

var sunDir = new Vector3(1, -1, 1).Unitized();  // 东南方向,往下
var sunTransform = new Transform(Vector3.Origin, sunDir);
var sun = new DirectionalLight(Colors.White, sunTransform, intensity: 80000);

顺便提一句:Elements 右手坐标系中 +Z 向上,而灯光的 -Z 是照射方向。平行光的位置(Transform 的平移分量)不影响渲染,只影响照射方向。

9.4.2 PointLight:点光源

点光源从一个位置向所有方向均匀辐射。强度以 candela(坎德拉)为单位:

var bulb = new PointLight(
    color: new Color(1.0, 0.95, 0.8, 1.0),   // 暖白
    transform: new Transform(5, 5, 3),          // 房间中央
    intensity: 500,
    name: "ceiling-light"
);

9.4.3 SpotLight:聚光灯

聚光灯从一点向锥形区域发射光线,有内锥和外锥两个角度参数(单位弧度):

var spotlight = new SpotLight(
    color: Colors.White,
    transform: new Transform(new Vector3(0, 0, 5), Vector3.ZAxis.Negate()),  // 从上往下照
    intensity: 2000,
    innerConeAngle: Math.PI / 8,   // 内锥 22.5 度(完全亮区域)
    outerConeAngle: Math.PI / 4,   // 外锥 45 度(衰减区域边界)
    name: "track-light"
);

约束条件:

  • innerConeAngleouterConeAngle 必须在 [0, PI/2] 范围内
  • innerConeAngle 必须 ≤ outerConeAngle
  • 内锥与外锥之间为光线衰减区(半影)

9.5 命名颜色与 BuiltInMaterials 内置材质

BuiltInMaterials 是一个静态工具类,提供了一套带有固定 GUID 的预设材质,适用于 AEC 常见场景。因为 GUID 固定,序列化到 JSON 时材质引用保持一致:

材质 颜色 Specular Glossiness 用途
BuiltInMaterials.Glass 白色, α=0.2 1.0 1.0 玻璃/幕墙
BuiltInMaterials.Steel 暖灰色 0.1 0.4 钢结构
BuiltInMaterials.Concrete 中灰 0.0 0.0 混凝土
BuiltInMaterials.Wood 木色 (224,206,155)/255 0.0 0.0 木材
BuiltInMaterials.Mass 淡蓝, α=0.2 0.0 0.0 体量模型
BuiltInMaterials.Default 白色 0.0 0.0 默认材质
BuiltInMaterials.Black 黑色 0.0 0.0 纯黑
BuiltInMaterials.Edges 深灰 Unlit 0.0 0.0 模型边线
BuiltInMaterials.Points 红色 0.0 0.0 点标记
BuiltInMaterials.Topography 卡其色 0.0 0.0 地形
BuiltInMaterials.Trans 全透明 0.0 0.0 完全透明
BuiltInMaterials.Void 石灰绿, α=0.1 0.1 0.1 减除体示意
BuiltInMaterials.XAxis / YAxis / ZAxis 红/绿/蓝 Unlit 0.1 0.1 坐标轴指示

使用方式:

var beam = new Beam(line, profile, BuiltInMaterials.Steel);
var floor = new Floor(profile, BuiltInMaterials.Concrete, transform: new Transform(0, 0, 3));

这些内置材质可以按需修改(因为是 Material 实例的属性是可读写的),但不建议这样做——它们被多个元素共享,修改一处会影响所有引用者。如果需要变体,最好创建自己的 Material 实例。


9.6 渲染管线:Material 到 glTF PBR 的导出

Elements 的 Model.ToGlTF() 方法(位于 Elements.Serialization.glTF 命名空间)将模型中的所有几何元素、材质和灯光导出为 glTF 2.0 格式(.glb.gltf)。

9.6.1 导出方法

using Elements.Serialization.glTF;

// 导出为二进制 GLB
model.ToGlTF("output.glb");

// 导出为 JSON + 外部 buffer
model.ToGlTF("output.gltf", useBinarySerialization: false);

// 带边线绘制
model.ToGlTF("output.glb", drawEdges: true);

// 导出到字节数组(适合 Web API 返回)
byte[] glbBytes = model.ToGlTF(drawEdges: false);

// 导出到流
using var stream = new MemoryStream();
model.ToGlTF(stream);

// 导出为 Base64 字符串(嵌入 HTML)
string base64 = model.ToBase64String();

9.6.2 材质转换映射

导出管线将 Elements 的 Specular-Glossiness 材质自动转换为 glTF 的 PBR Metallic-Roughness 标准:

Elements 属性 glTF 输出 转换逻辑
Color pbrMetallicRoughness.baseColorFactor RGBA 数组,sRGB→线性空间
GlossinessFactor pbrMetallicRoughness.roughnessFactor roughness = 1.0 - glossiness
SpecularFactor KHR_materials_specular.specularFactor 扩展属性,直接映射
Unlit = true KHR_materials_unlit 激活 Unlit 扩展
DoubleSided material.doubleSided 直接映射
Texture pbrMetallicRoughness.baseColorTexture 图片嵌入 buffer
NormalTexture material.normalTexture 图片嵌入 buffer
EmissiveTexture material.emissiveTexture 图片嵌入 buffer
EmissiveFactor material.emissiveFactor 三个分量相同
Color.Alpha < 1 alphaMode: "BLEND" 启用混合模式

注意:MetallicFactor 固定为 0(转换自 Spec-Gloss 工作流时无金属度信息)。

9.6.3 灯光导出

所有 Light 子类通过 KHR_lights_punctual 扩展写入 glTF。灯光节点的变换矩阵从 Transform 计算,颜色和强度直接映射:

// glTF 中的灯光结构(由导出管线自动生成)
{
  "extensions": {
    "KHR_lights_punctual": {
      "lights": [
        {
          "color": [1.0, 1.0, 1.0],
          "type": "directional",
          "intensity": 100000,
          "name": "Sun"
        }
      ]
    }
  }
}

9.6.4 完整导出示例

以下代码创建一个包含楼板、柱子和灯光的场景,并导出为 glTF:

using Elements;
using Elements.Geometry;
using Elements.Serialization.glTF;

// 创建模型
var model = new Model();

// 创建材质
var concreteMat = new Material("concrete", Colors.Gray, specularFactor: 0.0, glossinessFactor: 0.0);
var steelMat = new Material("steel", new Color(0.6, 0.5, 0.5, 1.0), specularFactor: 0.1, glossinessFactor: 0.4);

// 创建楼板
var floor = new Floor(
    Polygon.Rectangle(10, 8),
    concreteMat,
    transform: new Transform(0, 0, 0)
);

// 创建柱子
var colProfile = Polygon.Rectangle(0.3, 0.3);
for (int x = 0; x <= 10; x += 5)
{
    for (int y = 0; y <= 8; y += 4)
    {
        var col = new Column(
            new Vector3(x, y, 0),
            3.5,
            colProfile,
            steelMat
        );
        model.AddElement(col);
    }
}

model.AddElement(floor);

// 添加平行光(太阳)
var sun = new DirectionalLight(
    Colors.White,
    new Transform(Vector3.Origin, new Vector3(1, 1, 1)),
    intensity: 100000
);
model.AddElement(sun);

// 添加点光源
var pointLight = new PointLight(
    new Color(1.0, 0.95, 0.8),
    new Transform(5, 4, 2.5),
    intensity: 300
);
model.AddElement(pointLight);

// 导出
model.ToGlTF("building.glb");

9.7 ModifyVertexAttributes:逐顶点修改钩子

GeometricElement 上有一个委托属性 ModifyVertexAttributes,在三角剖分(Tessellation)过程中逐顶点调用。其签名为:

public Func<
    (Vector3 position, Vector3 normal, UV uv, Color? color),   // 输入
    (Vector3 position, Vector3 normal, UV uv, Color? color)    // 输出
> ModifyVertexAttributes { get; set; }

每次剖分生成一个顶点时,该函数接收顶点的位置、法线、UV 和可选颜色,返回修改后的版本。你可以利用它:

  • 分析可视化:根据顶点位置计算分析值,映射颜色
  • 顶点位移:实现地形起伏、波浪效果
  • 程序化 UV:动态计算纹理坐标

9.7.1 分析可视化示例

以下示例创建一个楼板,并根据每个顶点到中心点的距离映射红-蓝渐变色:

var floorProfile = Polygon.Rectangle(20, 20);
var floor = new Floor(floorProfile, new Material("heatmap", Colors.White, unlit: true));

// 中心点
var center = new Vector3(10, 10, 0);
double maxDist = center.DistanceTo(new Vector3(0, 0, 0));  // 对角线半长

floor.ModifyVertexAttributes = (input) =>
{
    var dist = input.position.DistanceTo(center);
    var t = Math.Min(dist / maxDist, 1.0);  // 归一化 [0, 1]

    // 靠近中心为蓝色,远离中心为红色
    var color = new Color(t, 0.0, 1.0 - t, 1.0);

    return (input.position, input.normal, input.uv, color);
};

9.7.2 地形顶点位移示例

// 假设有一个 Mass 作为地形
var terrain = new Mass(Polygon.Rectangle(50, 50), 10.0);

terrain.ModifyVertexAttributes = (input) =>
{
    var pos = input.position;
    // 使用正弦波模拟地形起伏
    var displacement = Math.Sin(pos.X * 0.3) * Math.Cos(pos.Y * 0.3) * 2.0;

    // 海拔越高颜色越浅
    var h = (displacement + 2.0) / 4.0;  // 归一化
    var color = new Color(1 - h * 0.5, 1 - h * 0.3, 1 - h * 0.5, 1.0);

    var newPos = new Vector3(pos.X, pos.Y, pos.Z + displacement);
    return (newPos, input.normal, input.uv, color);
};

注意:ModifyVertexAttributes 只影响三角剖分阶段的顶点颜色属性(glTF 中的 COLOR_0)。材质自身的 Color 属性仍然作用于渲染管线。如需完全掌控颜色,建议同时设置 unlit: true 关闭光照,避免顶点颜色与材质颜色相乘。

9.7.3 内置分析可视化:AnalysisMesh

Elements 内置了一个更高级的分析可视化方案:AnalysisMesh(位于 Elements.Analysis 命名空间)。它将多边形区域划分为网格单元,对每个单元中心调用分析函数,用颜色梯度显示结果:

using Elements.Analysis;

var perimeter = Polygon.Rectangle(30, 20);
var colorScale = new ColorScale(new[] { Colors.Blue, Colors.Green, Colors.Red }, 5);

var analysisMesh = new AnalysisMesh(
    perimeter: perimeter,
    uLength: 0.5,
    vLength: 0.5,
    colorScale: colorScale,
    analyze: (point) =>
    {
        // 假设分析函数:距中心点的距离
        return point.DistanceTo(new Vector3(15, 10, 0));
    },
    name: "distance-analysis"
);

analysisMesh.Analyze();
model.AddElement(analysisMesh);

ColorScale 根据分析值自动从颜色梯度中选择对应颜色。


9.8 实战示例:建筑外立面材质与光照

以下完整示例创建一个简化的三层办公楼外立面:混凝土楼板、玻璃幕墙、钢柱,加上阳光和环境点光源,导出为可直接在 Web 查看的 .glb 文件。

using System;
using Elements;
using Elements.Geometry;
using Elements.Serialization.glTF;

class BuildingFacade
{
    static void Main()
    {
        var model = new Model();
        double floorHeight = 3.6;
        int floorCount = 3;
        double width = 15;
        double depth = 10;

        // ===== 材质 =====
        var concreteSlab = new Material("concrete-slab",
            new Color(0.7, 0.7, 0.7, 1.0),
            specularFactor: 0.0, glossinessFactor: 0.05);

        var glassCurtain = new Material("glass-curtain",
            new Color(0.6, 0.75, 0.9, 0.35),
            specularFactor: 1.0, glossinessFactor: 0.95,
            doubleSided: true);

        var steelColumn = new Material("steel-column",
            new Color(0.35, 0.35, 0.35, 1.0),
            specularFactor: 0.3, glossinessFactor: 0.6);

        // ===== 结构 =====
        var slabProfile = Polygon.Rectangle(width, depth);
        var colProfile = Polygon.Rectangle(0.25, 0.25);

        for (int f = 0; f < floorCount; f++)
        {
            double z = f * floorHeight;

            // 楼板
            var slab = new Floor(slabProfile, concreteSlab,
                transform: new Transform(0, 0, z + 0.2));
            model.AddElement(slab);

            // 四角柱子
            foreach (var corner in new[]
            {
                (0.15, 0.15), (width - 0.15, 0.15),
                (0.15, depth - 0.15), (width - 0.15, depth - 0.15)
            })
            {
                var col = new Column(
                    new Vector3(corner.Item1, corner.Item2, z),
                    floorHeight, colProfile, steelColumn);
                model.AddElement(col);
            }
        }

        // 幕墙(简化:用 Floor 表达玻璃面板)
        var glassPanelEast = new Floor(
            Polygon.Rectangle(0.02, depth),
            glassCurtain,
            transform: new Transform(new Vector3(width, 0, 1.8),
                                     new Vector3(0, -1, 0),  // 旋转 90 度竖直
                                     Vector3.ZAxis));
        glassPanelEast.Transform.Scale(new Vector3(1, 1, floorCount * floorHeight));
        model.AddElement(glassPanelEast);

        // ===== 光照 =====
        // 太阳(西南方向,45度仰角)
        var sunDir = new Vector3(1, -1, 1).Unitized();
        var sun = new DirectionalLight(
            Colors.White,
            new Transform(Vector3.Origin, sunDir),
            intensity: 90000,
            name: "Sun");
        model.AddElement(sun);

        // 室内点光源
        for (int f = 0; f < floorCount; f++)
        {
            var roomLight = new PointLight(
                new Color(1.0, 0.92, 0.8),
                new Transform(width / 2, depth / 2, f * floorHeight + 2.8),
                intensity: 400,
                name: $"interior-light-{f + 1}");
            model.AddElement(roomLight);
        }

        // ===== 导出 =====
        model.ToGlTF("office-building.glb", drawEdges: false);
        Console.WriteLine("已导出 office-building.glb");
    }
}

运行以上代码生成的 office-building.glb 可以在 glTF Viewer 或任何支持 glTF 的引擎中直接打开,看到带有正确 PBR 材质和灯光的建筑模型。


9.9 小结

本章覆盖了 Elements 中材质与光照的完整体系:

  • Material 是继承 Element 的一等公民,携带 RGBA 颜色、Specular-Glossiness PBR 参数、纹理路径等 12 个属性
  • Colors 提供 30 种常用颜色;BuiltInMaterials 提供 17 种 AEC 标准材质
  • Light 有三个子类:DirectionalLight(太阳)、PointLight(点光)、SpotLight(聚光),通过 Transform 控制位置和方向
  • glTF 导出自动将 Spec-Gloss 转换为 Metallic-Roughness PBR,并支持纹理、灯光、双面渲染
  • ModifyVertexAttributes 提供逐顶点插值钩子,用于分析可视化和顶点位移

下一章将讨论 Elements 的多格式序列化能力:JSON、glTF、IFC 的导入与导出。


← 上一章 目录 下一章 →