CubeMesh.axaml代码
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="363" Width="533" x:Class="AvaloniaUI.CubeMesh" xmlns:local="using:AvaloniaUI" Title="CubeMesh"> <Grid Margin="10" RowDefinitions="auto,*,auto"> <TextBlock Grid.Row="0" Text="Cube + ScreenSpaceLines3D (OpenGL ES 3.0 via ANGLE)" /> <Border Grid.Row="1" BorderBrush="Yellow" BorderThickness="1" Margin="0,8,0,8"> <local:CubeMeshView x:Name="cube" Angle="{Binding #slider.Value}" /> </Border> <StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center"> <TextBlock Text="Angle:" VerticalAlignment="Center" /> <Slider x:Name="slider" Minimum="0" Maximum="360" Width="360" Value="{Binding #cube.Angle}" /> <TextBlock Text="{Binding #cube.Angle, StringFormat={}{0:0}}" VerticalAlignment="Center" /> </StackPanel> </Grid> </Window>
CubeMesh.axaml.cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Controls;
using AvaloniaUI.Demos.Book._23.Tools;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System;
using System.Numerics;
namespace AvaloniaUI;
public sealed class CubeMeshView : OpenGlControlBase
{
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<CubeMeshView, double>(nameof(Angle));
public double Angle
{
get => GetValue(AngleProperty);
set => SetValue(AngleProperty, value);
}
private int cubeProgram;
private int cubeVao;
private int cubeVbo;
private int cubeIbo;
private int cubeIndexCount;
private int linesProgram;
private int linesVao;
private int linesVbo;
private int linesVertexCount;
private readonly ScreenSpaceLines3D axes = new();
protected override void OnOpenGlInit(GlInterface gl)
{
GL.LoadBindings(new BindingsContext(gl));
CreateCubeResources();
CreateLinesResources();
axes.thickness = 1f;
axes.color = new Vector4(0.8f, 0f, 0f, 1f);
axes.SetAxes(20f);
}
protected override void OnOpenGlDeinit(GlInterface gl)
{
if (cubeProgram != 0) GL.DeleteProgram(cubeProgram);
if (cubeVbo != 0) GL.DeleteBuffer(cubeVbo);
if (cubeIbo != 0) GL.DeleteBuffer(cubeIbo);
if (cubeVao != 0) GL.DeleteVertexArray(cubeVao);
if (linesProgram != 0) GL.DeleteProgram(linesProgram);
if (linesVbo != 0) GL.DeleteBuffer(linesVbo);
if (linesVao != 0) GL.DeleteVertexArray(linesVao);
cubeProgram = 0;
cubeVbo = 0;
cubeIbo = 0;
cubeVao = 0;
cubeIndexCount = 0;
linesProgram = 0;
linesVbo = 0;
linesVao = 0;
linesVertexCount = 0;
}
protected override void OnOpenGlRender(GlInterface gl, int fb)
{
RequestNextFrameRendering();
var width = Math.Max(1, (int)Bounds.Width);
var height = Math.Max(1, (int)Bounds.Height);
GL.Viewport(0, 0, width, height);
GL.ClearColor(0.15f, 0.15f, 0.15f, 1f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
var aspect = width / (float)height;
var projection = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, aspect, 0.1f, 200f);
var view = CreateLookAt(new Vector3(-35, 25, 25), new Vector3(0, 0, 0), Vector3.UnitY);
var vp = view * projection;
DrawCube(vp);
DrawAxes(vp, width, height);
}
private void DrawCube(Matrix4x4 vp)
{
GL.Enable(EnableCap.DepthTest);
GL.UseProgram(cubeProgram);
GL.BindVertexArray(cubeVao);
var model =
Matrix4x4.CreateFromYawPitchRoll((float)(Angle * Math.PI / 180.0), 0f, 0f) *
Matrix4x4.CreateTranslation(0f, 0f, 0f);
var mvp = model * vp;
var location = GL.GetUniformLocation(cubeProgram, "uMvp");
GL.UniformMatrix4(location, 1, false, ToFloat16(mvp));
GL.DrawElements(PrimitiveType.Triangles, cubeIndexCount, DrawElementsType.UnsignedShort, IntPtr.Zero);
}
private void DrawAxes(Matrix4x4 vp, int width, int height)
{
GL.Disable(EnableCap.DepthTest);
axes.Rebuild(vp, width, height);
linesVertexCount = axes.vertexCount;
if (linesVertexCount == 0)
return;
GL.BindBuffer(BufferTarget.ArrayBuffer, linesVbo);
GL.BufferData(BufferTarget.ArrayBuffer, axes.vertexData.Length * sizeof(float), axes.vertexData, BufferUsageHint.DynamicDraw);
GL.UseProgram(linesProgram);
GL.BindVertexArray(linesVao);
GL.DrawArrays(PrimitiveType.Triangles, 0, linesVertexCount);
}
private void CreateCubeResources()
{
cubeProgram = CreateProgram(
"""
#version 300 es
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aColor;
uniform mat4 uMvp;
out vec3 vColor;
void main()
{
gl_Position = uMvp * vec4(aPos, 1.0);
vColor = aColor;
}
""",
"""
#version 300 es
precision mediump float;
in vec3 vColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(vColor, 1.0);
}
""");
// 立方体:每顶点 pos(3) + color(3)
var vertices = new float[]
{
// x,y,z, r,g,b
-10,-10,-10, 1,0,0,
10,-10,-10, 0,1,0,
10, 10,-10, 0,0,1,
-10, 10,-10, 1,1,0,
-10,-10, 10, 1,0,1,
10,-10, 10, 0,1,1,
10, 10, 10, 1,1,1,
-10, 10, 10, 0.5f,0.5f,0.5f,
};
// 12 个三角形 = 36 indices
var indices = new ushort[]
{
0,1,2, 0,2,3, // back
4,6,5, 4,7,6, // front
0,3,7, 0,7,4, // left
1,5,6, 1,6,2, // right
3,2,6, 3,6,7, // top
0,4,5, 0,5,1, // bottom
};
cubeIndexCount = indices.Length;
cubeVao = GL.GenVertexArray();
cubeVbo = GL.GenBuffer();
cubeIbo = GL.GenBuffer();
GL.BindVertexArray(cubeVao);
GL.BindBuffer(BufferTarget.ArrayBuffer, cubeVbo);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, cubeIbo);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(ushort), indices, BufferUsageHint.StaticDraw);
var stride = 6 * sizeof(float);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
}
private void CreateLinesResources()
{
linesProgram = CreateProgram(
"""
#version 300 es
layout(location=0) in vec4 aClipPos;
layout(location=1) in vec4 aColor;
out vec4 vColor;
void main()
{
gl_Position = aClipPos;
vColor = aColor;
}
""",
"""
#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 FragColor;
void main()
{
FragColor = vColor;
}
""");
linesVao = GL.GenVertexArray();
linesVbo = GL.GenBuffer();
GL.BindVertexArray(linesVao);
GL.BindBuffer(BufferTarget.ArrayBuffer, linesVbo);
var stride = (4 + 4) * sizeof(float);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 4, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 4 * sizeof(float));
}
private static int CreateProgram(string vsSource, string fsSource)
{
var vs = CompileShader(ShaderType.VertexShader, vsSource);
var fs = CompileShader(ShaderType.FragmentShader, fsSource);
var program = GL.CreateProgram();
GL.AttachShader(program, vs);
GL.AttachShader(program, fs);
GL.LinkProgram(program);
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var linked);
if (linked == 0)
{
var log = GL.GetProgramInfoLog(program);
Console.WriteLine(log);
throw new InvalidOperationException($"Link failed: {log}");
}
GL.DetachShader(program, vs);
GL.DetachShader(program, fs);
GL.DeleteShader(vs);
GL.DeleteShader(fs);
return program;
}
private static int CompileShader(ShaderType type, string source)
{
var shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out var ok);
if (ok == 0)
{
var log = GL.GetShaderInfoLog(shader);
Console.WriteLine(log);
GL.DeleteShader(shader);
throw new InvalidOperationException($"{type} compile failed: {log}");
}
return shader;
}
private static float[] ToFloat16(Matrix4x4 m)
=>
[
m.M11, m.M12, m.M13, m.M14,
m.M21, m.M22, m.M23, m.M24,
m.M31, m.M32, m.M33, m.M34,
m.M41, m.M42, m.M43, m.M44,
];
private static Matrix4x4 CreateLookAt(Vector3 eye, Vector3 target, Vector3 up)
{
var z = Vector3.Normalize(eye - target);
var x = Vector3.Normalize(Vector3.Cross(up, z));
var y = Vector3.Cross(z, x);
var tx = -Vector3.Dot(x, eye);
var ty = -Vector3.Dot(y, eye);
var tz = -Vector3.Dot(z, eye);
return new Matrix4x4(
x.X, y.X, z.X, 0,
x.Y, y.Y, z.Y, 0,
x.Z, y.Z, z.Z, 0,
tx, ty, tz, 1);
}
private sealed class BindingsContext(GlInterface gl) : IBindingsContext
{
public IntPtr GetProcAddress(string procName) => gl.GetProcAddress(procName);
}
}
public partial class CubeMesh : Window
{
public CubeMesh()
{
InitializeComponent();
}
}
运行效果

浙公网安备 33010602011771号