Skip to content

Commit

Permalink
Add Ghost Mode
Browse files Browse the repository at this point in the history
Ghost mode lets you display a ghost version of a structure in all maps.
This is useful for lining up an IRB by checking a target against other
maps seemlessly. The element is labeled as Ghost and Global for easy
tracking.

Currently this only works for some subset of things. For instance,
it works for static instance groups, but not static instances, at
least not once you swap maps.
  • Loading branch information
Froggy618157725 committed Jul 12, 2024
1 parent 125b97d commit 8229ec3
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 41 deletions.
38 changes: 29 additions & 9 deletions crates/alkahest-renderer/assets/shaders/gui/outline.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ cbuffer cb_outline : register(b0) {
float time_since_selection;
};

#define OUTLINE_COLOR float3(1.0, 0.6, 0.2) * 0.6
#define OUTLINE_COLOR float3(1.0, 0.6, 0.2)
#define OUTLINE_COLOR_BACK (OUTLINE_COLOR * 0.6)
#define GHOST_COLOR float3(0.2, 0.6, 1.0)
#define OUTLINE_WIDTH 2

Texture2D DepthTargetOutline : register(t0);
Texture2D DepthTargetScene : register(t1);
Texture2D DepthTargetGhost : register(t2);

SamplerState SampleType : register(s1);

