原文链接及内容

效果如下图所示:

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
skyBox: false,
shouldAnimate: true,
baseLayerPicker: false,
shadows: true,
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
Cesium.TileMapServiceImageryProvider.fromUrl(
Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII")
)
),
});
viewer.cesiumWidget.creditContainer.style.display = "none";

const { scene, camera } = viewer;
scene.debugShowFramesPerSecond = true; //显示帧率,监控性能。

const scratchColor = new Cesium.Color();

// 定义全局缩放矩阵,将体素数据缩放到 WGS84 椭球的最大半径。
// fromScale方法:创建均匀缩放矩阵,x/y/z 轴均缩放至地球尺度
const globalTransform = Cesium.Matrix4.fromScale(
Cesium.Cartesian3.fromElements(
Cesium.Ellipsoid.WGS84.maximumRadius,
Cesium.Ellipsoid.WGS84.maximumRadius,
Cesium.Ellipsoid.WGS84.maximumRadius
)
);

//定义多瓦片(层级)体素提供者,生成 4x4x4 分辨率的体素网格,支持3级瓦片。
function ProceduralMultiTileVoxelProvider(shape) {
this.shape = shape; //体素形状
this.dimensions = new Cesium.Cartesian3(4, 4, 4); //基础分辨率为 4x4x4。
this.names = ["color"]; //元数据名称,表示颜色属性。
this.types = [Cesium.MetadataType.VEC4]; //颜色为 4 通道向量(RGBA)
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32]; //每个通道为 32 位浮点数。
this._levelCount = 3; //支持瓦片级别 0、1、2(分辨率分别为 4x4x4、8x8x8、16x16x16)。
this.globalTransform = globalTransform; //应用全局缩放矩阵
}

// 体素数据请求
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
const { tileLevel, tileX, tileY, tileZ } = options;

if (tileLevel >= this._levelCount) {
return Promise.reject(`${this._levelCount - 1}级以上的瓦片不可用`);
}

const dimensions = this.dimensions;
const type = this.types[0];
// 基于瓦片坐标(tileX, tileY, tileZ)计算种子,确保一致性
const randomSeed =
tileZ * dimensions.y * dimensions.x + tileY * dimensions.x + tileX;
// 生成瓦片颜色数据
const dataTile = constructRandomTileData(dimensions, type, randomSeed);

// 将颜色数据封装为体素内容
const content = Cesium.VoxelContent.fromMetadataArray([dataTile]);
return Promise.resolve(content);
};

// 随机瓦片数据生成
function constructRandomTileData(dimensions, type, randomSeed) {
Cesium.Math.setRandomNumberSeed(randomSeed);
// 总“体素”数
const voxelCount = dimensions.x * dimensions.y * dimensions.z;
// channelCount:4(RGBA)
const channelCount = Cesium.MetadataType.getComponentCount(type);
const dataColor = new Float32Array(voxelCount * channelCount);

for (let z = 0; z < dimensions.z; z++) {
const indexZ = z * dimensions.y * dimensions.x;
for (let y = 0; y < dimensions.y; y++) {
const indexZY = indexZ + y * dimensions.x;
for (let x = 0; x < dimensions.x; x++) {
const lerperX = x / (dimensions.x - 1);
const lerperY = y / (dimensions.y - 1);
const lerperZ = z / (dimensions.z - 1);

/**
* 颜色计算:h、s、l、alpha都各有公式计算
*/
const h = Cesium.Math.nextRandomNumber();
const s = 1.0 - lerperY * 0.2;
const l = 0.5;//固定值
const color = Cesium.Color.fromHsl(h, s, l, 1.0, scratchColor);

const random2 = Cesium.Math.nextRandomNumber();
const alphaRandom = Math.floor(random2 + 0.5);

const index = (indexZY + x) * channelCount;
dataColor[index + 0] = color.red;
dataColor[index + 1] = color.green;
dataColor[index + 2] = color.blue;
dataColor[index + 3] = alphaRandom;
}
}
}

return dataColor;
}

const provider = new ProceduralMultiTileVoxelProvider(
Cesium.VoxelShapeType.BOX
);

// 自定义着色器
const customShader = new Cesium.CustomShader({
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
// 光照计算
vec3 voxelNormal = fsInput.attributes.normalEC;//体素法线(眼坐标系)
// diffuse:法线与光照方向(czm_lightDirectionEC)的点积,计算漫反射。
float diffuse = max(0.0, dot(voxelNormal, czm_lightDirectionEC));
// 结合环境光(0.5)和漫反射光
float lighting = 0.5 + 0.5 * diffuse;

//颜色处理
int tileIndex = fsInput.voxel.tileIndex;
int sampleIndex = fsInput.voxel.sampleIndex;
// 体素颜色(color.rgb)乘以光照系数
vec3 cellColor = fsInput.metadata.color.rgb * lighting;
// 如果体素被选中:material.diffuse:将 cellColor 与白色(vec3(1.0))混合(50%),实现高亮
if (tileIndex == u_selectedTile && sampleIndex == u_selectedSample) {
material.diffuse = mix(cellColor, vec3(1.0), 0.5);
material.alpha = fsInput.metadata.color.a;
} else {
// 否则:使用原始颜色(cellColor)
material.diffuse = cellColor;
// material.alpha 直接使用体素的 color.a
material.alpha = fsInput.metadata.color.a;
}
}`,
uniforms: {
u_selectedTile: {
type: Cesium.UniformType.INT,
value: -1.0,
},
u_selectedSample: {
type: Cesium.UniformType.INT,
value: -1.0,
},
},
});

// 创建体素图元:绑定提供者和着色器,启用最近邻采样并聚焦边界球。
const voxelPrimitive = scene.primitives.add(
new Cesium.VoxelPrimitive({
provider: provider,
customShader: customShader,//应用光照和选中高亮效果着色器
})
);
// 使用最近邻采样,保持体素边缘清晰。
voxelPrimitive.nearestSampling = true;

camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
duration: 0.0,
});

const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
const pickedCoordinate = document.getElementById("pickedCoordinate");
const pickedColor = document.getElementById("pickedColor");
// 拾取体素并显示其中心坐标和颜色
handler.setInputAction(function (movement) {
const mousePosition = movement.endPosition;
const voxelCell = scene.pickVoxel(mousePosition);
if (!Cesium.defined(voxelCell)) {
return;
}
const { tileIndex, sampleIndex, orientedBoundingBox } = voxelCell;
// 获取体素中心的笛卡尔坐标,转换为整数
const [x, y, z] = Object.values(orientedBoundingBox.center).map(Math.round);
pickedCoordinate.innerHTML = `体素中心坐标: x = ${x}, y = ${y}, z = ${z}`;
// 获取颜色(getProperty("color")),转换为 CSS 颜色,更新 pickedColor 元素的背景色
const rgbaValues = voxelCell.getProperty("color");
const color = new Cesium.Color(...rgbaValues);
pickedColor.style.backgroundColor = color.toCssColorString();

const { customShader } = voxelCell.primitive;
// 更新着色器统一变量(u_selectedTile, u_selectedSample),触发高亮效果。
customShader.setUniform("u_selectedTile", tileIndex);
customShader.setUniform("u_selectedSample", sampleIndex);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);