using System.Numerics; using System.Runtime.InteropServices; using Vortice.D3DCompiler; using Vortice.Direct3D; using Vortice.Direct3D11; using Vortice.DXGI; using Vortice.Mathematics; using XFEEDToolBox.Core.Galaxy; using XFEEDToolBox.Core.Rendering; namespace XFEEDToolBox.Rendering; internal sealed partial class GalaxyGraphics { private const int NebulaBudget=12; private ID3D11VertexShader nebulaVs=null!; private ID3D11PixelShader nebulaPs=null!,nebulaCompositePs=null!; private ID3D11Buffer nebulaConstants=null!; private ID3D11Texture2D? fogTexture; private ID3D11RenderTargetView? fogTarget; private ID3D11ShaderResourceView? fogView; private readonly Dictionary nebulaTextures=new(StringComparer.OrdinalIgnoreCase); private readonly HashSet requestedNebulae=new(StringComparer.OrdinalIgnoreCase); private readonly List visibleNebulae=new(NebulaBudget); private readonly List nebulaCandidates=new(172); private readonly GalaxyStar[] attenuatedStars=new GalaxyStar[StarCapacity]; private Vector3 nebulaEye; private bool disposed; internal event Action? ResourcesChanged; internal int VisibleNebulae=>visibleNebulae.Count; internal int PendingNebulaLoads {get;private set;} internal int ResidentNebulaTextures=>nebulaTextures.Count; internal long NebulaGpuBytes=>nebulaTextures.Count*(64L*64*64*4+256*16); internal bool NebulaeEnabled {get;set;}=true; private void InitializeNebulae(string shader) { nebulaVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSNebula","vs_5_0").Span); nebulaPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSNebula","ps_5_0").Span); nebulaCompositePs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSNebulaComposite","ps_5_0").Span); nebulaConstants=device.CreateBuffer(64,BindFlags.ConstantBuffer); } private void PrepareNebulae(double w,double h,double yaw,double pitch,Vector3 centre,float scale,float panX,float panY,bool showBackground=true) { visibleNebulae.Clear();nebulaCandidates.Clear();PendingNebulaLoads=0;if(!showBackground||!NebulaeEnabled||scale<=0)return; var camera=GalaxyCamera.Create(new(centre.X,centre.Y,centre.Z),w,h,yaw,pitch,scale,panX,panY); var right=new Vector3((float)camera.Right.X,(float)camera.Right.Y,(float)camera.Right.Z); var up=new Vector3((float)camera.Up.X,(float)camera.Up.Y,(float)camera.Up.Z); var depth=new Vector3((float)camera.Forward.X,(float)camera.Forward.Y,(float)camera.Forward.Z); nebulaEye=new((float)camera.Eye.X,(float)camera.Eye.Y,(float)camera.Eye.Z); var focal=(float)camera.FocalLength;var near=(float)camera.Near; foreach(var nebula in EliteNebulae.Reference.Nebulae) { var relative=nebula.Centre-nebulaEye;var centreDepth=Vector3.Dot(relative,depth);var worldRadius=nebula.HalfSize*1.732051f; if(centreDepth+worldRadius<=near)continue; Vector4 rect; if(centreDepth-worldRadius<=near)rect=new(0,0,(float)w,(float)h); else { var x=(float)w/2+Vector3.Dot(relative,right)*focal/centreDepth;var y=(float)h/2-Vector3.Dot(relative,up)*focal/centreDepth; var radius=worldRadius*focal/(centreDepth-worldRadius); if(radius<1.5f||x+radius<0||y+radius<0||x-radius>w||y-radius>h)continue; rect=new(Math.Max(0,x-radius),Math.Max(0,y-radius),Math.Min((float)w,x+radius),Math.Min((float)h,y+radius)); } nebulaCandidates.Add(new(nebula,relative,rect,centreDepth,right,up,depth)); } // A small distant cloud cannot evict the large cloud currently filling the viewport. nebulaCandidates.Sort((a,b)=>((b.Rect.Z-b.Rect.X)*(b.Rect.W-b.Rect.Y)).CompareTo((a.Rect.Z-a.Rect.X)*(a.Rect.W-a.Rect.Y))); var selected=nebulaCandidates.Take(NebulaBudget).ToArray();var keep=selected.Select(x=>x.Nebula.Template).ToHashSet(StringComparer.OrdinalIgnoreCase); foreach(var key in nebulaTextures.Keys.Where(k=>!keep.Contains(k)).ToArray()){nebulaTextures[key].Dispose();nebulaTextures.Remove(key);} foreach(var draw in selected) { var name=draw.Nebula.Template; if(!nebulaTextures.ContainsKey(name)) { var task=LocalShipModels.LoadNebulaAsync(name); if(!task.IsCompleted)PendingNebulaLoads++; if(task.IsCompletedSuccessfully)nebulaTextures[name]=CreateNebulaTexture(task.Result); else if(requestedNebulae.Add(name))_ = task.ContinueWith(completed=> { App.MainWindow.DispatcherQueue.TryEnqueue(()=> { if(disposed)return; if(completed.IsFaulted)App.Runtime.Log("星云资源",completed.Exception!.GetBaseException().Message); ResourcesChanged?.Invoke(); }); },TaskScheduler.Default); } if(nebulaTextures.ContainsKey(name))visibleNebulae.Add(draw); } visibleNebulae.Sort((a,b)=>b.Depth.CompareTo(a.Depth)); } private NebulaGpu CreateNebulaTexture(NebulaVolume volume) { var pixels=GCHandle.Alloc(volume.Rgba,GCHandleType.Pinned);var transfer=GCHandle.Alloc(volume.Transfer,GCHandleType.Pinned); try { var texture=device.CreateTexture3D(new Texture3DDescription{Width=64,Height=64,Depth=64,MipLevels=1,Format=Format.R8G8B8A8_UNorm, BindFlags=BindFlags.ShaderResource,Usage=ResourceUsage.Immutable},[new SubresourceData(pixels.AddrOfPinnedObject(),256,16384)]); var palette=device.CreateTexture2D(new Texture2DDescription{Width=256,Height=1,MipLevels=1,ArraySize=1,Format=Format.R32G32B32A32_Float, SampleDescription=new(1,0),BindFlags=BindFlags.ShaderResource,Usage=ResourceUsage.Immutable},[new SubresourceData(transfer.AddrOfPinnedObject(),4096,0)]); return new(texture,device.CreateShaderResourceView(texture),palette,device.CreateShaderResourceView(palette),volume); } finally{pixels.Free();transfer.Free();} } private void DrawNebulae(ID3D11RenderTargetView? destination=null) { if(visibleNebulae.Count==0)return; var ratio=Math.Min(1,Math.Min(1280d/width,720d/height));var fw=Math.Max(1,(int)Math.Ceiling(width*ratio));var fh=Math.Max(1,(int)Math.Ceiling(height*ratio)); if(fogTexture is null||fogTexture.Description.Width!=fw||fogTexture.Description.Height!=fh) { fogView?.Dispose();fogTarget?.Dispose();fogTexture?.Dispose(); fogTexture=device.CreateTexture2D(Format.R8G8B8A8_UNorm,(uint)fw,(uint)fh,mipLevels:1,bindFlags:BindFlags.ShaderResource|BindFlags.RenderTarget); fogTarget=device.CreateRenderTargetView(fogTexture);fogView=device.CreateShaderResourceView(fogTexture); } context.OMSetRenderTargets(fogTarget!);context.RSSetViewport(new Viewport(fw,fh));context.ClearRenderTargetView(fogTarget!,new Color4(0,0,0,0)); context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.VSSetShader(nebulaVs);context.PSSetShader(nebulaPs); context.VSSetConstantBuffer(1,nebulaConstants);context.PSSetConstantBuffer(1,nebulaConstants);context.PSSetSampler(0,sampler); foreach(var draw in visibleNebulae) { var data=new NebulaCamera(new(draw.Relative,draw.Nebula.HalfSize),new(draw.Nebula.Orientation.X,draw.Nebula.Orientation.Y,draw.Nebula.Orientation.Z,draw.Nebula.Orientation.W),draw.Rect,new(40,draw.Nebula.Dark?1:0,0,0)); context.UpdateSubresource(in data,nebulaConstants);var texture=nebulaTextures[draw.Nebula.Template]; context.PSSetShaderResource(2,texture.VolumeView);context.PSSetShaderResource(3,texture.TransferView);context.Draw(6,0);DrawCalls++; } context.PSSetShaderResource(2,null!);context.PSSetShaderResource(3,null!); context.OMSetRenderTargets(destination??target!);context.RSSetViewport(new Viewport(width,height));context.VSSetShader(skyVs);context.PSSetShader(nebulaCompositePs); context.PSSetShaderResource(4,fogView!);context.Draw(3,0);context.PSSetShaderResource(4,null!);DrawCalls++; } private ReadOnlySpan ApplyNebulaTransmission(ReadOnlySpan stars) { if(visibleNebulae.Count==0)return stars; stars.CopyTo(attenuatedStars); for(var i=0;idraw.Rect.Z||star.Position.Y>draw.Rect.W)continue; var inverse=Quaternion.Conjugate(draw.Nebula.Orientation); var world=new Vector3(star.World.X,star.World.Y,star.World.Z); var local=Vector3.Transform(world-draw.Nebula.Centre,inverse)/draw.Nebula.HalfSize; var towardViewer=nebulaEye-world;if(towardViewer.LengthSquared()<1e-12f)continue; var direction=Vector3.Transform(Vector3.Normalize(towardViewer),inverse); attenuation*=nebulaTextures[draw.Nebula.Template].Pixels.Transmission(local,direction,towardViewer.Length()/draw.Nebula.HalfSize); } var transmission=Math.Max(.015f,attenuation); attenuatedStars[i]=star with{Colour=star.Colour with{W=star.Colour.W*transmission}, OriginalColour=star.OriginalColour is{} original?original with{W=original.W*transmission}:null}; } return attenuatedStars.AsSpan(0,stars.Length); } private void DisposeNebulae() { disposed=true;foreach(var texture in nebulaTextures.Values)texture.Dispose();nebulaTextures.Clear(); fogView?.Dispose();fogTarget?.Dispose();fogTexture?.Dispose();nebulaVs.Dispose();nebulaPs.Dispose();nebulaCompositePs.Dispose();nebulaConstants.Dispose(); } [StructLayout(LayoutKind.Sequential)] private readonly record struct NebulaCamera(Vector4 CentreHalf,Vector4 Orientation,Vector4 Rect,Vector4 Settings); private sealed record NebulaDraw(GalacticNebula Nebula,Vector3 Relative,Vector4 Rect,float Depth,Vector3 Right,Vector3 Up,Vector3 ViewDepth); private sealed record NebulaGpu(ID3D11Texture3D Volume,ID3D11ShaderResourceView VolumeView,ID3D11Texture2D Transfer,ID3D11ShaderResourceView TransferView,NebulaVolume Pixels):IDisposable {public void Dispose(){VolumeView.Dispose();Volume.Dispose();TransferView.Dispose();Transfer.Dispose();}} }