#渲染引擎 #3d渲染 #图形 #材质 #游戏开发 #对象 #纹理

enigma-3d

一个专注于简单性和易用性的3D渲染引擎。功能远未完善,不推荐用于生产环境。

3个版本

新版本 0.2.11 2024年8月18日
0.2.10 2024年8月12日
0.2.9 2024年8月12日
0.2.8 2024年5月26日
0.2.7 2024年5月26日

#399 in 游戏开发

Download history 214/week @ 2024-05-26 23/week @ 2024-06-02 1/week @ 2024-06-09 14/week @ 2024-07-28 223/week @ 2024-08-11

每月下载量237

Apache-2.0

1.5MB
3.5K SLoC

Rust 3K SLoC // 0.0% comments GLSL 486 SLoC // 0.1% comments

enigma-3d是我第一次尝试为Rust编写一个小型的图形API和游戏引擎。请注意,我不是专业的图形程序员,因此代码很可能违反了一些惯例。目前我也没有关注性能。话虽如此,我已经实现了以下功能

特性列表

  • 从GLTF和OBJ加载模型
  • 不透明和透明渲染
  • MaterialShaderShapeObject抽象
  • PBR着色
  • 3步可自定义渲染管线:顶点 -> 几何体 -> 片段
  • 纹理、法线和顶点颜色
  • 每个对象最多4个点光源
  • 一个环境光
  • 一个Camera
  • 一个简单的事件系统,可以将函数和键盘按键以及KeyCode修饰符注入到EventLoop中。目前事件按顺序逐一处理
  • 一个简单的更新系统,可以将函数注入到更新循环中。目前,函数按顺序逐一处理
  • 屏幕到世界坐标的转换,包括选择系统
  • 后期处理
  • 天空盒和天空反射
  • egui集成以实现简单的UI
  • include_bytes!include_str!宏中加载资源,并将它们包含在构建的应用程序中
  • AppState中添加和携带任意数量的数据
  • 将当前加载的AppState序列化为json,并将序列化的AppState注入到正在运行的程序中。
  • 优化:纹理被缓存
  • 优化:材质在对象之间共享,并通过AppState进行管理
  • 优化:通过GPU Instancing将克隆的对象批量到一个Draw Call中

如何安装和运行

在最新版本中安装库非常简单,您只需运行 cargo add enigma-3d 即可。从那里,您就可以在代码库中访问库了。当前版本应该包括大部分基本功能,但在优化方面可能有些过时。我很快就会有一个新版本。

关于运行示例,由于示例大小,它们被隐藏在功能标志之后,这可能会使包膨胀。在将存储库克隆到您的计算机上后,运行 cargo run --example=engine --features=examplecargo run --example=chessboard --features=example 应该可以解决问题。Cargo 应该会为您处理所有依赖项。

棋盘示例 image 带有地面几何草着色器和树风着色器的棋盘示例 image 具有PBR辉光后处理和透明对象 image 一些更多的后处理,包括黑白着色器和红色轮廓而不是黑色轮廓 image

示例游戏

一个用enigma开发的小游戏,可以在以下位置找到: https://github.com/JeremiasMeister/enigma-flappy-bird 请注意,它最初是用较旧的enigma-3d版本开发的,可能已经无法编译。但您总是可以检查Windows的构建版本 image

engine.rs 示例,主函数

API非常简单易用;请参见下面的示例。

