CyberEngine

CyberEngine is a game engine coded in C#. It includes a custom Visual Studio project type for game content with NuGet support, game content pipeline, user interface framework, and physics engine. Graphics support for Direct3D 11, Direct3D 12, Vulkan, OpenGL, rasterisation, and ray tracing. The engine supports Windows with most functions also supported on Linux.
The engine abstracts all rendering APIs behind interfaces so only one application needs to be created. Not all APIs share the same features, so a lowest common feature set was used where possible. The engine adheres closest to Direct3D terminology and features.
Graphics
CyberEngine supports Direct3D 11, Direct3D 12, Vulkan, and OpenGL. The engine currently uses Vortice for Direct3D 11, Direct3D 12, and Vulkan.
Vortice
Vortice is an open source project that uses SharpGen to generate C# wrappers for native assemblies. I have contributed some bug fixes and features to this project.
OpenGL
For OpenGL, a code generator examines the specification XML and creates the bindings automatically. Below is an example of the method glDrawArrays.
<Function Name="glDrawArrays">
<Parameter Name="mode">
<Type Name="PrimitiveType" />
</Parameter>
<Parameter Name="first">
<Type Name="GLint" />
</Parameter>
<Parameter Name="count">
<Type Name="GLsizei" />
</Parameter>
</Function>
[Feature("GL_VERSION_1_1")]
public delegate void DrawArraysDelegate(Enumerators.PrimitiveType mode, int first, uint count);




Physically Based Rendering (PBR)
CyberEngine supports a Physically Based Rendering (PBR) pipeline. Auto exposure is calculated by creating a luminance histogram in a compute shader and processing the results on the CPU. Some images from a test program are shown below.




Atmospheric Scattering
A current project requires an atmospheric scattering shader to render planets. A few images from an editor and different settings are shown below.









Shadow Mapping
An example of deferred rendering and shadow mapping which uses multiple render targets is illustrated below.


Sprites
CyberEngine uses a sprite batch to render images and text from fonts. The fonts can be created in the content pipeline by supplying the font properties and a texture is automatically generated, or a custom font can be created externally in image editing software.


Ray Tracing
CyberEngine supports ray tracing with Direct3D 12 and a simple example is illustrated below.

CPU Ray Tracing
In order to debug shader concepts, a CPU based ray tracer was architected in C#. A scene can be rendered by supplying geometry and hit and miss functions. A multithreading option can be enabled to speed up the output of an image by dividing rays among threads.

Shaders
Cross Compiler
CyberEngine compiles HLSL shaders for Direct3D and cross compiles into SPIR-V for Vulkan and GLSL for OpenGL. This allows one shader to be written and maintained while supporting all graphics APIs. Shaders use compiler directives to state how buffers should be bound to shader programs. One possible improvement is to automate the assignment of binding indices.
Descriptor Table Code Generator
CyberEngine auto generates C# code for constant buffers by analyzing the shader code and creating an analogue constant block in C#. The shader is also analyzed to auto generate a descriptor table eliminating the need to manually keep shader code and C# code in sync.
Below is a simple example of a vertex and pixel shader with its auto generated C# code that renders a cube with a texture.
/* Includes ***************************************************/
#include "CyberEngine\VertexStructs\PositionTextureVertex.hlsl"
#include "CyberEngine\PixelStructs\PositionTexturePixel.hlsl"
/* Constant buffers ***************************************************/
#if OPENGL
[[vk::binding(0)]]
#elif VULKAN
[[vk::binding(0, 0)]]
#endif
cbuffer WorldViewProjectionConstantBlock : register(b0)
{
// World, view and projection matrix
row_major float4x4 WorldViewProjection;
};
/* Vertex shader ***************************************************/
PositionTexturePixel VS(PositionTextureVertex vertex)
{
PositionTexturePixel pixel = (PositionTexturePixel) 0;
// Transform the position to screen space
pixel.Position = mul(vertex.Position, WorldViewProjection);
// Set the properties
pixel.TextureCoordinate = vertex.TextureCoordinate;
return pixel;
}
// Auto Generated File. Do not modify.
using System;
using CyberneticGames.CyberEngine.Data;
using CyberneticGames.CyberEngine.Graphics;
using CyberneticGames.CyberEngine.Mathematics;
namespace CoreTester.Graphics
{
public partial class MeshGraphicsProgram
{
public static readonly DescriptorTable VertexDescriptorTable = new DescriptorTable(ShaderStage.Vertex,
new[]
{
new DescriptorElement(DescriptorType.ConstantBuffer),
});
public static void SetWorldViewProjectionConstantBlock(IDescriptorBinding descriptorBinding, int tableIndex, IConstantBuffer buffer) => descriptorBinding.SetConstantBuffer(tableIndex, 0, buffer);
public class WorldViewProjectionConstantBlock : ConstantBlock
{
private static readonly IConstantValueLayout[] ValueLayouts = new IConstantValueLayout[]
{
new ConstantValueLayout<CyberneticGames.CyberEngine.Mathematics.MatrixF>(nameof(WorldViewProjection)),
};
public WorldViewProjectionConstantBlock(GraphicsEngine graphicsEngine)
: base(ValueLayouts, graphicsEngine)
{
}
public CyberneticGames.CyberEngine.Mathematics.MatrixF WorldViewProjection { set => Data.SetValue(nameof(WorldViewProjection), value); }
}
}
}
/* Includes ***************************************************/
#include "CyberEngine\PixelStructs\PositionTexturePixel.hlsl"
/* Constants ***************************************************/
// Alpha channel clip threshold
static const float ClipThreshold = 0.001;
/* Textures and samplers ***************************************************/
#if OPENGL
[[vk::binding(0)]]
#elif VULKAN
[[vk::binding(0, 1)]]
#endif
Texture2D<float4> Texture : register(t0);
#if VULKAN
[[vk::binding(1, 1)]]
#endif
SamplerState TextureSampler : register(s0);
/* Pixel shader ***************************************************/
float4 PS(PositionTexturePixel pixel) : SV_Target0
{
// Get the colour
float4 colour = Texture.Sample(TextureSampler, pixel.TextureCoordinate);
// Clip pixels that are transparent
clip(colour.a - ClipThreshold);
return float4(colour.rgb, 1);
}
// Auto Generated File. Do not modify.
using System;
using CyberneticGames.CyberEngine.Data;
using CyberneticGames.CyberEngine.Graphics;
using CyberneticGames.CyberEngine.Mathematics;
namespace CoreTester.Graphics
{
public partial class MeshGraphicsProgram
{
public static readonly DescriptorTable PixelDescriptorTable = new DescriptorTable(ShaderStage.Pixel,
new[]
{
new DescriptorElement(DescriptorType.SampledTexture),
});
public static void SetTexture(IDescriptorBinding descriptorBinding, int tableIndex, ITextureView texture, ITextureSampler textureSampler) => descriptorBinding.SetSampledTexture(tableIndex, 0, texture, textureSampler);
}
}
Geometry Shader
CyberEngine supports vertex, geometry, pixel, and compute shaders.

