第一个完整应用
把 快速开始 里的积木串成一个模型查看器:加载 glTF 模型、对准相机、逐帧自转、鼠标环绕、点击拾取并高亮。
装包
模型加载不在基础包里,需要额外装 glTF 加载器:
shell
dotnet add package Aura3D.Avalonia
dotnet add package Aura3D.Model.GltfLoader准备一个 .glb 模型文件(叫它 model.glb),放到程序运行目录,或把下面代码里的路径换成你的绝对路径。
XAML:一个视图,三个回调
xaml
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:a="https://github.com/CeSun/Aura3D"
x:Class="MyApp.MainWindow">
<a:Aura3DView
x:Name="view"
SceneInitialized="OnSceneInitialized"
SceneUpdated="OnSceneUpdated"
PointerPressed="OnPointerPressed"/>
</Window>SceneInitialized— GL 就绪、Scene可用,在这里建场景、加载模型、对准相机。SceneUpdated— 每帧渲染前触发,参数带DeltaTime(秒),逐帧动画放这里。PointerPressed— Avalonia 控件的标准指针事件,Aura3DView就是个控件,直接用它拿到点击位置做拾取。
完整代码
MainWindow.axaml.cs:
csharp
using System;
using System.Numerics;
using Aura3D.Avalonia;
using Aura3D.Core.Nodes;
using Aura3D.Core.Resources;
using Aura3D.Model;
using Avalonia.Controls;
using Avalonia.Input;
public partial class MainWindow : Window
{
private const string ModelPath = "model.glb"; // 换成你自己的模型
private CameraController? _controller;
private Model? _model;
// 记录上一次被高亮的部件,点击新部件时先还原它
private Mesh? _highlighted;
private Texture? _savedBaseColor;
public MainWindow()
{
InitializeComponent();
}
private void OnSceneInitialized(object sender, InitializedRoutedEventArgs args)
{
var view = (Aura3DView)sender;
var scene = args.Scene;
scene.Background = Texture.CreateFromColor(System.Drawing.Color.Gray);
// 1) 加载模型
var model = ModelLoader.LoadGlbModel(ModelPath);
model.Name = "Hero";
// 2) 摆位置:放在世界原点,正立朝向相机
model.Position = new Vector3(0, 0, 0);
// 3) 打一盏方向光 —— 默认 BlinnPhong 没光就全黑
var light = new DirectionalLight();
light.LightColor = System.Drawing.Color.White;
light.RotationDegrees = new Vector3(-30, -20, 0);
view.AddNode(light);
// 4) 相机对准:按模型包围盒自动取景,padding 留一点呼吸空间
view.MainCamera.FitToBoundingBox(model.BoundingBox, padding: 0.5f);
view.AddNode(model);
_model = model;
// 5) 鼠标环绕
_controller = new CameraController(view) { MoveSpeed = 20f };
}
private void OnSceneUpdated(object sender, UpdateRoutedEventArgs e)
{
if (_model == null)
return;
// 每帧绕 Y 轴转一点:30 度/秒 × 本帧时长
_model.RotationDegrees += new Vector3(0, 30, 0) * (float)e.DeltaTime;
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
var view = (Aura3DView)sender!;
var scene = view.Scene;
if (scene == null)
return;
// 只在左键点击时拾取,避免和右键环绕冲突
if (!e.GetCurrentPoint(view).Properties.IsLeftButtonPressed)
return;
var position = e.GetPosition(view);
var hit = scene.PickClosest((float)position.X, (float)position.Y, view.MainCamera);
if (hit == null)
return;
if (hit.Value.Node is not Mesh mesh || mesh.Material == null)
return;
// 先还原上一次的高亮
if (_highlighted?.Material != null)
_highlighted.Material.SetTexture("BaseColor", _savedBaseColor);
// 记下原始颜色、把命中的部件涂红
_highlighted = mesh;
_savedBaseColor = mesh.Material.GetTexture("BaseColor");
mesh.Material.SetTexture("BaseColor", Texture.CreateFromColor(System.Drawing.Color.Red));
view.RequestNextFrameRendering();
}
}
ModelLoader由Aura3D.Model.GltfLoader包提供,命名空间Aura3D.Model——若编译器找不到它,说明还没装那个包。
LightColor和Texture.CreateFromColor(...)用的是System.Drawing.Color,不是 Avalonia 的Avalonia.Media.Color。上面写成System.Drawing.Color.White是为了避免和 Avalonia 的同名Color混淆;如果你的文件里已经using了Avalonia.Media,就照这样全限定写。
逐段在做什么
- 加载 —
ModelLoader.LoadGlbModel(path)返回一个Model节点树,里面是若干Mesh部件。要连动画一起取用LoadGlbModelAndAnimations。 - 摆位置 —
Model继承自Node,和盒子里用的一样有Position/RotationDegrees/Scale。 - 打光 — 一盏
DirectionalLight就够看到模型;RotationDegrees决定光从哪来。 - 对准 —
FitToBoundingBox(model.BoundingBox, padding)让相机退到刚好框住整个模型,省去手调距离。模型尺度未知时这一步尤其重要。 - 逐帧自转 —
SceneUpdated每帧触发,用e.DeltaTime换算角速度,转得与帧率无关。 - 点击拾取 —
Scene.PickClosest(x, y, camera)返回该屏幕点最近的命中PickResult?,hit.Value.Node就是被点到的Mesh;用SetTexture("BaseColor", ...)换它的颜色做高亮,记住还原上一件。
常用选项
- 只想按需渲染(静止时省电):
view.AutoRequestNextFrameRendering = false;,然后在动画或点击后调view.RequestNextFrameRendering()推一帧。本例用默认的自动逐帧。 - 拾取全部命中:
scene.Pick(x, y, camera)返回按距离排序的List<PickResult>,可穿过半透明挡板拿后面那件;PickClosest只拿最近一件。 - 环绕手感:
CameraController的MouseSensitivity、ZoomSpeed、PanSpeed,或用EnableZoom/EnablePan关掉对应操作。 - 取景更紧/更松:调
FitToBoundingBox的padding,或改view.MainCamera.FieldOfView。
WARNING
默认前向管线每种光源最多 4 盏,超出的会被忽略。需要更多灯或物理光照,参考 选择与配置管线。
常见坑
- 画面全黑:九成是没加灯,或
RotationDegrees把方向光转到背对模型、相机没对准。先确认加了一盏方向光。 - 模型巨大/极小、相机跑很远:这是正常现象,
FitToBoundingBox会按模型实际尺寸把相机拉开;想看细节就滚轮推近。 - 点击拾取有偏移(高 DPI):
e.GetPosition(view)给的是逻辑像素。若命中点和实际有偏移,参考 常见坑与排障 里的坐标换算。 BaseColor不能写进初始化器:它是扩展属性,mesh.Material = new Material { BaseColor = ... }编译不过;先 new 再赋值,或用SetTexture("BaseColor", ...)。- 高亮没还原就点下一件:本例每次点击先把上一件的颜色
SetTexture回去,否则点过的部件会一直红着。
想看更多真实写法
- Picking 示例 —
ObjectPicked事件、全命中列表、实例拾取带回InstanceIndex - ModelViewer 示例 — 模型树、包围盒、
Clone(CopyType)的共享边界 - Camera 示例 — 投影切换、
LookAt、FitToBoundingBox、WorldToScreen