class_name SonarReliefStyle extends RefCounted ## Single owner of the holographic look of the sonar relief: palette, grid ## spacing, line widths and intensities. Display only: nothing here is a ## gameplay value. ## Near-black table the hologram floats on. Black also means "unknown". const COLOR_BACKGROUND := Color("010507") ## Height ramp, relative to the scan: dark teal low, cyan mid, pale on peaks. const COLOR_DEEP := Color("06404a") const COLOR_MIDDLE := Color("17b8c4") const COLOR_PEAK := Color("d8fbff") ## Metal returns keep one warm accent so an anomaly never reads as terrain. const COLOR_METAL := Color("ff7a2a") const COLOR_VEHICLE := Color("f2ffff") ## Grid lines follow the sample grid: one minor line every N cells. const GRID_MINOR_CELLS := 2.0 const GRID_MAJOR_CELLS := 6.0 const GRID_MINOR_WIDTH_PX := 1.2 const GRID_MAJOR_WIDTH_PX := 2.2 const GRID_MINOR_INTENSITY := 0.30 const GRID_MAJOR_INTENSITY := 0.80 ## Iso-height contours, in fractions of the scan's height span. const CONTOUR_STEP := 0.125 const CONTOUR_WIDTH_PX := 1.0 const CONTOUR_INTENSITY := 0.20 ## Translucent surface under the grid and the rim that outlines ridges. const SURFACE_INTENSITY := 0.50 const RIM_INTENSITY := 0.35 const RIM_POWER := 2.5 ## How much brighter the peaks' lines are than the deep ones. const PEAK_LINE_BOOST := 0.55 const _SHADER_CODE := """ shader_type spatial; render_mode unshaded, cull_disabled, depth_draw_always; uniform vec3 background : source_color; uniform vec3 color_deep : source_color; uniform vec3 color_middle : source_color; uniform vec3 color_peak : source_color; uniform vec3 color_metal : source_color; uniform float grid_minor_cells; uniform float grid_major_cells; uniform float grid_minor_width_px; uniform float grid_major_width_px; uniform float grid_minor_intensity; uniform float grid_major_intensity; uniform float contour_step; uniform float contour_width_px; uniform float contour_intensity; uniform float surface_intensity; uniform float rim_intensity; uniform float rim_power; uniform float peak_line_boost; uniform float opacity = 1.0; // Anti-aliased line on every multiple of `spacing`, `width_px` wide on screen. // Fades out when lines would be closer than a few pixels, so it never shimmers. float grid_line(float coordinate, float spacing, float width_px) { float scaled = coordinate / spacing; float pixel = max(fwidth(scaled), 1e-5); float distance_px = abs(fract(scaled - 0.5) - 0.5) / pixel; float line = 1.0 - smoothstep(width_px * 0.5 - 0.5, width_px * 0.5 + 0.5, distance_px); return line * clamp((1.0 / pixel - 2.0) / 3.0, 0.0, 1.0); } void fragment() { // UV carries (column, row) of the sample grid; COLOR.r the relative height, // COLOR.g the metal flag, COLOR.b the echo intensity. float height = clamp(COLOR.r, 0.0, 1.0); vec3 ramp = mix(color_deep, color_middle, smoothstep(0.0, 0.55, height)); ramp = mix(ramp, color_peak, smoothstep(0.55, 1.0, height)); // The translucent surface stays in the teal-to-cyan range so peaks do not // wash out to grey; only the lines reach the pale peak colour. vec3 fill = mix(color_deep, color_middle, smoothstep(0.0, 0.85, height)); float metal = step(0.5, COLOR.g); ramp = mix(ramp, color_metal, metal); fill = mix(fill, color_metal, metal); float minor = max( grid_line(UV.x, grid_minor_cells, grid_minor_width_px), grid_line(UV.y, grid_minor_cells, grid_minor_width_px) ); float major = max( grid_line(UV.x, grid_major_cells, grid_major_width_px), grid_line(UV.y, grid_major_cells, grid_major_width_px) ); float contour = grid_line(height, contour_step, contour_width_px); float rim = pow(1.0 - abs(dot(normalize(NORMAL), normalize(VIEW))), rim_power); float lines = max(minor * grid_minor_intensity, major * grid_major_intensity); lines = max(lines, contour * contour_intensity); lines *= 1.0 + peak_line_boost * height; float surface = surface_intensity * (0.22 + 0.78 * height) * (0.8 + 0.2 * COLOR.b); vec3 glow = fill * (surface + rim * rim_intensity) + ramp * lines; ALBEDO = background + glow; //FADE ALPHA = opacity; } """ static var _opaque_shader: Shader static var _fading_shader: Shader ## The current scan is opaque: depth-tested, so ridges hide what lies behind ## them and nothing depends on draw order. The previous scan fades out with the ## same shader, alpha-blended and still writing depth. static func relief_material(fading: bool) -> ShaderMaterial: if _opaque_shader == null: _opaque_shader = Shader.new() _opaque_shader.code = _SHADER_CODE _fading_shader = Shader.new() _fading_shader.code = _SHADER_CODE.replace("//FADE ", "") var material := ShaderMaterial.new() material.shader = _fading_shader if fading else _opaque_shader material.set_shader_parameter("background", COLOR_BACKGROUND) material.set_shader_parameter("color_deep", COLOR_DEEP) material.set_shader_parameter("color_middle", COLOR_MIDDLE) material.set_shader_parameter("color_peak", COLOR_PEAK) material.set_shader_parameter("color_metal", COLOR_METAL) material.set_shader_parameter("grid_minor_cells", GRID_MINOR_CELLS) material.set_shader_parameter("grid_major_cells", GRID_MAJOR_CELLS) material.set_shader_parameter("grid_minor_width_px", GRID_MINOR_WIDTH_PX) material.set_shader_parameter("grid_major_width_px", GRID_MAJOR_WIDTH_PX) material.set_shader_parameter("grid_minor_intensity", GRID_MINOR_INTENSITY) material.set_shader_parameter("grid_major_intensity", GRID_MAJOR_INTENSITY) material.set_shader_parameter("contour_step", CONTOUR_STEP) material.set_shader_parameter("contour_width_px", CONTOUR_WIDTH_PX) material.set_shader_parameter("contour_intensity", CONTOUR_INTENSITY) material.set_shader_parameter("surface_intensity", SURFACE_INTENSITY) material.set_shader_parameter("rim_intensity", RIM_INTENSITY) material.set_shader_parameter("rim_power", RIM_POWER) material.set_shader_parameter("peak_line_boost", PEAK_LINE_BOOST) return material static func set_opacity(material: ShaderMaterial, opacity: float) -> void: material.set_shader_parameter("opacity", clampf(opacity, 0.0, 1.0)) static func marker_material(color: Color) -> StandardMaterial3D: var material := StandardMaterial3D.new() material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED material.albedo_color = color return material ## Colour of a relative height (0 low, 1 peak), for the depth scale legend. static func height_gradient() -> Gradient: var gradient := Gradient.new() gradient.offsets = PackedFloat32Array([0.0, 0.55, 1.0]) gradient.colors = PackedColorArray([COLOR_DEEP, COLOR_MIDDLE, COLOR_PEAK]) return gradient