Compare commits

...

13 Commits

Author SHA1 Message Date
Acruid
0d356eb8f7 Removed wrong assert, rebase damage? 2021-03-13 00:29:37 -08:00
Adam Coggeshall
457705ae9c Improve 3D render bounds + draw order. 2021-03-13 00:15:46 -08:00
Adam Coggeshall
a9d8fed436 Fix 3D grid culling. 2021-03-13 00:15:45 -08:00
Adam Coggeshall
72bcc4a35e Increase accuracy of angle -> direction conversons for sprites. 2021-03-13 00:15:45 -08:00
Adam Coggeshall
0f0d47c629 Make 3D sprites rotate based on camera rotation. 2021-03-13 00:15:45 -08:00
Adam Coggeshall
027e36f125 Enable depth buffer and 3D wall rendering 2021-03-13 00:15:44 -08:00
Adam Coggeshall
298efa3a91 Add 3D sprite rendering modes + very dumb mode inference system. 2021-03-13 00:15:44 -08:00
Adam Coggeshall
8c335fc5d0 Add 3D sprites. 2021-03-13 00:15:44 -08:00
Adam Coggeshall
2a2c97e5ba 3D sprites work but are untextured. 2021-03-13 00:15:43 -08:00
Adam Coggeshall
117f4a94c2 Very basic 3D maybe almost works? 2021-03-13 00:15:43 -08:00
Adam Coggeshall
a692dc7817 Progress on 3D rendering (DOES NOT BUILD) 2021-03-13 00:15:42 -08:00
Adam Coggeshall
c8b2394dfb Add toggle-able 3D rendering mode. 2021-03-13 00:15:42 -08:00
Adam Coggeshall
4417f685ac Add support for 3D view/proj matrices. 2021-03-13 00:15:42 -08:00
20 changed files with 694 additions and 101 deletions

View File

@@ -0,0 +1,5 @@
kind model;
void fragment() {
COLOR = texture(TEXTURE, UV);
}

View File

@@ -12,16 +12,16 @@ uniform mat3 modelMatrix;
layout (std140) uniform projectionViewMatrices
{
mat3 projectionMatrix;
mat3 viewMatrix;
mat4 projectionMatrix;
mat4 viewMatrix;
};
void main()
{
vec3 transformed = modelMatrix * vec3(aPos, 1.0);
vec4 transformed = mat4(modelMatrix) * vec4(aPos, 1.0, 1.0);
worldPosition = transformed.xy;
transformed = projectionMatrix * viewMatrix * transformed;
gl_Position = vec4(transformed, 1.0);
gl_Position = transformed;
UV = tCoord;
}

View File

@@ -0,0 +1,34 @@
#version 330 core
out vec4 FragColor;
in vec2 UV;
in vec2 Pos;
uniform sampler2D TEXTURE;
uniform sampler2D lightMap;
uniform vec4 modulate;
layout (std140) uniform uniformConstants
{
vec2 SCREEN_PIXEL_SIZE;
float TIME;
};
uniform vec2 TEXTURE_PIXEL_SIZE;
[SHADER_HEADER_CODE]
void main()
{
vec4 FRAGCOORD = gl_FragCoord;
vec4 COLOR;
[SHADER_CODE]
FragColor = COLOR * modulate;
if (FragColor.a < 0.5)
discard;
}

View File

@@ -0,0 +1,41 @@
#version 330 core
// Vertex position.
layout (location = 0) in vec3 aPos;
// Texture coordinates.
layout (location = 1) in vec2 tCoord;
out vec2 UV;
out vec2 Pos;
// Maybe we should merge these CPU side.
// idk yet.
uniform mat4 modelMatrix;
layout (std140) uniform projectionViewMatrices
{
mat4 projectionMatrix;
mat4 viewMatrix;
};
layout (std140) uniform uniformConstants
{
vec2 SCREEN_PIXEL_SIZE;
float TIME;
};
// Allows us to do texture atlassing with texture coordinates 0->1
// Input texture coordinates get mapped to this range.
uniform vec4 modifyUV;
[SHADER_HEADER_CODE]
void main()
{
vec4 VERTEX = projectionMatrix * viewMatrix * modelMatrix * vec4(aPos, 1.0);
[SHADER_CODE]
gl_Position = VERTEX;
Pos = (VERTEX.xy + 1) / 2;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
}

View File

