screen space reflections vatten och dämpad motion blur på skärnor
This commit is contained in:
@@ -85,7 +85,7 @@ internal static class GraphicsReport
|
||||
camera.SetPlayerPose(center.Add(new(0,0,-12*scale),cell),camera.Forward,camera.Up);
|
||||
var moving=Frame(stars);
|
||||
var stopped=Frame(stars);
|
||||
int Coverage(float[] pixels)=>Enumerable.Range(0,width*height).Count(i=>pixels[i*4]>.035);
|
||||
int Coverage(float[] pixels)=>Enumerable.Range(0,width*height).Count(i=>pixels[i*4]>.001);
|
||||
var movingCoverage=Coverage(moving);var stillCoverage=Coverage(stopped);
|
||||
if(moving.Any(p=>!float.IsFinite(p))||movingCoverage<stillCoverage*1.5)
|
||||
throw new InvalidOperationException($"Star trails did not stretch the moving stars: moving {movingCoverage}, stopped {stillCoverage} pixels.");
|
||||
@@ -95,10 +95,25 @@ internal static class GraphicsReport
|
||||
if(trails.History.Valid||cut.Zip(stopped,(a,b)=>System.Math.Abs(a-b)).Max()>.0001)
|
||||
throw new InvalidOperationException("Star trails retained an afterimage after stopping or a camera cut.");
|
||||
using var single=new GpuMesh(gl,7,[0,0,-10,1,1,1,2]);
|
||||
double Energy(float[] pixels) => pixels.Where((_,i)=>i%4<3).Sum(v=>(double)v);
|
||||
var energyRatios = new List<double>();
|
||||
foreach (var travel in new[] { .05, 1d, 12d, 80d, 180d, 300d })
|
||||
{
|
||||
camera.SetPlayerPose(center,new(0,0,-1),new(0,1,0),resetMotionHistory:true);
|
||||
var point = Frame(single);
|
||||
var dx=travel*20*System.Math.Tan(camera.FieldOfViewRadians*.5)/width;
|
||||
camera.SetPlayerPose(center.Add(new(dx*scale,0,0),cell),camera.Forward,camera.Up);
|
||||
var streak = Frame(single);
|
||||
var ratio=Energy(streak)/Energy(point); energyRatios.Add(ratio);
|
||||
// movingBrightness is an optional artistic multiplier; the default 1 conserves HDR energy.
|
||||
var expected=1+(settings.MovingBrightness-1)*(1-System.Math.Exp(-System.Math.Min(travel*settings.ShutterFraction,settings.MaximumTrailPixels)/14));
|
||||
if(System.Math.Abs(ratio/expected-1)>.04)
|
||||
throw new InvalidOperationException($"Star energy changed with motion: {travel} px, ratio {ratio:F4}, expected {expected:F4}.");
|
||||
}
|
||||
camera.SetPlayerPose(center,camera.Forward,camera.Up,resetMotionHistory:true);Frame(single);
|
||||
camera.SetPlayerPose(center.Add(new(-6*scale,0,0),cell),camera.Forward,camera.Up);
|
||||
var bounded=Frame(single);
|
||||
var columns=Enumerable.Range(0,width).Where(x=>Enumerable.Range(0,height).Any(y=>bounded[(y*width+x)*4]>.035)).ToArray();
|
||||
var columns=Enumerable.Range(0,width).Where(x=>Enumerable.Range(0,height).Any(y=>bounded[(y*width+x)*4]>.001)).ToArray();
|
||||
var span=columns.Length==0?0:columns[^1]-columns[0]+1;
|
||||
if(span<settings.MaximumTrailPixels*.5||span>settings.MaximumTrailPixels+14)
|
||||
throw new InvalidOperationException($"Star trail pixel limit failed: span {span}, limit {settings.MaximumTrailPixels}.");
|
||||
@@ -106,7 +121,7 @@ internal static class GraphicsReport
|
||||
var directory=Path.Combine(AppContext.BaseDirectory,"graphics-report");Directory.CreateDirectory(directory);
|
||||
WritePng(Path.Combine(directory,"star-motion-blur.png"),width,height,moving);
|
||||
WritePng(Path.Combine(directory,"star-motion-stopped.png"),width,height,stopped);
|
||||
Console.WriteLine($"Star trails passed: {movingCoverage} moving vs {stillCoverage} stationary pixels; capped trail {span} px; camera cut/stop clean; OpenGL {gl.GetStringS(GLEnum.Renderer)}.");
|
||||
Console.WriteLine($"Star trails passed: {movingCoverage} moving vs {stillCoverage} stationary pixels; energy ratios {string.Join(", ",energyRatios.Select(r=>r.ToString("F3")))}; capped trail {span} px; camera cut/stop clean; OpenGL {gl.GetStringS(GLEnum.Renderer)}.");
|
||||
gl.BindFramebuffer(GLEnum.Framebuffer,0);gl.Disable(GLEnum.Blend);gl.Enable(GLEnum.DepthTest);gl.DepthMask(true);
|
||||
}
|
||||
|
||||
@@ -339,6 +354,7 @@ internal static class GraphicsReport
|
||||
|
||||
public static void RunOcean(GL gl, UniverseRenderer renderer, UniverseNeighborhood neighborhood, double cellSize, int width, int height)
|
||||
{
|
||||
ValidateOceanReflections(gl);
|
||||
var directory = Path.Combine(AppContext.BaseDirectory, "graphics-report"); Directory.CreateDirectory(directory);
|
||||
var origin = UniversePosition.AtCellCenter(neighborhood.ActiveSystem.Coordinate, cellSize);
|
||||
var camera = new FlyCamera(origin, cellSize);
|
||||
@@ -362,12 +378,96 @@ internal static class GraphicsReport
|
||||
var after = Capture("earth-ocean-animated.png");
|
||||
var difference = before.Zip(after, (a, b) => System.Math.Abs(a - b)).Average();
|
||||
if (difference < .00001) throw new InvalidOperationException("Ocean animation did not change the rendered image.");
|
||||
renderer.SimulationTimeSeconds = 12;
|
||||
UniverseNeighborhood WithoutSsr(UniverseNeighborhood world) => world with { ActiveSystem = world.ActiveSystem with
|
||||
{ Planets = world.ActiveSystem.Planets.Select(p => p.Ocean is { } ocean ? p with
|
||||
{ Ocean = ocean with { ScreenSpaceReflections = ocean.ScreenSpaceReflections with { Enabled = false } } } : p).ToArray() } };
|
||||
float[] Snapshot(UniverseNeighborhood world, string file)
|
||||
{
|
||||
camera.SetPlayerPose(camera.Position,camera.Forward,camera.Up,resetMotionHistory:true);
|
||||
renderer.Render(camera,world,0); gl.Finish();
|
||||
if(gl.GetError()!=GLEnum.NoError)throw new InvalidOperationException("Ocean SSR GL error.");
|
||||
var pixels=Read(gl,width,height);WritePng(Path.Combine(directory,file),width,height,pixels,false);return pixels;
|
||||
}
|
||||
Snapshot(WithoutSsr(neighborhood),"earth-ocean-reflections-off.png");
|
||||
var withReflections=Snapshot(neighborhood,"earth-ocean-reflections-on.png");
|
||||
var withoutReflections=Snapshot(WithoutSsr(neighborhood),"earth-ocean-reflections-off.png");
|
||||
var reflectionDifference=withReflections.Zip(withoutReflections,(a,b)=>System.Math.Abs(a-b)).Average();
|
||||
if(reflectionDifference<.0001)throw new InvalidOperationException($"Water reflections did not change the production image: {reflectionDifference}.");
|
||||
renderer.ProfileFrame=true;Snapshot(neighborhood,"earth-ocean-reflections-on.png");renderer.ProfileFrame=false;
|
||||
var reflectionMs=renderer.FrameTimings.GetValueOrDefault("water reflections");
|
||||
var earth=neighborhood.ActiveSystem.Planets.Single(p=>p.Id=="earth");
|
||||
var up=(camera.Position.RelativeTo(origin,cellSize)-earth.PositionMeters).Normalized();
|
||||
var night=neighborhood with { ActiveSystem=neighborhood.ActiveSystem with
|
||||
{ Stars=neighborhood.ActiveSystem.Stars.Select(s=>s with {PositionMeters=earth.PositionMeters-up*UniverseScale.AstronomicalUnitMeters}).ToArray() } };
|
||||
Snapshot(WithoutSsr(night),"earth-ocean-night-reflections-off.png");
|
||||
var nightOn=Snapshot(night,"earth-ocean-night-reflections-on.png");
|
||||
var nightOff=Snapshot(WithoutSsr(night),"earth-ocean-night-reflections-off.png");
|
||||
var nightDifference=nightOn.Zip(nightOff,(a,b)=>System.Math.Abs(a-b)).Average();
|
||||
if(nightDifference<.00001)throw new InvalidOperationException("The night sky did not reflect in the water.");
|
||||
renderer.SimulationTimeSeconds = null;
|
||||
for (var i = 0; i < 18; i++) camera.Update(.1, -camera.Up, neighborhood);
|
||||
Capture("earth-underwater.png");
|
||||
camera.RequestFocus([neighborhood.ActiveSystem], "earth");
|
||||
for (var i = 0; i < 400; i++) camera.Update(1d / 60, DVector3.Zero, neighborhood);
|
||||
Capture("earth-ocean-orbit.png");
|
||||
Console.WriteLine($"Ocean scene passed: finite frames, wave animation delta {difference:G5}, underwater and orbit views. Full rendering {timer.Elapsed.TotalMilliseconds / 10:F1} ms/frame. Images: {directory}");
|
||||
Console.WriteLine($"Ocean scene passed: finite frames, wave animation delta {difference:G5}, reflection delta {reflectionDifference:G5} day / {nightDifference:G5} night; SSR {reflectionMs:F2} ms at {width}x{height}; underwater and orbit views. Full rendering {timer.Elapsed.TotalMilliseconds / 10:F1} ms/frame. Images: {directory}");
|
||||
}
|
||||
|
||||
private static void ValidateOceanReflections(GL gl)
|
||||
{
|
||||
const int width=640,height=360; const float far=100000;
|
||||
using var source=new RenderTarget(gl,width,height,true,auxiliaryColors:2);
|
||||
using var reflection=new RenderTarget(gl,width/2,height/2);
|
||||
using var composite=new RenderTarget(gl,width,height);
|
||||
using var fullscreen=new GpuMesh(gl,3,[-1,-1,0,3,-1,0,-1,3,0]);
|
||||
using var fixture=ShaderProgram.Create(gl,ShaderSources.Load("fullscreen.vert"),"""
|
||||
#version 330 core
|
||||
in vec2 vUv;
|
||||
layout(location=0)out vec4 color;
|
||||
layout(location=1)out vec4 normal;
|
||||
layout(location=2)out vec4 fallback;
|
||||
void main(){
|
||||
vec2 p=(vUv*2.0-1.0)*.67;
|
||||
vec3 ray=normalize(vec3(p.x*1.7777778,p.y,-1));
|
||||
float wall=-12.0/ray.z,water=ray.y<0.0?-2.0/ray.y:1e8;
|
||||
vec3 hit=ray*wall;
|
||||
bool object=abs(hit.x)<3.0&&hit.y>-2.0&&hit.y<4.0;
|
||||
float t=object?wall:1e8;
|
||||
color=vec4(.08,.2,.4,1);normal=vec4(0);fallback=vec4(0);
|
||||
if(object)color=vec4(2,.04,.02,1);
|
||||
if(water<t){t=water;color=vec4(.03,.07,.12,1);normal=vec4(0,.7,0,1);fallback=vec4(.03,.07,.12,1);}
|
||||
gl_FragDepth=t<1e7?log2(1.0-t*ray.z)/log2(100001.0):1.0;
|
||||
}
|
||||
""");
|
||||
source.Bind(allColors:true);gl.Disable(GLEnum.Blend);gl.Enable(GLEnum.DepthTest);gl.DepthFunc(GLEnum.Always);gl.DepthMask(true);
|
||||
fixture.Use();fullscreen.Draw();source.GenerateColorMipmaps();
|
||||
source.Bind();gl.Finish();var original=Read(gl,width,height);
|
||||
using var trace=ShaderProgram.Create(gl,ShaderSources.Load("fullscreen.vert"),ShaderSources.Load("ocean-reflection.frag"));
|
||||
using var resolve=ShaderProgram.Create(gl,ShaderSources.Load("fullscreen.vert"),ShaderSources.Load("ocean-reflection-composite.frag"));
|
||||
void Bind(int unit,uint texture){gl.ActiveTexture((GLEnum)((int)GLEnum.Texture0+unit));gl.BindTexture(GLEnum.Texture2D,texture);}
|
||||
reflection.Bind();gl.Disable(GLEnum.DepthTest);gl.DepthMask(false);trace.Use();
|
||||
Bind(0,source.Color);Bind(1,source.Depth);Bind(2,source.AuxiliaryColors[0]);
|
||||
trace.Set("uScene",0);trace.Set("uDepth",1);trace.Set("uWaterNormals",2);trace.Set("uFar",far);
|
||||
trace.Set("uForward",new Vector3(0,0,-1));trace.Set("uRight",Vector3.UnitX);trace.Set("uUp",Vector3.UnitY);
|
||||
trace.Set("uAspect",(float)width/height);trace.Set("uTanHalfFov",.67f);
|
||||
trace.Set("uMaxSteps",48);trace.Set("uTraceSettings",new Vector3(100,1.5f,.12f));
|
||||
trace.Set("uReflectionSettings",new Vector3(.07f,2,.08f));trace.Set("uMaxSurfaceDistance",1000f);
|
||||
trace.Set("uReflectionStrength",1f);trace.Set("uReflectSky",0);fullscreen.Draw();gl.Finish();
|
||||
var raw=Read(gl,width/2,height/2);var offset=(height/2/5*(width/2)+width/4)*4;
|
||||
if(raw[offset+3]<.4 || raw[offset]<raw[offset+1]*4)
|
||||
throw new InvalidOperationException($"SSR did not reflect the visible red wall: {string.Join(",",raw.Skip(offset).Take(4))}.");
|
||||
composite.Bind();resolve.Use();Bind(0,source.Color);Bind(1,source.Depth);Bind(2,source.AuxiliaryColors[0]);
|
||||
Bind(3,source.AuxiliaryColors[1]);Bind(5,reflection.Color);
|
||||
resolve.Set("uScene",0);resolve.Set("uDepth",1);resolve.Set("uWaterNormals",2);resolve.Set("uWaterFallback",3);
|
||||
resolve.Set("uReflection",5);resolve.Set("uClouds",0);resolve.Set("uHasClouds",0);resolve.Set("uFar",far);fullscreen.Draw();gl.Finish();
|
||||
var result=Read(gl,width,height);
|
||||
var landError=Enumerable.Range(width*height/2,width*height/2).SelectMany(i=>Enumerable.Range(0,3).Select(c=>System.Math.Abs(original[i*4+c]-result[i*4+c]))).Max();
|
||||
if(landError>.001 || gl.GetError()!=GLEnum.NoError)throw new InvalidOperationException($"SSR modified non-water geometry: {landError}.");
|
||||
var dir=Path.Combine(AppContext.BaseDirectory,"graphics-report");Directory.CreateDirectory(dir);
|
||||
WritePng(Path.Combine(dir,"ocean-ssr-wall-test.png"),width,height,result);
|
||||
Console.WriteLine($"SSR GPU test passed: visible wall reflected, confidence {raw[offset+3]:F3}, non-water error {landError:G3}.");
|
||||
gl.BindFramebuffer(GLEnum.Framebuffer,0);gl.Enable(GLEnum.DepthTest);gl.DepthFunc(GLEnum.Lequal);gl.DepthMask(true);
|
||||
}
|
||||
public static void RunScene(GL gl, UniverseRenderer renderer, UniverseNeighborhood neighborhood, double cellSize, int width, int height)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ internal sealed class PerformanceOverlay : IDisposable
|
||||
AddMetric("GAS GIANTS", "GAS GIANTS");
|
||||
AddMetric("RINGS / ROCKS", "RINGS / ROCKS");
|
||||
AddMetric("OCEAN", "OCEAN");
|
||||
AddMetric("WATER REFLECTIONS", "WATER REFLECTIONS");
|
||||
AddMetric("ATMOSPHERE", "ATMOSPHERE");
|
||||
AddMetric("CLOUDS / COMPOSITE", "CLOUDS / COMPOSITE");
|
||||
AddMetric("SUN GLOW", "SUN GLOW");
|
||||
|
||||
@@ -8,10 +8,11 @@ internal sealed unsafe class RenderTarget : IDisposable
|
||||
public uint Framebuffer { get; }
|
||||
public uint Color { get; }
|
||||
public uint Depth { get; }
|
||||
public uint[] AuxiliaryColors { get; }
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public RenderTarget(GL gl, int width, int height, bool depth = false, bool fullPrecision = false)
|
||||
public RenderTarget(GL gl, int width, int height, bool depth = false, bool fullPrecision = false, int auxiliaryColors = 0)
|
||||
{
|
||||
_gl = gl;
|
||||
Width = width;
|
||||
@@ -23,6 +24,15 @@ internal sealed unsafe class RenderTarget : IDisposable
|
||||
gl.TexImage2D(GLEnum.Texture2D, 0, fullPrecision ? InternalFormat.Rgba32f : InternalFormat.Rgba16f, (uint)width, (uint)height, 0, PixelFormat.Rgba, PixelType.Float, null);
|
||||
SetParameters(gl, GLEnum.Linear);
|
||||
gl.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.ColorAttachment0, GLEnum.Texture2D, Color, 0);
|
||||
AuxiliaryColors = new uint[auxiliaryColors];
|
||||
for (var i = 0; i < auxiliaryColors; i++)
|
||||
{
|
||||
var texture = gl.GenTexture(); AuxiliaryColors[i] = texture;
|
||||
gl.BindTexture(GLEnum.Texture2D, texture);
|
||||
gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.Rgba16f, (uint)width, (uint)height, 0, PixelFormat.Rgba, PixelType.Float, null);
|
||||
SetParameters(gl, GLEnum.Nearest);
|
||||
gl.FramebufferTexture2D(GLEnum.Framebuffer, (GLEnum)((uint)GLEnum.ColorAttachment1 + i), GLEnum.Texture2D, texture, 0);
|
||||
}
|
||||
if (depth)
|
||||
{
|
||||
Depth = gl.GenTexture();
|
||||
@@ -36,10 +46,24 @@ internal sealed unsafe class RenderTarget : IDisposable
|
||||
gl.BindFramebuffer(GLEnum.Framebuffer, 0);
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
public void Bind(bool allColors = false)
|
||||
{
|
||||
_gl.BindFramebuffer(GLEnum.Framebuffer, Framebuffer);
|
||||
_gl.Viewport(0, 0, (uint)Width, (uint)Height);
|
||||
if (allColors && AuxiliaryColors.Length > 0)
|
||||
{
|
||||
var attachments = stackalloc GLEnum[AuxiliaryColors.Length + 1];
|
||||
for (var i = 0; i <= AuxiliaryColors.Length; i++) attachments[i] = (GLEnum)((uint)GLEnum.ColorAttachment0 + i);
|
||||
_gl.DrawBuffers((uint)(AuxiliaryColors.Length + 1), attachments);
|
||||
}
|
||||
else _gl.DrawBuffer(GLEnum.ColorAttachment0);
|
||||
}
|
||||
|
||||
public void GenerateColorMipmaps()
|
||||
{
|
||||
_gl.BindTexture(GLEnum.Texture2D, Color);
|
||||
_gl.GenerateMipmap(GLEnum.Texture2D);
|
||||
_gl.TexParameter(GLEnum.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.LinearMipmapLinear);
|
||||
}
|
||||
|
||||
private static void SetParameters(GL gl, GLEnum filter)
|
||||
@@ -53,6 +77,7 @@ internal sealed unsafe class RenderTarget : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteTexture(Color);
|
||||
foreach (var texture in AuxiliaryColors) _gl.DeleteTexture(texture);
|
||||
if (Depth != 0) _gl.DeleteTexture(Depth);
|
||||
_gl.DeleteFramebuffer(Framebuffer);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ internal static class ShaderSources
|
||||
}
|
||||
private static string Expand(string name, string source)
|
||||
{
|
||||
foreach (var include in new[] { "noise", "planet-height", "camera-ray", "ring-optics", "rock-common", "asteroid-shape", "cloud-common" })
|
||||
foreach (var include in new[] { "noise", "planet-height", "camera-ray", "ring-optics", "rock-common", "asteroid-shape", "cloud-common", "screen-space-reflection" })
|
||||
{
|
||||
var directive = $"#include \"{include}.glsl\"";
|
||||
if (source.Contains(directive, StringComparison.Ordinal))
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#version 330 core
|
||||
in vec2 vUv;
|
||||
uniform sampler2D uScene;
|
||||
uniform sampler2D uDepth;
|
||||
uniform sampler2D uWaterNormals;
|
||||
uniform sampler2D uWaterFallback;
|
||||
uniform sampler2D uReflection;
|
||||
uniform sampler2D uClouds;
|
||||
uniform int uHasClouds;
|
||||
uniform float uFar;
|
||||
out vec4 fragColor;
|
||||
float depth(vec2 uv) { return exp2(textureLod(uDepth,uv,0).r*log2(uFar+1.0))-1.0; }
|
||||
void main() {
|
||||
vec3 scene=textureLod(uScene,vUv,0).rgb;
|
||||
vec4 surface=texture(uWaterNormals,vUv);
|
||||
float reflectance=length(surface.rgb);
|
||||
fragColor=vec4(scene,1);
|
||||
if(surface.a<.5 || reflectance<.0001)return;
|
||||
// Depth/normal-aware bilinear upsampling prevents a half-resolution shoreline from reflecting onto land.
|
||||
ivec2 size=textureSize(uReflection,0);
|
||||
vec2 p=vUv*vec2(size)-.5,base=floor(p),f=fract(p);
|
||||
float z=depth(vUv),weight=0.0;
|
||||
vec4 sum=vec4(0);
|
||||
for(int y=0;y<2;y++)for(int x=0;x<2;x++) {
|
||||
ivec2 coord=clamp(ivec2(base)+ivec2(x,y),ivec2(0),size-1);
|
||||
vec2 uv=(vec2(coord)+.5)/vec2(size);
|
||||
vec4 other=textureLod(uWaterNormals,uv,0);
|
||||
if(other.a<.5 || length(other.rgb)<.0001)continue;
|
||||
float w=(x==0?1.0-f.x:f.x)*(y==0?1.0-f.y:f.y);
|
||||
w*=exp(-abs(depth(uv)-z)/max(.3,z*.025));
|
||||
w*=smoothstep(.8,.99,dot(normalize(surface.rgb),normalize(other.rgb)));
|
||||
sum+=texelFetch(uReflection,coord,0)*w;weight+=w;
|
||||
}
|
||||
if(weight<=.00001 || sum.a<=.00001)return;
|
||||
float confidence=clamp(sum.a/weight,0.0,1.0);
|
||||
if(uHasClouds!=0)confidence*=texture(uClouds,vUv).a;
|
||||
vec3 reflected=sum.rgb/sum.a;
|
||||
vec3 fallback=texture(uWaterFallback,vUv).rgb;
|
||||
fragColor=vec4(max(vec3(0),scene+(reflected*reflectance-fallback)*confidence),1);
|
||||
}
|
||||
17
src/Universe.App/Graphics/Shaders/ocean-reflection.frag.glsl
Normal file
17
src/Universe.App/Graphics/Shaders/ocean-reflection.frag.glsl
Normal file
@@ -0,0 +1,17 @@
|
||||
#version 330 core
|
||||
#include "camera-ray.glsl"
|
||||
#include "screen-space-reflection.glsl"
|
||||
in vec2 vUv;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor=vec4(0);
|
||||
vec4 surface=textureLod(uWaterNormals,vUv,0.0);
|
||||
if(surface.a<.5 || length(surface.rgb)<.0001)return;
|
||||
vec3 ray=cameraRay(vUv);
|
||||
float distance=linearDepth(vUv)/max(.001,dot(ray,uForward));
|
||||
if(distance<=.03 || distance>uMaxSurfaceDistance)return;
|
||||
vec4 hit=traceReflection(ray*distance,normalize(surface.rgb));
|
||||
float fade=1.0-smoothstep(uMaxSurfaceDistance*.6,uMaxSurfaceDistance,distance);
|
||||
fade*=1.0-smoothstep(.6,1.0,uReflectionSettings.z);
|
||||
fragColor=hit*(fade*uReflectionStrength);
|
||||
}
|
||||
@@ -27,7 +27,9 @@ uniform vec3 uLightDirections[2];
|
||||
uniform vec3 uLightColors[2];
|
||||
uniform float uLightFlux[2];
|
||||
uniform float uTime;
|
||||
out vec4 fragColor;
|
||||
layout(location=0) out vec4 fragColor;
|
||||
layout(location=1) out vec4 reflectionNormal;
|
||||
layout(location=2) out vec4 reflectionFallback;
|
||||
const float PI=3.14159265359;
|
||||
vec3 toLocal(vec3 v) { return vec3(dot(v,uAxisX),dot(v,uAxisY),dot(v,uAxisZ)); }
|
||||
vec3 toWorld(vec3 v) { return uAxisX*v.x+uAxisY*v.y+uAxisZ*v.z; }
|
||||
@@ -98,25 +100,9 @@ vec3 skyReflection(vec3 direction,vec3 up) {
|
||||
}
|
||||
return sky*(.006+daylight*1.35);
|
||||
}
|
||||
vec3 screenReflection(vec3 position,vec3 direction,vec3 fallback) {
|
||||
vec3 world=toWorld(position-vec3(0,uCameraHeight,0));
|
||||
vec3 reflected=toWorld(direction);
|
||||
float stepSize=3.0;
|
||||
for(int i=0;i<9;i++) {
|
||||
vec3 q=world+reflected*stepSize;
|
||||
float z=dot(q,uForward);
|
||||
if(z>.1) {
|
||||
vec2 uv=.5+vec2(dot(q,uRight)/(uAspect*uTanHalfFov),dot(q,uUp)/uTanHalfFov)/(2.0*z);
|
||||
if(all(greaterThan(uv,vec2(.015))) && all(lessThan(uv,vec2(.985)))) {
|
||||
float depth=exp2(texture(uDepth,uv).r*log2(uFar+1.0))-1.0;
|
||||
if(z>depth && z-depth<max(1.0,stepSize*.09))return texture(uScene,uv).rgb;
|
||||
}
|
||||
}
|
||||
stepSize*=1.9;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
void main() {
|
||||
// Premultiplied-alpha MRT blending preserves earlier ocean planets outside this one's silhouette.
|
||||
reflectionNormal=vec4(0); reflectionFallback=vec4(0);
|
||||
vec3 scene=texture(uScene,vUv).rgb;
|
||||
float originalDepth=texture(uDepth,vUv).r;
|
||||
gl_FragDepth=originalDepth;
|
||||
@@ -167,7 +153,6 @@ void main() {
|
||||
vec3 reflectionDirection=reflect(rd,n);
|
||||
vec3 reflected=skyReflection(reflectionDirection,up);
|
||||
reflected=cloudReflection(toWorld(p-ro),toWorld(reflectionDirection),reflected);
|
||||
if(t<1500.0 && !underwater)reflected=screenReflection(p,reflectionDirection,reflected);
|
||||
vec2 refractUv=clamp(vUv+vec2(dot(toWorld(n),uRight),dot(toWorld(n),uUp))*uWaterOptics.z/(1.0+t*.012),vec2(.001),vec2(.999));
|
||||
float floorDistance=sceneDistance(refractUv,cameraRay(refractUv));
|
||||
float thickness=max(.0,min(400.0,floorDistance-t));
|
||||
@@ -204,5 +189,8 @@ void main() {
|
||||
color=color*through+uShallowColor*(1.0-through)*.5;
|
||||
}
|
||||
fragColor=vec4(max(color,vec3(0)),1);
|
||||
float reflectance=underwater?0.0:fresnel*uWaterOptics.y*(1.0-foam);
|
||||
reflectionNormal=vec4(toWorld(n)*reflectance,1);
|
||||
reflectionFallback=vec4(reflected*reflectance,1);
|
||||
gl_FragDepth=clamp(log2(1.0+t*max(.001,dot(worldRay,uForward)))/log2(uFar+1.0),0.0,1.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Perspective-correct screen-space segment traversal, following the DDA principle in
|
||||
// McGuire & Mara, JCGT 3(4), 2014. Camera-space Z is positive and depth is logarithmic.
|
||||
uniform sampler2D uScene;
|
||||
uniform sampler2D uDepth;
|
||||
uniform sampler2D uWaterNormals;
|
||||
uniform float uFar;
|
||||
uniform int uMaxSteps;
|
||||
uniform vec3 uTraceSettings; // maximum ray distance, thickness, normal bias (metres)
|
||||
uniform vec3 uReflectionSettings; // edge fade, pixel stride, roughness
|
||||
uniform float uMaxSurfaceDistance;
|
||||
uniform float uReflectionStrength;
|
||||
uniform int uReflectSky;
|
||||
|
||||
float linearDepth(vec2 uv) { return exp2(textureLod(uDepth,uv,0.0).r*log2(uFar+1.0))-1.0; }
|
||||
vec3 viewPoint(vec3 world) { return vec3(dot(world,uRight),dot(world,uUp),dot(world,uForward)); }
|
||||
vec2 projectPoint(vec3 p) { return .5+p.xy/(2.0*p.z*uTanHalfFov*vec2(uAspect,1)); }
|
||||
float edgeConfidence(vec2 uv) {
|
||||
float edge=min(min(uv.x,uv.y),min(1.0-uv.x,1.0-uv.y));
|
||||
return smoothstep(0.0,uReflectionSettings.x,edge);
|
||||
}
|
||||
vec4 traceReflection(vec3 world,vec3 normal) {
|
||||
vec3 reflection=reflect(normalize(world),normal);
|
||||
vec3 origin=viewPoint(world+normal*uTraceSettings.z),direction=viewPoint(reflection);
|
||||
if(origin.z<=.02)return vec4(0);
|
||||
float distanceLimit=uTraceSettings.x;
|
||||
if(direction.z<0.0)distanceLimit=min(distanceLimit,(.05-origin.z)/direction.z);
|
||||
vec4 result=vec4(0);
|
||||
float roughness=uReflectionSettings.z;
|
||||
float mip=roughness*5.0;
|
||||
// Infinite/distant sky lookup includes the actual stars, atmospheric colour and cloud texture.
|
||||
// Opaque foreground pixels are never treated as sky or used to fill missing geometry.
|
||||
if(uReflectSky!=0 && direction.z>.001) {
|
||||
vec2 uv=projectPoint(direction);
|
||||
if(all(greaterThan(uv,vec2(0)))&&all(lessThan(uv,vec2(1)))) {
|
||||
float z=linearDepth(uv);
|
||||
if(z>uTraceSettings.x*4.0 && textureLod(uWaterNormals,uv,0.0).a<.5) {
|
||||
float confidence=edgeConfidence(uv);
|
||||
result=vec4(textureLod(uScene,uv,mip).rgb*confidence,confidence);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(distanceLimit<=uTraceSettings.z)return result;
|
||||
vec3 end=origin+direction*distanceLimit;
|
||||
vec2 startUv=projectPoint(origin),endUv=projectPoint(end);
|
||||
vec2 size=vec2(textureSize(uDepth,0));
|
||||
float pixelLength=length((endUv-startUv)*size);
|
||||
float stepFraction=max(uReflectionSettings.y/max(pixelLength,1.0),1.0/float(uMaxSteps));
|
||||
vec3 q0=origin/origin.z,q1=end/end.z;
|
||||
float k0=1.0/origin.z,k1=1.0/end.z;
|
||||
float previous=0.0,previousZ=origin.z;
|
||||
for(int i=0;i<128;i++) {
|
||||
if(i>=uMaxSteps)break;
|
||||
float fraction=min(1.0,float(i+1)*stepFraction);
|
||||
vec2 uv=mix(startUv,endUv,fraction);
|
||||
if(any(lessThanEqual(uv,vec2(0)))||any(greaterThanEqual(uv,vec2(1))))break;
|
||||
float rayZ=1.0/mix(k0,k1,fraction);
|
||||
float sceneZ=linearDepth(uv);
|
||||
float thickness=uTraceSettings.y+sceneZ*.00005;
|
||||
bool water=textureLod(uWaterNormals,uv,0.0).a>.5;
|
||||
if(!water && sceneZ<uFar*.99 && max(previousZ,rayZ)>=sceneZ && min(previousZ,rayZ)<=sceneZ+thickness) {
|
||||
float lo=previous,hi=fraction;
|
||||
// Refine the front crossing instead of accepting an entire coarse depth interval.
|
||||
for(int j=0;j<7;j++) {
|
||||
float mid=(lo+hi)*.5;
|
||||
vec2 midUv=mix(startUv,endUv,mid);
|
||||
float delta=1.0/mix(k0,k1,mid)-linearDepth(midUv);
|
||||
if(delta>0.0)hi=mid;else lo=mid;
|
||||
}
|
||||
float hit=(lo+hi)*.5;
|
||||
vec2 hitUv=mix(startUv,endUv,hit);
|
||||
vec3 point=mix(q0,q1,hit)/mix(k0,k1,hit);
|
||||
float error=point.z-linearDepth(hitUv);
|
||||
float travel=length(point-origin);
|
||||
if(abs(error)<thickness && travel>uTraceSettings.z*2.0 && textureLod(uWaterNormals,hitUv,0.0).a<.5) {
|
||||
float confidence=edgeConfidence(hitUv)*(1.0-smoothstep(distanceLimit*.7,distanceLimit,travel));
|
||||
confidence*=1.0-smoothstep(thickness*.3,thickness,abs(error));
|
||||
return vec4(textureLod(uScene,hitUv,mip).rgb*confidence,confidence);
|
||||
}
|
||||
}
|
||||
previous=fraction; previousZ=rayZ;
|
||||
if(fraction>=1.0)break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -4,14 +4,21 @@ flat in vec3 gColor;
|
||||
flat in float gLength;
|
||||
flat in float gSigma;
|
||||
out vec4 fragColor;
|
||||
// Gaussian convolved with a normalized shutter interval. Its integral is the
|
||||
// same for a stationary point and a long streak (no extra light from area).
|
||||
float erfApprox(float x) {
|
||||
float t=1.0/(1.0+.3275911*abs(x));
|
||||
float p=(((((1.061405429*t-1.453152027)*t)+1.421413741)*t-.284496736)*t+.254829592)*t;
|
||||
return sign(x)*(1.0-p*exp(-x*x));
|
||||
}
|
||||
void main() {
|
||||
float closest=clamp(gPixel.x,0.0,gLength);
|
||||
vec2 delta=vec2(gPixel.x-closest,gPixel.y);
|
||||
float sigma2=gSigma*gSigma+1.0/12.0;
|
||||
float profile=exp(-dot(delta,delta)/(2.0*sigma2));
|
||||
float along=gLength>.5?mix(.32,1.0,smoothstep(0.0,1.0,closest/gLength)):1.0;
|
||||
float head=exp(-((gPixel.x-gLength)*(gPixel.x-gLength)+gPixel.y*gPixel.y)/(2.0*sigma2));
|
||||
float brightness=profile*along+head*.15;
|
||||
if(brightness<.003)discard;
|
||||
float sigma=sqrt(sigma2);
|
||||
float along;
|
||||
if(gLength<.2)along=exp(-pow(gPixel.x-gLength*.5,2.0)/(2.0*sigma2));
|
||||
else along=1.253314137*sigma/gLength*(erfApprox(gPixel.x/(1.414213562*sigma))
|
||||
-erfApprox((gPixel.x-gLength)/(1.414213562*sigma)));
|
||||
float brightness=exp(-gPixel.y*gPixel.y/(2.0*sigma2))*max(0.0,along);
|
||||
if(brightness<.00001)discard;
|
||||
fragColor=vec4(gColor*brightness,0);
|
||||
}
|
||||
|
||||
@@ -33,26 +33,28 @@ void main() {
|
||||
if(tail.w<nearPlane)tail=mix(head,tail,(head.w-nearPlane)/(head.w-tail.w));
|
||||
vec2 h=head.xy/head.w*uViewport.xy*.5;
|
||||
vec2 t=mix(h,tail.xy/tail.w*uViewport.xy*.5,uShutter);
|
||||
float fullLength=length(h-t);
|
||||
if(fullLength>uMaximumTrailPixels)t=h-(h-t)*(uMaximumTrailPixels/fullLength);
|
||||
vec2 originalTail=t;
|
||||
gLength=length(h-t);
|
||||
vec2 delta=h-t;
|
||||
float lo=0.0,hi=1.0;
|
||||
vec2 bounds=uViewport.xy*.5+vec2(8.0);
|
||||
if(!clipAxis(t.x,delta.x,bounds.x,lo,hi)||!clipAxis(t.y,delta.y,bounds.y,lo,hi))return;
|
||||
h=t+delta*hi;t+=delta*lo;
|
||||
float distancePixels=length(h-t);
|
||||
if(distancePixels>uMaximumTrailPixels)t=h-(h-t)*(uMaximumTrailPixels/distancePixels);
|
||||
gLength=length(h-t);
|
||||
vec2 direction=gLength>.001?(h-t)/gLength:vec2(1,0);
|
||||
vec2 direction=length(h-t)>.001?normalize(h-t):vec2(1,0);
|
||||
vec2 perpendicular=vec2(-direction.y,direction.x);
|
||||
// A minimum Gaussian footprint averages subpixel motion instead of toggling
|
||||
// a one-pixel point on and off as the camera passes it.
|
||||
gSigma=max(uMinimumWidthPixels/2.0,star[0].size*.3);
|
||||
float radius=gSigma*3.0;
|
||||
float radius=sqrt(gSigma*gSigma+1.0/12.0)*3.5;
|
||||
float moving=1.0-exp(-gLength/14.0);
|
||||
float boost=mix(1.0,uMovingBrightness,moving);
|
||||
gColor=star[0].color*.7*boost;
|
||||
vertex(t-direction*radius-perpendicular*radius,vec2(-radius,-radius));
|
||||
vertex(t-direction*radius+perpendicular*radius,vec2(-radius,radius));
|
||||
vertex(h+direction*radius-perpendicular*radius,vec2(gLength+radius,-radius));
|
||||
vertex(h+direction*radius+perpendicular*radius,vec2(gLength+radius,radius));
|
||||
gColor=star[0].color*.805*boost;
|
||||
float start=length(t-originalTail),end=length(h-originalTail);
|
||||
vertex(t-direction*radius-perpendicular*radius,vec2(start-radius,-radius));
|
||||
vertex(t-direction*radius+perpendicular*radius,vec2(start-radius,radius));
|
||||
vertex(h+direction*radius-perpendicular*radius,vec2(end+radius,-radius));
|
||||
vertex(h+direction*radius+perpendicular*radius,vec2(end+radius,radius));
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
private ShaderProgram _sunGlow;
|
||||
private ShaderProgram _blackHole;
|
||||
private ShaderProgram _ocean;
|
||||
private ShaderProgram _oceanReflection;
|
||||
private ShaderProgram _oceanReflectionComposite;
|
||||
private ShaderProgram _clouds;
|
||||
private ShaderProgram _cloudComposite;
|
||||
private readonly ShaderProgram _overlay;
|
||||
@@ -52,6 +54,10 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
private readonly CloudNoiseTexture _cloudNoise;
|
||||
private readonly SurfaceMaterials _materials;
|
||||
private RenderTarget? _cloudRaw;
|
||||
private RenderTarget? _waterReflectionRaw;
|
||||
private RenderTarget _waterReflectionTarget = null!;
|
||||
private bool _oceanRendered;
|
||||
private string? _cloudLayerPlanetId;
|
||||
internal RenderTarget? CloudLayer => _cloudRaw;
|
||||
private readonly GpuMesh _navigationStars;
|
||||
private RenderTarget _scene = null!;
|
||||
@@ -118,6 +124,8 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_sunGlow = Program("fullscreen.vert", "sun-glow.frag");
|
||||
_blackHole = Program("fullscreen.vert", "black-hole.frag");
|
||||
_ocean = Program("fullscreen.vert", "ocean.frag");
|
||||
_oceanReflection = Program("fullscreen.vert", "ocean-reflection.frag");
|
||||
_oceanReflectionComposite = Program("fullscreen.vert", "ocean-reflection-composite.frag");
|
||||
_clouds = Program("fullscreen.vert", "cloud.frag");
|
||||
_cloudComposite = Program("fullscreen.vert", "cloud-composite.frag");
|
||||
_overlay = ShaderProgram.Create(gl, """
|
||||
@@ -178,7 +186,8 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
foreach (var pair in new[] { ("terrain.vert","terrain.frag"),("star.vert","star.frag"),("fullscreen.vert","sky.frag"),("point.vert","point.frag"),
|
||||
("fullscreen.vert","atmosphere.frag"),("fullscreen.vert","bright.frag"),("fullscreen.vert","blur.frag"),("fullscreen.vert","final.frag"),("fullscreen.vert","ring.frag"),
|
||||
("fullscreen.vert","gas.frag"),("rock.vert","rock.frag"),("rock-point.vert","rock-point.frag"),("fullscreen.vert","sun-glow.frag"),("fullscreen.vert","ocean.frag"),
|
||||
("fullscreen.vert","cloud.frag"),("fullscreen.vert","cloud-composite.frag"),("fullscreen.vert","black-hole.frag") })
|
||||
("fullscreen.vert","cloud.frag"),("fullscreen.vert","cloud-composite.frag"),("fullscreen.vert","black-hole.frag"),
|
||||
("fullscreen.vert","ocean-reflection.frag"),("fullscreen.vert","ocean-reflection-composite.frag") })
|
||||
replacements.Add(Program(pair.Item1, pair.Item2));
|
||||
_starTrails.ReloadShaders();
|
||||
}
|
||||
@@ -198,6 +207,8 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_ocean.Dispose(); _ocean = replacements[13];
|
||||
_clouds.Dispose(); _clouds = replacements[14]; _cloudComposite.Dispose(); _cloudComposite = replacements[15];
|
||||
_blackHole.Dispose(); _blackHole = replacements[16];
|
||||
_oceanReflection.Dispose(); _oceanReflection = replacements[17];
|
||||
_oceanReflectionComposite.Dispose(); _oceanReflectionComposite = replacements[18];
|
||||
}
|
||||
|
||||
public void SetActiveSystem(SectorCoordinate coordinate)
|
||||
@@ -236,9 +247,11 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_width = System.Math.Max(width, 1);
|
||||
_height = System.Math.Max(height, 1);
|
||||
_cloudRaw?.Dispose(); _cloudRaw = null;
|
||||
_waterReflectionRaw?.Dispose(); _waterReflectionRaw = null; _waterReflectionTarget?.Dispose();
|
||||
_scene?.Dispose(); _gasInput?.Dispose(); _glareTarget?.Dispose(); _scatterA?.Dispose(); _scatterB?.Dispose(); _bloomA?.Dispose(); _bloomB?.Dispose();
|
||||
_blackHoleTarget?.Dispose();
|
||||
_scene = new(_gl, _width, _height, true);
|
||||
_scene = new(_gl, _width, _height, true, auxiliaryColors: 2);
|
||||
_waterReflectionTarget = new(_gl, _width, _height);
|
||||
_gasInput = new(_gl, _width, _height, true);
|
||||
_glareTarget = new(_gl, _width, _height);
|
||||
_blackHoleTarget = new(_gl, _width, _height);
|
||||
@@ -276,10 +289,12 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
var starPositions = system.Stars.Select(s => center.Add(s.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters)).ToArray();
|
||||
BindNoise();
|
||||
_gpuProfiler.BeginFrame(DebugMode);
|
||||
_scene.Bind();
|
||||
_oceanRendered = false; _cloudLayerPlanetId = null;
|
||||
_scene.Bind(allColors: true);
|
||||
_gl.DepthMask(true);
|
||||
_gl.ClearColor(0, 0, 0, 1);
|
||||
_gl.ClearColor(0, 0, 0, 0);
|
||||
_gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
_scene.Bind();
|
||||
_gl.Disable(GLEnum.Blend);
|
||||
_gl.Disable(GLEnum.DepthTest);
|
||||
_gl.DepthMask(false);
|
||||
@@ -330,6 +345,10 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
composed = DrawClouds(camera, system, far, composed);
|
||||
_gpuProfiler.EndPass();
|
||||
Mark("clouds");
|
||||
_gpuProfiler.BeginPass("WATER REFLECTIONS");
|
||||
composed = DrawOceanReflections(camera, system, far, composed);
|
||||
_gpuProfiler.EndPass();
|
||||
Mark("water reflections");
|
||||
_gpuProfiler.BeginPass("BLACK HOLE / LENSING");
|
||||
composed = DrawBlackHoles(camera, system, far, composed);
|
||||
_gpuProfiler.EndPass();
|
||||
@@ -674,7 +693,9 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_gl.BindFramebuffer(GLEnum.DrawFramebuffer, _gasInput.Framebuffer);
|
||||
_gl.BlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height,
|
||||
ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit, BlitFramebufferFilter.Nearest);
|
||||
_scene.Bind(); _gl.Enable(GLEnum.DepthTest); _gl.DepthFunc(GLEnum.Always); _gl.DepthMask(true);
|
||||
_scene.Bind(allColors: true); _gl.Enable(GLEnum.DepthTest); _gl.DepthFunc(GLEnum.Always); _gl.DepthMask(true);
|
||||
_gl.Enable(GLEnum.Blend); _gl.BlendFunc(GLEnum.One, GLEnum.OneMinusSrcAlpha);
|
||||
_oceanRendered = true;
|
||||
_ocean.Use(); SetCameraRay(_ocean, camera); BindPlanetTerrain(_ocean, planet, textures);
|
||||
BindCloudSettings(_ocean, camera, system, planet, relative);
|
||||
BindTexture(0, _gasInput.Color); BindTexture(1, _gasInput.Depth);
|
||||
@@ -717,9 +738,49 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
System.Math.Pow((s.PositionMeters - planet.PositionMeters).Length / UniverseScale.AstronomicalUnitMeters, 2), .02, 7)).ToArray());
|
||||
_fullscreen.Draw();
|
||||
}
|
||||
_gl.Disable(GLEnum.Blend); _scene.Bind();
|
||||
_gl.DepthFunc(GLEnum.Lequal);
|
||||
}
|
||||
|
||||
private RenderTarget DrawOceanReflections(FlyCamera camera, StarSystemData system, float far, RenderTarget source)
|
||||
{
|
||||
if (!_oceanRendered) return source;
|
||||
var center = UniversePosition.AtCellCenter(system.Coordinate, camera.CellSizeMeters);
|
||||
// Only a nearby ocean needs detailed SSR; orbital views retain the cheaper analytic reflection.
|
||||
var planet = system.Planets.Where(p => p.Ocean is { Enabled: true, ScreenSpaceReflections: { Enabled: true, Strength: > 0 } })
|
||||
.Where(p => System.Math.Abs(center.Add(p.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters).Length
|
||||
- p.RadiusMeters - p.Ocean!.SeaLevelMeters) < p.Ocean.ScreenSpaceReflections.MaxSurfaceDistanceMeters)
|
||||
.MinBy(p => center.Add(p.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters).Length - p.RadiusMeters);
|
||||
if (planet is null) return source;
|
||||
var r = planet.Ocean!.ScreenSpaceReflections;
|
||||
var width = System.Math.Max(1, (int)System.Math.Round(_width * r.ResolutionScale));
|
||||
var height = System.Math.Max(1, (int)System.Math.Round(_height * r.ResolutionScale));
|
||||
if (_waterReflectionRaw is null || _waterReflectionRaw.Width != width || _waterReflectionRaw.Height != height)
|
||||
{ _waterReflectionRaw?.Dispose(); _waterReflectionRaw = new(_gl, width, height); }
|
||||
source.GenerateColorMipmaps();
|
||||
_gl.Disable(GLEnum.Blend); _gl.Disable(GLEnum.DepthTest); _gl.DepthMask(false);
|
||||
_waterReflectionRaw.Bind(); _oceanReflection.Use(); SetCameraRay(_oceanReflection, camera);
|
||||
BindTexture(0, source.Color); BindTexture(1, _scene.Depth); BindTexture(2, _scene.AuxiliaryColors[0]);
|
||||
_oceanReflection.Set("uScene", 0); _oceanReflection.Set("uDepth", 1); _oceanReflection.Set("uWaterNormals", 2);
|
||||
_oceanReflection.Set("uFar", far); _oceanReflection.Set("uMaxSteps", r.MaxSteps);
|
||||
_oceanReflection.Set("uTraceSettings", new Vector3((float)r.MaxDistanceMeters, (float)r.ThicknessMeters, (float)r.NormalBiasMeters));
|
||||
_oceanReflection.Set("uReflectionSettings", new Vector3((float)r.EdgeFade, (float)r.StridePixels, (float)planet.Ocean.Roughness));
|
||||
_oceanReflection.Set("uMaxSurfaceDistance", (float)r.MaxSurfaceDistanceMeters);
|
||||
_oceanReflection.Set("uReflectionStrength", (float)r.Strength); _oceanReflection.Set("uReflectSky", r.ReflectSky ? 1 : 0);
|
||||
_fullscreen.Draw();
|
||||
_waterReflectionTarget.Bind(); _oceanReflectionComposite.Use();
|
||||
BindTexture(0, source.Color); BindTexture(1, _scene.Depth); BindTexture(2, _scene.AuxiliaryColors[0]);
|
||||
BindTexture(3, _scene.AuxiliaryColors[1]); BindTexture(5, _waterReflectionRaw.Color);
|
||||
if (_cloudRaw is not null) BindTexture(6, _cloudRaw.Color);
|
||||
_oceanReflectionComposite.Set("uScene", 0); _oceanReflectionComposite.Set("uDepth", 1);
|
||||
_oceanReflectionComposite.Set("uWaterNormals", 2); _oceanReflectionComposite.Set("uWaterFallback", 3);
|
||||
_oceanReflectionComposite.Set("uReflection", 5); _oceanReflectionComposite.Set("uClouds", 6);
|
||||
_oceanReflectionComposite.Set("uHasClouds", _cloudLayerPlanetId == planet.Id ? 1 : 0);
|
||||
_oceanReflectionComposite.Set("uFar", far); _fullscreen.Draw();
|
||||
_gl.Enable(GLEnum.DepthTest); _gl.DepthMask(true);
|
||||
return _waterReflectionTarget;
|
||||
}
|
||||
|
||||
private void DrawRings(FlyCamera camera, StarSystemData system, float far)
|
||||
{
|
||||
var center = UniversePosition.AtCellCenter(system.Coordinate, camera.CellSizeMeters);
|
||||
@@ -968,6 +1029,7 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_cloudComposite.Set("uScene", 0); _cloudComposite.Set("uDepth", 1); _cloudComposite.Set("uClouds", 2);
|
||||
_cloudComposite.Set("uCloudTexel", new Vector3(1f / width, 1f / height, 0));
|
||||
_fullscreen.Draw(); source = target;
|
||||
_cloudLayerPlanetId = planet.Id;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
@@ -1119,6 +1181,7 @@ internal sealed class UniverseRenderer : IDisposable
|
||||
_sunGlow.Dispose(); _glareTarget.Dispose();
|
||||
_blackHole.Dispose(); _blackHoleTarget.Dispose();
|
||||
_ocean.Dispose();
|
||||
_oceanReflection.Dispose(); _oceanReflectionComposite.Dispose(); _waterReflectionRaw?.Dispose(); _waterReflectionTarget.Dispose();
|
||||
_clouds.Dispose(); _cloudComposite.Dispose(); _cloudNoise.Dispose(); _emptyCloudDensity.Dispose(); _materials.Dispose(); _cloudRaw?.Dispose();
|
||||
_scene.Dispose(); _scatterA.Dispose(); _scatterB.Dispose(); _bloomA.Dispose(); _bloomB.Dispose(); _noise.Dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user