Compute Shader
An example of a compute shader that generates the visible geometry from a 3D voxel texture.

An example of a compute shader executed in a console application to create a noise texture and save to an image.

User Interface
A user interface framework inspired by WPF created from scratch, supports controls, control styles, and data binding. The user interface is rendered through a drawing interface allowing it to be decoupled from the graphics APIs including Direct3D 11, Direct3D 12, OpenGL, and Vulkan. This also enabled the ability to render to an image, allowing automated tests to be debugged by viewing the results of the UI.
Styling
An example of a Button style is shown below. It illustrates setting default properties and changing properties on triggers.
<c:ResourceDictionary xmlns="assembly:CyberneticGames.CyberEngine.Xpf.Controls;namespace:CyberneticGames.CyberEngine.Xpf.Controls"
xmlns:c="assembly:CyberneticGames.CyberEngine.Xpf.Core;namespace:CyberneticGames.CyberEngine.Xpf">
<c:Style TargetType="{c:Type Button}">
<c:Setter Property="Button.Margin" Value="3" />
<c:Setter Property="Button.Padding" Value="2" />
<c:Setter Property="TextBlock.Foreground" Value="#ffffff" />
<c:Setter Property="Button.Background" Value="#262626" />
<c:Setter Property="Button.BorderBrush" Value="#666666" />
<c:Setter Property="Button.BorderThickness" Value="1" />
<c:Setter Property="Button.Template">
<c:Setter.Value>
<c:Template>
<Border Background="{c:TemplateBinding Background}" BorderBrush="{c:TemplateBinding BorderBrush}" BorderThickness="{c:TemplateBinding BorderThickness}">
<ContentPresenter Margin="{c:TemplateBinding Padding}" />
</Border>
</c:Template>
</c:Setter.Value>
</c:Setter>
<c:Style.Triggers>
<c:PropertyTrigger Property="Button.IsMouseWithin" Value="True">
<c:Setter Property="TextBlock.Foreground" Value="#ffffff" />
<c:Setter Property="Button.Background" Value="#1eb300" />
<c:Setter Property="Button.BorderBrush" Value="#158000" />
</c:PropertyTrigger>
<c:PropertyTrigger Property="Button.IsPressed" Value="True">
<c:Setter Property="TextBlock.Foreground" Value="#ffffff" />
<c:Setter Property="Button.Background" Value="#158000" />
<c:Setter Property="Button.BorderBrush" Value="#116600" />
</c:PropertyTrigger>
<c:PropertyTrigger Property="Button.IsEnabled" Value="False">
<c:Setter Property="TextBlock.Foreground" Value="#999999" />
<c:Setter Property="Button.Background" Value="#1a1a1a" />
<c:Setter Property="Button.BorderBrush" Value="#333333" />
</c:PropertyTrigger>
</c:Style.Triggers>
</c:Style>
</c:ResourceDictionary>
Data Binding
An example of how data binding is configured is shown below. Data binding relies on reflection to resolve properties. Events can also be hooked up to the code file by name, or can be bound to a view model command adhering to the MVVM design pattern.
<TabItem Header="Button">
<StackPanel>
<TextBlock Name="ButtonTextBlock" />
<Button Click="Button_Click">
<TextBlock Text="Button" />
</Button>
<RepeatButton Click="RepeatButton_Click">
<TextBlock Text="RepeatButton" />
</RepeatButton>
<ToggleButton Name="ToggleButton">
<TextBlock Text="ToggleButton" />
</ToggleButton>
<TextBlock Text="{c:Binding Source={c:NamedElementBindingSource ToggleButton}, IsChecked}" />
<TextBlock Text="Text to show and hide with toggle button" Visibility="{c:Binding Source={c:NamedElementBindingSource ToggleButton}, IsChecked, Converter={c:Resource BooleanToVisibilityConverter}}" />
Examples
Below are a few screens from a test application showing some of the controls.




