In this tutorial, we want to learn how to produce 2D graphics and how to react to keyboard input. Two-dimensional renderings are useful in their own right. In addition, they can be a final stage of a multi-stage rendering pipleline starting in 3D.
We start from scratch with the main program. We will implement the FlatRendering class subsequently. Note that we do not need a camera, since there is no need for a projection from 3D to 2D and it is simply a part of the x-y-plane that will be displayed.
public static void Main()
{
var graphic = new OpenTkGraphic();
var rendering = new FlatRendering(graphic);
using var window = new OpenTkWindow("Tutorial", graphic, 1024, 768);
window.Show(rendering);
}
The rendering class is implementated the same way as for 3D renderings. The only difference is that the high-level shading approach cannot be used, but instead the rendering needs to provide and use 2D shaders. Just below is a simple 2-dimensional shader. If you want to learn more about writing low-level shaders refer to the GLSL shader tutorial. EduGraf does not come with prefabricated 2D shaders or a framework to create them, because it is mainly targetted at 3D rendering.
public class FlatShading : GlShading
{
private const string VertShader = @"
#version 410
in vec2 Position;
uniform mat3 Model;
void main(void)
{
gl_Position = vec4(vec3(Position, 1) * Model, 1);
gl_Position.z = 0;
}";
private const string FragShader = @"
# version 410
uniform vec4 color;
out vec4 fragment;
void main() { fragment = color; }";
public FlatShading(GlGraphic graphic, Color4 color)
: base("TwoD", graphic, VertShader, FragShader)
{
DoInContext(() => Set("color", color));
}
}
With this shader, we can create a 2D object in the OnLoad()-method of the rendering. Note that instead of calling Geometry.Create() we call the method Geometry.Create2D(). There is the indexed and the non-indexed triangle mesh format in two dimensions as well. Refer to the tutorial section about geometries, if you want to learn more.
private static readonly float[] Positions = [
-0.5f,-0.5f,
+0.5f,-0.5f,
+0.0f, 0.5f
];
private VisualPart? _figure;
public override void OnLoad(Window window)
{
var shading = new FlatShading(graphic, new Color4(1, 0, 0, 1));
var geometry = Geometry.Create2D(Positions);
var surface = Graphic.CreateSurface(shading, geometry);
_figure = Graphic.CreateVisual("", surface);
Scene.Add(_figure);
}
React to keyboard input
Let us make it a bit more fun by giving the user the possibility to control this triangle and manouver it around in the scene. You can declare a local event handler in the Main()-method and pass it to the window-constructor as an additional last argument.
void ActOnKey(InputEvent e, ConsoleKey key, Action action)
{
if (e is KeyInputEvent ke && ke.Key == key && ke.Pressing == Pressing.Down) action();
}
void EventHandler(InputEvent e)
{
ActOnKey(e, ConsoleKey.A, rendering.Left);
ActOnKey(e, ConsoleKey.S, rendering.Down);
ActOnKey(e, ConsoleKey.D, rendering.Right);
ActOnKey(e, ConsoleKey.W, rendering.Up);
}
We obviously need to add the methods above to the FlatRendering class and apply a translation to _figure that corresponds the method name. When running the program, you should now be able to move the figure around in the window. It looks better, if the triangle is rotated additionally to the translation in the direction of the move. This is achieved by the following code added to the rendering that uses an absolute approach to define a transformation.
private int _x;
private int _y;
private float _angle;
public void Left()
{
_x--;
_angle = 0.5f * MathF.PI;
UpdateTranslation();
}
private void UpdateTranslation()
{
_figure!.Transform =
Matrix4.Scale(Scale) *
Matrix4.RotationZ(_angle) *
Matrix4.Translation(Scale * (_x * Vector3.UnitX + _y * Vector3.UnitY));
}