@@ -13,8 +13,8 @@ out vec2 Pos;
uniform mat3 modelMatrix;
layout (std140) uniform projectionViewMatrices
{
mat3 projectionMatrix;
mat3 viewMatrix;
mat4 projectionMatrix;
mat4 viewMatrix;
};
layout (std140) uniform uniformConstants
@@ -31,7 +31,7 @@ uniform vec4 modifyUV;
void main()
{
vec3 transformed = projectionMatrix * viewMatrix * modelMatrix * vec3(aPos, 1.0);
vec4 transformed = projectionMatrix * viewMatrix * mat4(modelMatrix) * vec4(aPos, 1.0, 1.0);
vec2 VERTEX = transformed.xy;
[SHADER_CODE]

View File

@@ -224,8 +224,7 @@ namespace Robust.Client.GameObjects
DebugTools.Assert("Component does not exist for state.");
}
DebugTools.Assert(kvStates.Value.curState != null,
"Component state is null.");
//DebugTools.Assert(kvStates.Value.curState != null, "Component state is null.");
component.HandleComponentState(kvStates.Value.curState, kvStates.Value.nextState);
}

View File

@@ -25,6 +25,8 @@ using Robust.Shared.Animations;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.ViewVariables;
using Sprite3DRenderMode = Robust.Client.Graphics.Clyde.Clyde.Render3D.Sprite3DRenderMode;
namespace Robust.Client.GameObjects
{
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent, IClickTargetComponent,
@@ -991,6 +993,106 @@ namespace Robust.Client.GameObjects
return LayerGetActualRSI(layer);
}
/// Render the sprite in 3D. This is almost entirely copy-pasted from the 2D version.
internal void Render3D(Graphics.Clyde.Clyde.Render3D render, Vector2 worldPosition, Angle worldRotation,
Sprite3DRenderMode renderMode, Direction? overrideDirection = null
) {
Angle angle;
Angle spriteAngle;
if (Directional)
{
angle = Rotation;
if (renderMode == Sprite3DRenderMode.SimpleSprite)
{
spriteAngle = worldRotation + render.CameraAngle2D;
} else
{
spriteAngle = worldRotation;
}
}
else
{
angle = Rotation + worldRotation;
spriteAngle = worldRotation;
}
var mRotation = Matrix4.CreateRotationZ((float)angle);
var mWorldOffset = Matrix4.CreateTranslation(worldPosition.X, worldPosition.Y, 0);
// Draw layers
int layer_n = 0;
foreach (var layer in Layers)
{
if (!layer.Visible)
{
continue;
}
// TODO: Implement layer-specific rotation and scale.
var texture = layer.Texture;
if (layer.State.IsValid)
{
// Pull texture from RSI state instead.
var rsi = layer.RSI ?? BaseRSI;
if (rsi != null)
{
var state = rsi[layer.State];
RSI.State.Direction layerSpecificDir;
if (state.Directions == RSI.State.DirectionType.Dir1)
{
layerSpecificDir = RSI.State.Direction.South;
}
else
{
RSI.State.Direction dir;
if (overrideDirection != null)
{
dir = overrideDirection.Value.Convert(state.Directions);
}
else
{
dir = GetDir(state.Directions, spriteAngle);
}
layerSpecificDir = OffsetRsiDir(dir, layer.DirOffset);
}
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
}
}
texture ??= resourceCache.GetFallback<TextureResource>();
float offsetZ = renderMode == Sprite3DRenderMode.SimpleSprite ? (float)drawDepth * 0.001f + layer_n * 0.001f : 0.001f;
var mOffset = Matrix4.CreateTranslation(new Vector3(Offset.X, Offset.Y, offsetZ));
bool isWall = renderMode == Sprite3DRenderMode.Wall;
int rectCount = isWall ? 5 : 1;
for (int i = 0; i < rectCount; i++)
{
var m3D = render.GetSpriteTransform(renderMode, i);
var transform = mOffset * mRotation * m3D * mWorldOffset;
var rectColor = color * layer.Color;
var rectTexture = texture;
if (isWall && i == rectCount - 1)
{
rectColor = Color.Black;
}
render.DrawRect3D(transform, rectTexture, rectColor);
}
layer_n++;
}
}
internal void Render(DrawingHandleWorld drawingHandle, in Matrix3 worldTransform, Angle worldRotation, Direction? overrideDirection=null)
{
var angle = Rotation;
@@ -1400,7 +1502,13 @@ namespace Robust.Client.GameObjects
}
var angle = new Angle(worldRotation);
return angle.GetDir().Convert(type);
if (type == RSI.State.DirectionType.Dir4)
{
return angle.GetCardinalDir().Convert(type);
} else
{
return angle.GetDir().Convert(type);
}
}
private static RSI.State.Direction OffsetRsiDir(RSI.State.Direction dir, DirectionOffset offset)

View File