3D UI
The user interface can be drawn to a render target and later rendered on to a 3D object. User input is transformed into the 3D scene so it can be interacted with.


WPF
A game control for WPF allows scenes to be hosted in WPF applications. Support exists for all graphics APIs. Since WPF runs on Direct3D 9, render targets must be copied to a Direct3D 9 texture for display in WPF applications.




Physics
CyberEngine contains a constraint based physics engine. Narrow phase collision detection uses optimized algorithms between common shapes and GJK, which can be extended to support custom convex hulls. Collision response uses an iterative solver that supports contact manifolds and static and dynamic friction.

Models
CyberEngine supports 3D models and animation. The content pipeline supports Collada and GLTF files. A test application to view models and animations is illustrated below.

Instanced Animation
Multiple skinned model instances with bone vertex weights can be rendered in a single draw call using instanced geometry and an array of bone transforms for each instance.

Linux
CyberEngine supports Linux using OpenGL and Vulkan.

Visual Studio Project Type
CyberEngine uses a Visual Studio extension to provide a custom project type for game content. It allows content files to be added to a project and configure which importer to handle processing and writing the shipped content file.
During game execution, the file is read which contains the reader type name to properly parse and load the content. The importers are decoupled from the readers so the writers do not need to be shipped with the game.
The importers are contained in assemblies and packaged into NuGet packages which can be added to any game content project. The project type in Visual Studio supports using the NuGet package manager to manage NuGet packages for the project.
The project system is built on MSBuild, so it can be supported in Visual Studio and integrate with C# projects that also use MSBuild.

<ItemGroup>
<PackageReference Include="CyberneticGames.CyberEngine.Content.Pipeline" Version="1.*" />
<PackageReference Include="CyberneticGames.CyberEngine.Content.Pipeline.Direct3D11" Version="1.*" />
<PackageReference Include="CyberneticGames.CyberEngine.Graphics" Version="1.*" />
<PackageReference Include="Mapromar.Build.ProjectDependency" Version="1.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Content Include="..\Shared.TesterContent\Shaders\MeshVS.hlsl">
<Link>Shaders\MeshVS.hlsl</Link>
<Importer_Type>CyberneticGames.CyberEngine.Content.Pipeline.CrossShader.VertexShaderImporter</Importer_Type>
<Importer_EntryPoint>VS</Importer_EntryPoint>
<Importer_Profile>vs_4_0</Importer_Profile>
<Importer_EnableDebugging Condition="'$(Configuration)' == 'Debug'">True</Importer_EnableDebugging>
<Importer_Direct3D11Version>4.0</Importer_Direct3D11Version>
<Importer_Direct3D12Version>4.0</Importer_Direct3D12Version>
<Importer_OpenGLVersion>330</Importer_OpenGLVersion>
<Importer_VulkanVersion>450</Importer_VulkanVersion>
</Content>
<Content Include="..\Shared.TesterContent\Shaders\MeshPS.hlsl">
<Link>Shaders\MeshPS.hlsl</Link>
<Importer_Type>CyberneticGames.CyberEngine.Content.Pipeline.CrossShader.PixelShaderImporter</Importer_Type>
<Importer_EntryPoint>PS</Importer_EntryPoint>
<Importer_Profile>ps_4_0</Importer_Profile>
<Importer_EnableDebugging Condition="'$(Configuration)' == 'Debug'">True</Importer_EnableDebugging>
<Importer_Direct3D11Version>4.0</Importer_Direct3D11Version>
<Importer_Direct3D12Version>4.0</Importer_Direct3D12Version>
<Importer_OpenGLVersion>330</Importer_OpenGLVersion>
<Importer_VulkanVersion>450</Importer_VulkanVersion>
</Content>
</ItemGroup>

Vegabot
Vegabot is a game built on CyberEngine that includes an orbital mechanics simulator. A few images captured from tools and test applications are shown below.





