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();
    }
}

运行效果

image

 

posted on 2026-08-15 15:35  dalgleish  阅读(5)  评论(0)    收藏  举报