@@ -25,5 +25,7 @@ namespace Robust.Client.Graphics.ClientEye
matrix.R1C2 = -Position.Y / Zoom.Y;
return matrix;
}
public bool Is3D { get; } = true;
}
}

View File

@@ -26,6 +26,33 @@ namespace Robust.Client.Graphics.Clyde
mapId = _eyeManager.CurrentMap;
}
if (_eyeManager.CurrentEye.Is3D)
{
var render3d = GetRender3D();
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
{
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
if (!regionMaybe.HasValue)
{
continue;
}
var region = regionMaybe.Value;
var transform = Matrix4.CreateTranslation(new Vector3(
mapGrid.WorldPosition.X+tile.X+.5f,
mapGrid.WorldPosition.Y+tile.Y+.5f,
-.5f));
render3d.DrawRect3D(transform, _tileDefinitionManager.TileTextureAtlas, Color.White, region);
}
}
return;
}
var atlasTexture = _tileDefinitionManager.TileTextureAtlas;
var loadedTex = _loadedTextures[((ClydeTexture) atlasTexture).TextureId];

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using OpenTK.Graphics.OpenGL;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
public class Render3D
{
private Clyde clyde;
private ClydeShaderInstance modelShader;
private OGLHandle VAO3D = new OGLHandle(GL.GenVertexArray());
private Buffer VBO3D;
public enum Sprite3DRenderMode
{
Floor, // Draw Flat on the ground, no extra transformations
Table, // Higher than floor
SimpleSprite, // Always rotate to face the camera
Wall // Render 4 sides + top cap
}
public Render3D(Clyde clyde)
{
this.clyde = clyde;
// Load shader
var defaultModelShader = clyde._resourceCache
.GetResource<ShaderSourceResource>("/Shaders/Internal/default-model.swsl").ClydeHandle;
this.modelShader = (ClydeShaderInstance)clyde.InstanceShader(defaultModelShader);
// Set up test mesh
int vert_size = sizeof(float) * 5;
GL.BindVertexArray(VAO3D.Handle);
clyde._objectLabelMaybe(ObjectLabelIdentifier.VertexArray, VAO3D, "VAO3D");
var test = new float[] {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f,
};
var test_bytes = MemoryMarshal.AsBytes<float>(test);
VBO3D = new Buffer(clyde, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw, test_bytes, "VBO3D");
VBO3D.Use();
// Vertex Coords
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, vert_size, 0);
GL.EnableVertexAttribArray(0);
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, vert_size, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
}
/// Terribly unoptimized, should probably have a sprite batch for 3D quads if this evolves beyond just being a meme.
public void DrawRect3D(Matrix4 transform, Texture texture, Color color, UIBox2? subRegion = null)
{
var region = new Vector4(0, 0, 1, 1);
if (texture is AtlasTexture atlas)
{
texture = atlas.SourceTexture;
region = new Vector4(
atlas.SubRegion.Left / texture.Width,
atlas.SubRegion.Top / texture.Height,
atlas.SubRegion.Right / texture.Width,
atlas.SubRegion.Bottom / texture.Height);
} else if (subRegion.HasValue)
{
region = new Vector4(
subRegion.Value.Left,
subRegion.Value.Top,
subRegion.Value.Right,
subRegion.Value.Bottom);
}
var loadedTexture = clyde._loadedTextures[ ((ClydeTexture)texture).TextureId ];
GL.BindVertexArray(this.VAO3D.Handle);
var (program, loaded) = clyde.ActivateShaderInstance(this.modelShader.Handle);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, loadedTexture.OpenGLObject.Handle);
program.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
program.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
// Need to take the transpose because linal is hell I guess.
transform.Transpose();
// Model matrix
program.SetUniformMaybe(UniIModelMatrix, transform);
// Set ModUV to our subregion
program.SetUniformMaybe(UniIModUV, region);
program.SetUniformMaybe(UniIModulate, color); // modulate
program.SetUniformMaybe(UniITexturePixelSize, new Vector2(1, 1)); // meh
GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
}
Vector3 CameraPos = Vector3.Zero;
Matrix4 FaceCameraRotation = Matrix4.Identity;
public Angle CameraAngle2D { get; private set; }
public (Matrix4,Matrix4) GetProjViewMatrices3D(Vector2 eyePos)
{
float dist = 10;
float dist_v = 10;//0.3f;
float angle = 0.3f;//clyde._renderTime * 0.05f;
var basePos = new Vector3(eyePos.X, eyePos.Y, 0);
CameraAngle2D = new Angle(angle);
CameraPos = basePos + new Vector3(MathF.Sin(angle) * -dist, MathF.Cos(angle) * -dist, dist_v);
//var s = Math.Sin(this._renderTime);
//var viewMatrixWorld = Matrix4.LookAt(new Vector3(0, 0, 0), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
var viewMatrixWorld = Matrix4.CreateTranslation(-CameraPos);
//viewMatrixWorld *= Matrix4.Scale(0.01f);
var cameraRotation = Matrix4.CreateRotationZ(angle);
cameraRotation *= Matrix4.CreateRotationX(-0.785f);
viewMatrixWorld *= cameraRotation;
//viewMatrixWorld.Invert();
//var viewMatrixWorld = Matrix4.Identity;
FaceCameraRotation = cameraRotation;
FaceCameraRotation.Transpose();
float aspect = (float)clyde.ScreenSize.X / clyde.ScreenSize.Y;
var projMatrixWorld = Matrix4.CreatePerspectiveFieldOfView(1.0f, aspect, 0.1f, 100);
return (projMatrixWorld, viewMatrixWorld);
}
public Matrix4 GetSpriteTransform(Sprite3DRenderMode renderMode, int n = 0)
{
switch (renderMode)
{
case Sprite3DRenderMode.Floor:
return Matrix4.CreateTranslation(0, 0, -.5f);
case Sprite3DRenderMode.Table:
return Matrix4.CreateTranslation(0, 0, -.25f);
case Sprite3DRenderMode.Wall:
switch (n)
{
case 0:
return Matrix4.CreateRotationY(MathHelper.PiOver2) * Matrix4.CreateTranslation(.5f, 0, 0);
case 1:
return Matrix4.CreateRotationY(MathHelper.PiOver2) * Matrix4.CreateTranslation(-.5f, 0, 0);
case 2:
return Matrix4.CreateRotationX(MathHelper.PiOver2) * Matrix4.CreateTranslation(0, .5f, 0);
case 3:
return Matrix4.CreateRotationX(MathHelper.PiOver2) * Matrix4.CreateTranslation(0, -.5f, 0);
default:
return Matrix4.CreateTranslation(0, 0, .5f);
}
case Sprite3DRenderMode.SimpleSprite:
return FaceCameraRotation;
}
return Matrix4.Identity;
}
public static Sprite3DRenderMode InferRenderMode(IEntity ent)
{
switch (ent.Name)
{
case "worktop":
return Sprite3DRenderMode.Table;
case "Solid wall":
return Sprite3DRenderMode.Wall;
case "Catwalk":
case "Wire":
return Sprite3DRenderMode.Floor;
}
if (ent.Name.Contains("Airlock"))
{
return Sprite3DRenderMode.Wall;
}
return Sprite3DRenderMode.SimpleSprite;
}
}
Render3D? render3d;
Render3D GetRender3D()
{
if (render3d == null)
{
render3d = new Render3D(this);
}
return render3d;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics.ClientEye;
using Robust.Client.Graphics.Drawing;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -69,6 +69,9 @@ namespace Robust.Client.Graphics.Clyde
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation)> _drawingSpriteList =
new RefList<(SpriteComponent, Matrix3, Angle)>();
private readonly RefList<(SpriteComponent sprite, Vector2 worldPosition, Angle worldRotation, Render3D.Sprite3DRenderMode renderMode)> _drawingSpriteList3D =
new RefList<(SpriteComponent, Vector2, Angle, Render3D.Sprite3DRenderMode)>();
public void Render()
{
var size = ScreenSize;
@@ -131,6 +134,16 @@ namespace Robust.Client.Graphics.Clyde
_drawLights(worldBounds);
}
if (eye.Is3D)
{
// Start 3D rendering. Enable depth buffer.
GL.Enable(EnableCap.DepthTest);
GL.DepthMask(true);
GL.DepthFunc(DepthFunction.Less);
worldBounds = Box2.CenteredAround(eye.Position.Position, new Vector2(20,20));
}
using (DebugGroup("Grids"))
{
_drawGrids(worldBounds);
@@ -141,6 +154,9 @@ namespace Robust.Client.Graphics.Clyde
DrawEntities(worldBounds);
}
// Any 3D rendering should be done.
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpace);
_lightingReady = false;
@@ -166,69 +182,91 @@ namespace Robust.Client.Graphics.Clyde
return;
}
// So we could calculate the correct size of the entities based on the contents of their sprite...
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
// TODO: Make this check more accurate.
var widerBounds = worldBounds.Enlarged(5);
var mapEntity = _mapManager.GetMapEntity(_eyeManager.CurrentMap);
var identity = Matrix3.Identity;
ProcessSpriteEntities(mapEntity, ref identity, Angle.Zero, widerBounds, _drawingSpriteList);
// We use a separate list for indexing so that the sort is faster.
var indexList = ArrayPool<int>.Shared.Rent(_drawingSpriteList.Count);
bool is3d = _eyeManager.CurrentEye.Is3D;
for (var i = 0; i < _drawingSpriteList.Count; i++)
if (is3d)
{
indexList[i] = i;
}
// Enlarge by a small amount so rendered objects and grids mostly line up with eachother.
var widerBounds = worldBounds.Enlarged(.5f);
Array.Sort(indexList, 0, _drawingSpriteList.Count, new SpriteDrawingOrderComparer(_drawingSpriteList));
ProcessSpriteEntities3D(mapEntity, ref identity, Angle.Zero, widerBounds, _drawingSpriteList3D);
for (var i = 0; i < _drawingSpriteList.Count; i++)
{
ref var entry = ref _drawingSpriteList[indexList[i]];
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
var clyde3d = GetRender3D();
for (var i = 0; i < _drawingSpriteList3D.Count; i++)
{
_renderHandle.UseRenderTarget(EntityPostRenderTarget);
_renderHandle.Clear(new Color());
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = entry.sprite.Owner.Transform.WorldPosition;
var screenPos = _eyeManager.WorldToScreen(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, ScreenSize.Y - roundedY);
flippedPos -= EntityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, ScreenSize));
ref var entry = ref _drawingSpriteList3D[i];
entry.sprite.Render3D(clyde3d, entry.worldPosition, entry.worldRotation, entry.renderMode);
}
_drawingSpriteList3D.Clear();
} else
{
// So we could calculate the correct size of the entities based on the contents of their sprite...
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
// TODO: Make this check more accurate.
var widerBounds = worldBounds.Enlarged(5);
ProcessSpriteEntities(mapEntity, ref identity, Angle.Zero, widerBounds, _drawingSpriteList);
// We use a separate list for indexing so that the sort is faster.
var indexList = ArrayPool<int>.Shared.Rent(_drawingSpriteList.Count);
for (var i = 0; i < _drawingSpriteList.Count; i++)
{
indexList[i] = i;
}
entry.sprite.Render(_renderHandle.DrawingHandleWorld, entry.worldMatrix, entry.worldRotation);
Array.Sort(indexList, 0, _drawingSpriteList.Count, new SpriteDrawingOrderComparer(_drawingSpriteList));
if (entry.sprite.PostShader != null)
for (var i = 0; i < _drawingSpriteList.Count; i++)
{
_renderHandle.UseRenderTarget(null);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, ScreenSize));
ref var entry = ref _drawingSpriteList[indexList[i]];
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
_renderHandle.UseRenderTarget(EntityPostRenderTarget);
_renderHandle.Clear(new Color());
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = entry.sprite.Owner.Transform.WorldPosition;
var screenPos = _eyeManager.WorldToScreen(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, ScreenSize.Y - roundedY);
flippedPos -= EntityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, ScreenSize));
}
_renderHandle.UseShader(entry.sprite.PostShader);
_renderHandle.SetSpace(CurrentSpace.ScreenSpace);
_renderHandle.SetModelTransform(Matrix3.Identity);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, entry.worldMatrix, entry.worldRotation);
var rounded = roundedPos - EntityPostRenderTarget.Size / 2;
if (entry.sprite.PostShader != null)
{
_renderHandle.UseRenderTarget(null);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, ScreenSize));
var box = UIBox2i.FromDimensions(rounded, EntityPostRenderTarget.Size);
_renderHandle.UseShader(entry.sprite.PostShader);
_renderHandle.SetSpace(CurrentSpace.ScreenSpace);
_renderHandle.SetModelTransform(Matrix3.Identity);
_renderHandle.DrawTexture(EntityPostRenderTarget.Texture, box.BottomLeft,
box.TopRight, Color.White, null, 0);
var rounded = roundedPos - EntityPostRenderTarget.Size / 2;
_renderHandle.SetSpace(CurrentSpace.WorldSpace);
_renderHandle.UseShader(null);
var box = UIBox2i.FromDimensions(rounded, EntityPostRenderTarget.Size);
_renderHandle.DrawTexture(EntityPostRenderTarget.Texture, box.BottomLeft,
box.TopRight, Color.White, null, 0);
_renderHandle.SetSpace(CurrentSpace.WorldSpace);
_renderHandle.UseShader(null);
}
}
_drawingSpriteList.Clear();
}
_drawingSpriteList.Clear();
FlushRenderQueue();
}
@@ -268,6 +306,11 @@ namespace Robust.Client.Graphics.Clyde
entry.Item1 = sprite;
entry.Item3 = childWorldRotation;
/*if (include3DRenderMode)
{
entry.Item4 = Render3D.InferRenderMode(childEntity);
}*/
}
}
@@ -278,6 +321,51 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void ProcessSpriteEntities3D(IEntity entity, ref Matrix3 parentTransform, Angle parentRotation,
Box2 worldBounds, RefList<(SpriteComponent, Vector2, Angle, Render3D.Sprite3DRenderMode)> list)
{
entity.TryGetComponent(out ContainerManagerComponent containerManager);
var localMatrix = entity.Transform.GetLocalMatrix();
Matrix3.Multiply(ref parentTransform, ref localMatrix, out var matrix);
var rotation = parentRotation + entity.Transform.LocalRotation;
foreach (var child in entity.Transform.ChildEntityUids)
{
var childEntity = _entityManager.GetEntity(child);
if (containerManager != null && containerManager.TryGetContainer(childEntity, out var container) &&
!container.ShowContents)
{
continue;
}
var childTransform = childEntity.Transform;
var worldPosition = Matrix3.Transform(matrix, childTransform.LocalPosition);
if (worldBounds.Contains(worldPosition))
{
if (childEntity.TryGetComponent(out SpriteComponent sprite) && sprite.Visible)
{
ref var entry = ref list.AllocAdd();
var childWorldRotation = rotation + childTransform.LocalRotation;
entry.Item1 = sprite;
entry.Item2 = worldPosition;
entry.Item3 = childWorldRotation;
entry.Item4 = Render3D.InferRenderMode(childEntity);
}
}
if (childTransform.ChildCount > 0)
{
ProcessSpriteEntities3D(childEntity, ref matrix, rotation, worldBounds, list);
}
}
}
private void _drawSplash(IRenderHandle handle)
{
var texture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png").Texture;
@@ -312,27 +400,32 @@ namespace Robust.Client.Graphics.Clyde
{
var eye = _eyeManager.CurrentEye;
var toScreen = _eyeManager.WorldToScreen(eye.Position.Position);
// Round camera position to a screen pixel to avoid weird issues on odd screen sizes.
toScreen = ((float) Math.Floor(toScreen.X), (float) Math.Floor(toScreen.Y));
var cameraWorldAdjusted = _eyeManager.ScreenToMap(toScreen);
var viewMatrixWorld = Matrix3.Identity;
viewMatrixWorld.R0C0 = 1 / eye.Zoom.X;
viewMatrixWorld.R1C1 = 1 / eye.Zoom.Y;
viewMatrixWorld.R0C2 = -cameraWorldAdjusted.X / eye.Zoom.X;
viewMatrixWorld.R1C2 = -cameraWorldAdjusted.Y / eye.Zoom.Y;
if (eye.Is3D)
{
var (projMatrixWorld, viewMatrixWorld) = GetRender3D().GetProjViewMatrices3D(eye.Position.Position);
return new ProjViewMatrices(projMatrixWorld, viewMatrixWorld);
} else
{
var toScreen = _eyeManager.WorldToScreen(eye.Position.Position);
// Round camera position to a screen pixel to avoid weird issues on odd screen sizes.
toScreen = ((float) Math.Floor(toScreen.X), (float) Math.Floor(toScreen.Y));
var cameraWorldAdjusted = _eyeManager.ScreenToMap(toScreen);
var projMatrixWorld = Matrix3.Identity;
projMatrixWorld.R0C0 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.X;
projMatrixWorld.R1C1 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.Y;
var viewMatrixWorld = Matrix4.CreateTranslation(new Vector3(-cameraWorldAdjusted.X, -cameraWorldAdjusted.Y, 0));
viewMatrixWorld *= Matrix4.Scale(1/ eye.Zoom.X, 1/ eye.Zoom.Y, 1);
return new ProjViewMatrices(projMatrixWorld, viewMatrixWorld);
var projMatrixWorld = Matrix4.Identity;
projMatrixWorld.M11 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.X;
projMatrixWorld.M22 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.Y;
return new ProjViewMatrices(projMatrixWorld, viewMatrixWorld);
}
}
private void _drawLights(Box2 worldBounds)
{
if (!_lightManager.Enabled)
if (!_lightManager.Enabled || _eyeManager.CurrentEye.Is3D)
{
return;
}
@@ -678,7 +771,8 @@ namespace Robust.Client.Graphics.Clyde
{
GL.ClearColor(color.ConvertOpenTK());
GL.ClearStencil(0);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
GL.ClearDepth(1);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit | ClearBufferMask.DepthBufferBit);
}
private (ShaderProgram, LoadedShader) ActivateShaderInstance(ClydeHandle handle)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -19,6 +19,9 @@ namespace Robust.Client.Graphics.Clyde
private string _shaderWrapCodeSpriteFrag;
private string _shaderWrapCodeSpriteVert;
private string _shaderWrapCodeModelFrag;
private string _shaderWrapCodeModelVert;
private readonly Dictionary<ClydeHandle, LoadedShader> _loadedShaders =
new Dictionary<ClydeHandle, LoadedShader>();
@@ -48,8 +51,8 @@ namespace Robust.Client.Graphics.Clyde
public ClydeHandle LoadShader(ParsedShader shader, string name = null)
{
var vertexSource = _shaderWrapCodeSpriteVert;
var fragmentSource = _shaderWrapCodeSpriteFrag;
var vertexSource = shader.Kind == ShaderKind.Model ? _shaderWrapCodeModelVert : _shaderWrapCodeSpriteVert;
var fragmentSource = shader.Kind == ShaderKind.Model ? _shaderWrapCodeModelFrag : _shaderWrapCodeSpriteFrag;
var (header, vertBody, fragBody) = _getShaderCode(shader);
@@ -91,11 +94,13 @@ namespace Robust.Client.Graphics.Clyde
_shaderWrapCodeSpriteFrag = _readFile("/Shaders/Internal/sprite.frag");
_shaderWrapCodeSpriteVert = _readFile("/Shaders/Internal/sprite.vert");
var defaultLoadedShader = _resourceCache
.GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl").ClydeHandle;
_defaultShader = (ClydeShaderInstance) InstanceShader(defaultLoadedShader);
_defaultShader = (ClydeShaderInstance)InstanceShader(defaultLoadedShader);
_shaderWrapCodeModelFrag = _readFile("/Shaders/Internal/model.frag");
_shaderWrapCodeModelVert = _readFile("/Shaders/Internal/model.vert");
_queuedShader = _defaultShader.Handle;

View File

@@ -548,37 +548,47 @@ namespace Robust.Client.Graphics.Clyde
[PublicAPI]
private struct ProjViewMatrices
{
[FieldOffset(0 * sizeof(float))] public Vector3 ProjMatrixC0;
[FieldOffset(4 * sizeof(float))] public Vector3 ProjMatrixC1;
[FieldOffset(8 * sizeof(float))] public Vector3 ProjMatrixC2;
[FieldOffset(12 * sizeof(float))] public Vector3 ViewMatrixC0;
[FieldOffset(16 * sizeof(float))] public Vector3 ViewMatrixC1;
[FieldOffset(20 * sizeof(float))] public Vector3 ViewMatrixC2;
[FieldOffset(0 * sizeof(float))] Matrix4 ProjMatrix;
[FieldOffset(16 * sizeof(float))] Matrix4 ViewMatrix;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ProjViewMatrices(in Matrix3 projMatrix, in Matrix3 viewMatrix)
{
ProjMatrixC0 = new Vector3(projMatrix.R0C0, projMatrix.R1C0, projMatrix.R2C0);
ProjMatrixC1 = new Vector3(projMatrix.R0C1, projMatrix.R1C1, projMatrix.R2C1);
ProjMatrixC2 = new Vector3(projMatrix.R0C2, projMatrix.R1C2, projMatrix.R2C2);
this.ProjMatrix = new Matrix4(
projMatrix.R0C0, projMatrix.R1C0, projMatrix.R2C0, 0,
projMatrix.R0C1, projMatrix.R1C1, projMatrix.R2C1, 0,
projMatrix.R0C2, projMatrix.R1C2, projMatrix.R2C2, 0,
0, 0, 0, 1
);
ViewMatrixC0 = new Vector3(viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0);
ViewMatrixC1 = new Vector3(viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1);
ViewMatrixC2 = new Vector3(viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2);
this.ViewMatrix = new Matrix4(
viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0, 0,
viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1, 0,
viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2, 0,
0, 0, 0, 1
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ProjViewMatrices(in ProjViewMatrices readProjMatrix, in Matrix3 viewMatrix)
{
ProjMatrixC0 = readProjMatrix.ProjMatrixC0;
ProjMatrixC1 = readProjMatrix.ProjMatrixC1;
ProjMatrixC2 = readProjMatrix.ProjMatrixC2;
this.ProjMatrix = readProjMatrix.ProjMatrix;
ViewMatrixC0 = new Vector3(viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0);
ViewMatrixC1 = new Vector3(viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1);
ViewMatrixC2 = new Vector3(viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2);
this.ViewMatrix = new Matrix4(
viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0, 0,
viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1, 0,
viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2, 0,
0, 0, 0, 1
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ProjViewMatrices(in Matrix4 projMatrix, in Matrix4 viewMatrix)
{
this.ProjMatrix = projMatrix;
this.ViewMatrix = viewMatrix;
}
}
[StructLayout(LayoutKind.Explicit, Size = sizeof(float) * 4)]

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenTK.Graphics.OpenGL;
@@ -399,6 +399,22 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniformMaybe(string uniformName, in Matrix4 value)
{
if (TryGetUniform(uniformName, out var slot))
{
SetUniformDirect(slot, value);
}
}
public void SetUniformMaybe(int uniformName, in Matrix4 value)
{
if (TryGetUniform(uniformName, out var slot))
{
SetUniformDirect(slot, value);
}
}
public void SetUniformMaybe(string uniformName, in Vector2 value)
{
if (TryGetUniform(uniformName, out var slot))

View File

@@ -1,4 +1,4 @@
using Robust.Client.Graphics.ClientEye;
using Robust.Client.Graphics.ClientEye;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Drawing

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -8,13 +8,14 @@ namespace Robust.Client.Graphics.Shaders
{
public ParsedShader(IReadOnlyDictionary<string, ShaderUniformDefinition> uniforms,
IReadOnlyDictionary<string, ShaderVaryingDefinition> varyings, IList<ShaderFunctionDefinition> functions,
ShaderLightMode lightMode, ShaderBlendMode blendMode)
ShaderLightMode lightMode, ShaderBlendMode blendMode, ShaderKind kind)
{
Uniforms = uniforms;
Varyings = varyings;
Functions = functions;
LightMode = lightMode;
BlendMode = blendMode;
Kind = kind;
}
public IReadOnlyDictionary<string, ShaderUniformDefinition> Uniforms { get; }
@@ -22,6 +23,7 @@ namespace Robust.Client.Graphics.Shaders
public IList<ShaderFunctionDefinition> Functions { get; }
public ShaderLightMode LightMode { get; }
public ShaderBlendMode BlendMode { get; }
public ShaderKind Kind { get; }
}
internal sealed class ShaderFunctionDefinition
@@ -163,6 +165,12 @@ namespace Robust.Client.Graphics.Shaders
};
}
internal enum ShaderKind
{
Sprite,
Model
}
internal enum ShaderLightMode
{
Default = 0,

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -42,6 +42,7 @@ namespace Robust.Client.Graphics.Shaders
ShaderLightMode? lightMode = null;
ShaderBlendMode? blendMode = null;
ShaderKind? shaderKind = null;
Token token;
@@ -56,7 +57,7 @@ namespace Robust.Client.Graphics.Shaders
if (!(token is TokenWord word))
{
throw new ShaderParseException("Expected 'light_mode', 'blend_mode', 'uniform', 'varying' or type.",
throw new ShaderParseException("Expected 'light_mode', 'blend_mode', 'kind', 'uniform', 'varying' or type.",
token.Position);
}
@@ -109,6 +110,34 @@ namespace Robust.Client.Graphics.Shaders
throw new ShaderParseException("Expected 'mix', 'add', 'subtract' or 'multiply'.");
}
}
else if (word.Word == "kind")
{
if (shaderKind != null)
{
throw new ShaderParseException("Already specified 'kind' before!");
}
_takeToken();
token = _takeToken();
switch (token)
{
case TokenWord t when t.Word == "sprite":
shaderKind = ShaderKind.Sprite;
break;
case TokenWord t when t.Word == "model":
shaderKind = ShaderKind.Model;
break;
default:
throw new ShaderParseException("Expected 'sprite' or 'model'.");
}
token = _takeToken();
if (!(token is TokenSymbol semicolonUnshadedSymbol) ||
semicolonUnshadedSymbol.Symbol != Symbols.Semicolon)
{
throw new ShaderParseException("Expected ';'", token.Position);
}
}
else
{
break;
@@ -149,7 +178,7 @@ namespace Robust.Client.Graphics.Shaders
return new ParsedShader(
_uniformsParsing.ToDictionary(p => p.Name, p => p),
_varyingsParsing.ToDictionary(p => p.Name, p => p),
_functionsParsing, lightMode ?? ShaderLightMode.Default, blendMode ?? ShaderBlendMode.Mix);
_functionsParsing, lightMode ?? ShaderLightMode.Default, blendMode ?? ShaderBlendMode.Mix, shaderKind ?? ShaderKind.Sprite);
}
private void _parseFunction()

View File

@@ -23,5 +23,7 @@ namespace Robust.Client.Interfaces.Graphics.ClientEye
/// Returns the view matrix for this eye.
/// </summary>
Matrix3 GetViewMatrix();
bool Is3D { get; }
}
}