Expand All @@ -24,9 +26,10 @@ float2 QueryTexelSize(Texture2D t) {
// Pixel Shader
float4 PSMain(VSOutput input) : SV_Target {
float depth = DepthTargetOutline.Sample(SampleType, input.uv).r;
float g_depth = DepthTargetGhost.Sample(SampleType, input.uv).r;

// if the pixel isn't 0 (we are on the depth silhouette)
if (depth != 0)
if (depth != 0 || g_depth != 0)
{
float timeNormMul = clamp(time_since_selection * 4.0, 0.0, 1.0);
float2 size = QueryTexelSize(DepthTargetScene);
Expand All @@ -40,30 +43,47 @@ float4 PSMain(VSOutput input) : SV_Target {
continue;
}

float2 offset = float2(i, j) * size * (3 - timeNormMul * 2);
float2 g_offset = float2(i, j) * size;
float2 offset = g_offset * (3 - timeNormMul * 2);

// and if one of the pixel-neighbor is black (we are on the border)
if (DepthTargetOutline.Sample(SampleType, input.uv + offset).r == 0)
if (depth !=0 && DepthTargetOutline.Sample(SampleType, input.uv + offset).r == 0)
{
float depthScene = DepthTargetScene.Sample(SampleType, input.uv).r;
if(depthScene > depth) // Behind scene
return float4(OUTLINE_COLOR, 0.65);
else // In front of scene
return float4(OUTLINE_COLOR, 1);
} else if (g_depth != 0 && DepthTargetGhost.Sample(SampleType, input.uv + g_offset).r == 0)
{
float depthScene = DepthTargetScene.Sample(SampleType, input.uv).r;
if(depthScene > depth) // Behind scene
return float4(GHOST_COLOR, 0.65);
else // In front of scene
return float4(GHOST_COLOR, 1);
}
}
}

// if we are on the silhouette but not on the border
float depthScene = DepthTargetScene.Sample(SampleType, input.uv).r;
float fillFlash = (1.0 - timeNormMul) * 0.20;
if(depthScene > depth) { // Behind scene
dither_discard(input.screen_pos, 0.15);
// return float4(OUTLINE_COLOR, 0.16 + fillFlash);
return float4(lerp(OUTLINE_COLOR, OUTLINE_COLOR_BACK, timeNormMul), 0.75 + fillFlash);
if (depth != 0) {
if(depthScene > depth) { // Behind scene
dither_discard(input.screen_pos, 0.15);
// return float4(outline_color, 0.16 + fillFlash);
return float4(lerp(OUTLINE_COLOR, OUTLINE_COLOR_BACK, timeNormMul), 0.75 + fillFlash);
}
}
if (g_depth != 0) {
if(depthScene <= g_depth) {
dither_discard(input.screen_pos, 0.50);
return float4(GHOST_COLOR, 0.75);
}
}

// else // In front of scene
// return float4(OUTLINE_COLOR, 0.015 + fillFlash);
// return float4(outline_color, 0.015 + fillFlash);
}

discard;
Expand Down
1 change: 1 addition & 0 deletions crates/alkahest-renderer/src/ecs/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ impl AsRef<str> for Label {

pub struct Hidden;
pub struct Global;
pub struct Ghost;

/// Marker component to indicate that the entity is allowed to be modified in
/// potentially destructive ways (e.g. deleting it, changing it's name, etc.)
Expand Down
4 changes: 2 additions & 2 deletions crates/alkahest-renderer/src/ecs/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub enum EntityTag {
Activity,
Ambient,
Global,
Ghost,
Havok,
Utility,
User,
Expand Down Expand Up @@ -116,6 +117,7 @@ impl Display for EntityTag {
EntityTag::Activity => write!(f, "Activity"),
EntityTag::Ambient => write!(f, "Ambient"),
EntityTag::Global => write!(f, "Global"),
EntityTag::Ghost => write!(f, "Ghost"),
EntityTag::Havok => write!(f, "Havok"),
EntityTag::Utility => write!(f, "Utility"),
EntityTag::User => write!(f, "User"),
Expand Down Expand Up @@ -145,8 +147,6 @@ pub fn insert_tag(scene: &mut Scene, ent: Entity, tag: EntityTag) {
e.insert(tag);
return;
}

scene.insert_one(ent, Tags::from_iter([tag])).ok();
}

pub fn remove_tag(scene: &mut Scene, ent: Entity, tag: EntityTag) {
Expand Down
33 changes: 21 additions & 12 deletions crates/alkahest-renderer/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ use alkahest_data::{
};
use anyhow::Context;
use bitflags::bitflags;
use glam::Vec3;
use hecs::Entity;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use strum::{EnumCount, EnumIter};
use windows::Win32::Graphics::Direct3D11::D3D11_VIEWPORT;

use crate::{
ecs::{
common::Hidden,
common::{Ghost, Hidden},
render::{
dynamic_geometry::update_dynamic_model_system,
static_geometry::update_static_instances_system,
Expand Down Expand Up @@ -157,19 +159,26 @@ impl Renderer {
self.draw_pickbuffer(scene, resources.get::<SelectedEntity>().selected());
}

if let Some(selected) = resources.get::<SelectedEntity>().selected() {
if !scene.entity(selected).map_or(true, |v| v.has::<Hidden>()) {
self.draw_outline(
scene,
selected,
resources
.get::<SelectedEntity>()
.time_selected
.elapsed()
.as_secs_f32(),
);
let ghosts : Vec<Entity> = scene.query::<&Ghost>().iter().map(|(e, _ )| e).collect();
let mut selected = resources.get::<SelectedEntity>().selected();
if let Some(sel) = selected {
if scene.entity(sel).map_or(true, |v| v.has::<Hidden>()) {
selected = None;
}
}

if ghosts.len() > 0 || selected.is_some() {
self.draw_outline(
scene,
selected,
ghosts,
resources
.get::<SelectedEntity>()
.time_selected
.elapsed()
.as_secs_f32()
);
}
}

unsafe {
Expand Down
59 changes: 44 additions & 15 deletions crates/alkahest-renderer/src/renderer/pickbuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ impl Renderer {
}

// TODO(cohae): move rendering logic to Pickbuffer (where possible)
pub(super) fn draw_outline(&self, scene: &Scene, selected: Entity, time_since_select: f32) {
pub(super) fn draw_outline(&self, scene: &Scene, selected: Option<Entity>, ghosts: Vec<Entity>, time_since_select: f32) {
gpu_event!(self.gpu, "selection_outline");

self.pickbuffer.outline_depth.clear(0.0, 0);
self.pickbuffer.ghost_depth.clear(0.0, 0);

unsafe {
const NO_RT: Option<ID3D11RenderTargetView> = None;
Expand All @@ -63,19 +64,40 @@ impl Renderer {
.OMGetRenderTargets(Some(&mut rt_backup), None);

// Draw the selected entity into the outline depth buffer
self.gpu
.context()
.OMSetRenderTargets(None, Some(&self.pickbuffer.outline_depth.view));
self.gpu
if let Some(sel) = selected {
self.gpu
.context()
.OMSetRenderTargets(None, Some(&self.pickbuffer.outline_depth.view));
self.gpu
.context()
.OMSetDepthStencilState(Some(&self.pickbuffer.outline_depth.state), 0);
draw_entity(
scene,
sel,
self,
Some(&self.pickbuffer.static_instance_cb),
TfxRenderStage::GenerateGbuffer,
);
}

// Draw the ghost entities into the ghost depth buffer
if ghosts.len() > 0 {
self.gpu
.context()
.OMSetDepthStencilState(Some(&self.pickbuffer.outline_depth.state), 0);
draw_entity(
scene,
selected,
self,
Some(&self.pickbuffer.static_instance_cb),
TfxRenderStage::GenerateGbuffer,
);
.OMSetRenderTargets(None, Some(&self.pickbuffer.ghost_depth.view));
self.gpu
.context()
.OMSetDepthStencilState(Some(&self.pickbuffer.ghost_depth.state), 0);
for ghost in ghosts {
draw_entity(
scene,
ghost,
self,
Some(&self.pickbuffer.static_instance_cb),
TfxRenderStage::GenerateGbuffer,
);
}
}

// Draw the outline itself
self.gpu
Expand All @@ -90,15 +112,16 @@ impl Renderer {
self.gpu.set_input_topology(EPrimitiveType::Triangles);
self.gpu
.context()
.VSSetShader(&self.pickbuffer.outline_vs, None);
.VSSetShader(&self.pickbuffer.outline_vs.clone(), None);
self.gpu
.context()
.PSSetShader(&self.pickbuffer.outline_ps, None);
.PSSetShader(&self.pickbuffer.outline_ps.clone(), None);
self.gpu.context().PSSetShaderResources(
0,
Some(&[
Some(self.pickbuffer.outline_depth.texture_view.clone()),
Some(self.data.lock().gbuffers.depth.texture_view.clone()),
Some(self.pickbuffer.ghost_depth.texture_view.clone()),
]),
);
self.pickbuffer.outline_cb.write(&time_since_select).ok();
Expand All @@ -115,6 +138,7 @@ pub struct Pickbuffer {

pub(super) selection_request: AtomicCell<Option<(u32, u32)>>,
pub outline_depth: DepthState,
pub ghost_depth: DepthState,
pub pick_buffer: RenderTarget,
pub pick_buffer_staging: CpuStagingBuffer,
pub static_instance_cb: ConstantBuffer<u8>,
Expand Down Expand Up @@ -157,6 +181,8 @@ impl Pickbuffer {
selection_request: AtomicCell::new(None),
outline_depth: DepthState::create(gctx.clone(), window_size)
.context("Outline Depth")?,
ghost_depth: DepthState::create(gctx.clone(), window_size)
.context("Ghost Depth")?,
pick_buffer: RenderTarget::create(
window_size,
DxgiFormat::R32_UINT,
Expand Down Expand Up @@ -193,6 +219,9 @@ impl Pickbuffer {
self.outline_depth
.resize(new_size)
.context("Outline Depth")?;
self.ghost_depth
.resize(new_size)
.context("Ghost Depth")?;
self.pick_buffer
.resize(new_size)
.context("Entity_Pickbuffer")?;
Expand Down
29 changes: 26 additions & 3 deletions crates/alkahest/src/gui/inspector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alkahest_data::map::{SLightCollection, SRespawnPoint};
use alkahest_renderer::{
camera::Camera,
ecs::{
common::{EntityWorldId, Global, Hidden, Label, Mutable},
common::{EntityWorldId, Ghost, Global, Hidden, Label, Mutable},
map::{CubemapVolume, NodeMetadata},
render::{
decorators::DecoratorRenderer, dynamic_geometry::DynamicModelComponent,
Expand Down Expand Up @@ -183,13 +183,30 @@ pub fn show_inspector_panel(

let mut global = e.has::<Global>();
let mut global_changed = false;
if e.has::<Mutable>() && !e.has::<Route>() {
if ui.checkbox(&mut global, "Show in all Maps").changed() {
let mutable = e.has::<Mutable>();
if !e.has::<Route>() {
if ui
.checkbox(
&mut global,
if mutable {
"Show in all Maps"
} else {
"Show ghost in all Maps"
},
)
.changed()
{
global_changed = true;
if global {
cmd.insert_one(ent, Global);
if !mutable {
cmd.insert_one(ent, Ghost);
}
} else {
cmd.remove_one::<Global>(ent);
if !mutable {
cmd.remove_one::<Ghost>(ent);
}
}
};
ui.separator();
Expand All @@ -199,8 +216,14 @@ pub fn show_inspector_panel(
if global_changed {
if global {
insert_tag(scene, ent, EntityTag::Global);
if !mutable {
insert_tag(scene, ent, EntityTag::Ghost);
}
} else {
remove_tag(scene, ent, EntityTag::Global);
if !mutable {
remove_tag(scene, ent, EntityTag::Ghost);
}
}
}
}
Expand Down

0 comments on commit 8229ec3

Please sign in to comment.