fn main() {
    // create an enigma eventloop and appstate
    let event_loop = enigma_3d::EventLoop::new("Enigma 3D Renderer Window", 1080, 720);
    let mut app_state = enigma_3d::AppState::new();

    // set the icon from the resources
    event_loop.set_icon_from_resource(resources::icon());

    // some default event setups like e.g. selection
    enigma_3d::init_default(&mut app_state);

    // create a material and assign the UV checker texture from resources
    let mut material = enigma_3d::material::Material::lit_pbr(event_loop.get_display_clone(), false);
    material.set_texture_from_resource(resources::uv_checker(), enigma_3d::material::TextureType::Albedo);
    material.set_name("opaque_mat");

    let mut transparent_material = enigma_3d::material::Material::lit_pbr(event_loop.get_display_clone(), true);
    transparent_material.set_transparency_strength(0.2);
    transparent_material.set_texture_from_resource(resources::uv_checker(), enigma_3d::material::TextureType::Albedo);
    transparent_material.set_name("transparent_mat");


    // create an object, and load the Suzanne model from resources
    let mut object = Object::load_from_gltf_resource(resources::suzanne());

    // set the material to the suzan object to the first shape (submesh) slot
    object.add_material(material.uuid);
    object.get_shapes_mut()[0].set_material_from_object_list(0);

    // set the name and position of the object
    object.name = "Suzanne".to_string();
    object.transform.set_position([0.0, 0.0, -2.0]);

    // adding the object to the app state
    app_state.add_object(object);

    //also add materials to appstate
    app_state.add_material(material);
    app_state.add_material(transparent_material);

    // create a bunch of lights
    let light1 = enigma_3d::light::Light::new([1.0, 1.0, 5.0], [0.0, 1.0, 0.0], 100.0, Some([1.0, 0.0, 0.0]), false);
    let light2 = enigma_3d::light::Light::new([5.0, 1.0, 1.0], [1.0, 0.0, 0.0], 100.0, None, false);
    let light3 = enigma_3d::light::Light::new([-5.0, 1.0, 1.0], [0.0, 0.0, 1.0], 100.0, None, false);
    let ambient_light = enigma_3d::light::Light::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0.1, None, false);

    // add the lights to the app state
    app_state.add_light(light1, enigma_3d::light::LightEmissionType::Source);
    app_state.add_light(light2, enigma_3d::light::LightEmissionType::Source);
    app_state.add_light(light3, enigma_3d::light::LightEmissionType::Source);
    app_state.add_light(ambient_light, enigma_3d::light::LightEmissionType::Ambient); // only one ambient light is supported atm

    // create and add a camera to the app state
    let camera = Camera::new(Some([0.0, 1.0, 1.0]), Some([20.0, 0.0, 0.0]), Some(90.0), Some(16. / 9.), Some(0.01), Some(1024.));
    app_state.set_camera(camera);

    // add events
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::A),
        Arc::new(rotate_left),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::D),
        Arc::new(rotate_right),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::W),
        Arc::new(rotate_up),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::S),
        Arc::new(rotate_down),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::E),
        Arc::new(roll_right),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::Q),
        Arc::new(roll_left),
        None,
    );
    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::Space),
        Arc::new(spawn_object),
        None,
    );

    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::S),
        Arc::new(save_app_state),
        Some(EventModifiers::new(true, false, false)),
    );

    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::O),
        Arc::new(load_app_state),
        Some(EventModifiers::new(true, false, false)),
    );

    app_state.inject_event(
        event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::N),
        Arc::new(reset),
        Some(EventModifiers::new(true, false, false)),
    );

    // add update functions
    app_state.inject_update_function(Arc::new(hopping_objects));
    app_state.inject_update_function(Arc::new(print_data));

    // add post processing effects
    //app_state.add_post_process(Box::new(enigma::postprocessing::grayscale::GrayScale::new(&event_loop.display.clone())));
    app_state.add_post_process(Box::new(enigma_3d::postprocessing::bloom::Bloom::new(&event_loop.display.clone(), 0.9, 15)));
    app_state.add_post_process(Box::new(enigma_3d::postprocessing::edge::Edge::new(&event_loop.display.clone(), 0.8, [1.0, 0.0, 0.0])));

    //add one ui function to the app state. multiple ui functions can be added modularly
    app_state.inject_gui(Arc::new(enigma_ui_function));


    // add some arbitrary state data. This can be used to store any kind of data in the app state
    // game globals, or other data that needs to be shared between different parts of the application
    app_state.add_state_data("intdata", Box::new(10i32));
    app_state.add_state_data("stringdata", Box::new("Hello World".to_string() as String));
    app_state.add_state_data("booldata", Box::new(true as bool));

    // run the event loop
    event_loop.run(app_state.convert_to_arc_mutex());
}

依赖关系

~23–38MB
~543K SLoC