358 lines
18 KiB
TypeScript
358 lines
18 KiB
TypeScript
import React from "react";
|
|
import {Button, InputNumber, message, Space, Switch, Tooltip} from "antd";
|
|
import {QuestionCircleOutlined} from "@ant-design/icons";
|
|
import axios from "axios";
|
|
import enhance from "../core/enhance.tsx";
|
|
import {col_style, row_style, setting_style} from "../Global.tsx";
|
|
|
|
export type Cesium_Graphics_Config = {
|
|
// 显示 Cesium FPS/帧耗时诊断面板,不改变画质。
|
|
debugShowFramesPerSecond: boolean
|
|
// 限制 Cesium 渲染循环目标帧率,降低上限可减少 GPU 占用。
|
|
targetFrameRate: number
|
|
// 使用浏览器推荐分辨率,避免高 DPI 屏幕按物理像素渲染。
|
|
useBrowserRecommendedResolution: boolean
|
|
// WebGL 内部渲染分辨率缩放,越低越省 GPU 但越模糊。
|
|
resolutionScale: number
|
|
// MSAA 多重采样数量,1 表示关闭几何抗锯齿。
|
|
msaaSamples: number
|
|
// FXAA 后处理抗锯齿,开销低但可能让文字和细线变软。
|
|
fxaa: boolean
|
|
// 场景阴影开关,大量模型或地形开启后 GPU 开销明显。
|
|
shadows: boolean
|
|
// 地球光照开关,开启后地表按光源方向产生明暗。
|
|
enableLighting: boolean
|
|
// 真实日照模式,使用太阳方向、系统时间和动态大气表达昼夜状态。
|
|
solarLighting: boolean
|
|
// 真实日照模式下的太阳光强度,只影响视觉明暗。
|
|
solarLightIntensity: number
|
|
// 地球瓦片屏幕空间误差,值越大越快但地面细节越粗。
|
|
maximumScreenSpaceError: number
|
|
// 仅 3D 模式加载 Terrarium 真实地形,2D/2.5D 不加载。
|
|
terrainEnabledIn3D: boolean
|
|
// 地形垂直起伏倍率,只改变视觉起伏,不增加高程精度。
|
|
terrainExaggeration: number
|
|
// 限制 Terrarium 地形最高请求层级,减少无效子瓦片请求。
|
|
terrainLimitMaximumLevel: boolean
|
|
// 启用层级限制时允许请求的最高地形层级。
|
|
terrainMaximumLevel: number
|
|
// 已解码地形高度瓦片缓存数量,越大越占内存但回看更快。
|
|
terrainCacheTiles: number
|
|
}
|
|
export const default_cesium_graphics_config: Cesium_Graphics_Config = {
|
|
debugShowFramesPerSecond: true,
|
|
targetFrameRate: 30,
|
|
useBrowserRecommendedResolution: true,
|
|
resolutionScale: 0.7,
|
|
msaaSamples: 1,
|
|
fxaa: false,
|
|
shadows: false,
|
|
enableLighting: false,
|
|
solarLighting: false,
|
|
solarLightIntensity: 1.5,
|
|
maximumScreenSpaceError: 4,
|
|
terrainEnabledIn3D: false,
|
|
terrainExaggeration: 1.0,
|
|
terrainLimitMaximumLevel: true,
|
|
terrainMaximumLevel: 15,
|
|
terrainCacheTiles: 128
|
|
};
|
|
export const cesium_graphics_config_event = "ecap_cesium_graphics_config_changed";
|
|
type Number_Config_Key = "targetFrameRate" | "resolutionScale" | "msaaSamples" | "solarLightIntensity" | "maximumScreenSpaceError" | "terrainExaggeration" | "terrainMaximumLevel" | "terrainCacheTiles"
|
|
type Boolean_Config_Key = Exclude<keyof Cesium_Graphics_Config, Number_Config_Key>
|
|
const graphics_presets: Record<"low" | "medium" | "high", Cesium_Graphics_Config> = {
|
|
low: {...default_cesium_graphics_config},
|
|
medium: {
|
|
debugShowFramesPerSecond: true,
|
|
targetFrameRate: 45,
|
|
useBrowserRecommendedResolution: true,
|
|
resolutionScale: 0.85,
|
|
msaaSamples: 1,
|
|
fxaa: true,
|
|
shadows: false,
|
|
enableLighting: false,
|
|
solarLighting: false,
|
|
solarLightIntensity: 1.5,
|
|
maximumScreenSpaceError: 3,
|
|
terrainEnabledIn3D: true,
|
|
terrainExaggeration: 1.0,
|
|
terrainLimitMaximumLevel: true,
|
|
terrainMaximumLevel: 15,
|
|
terrainCacheTiles: 128
|
|
},
|
|
high: {
|
|
debugShowFramesPerSecond: true,
|
|
targetFrameRate: 60,
|
|
useBrowserRecommendedResolution: false,
|
|
resolutionScale: 1.0,
|
|
msaaSamples: 4,
|
|
fxaa: true,
|
|
shadows: false,
|
|
enableLighting: true,
|
|
solarLighting: true,
|
|
solarLightIntensity: 1.5,
|
|
maximumScreenSpaceError: 2,
|
|
terrainEnabledIn3D: true,
|
|
terrainExaggeration: 1.0,
|
|
terrainLimitMaximumLevel: true,
|
|
terrainMaximumLevel: 17,
|
|
terrainCacheTiles: 256
|
|
}
|
|
};
|
|
const graphics_help: Record<keyof Cesium_Graphics_Config, string[]> = {
|
|
debugShowFramesPerSecond: ["显示 FPS、每帧耗时和诊断信息。", "只用于判断卡顿,基本不影响画质。"],
|
|
targetFrameRate: ["限制 Cesium 渲染循环的目标帧率。", "越高越流畅,GPU 占用越高;集显建议 30。"],
|
|
useBrowserRecommendedResolution: ["开启后按 CSS 像素渲染,忽略高 DPI 物理像素倍率。", "高 DPI 屏幕上通常能显著降低集显压力。"],
|
|
resolutionScale: ["调整 WebGL 内部渲染分辨率。", "0.7 约等于渲染 49% 像素;越低越快但更模糊。"],
|
|
msaaSamples: ["几何边缘多重采样抗锯齿。", "1 表示关闭;数值越高边缘越平滑,GPU 开销越大。"],
|
|
fxaa: ["屏幕空间后处理抗锯齿。", "开销通常低于 MSAA,但可能让文字和细线变软。"],
|
|
shadows: ["控制模型、Primitive 和地形阴影。", "画面更真实,但对大量模型和地形开销明显。"],
|
|
enableLighting: ["根据场景光源给地球表面做明暗。", "这个开关不驱动系统时间;需要真实昼夜时打开“真实日照”。"],
|
|
solarLighting: ["使用 Cesium 太阳方向、系统时间和动态大气表达昼夜。", "地球和地面对象保持地固坐标,不手动旋转地球;需要更真实的时间态势时开启。"],
|
|
solarLightIntensity: ["真实日照模式下的太阳光强度。", "数值越大白天越亮、明暗对比越强;不会改变飞机、基站和轨迹数据。"],
|
|
maximumScreenSpaceError: ["控制地球瓦片 LOD 的屏幕误差。", "数值越大越快但地面更粗糙;4 是集显平衡档。"],
|
|
terrainEnabledIn3D: ["3D 模式下加载 Terrarium 真实高程地形。", "会增加网络、解码、CPU 和 GPU 压力;2D/2.5D 默认不加载。"],
|
|
terrainExaggeration: ["控制地形垂直方向的视觉夸张倍率。", "1.0 为真实高度;0.0 压平地形;只改变视觉起伏,不增加高程精度,前端不限制范围。"],
|
|
terrainLimitMaximumLevel: ["限制 Terrarium 地形继续请求更深层级。", "只限制高程层级,减少无意义子瓦片请求。"],
|
|
terrainMaximumLevel: ["地形允许加载的最高瓦片层级。", "只有启用地形且限制层级时生效;越高越精细也越耗资源。"],
|
|
terrainCacheTiles: ["内存中保留的已解码地形瓦片数量。", "越大回看越快但占用更多内存;过小会增加重复请求和解码。"]
|
|
};
|
|
type Cesium_Graphics_Config_Json = {
|
|
debug_show_frames_per_second: boolean
|
|
target_frame_rate: number
|
|
use_browser_recommended_resolution: boolean
|
|
resolution_scale: number
|
|
msaa_samples: number
|
|
fxaa: boolean
|
|
shadows: boolean
|
|
enable_lighting: boolean
|
|
solar_lighting: boolean
|
|
solar_light_intensity: number
|
|
maximum_screen_space_error: number
|
|
terrain_enabled_in_3d: boolean
|
|
terrain_exaggeration: number
|
|
terrain_limit_maximum_level: boolean
|
|
terrain_maximum_level: number
|
|
terrain_cache_tiles: number
|
|
}
|
|
function number_value(value: unknown, fallback: number, min: number, max: number): number {
|
|
const next = Number(value);
|
|
if (!Number.isFinite(next)) return fallback;
|
|
return Math.min(max, Math.max(min, next));
|
|
}
|
|
function unrestricted_number_value(value: unknown, fallback: number): number {
|
|
const next = Number(value);
|
|
return Number.isFinite(next) ? next : fallback;
|
|
}
|
|
function boolean_value(value: unknown, fallback: boolean): boolean {
|
|
return typeof value === "boolean" ? value : fallback;
|
|
}
|
|
export function normalize_cesium_graphics_config(data: Partial<Cesium_Graphics_Config>): Cesium_Graphics_Config {
|
|
return {
|
|
debugShowFramesPerSecond: boolean_value(data.debugShowFramesPerSecond, default_cesium_graphics_config.debugShowFramesPerSecond),
|
|
targetFrameRate: number_value(data.targetFrameRate, default_cesium_graphics_config.targetFrameRate, 1, 120),
|
|
useBrowserRecommendedResolution: boolean_value(data.useBrowserRecommendedResolution, default_cesium_graphics_config.useBrowserRecommendedResolution),
|
|
resolutionScale: number_value(data.resolutionScale, default_cesium_graphics_config.resolutionScale, 0.2, 1.5),
|
|
msaaSamples: number_value(data.msaaSamples, default_cesium_graphics_config.msaaSamples, 1, 8),
|
|
fxaa: boolean_value(data.fxaa, default_cesium_graphics_config.fxaa),
|
|
shadows: boolean_value(data.shadows, default_cesium_graphics_config.shadows),
|
|
enableLighting: boolean_value(data.enableLighting, default_cesium_graphics_config.enableLighting),
|
|
solarLighting: boolean_value(data.solarLighting, default_cesium_graphics_config.solarLighting),
|
|
solarLightIntensity: number_value(data.solarLightIntensity, default_cesium_graphics_config.solarLightIntensity, 0, 10),
|
|
maximumScreenSpaceError: number_value(data.maximumScreenSpaceError, default_cesium_graphics_config.maximumScreenSpaceError, 1, 16),
|
|
terrainEnabledIn3D: boolean_value(data.terrainEnabledIn3D, default_cesium_graphics_config.terrainEnabledIn3D),
|
|
terrainExaggeration: unrestricted_number_value(data.terrainExaggeration, default_cesium_graphics_config.terrainExaggeration),
|
|
terrainLimitMaximumLevel: boolean_value(data.terrainLimitMaximumLevel, default_cesium_graphics_config.terrainLimitMaximumLevel),
|
|
terrainMaximumLevel: number_value(data.terrainMaximumLevel, default_cesium_graphics_config.terrainMaximumLevel, 0, 24),
|
|
terrainCacheTiles: Math.round(number_value(data.terrainCacheTiles, default_cesium_graphics_config.terrainCacheTiles, 16, 2048))
|
|
};
|
|
}
|
|
function from_server_config(data: Partial<Cesium_Graphics_Config_Json>): Cesium_Graphics_Config {
|
|
return normalize_cesium_graphics_config({
|
|
debugShowFramesPerSecond: data.debug_show_frames_per_second,
|
|
targetFrameRate: data.target_frame_rate,
|
|
useBrowserRecommendedResolution: data.use_browser_recommended_resolution,
|
|
resolutionScale: data.resolution_scale,
|
|
msaaSamples: data.msaa_samples,
|
|
fxaa: data.fxaa,
|
|
shadows: data.shadows,
|
|
enableLighting: data.enable_lighting,
|
|
solarLighting: data.solar_lighting,
|
|
solarLightIntensity: data.solar_light_intensity,
|
|
maximumScreenSpaceError: data.maximum_screen_space_error,
|
|
terrainEnabledIn3D: data.terrain_enabled_in_3d,
|
|
terrainExaggeration: data.terrain_exaggeration,
|
|
terrainLimitMaximumLevel: data.terrain_limit_maximum_level,
|
|
terrainMaximumLevel: data.terrain_maximum_level,
|
|
terrainCacheTiles: data.terrain_cache_tiles
|
|
});
|
|
}
|
|
function to_server_config(config: Cesium_Graphics_Config): Cesium_Graphics_Config_Json {
|
|
const normalized = normalize_cesium_graphics_config(config);
|
|
return {
|
|
debug_show_frames_per_second: normalized.debugShowFramesPerSecond,
|
|
target_frame_rate: normalized.targetFrameRate,
|
|
use_browser_recommended_resolution: normalized.useBrowserRecommendedResolution,
|
|
resolution_scale: normalized.resolutionScale,
|
|
msaa_samples: normalized.msaaSamples,
|
|
fxaa: normalized.fxaa,
|
|
shadows: normalized.shadows,
|
|
enable_lighting: normalized.enableLighting,
|
|
solar_lighting: normalized.solarLighting,
|
|
solar_light_intensity: normalized.solarLightIntensity,
|
|
maximum_screen_space_error: normalized.maximumScreenSpaceError,
|
|
terrain_enabled_in_3d: normalized.terrainEnabledIn3D,
|
|
terrain_exaggeration: normalized.terrainExaggeration,
|
|
terrain_limit_maximum_level: normalized.terrainLimitMaximumLevel,
|
|
terrain_maximum_level: normalized.terrainMaximumLevel,
|
|
terrain_cache_tiles: normalized.terrainCacheTiles
|
|
};
|
|
}
|
|
export async function load_cesium_graphics_config(): Promise<Cesium_Graphics_Config> {
|
|
const response = await axios.get<Cesium_Graphics_Config_Json>("/map/graphics");
|
|
return from_server_config(response.data);
|
|
}
|
|
export async function save_cesium_graphics_config(config: Cesium_Graphics_Config): Promise<Cesium_Graphics_Config> {
|
|
const response = await axios.post<Cesium_Graphics_Config_Json>("/map/graphics", to_server_config(config));
|
|
const normalized = from_server_config(response.data);
|
|
emit_cesium_graphics_config(normalized);
|
|
return normalized;
|
|
}
|
|
export function emit_cesium_graphics_config(config: Cesium_Graphics_Config) {
|
|
const normalized = normalize_cesium_graphics_config(config);
|
|
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: normalized}));
|
|
}
|
|
export class Cesium_Render_Settings extends enhance.Base {
|
|
config: Cesium_Graphics_Config = {...default_cesium_graphics_config};
|
|
number_input_drafts: Partial<Record<Number_Config_Key, number | null>> = {};
|
|
async on_mount() {
|
|
this.config = await load_cesium_graphics_config();
|
|
this.number_input_drafts = {};
|
|
this.flush();
|
|
}
|
|
number_input_value(key: Number_Config_Key): number | null {
|
|
return Object.prototype.hasOwnProperty.call(this.number_input_drafts, key) ? this.number_input_drafts[key]! : this.config[key];
|
|
}
|
|
set_number(key: Number_Config_Key, value: number | null) {
|
|
this.number_input_drafts[key] = value;
|
|
if (value === null) {
|
|
this.flush();
|
|
return;
|
|
}
|
|
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
|
emit_cesium_graphics_config(this.config);
|
|
this.flush();
|
|
}
|
|
blur_number(key: Number_Config_Key) {
|
|
if (Object.prototype.hasOwnProperty.call(this.number_input_drafts, key)) {
|
|
delete this.number_input_drafts[key];
|
|
this.flush();
|
|
}
|
|
}
|
|
set_boolean(key: Boolean_Config_Key, value: boolean) {
|
|
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
|
emit_cesium_graphics_config(this.config);
|
|
this.flush();
|
|
}
|
|
reset() {
|
|
this.config = {...default_cesium_graphics_config};
|
|
this.number_input_drafts = {};
|
|
emit_cesium_graphics_config(this.config);
|
|
this.flush();
|
|
}
|
|
apply_preset(key: keyof typeof graphics_presets) {
|
|
this.config = {...graphics_presets[key]};
|
|
this.number_input_drafts = {};
|
|
emit_cesium_graphics_config(this.config);
|
|
this.flush();
|
|
}
|
|
async save() {
|
|
this.config = await save_cesium_graphics_config(this.config);
|
|
this.number_input_drafts = {};
|
|
message.success("Cesium画质设置已保存");
|
|
this.flush();
|
|
}
|
|
render_label(label: string, key: keyof Cesium_Graphics_Config) {
|
|
return (
|
|
<Space size={4}>
|
|
<span>{label}</span>
|
|
<Tooltip placement="right" title={<div style={{maxWidth: 360}}>{graphics_help[key].map((line) => <div key={line}>{line}</div>)}</div>}>
|
|
<QuestionCircleOutlined style={{color: "#1677ff"}} />
|
|
</Tooltip>
|
|
</Space>
|
|
);
|
|
}
|
|
render_switch(label: string, key: Boolean_Config_Key) {
|
|
return (
|
|
<Space style={{display: "flex", justifyContent: "space-between", width: 280}}>
|
|
{this.render_label(label, key)}
|
|
<Switch checked={this.config[key]} onChange={(checked) => this.set_boolean(key, checked)} />
|
|
</Space>
|
|
);
|
|
}
|
|
render_number(label: string, key: Number_Config_Key, min: number, max: number, step: number) {
|
|
return (
|
|
<InputNumber
|
|
addonBefore={this.render_label(label, key)}
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
value={this.number_input_value(key)}
|
|
style={{width: 280}}
|
|
onChange={(value) => this.set_number(key, value)}
|
|
onBlur={() => this.blur_number(key)}
|
|
/>
|
|
);
|
|
}
|
|
render_terrain_exaggeration() {
|
|
const disabled = !this.config.terrainEnabledIn3D;
|
|
return (
|
|
<Space direction="vertical" size={4} style={{width: 280}}>
|
|
{this.render_label("地形起伏倍率", "terrainExaggeration")}
|
|
<InputNumber
|
|
step={0.1}
|
|
disabled={disabled}
|
|
value={this.number_input_value("terrainExaggeration")}
|
|
style={{width: 280}}
|
|
onChange={(value) => this.set_number("terrainExaggeration", value)}
|
|
onBlur={() => this.blur_number("terrainExaggeration")}
|
|
/>
|
|
</Space>
|
|
);
|
|
}
|
|
render(props: any) {
|
|
return (
|
|
<div style={setting_style}>
|
|
<h2 style={row_style}>Cesium画质设置</h2>
|
|
<Space direction="vertical" size={8} style={col_style}>
|
|
<Space>
|
|
<Button onClick={() => this.apply_preset("low")}>低画质</Button>
|
|
<Button onClick={() => this.apply_preset("medium")}>中画质</Button>
|
|
<Button onClick={() => this.apply_preset("high")}>高画质</Button>
|
|
</Space>
|
|
{this.render_switch("显示FPS", "debugShowFramesPerSecond")}
|
|
{this.render_number("目标帧率", "targetFrameRate", 1, 120, 1)}
|
|
{this.render_switch("浏览器推荐分辨率", "useBrowserRecommendedResolution")}
|
|
{this.render_number("分辨率缩放", "resolutionScale", 0.2, 1.5, 0.1)}
|
|
{this.render_number("MSAA采样", "msaaSamples", 1, 8, 1)}
|
|
{this.render_switch("FXAA", "fxaa")}
|
|
{this.render_switch("阴影", "shadows")}
|
|
{this.render_switch("地球光照", "enableLighting")}
|
|
{this.render_switch("真实日照", "solarLighting")}
|
|
{this.render_number("太阳光强度", "solarLightIntensity", 0, 10, 0.1)}
|
|
{this.render_number("地球细节误差", "maximumScreenSpaceError", 1, 16, 1)}
|
|
{this.render_switch("3D启用地形", "terrainEnabledIn3D")}
|
|
{this.render_terrain_exaggeration()}
|
|
{this.render_switch("限制地形层级", "terrainLimitMaximumLevel")}
|
|
{this.render_number("地形最大层级", "terrainMaximumLevel", 0, 24, 1)}
|
|
{this.render_number("地形缓存瓦片数", "terrainCacheTiles", 16, 2048, 16)}
|
|
<Space>
|
|
<Button onClick={() => this.reset()}>恢复默认画质</Button>
|
|
<Button type="primary" onClick={() => this.save()}>确定</Button>
|
|
</Space>
|
|
</Space>
|
|
</div>
|
|
);
|
|
}
|
|
}
|