Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/render_ui_to_texture.rs
6595 views
1
//! Shows how to render UI to a texture. Useful for displaying UI in 3D space.
2
3
use std::f32::consts::PI;
4
5
use bevy::picking::PickingSystems;
6
use bevy::{
7
asset::{uuid::Uuid, RenderAssetUsages},
8
camera::RenderTarget,
9
color::palettes::css::{BLUE, GRAY, RED},
10
input::ButtonState,
11
picking::{
12
backend::ray::RayMap,
13
pointer::{Location, PointerAction, PointerId, PointerInput},
14
},
15
prelude::*,
16
render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
17
window::{PrimaryWindow, WindowEvent},
18
};
19
20
const CUBE_POINTER_ID: PointerId = PointerId::Custom(Uuid::from_u128(90870987));
21
22
fn main() {
23
App::new()
24
.add_plugins(DefaultPlugins)
25
.add_systems(Startup, setup)
26
.add_systems(Update, rotator_system)
27
.add_systems(First, drive_diegetic_pointer.in_set(PickingSystems::Input))
28
.run();
29
}
30
31
// Marks the cube, to which the UI texture is applied.
32
#[derive(Component)]
33
struct Cube;
34
35
fn setup(
36
mut commands: Commands,
37
mut meshes: ResMut<Assets<Mesh>>,
38
mut materials: ResMut<Assets<StandardMaterial>>,
39
mut images: ResMut<Assets<Image>>,
40
) {
41
let size = Extent3d {
42
width: 512,
43
height: 512,
44
..default()
45
};
46
47
// This is the texture that will be rendered to.
48
let mut image = Image::new_fill(
49
size,
50
TextureDimension::D2,
51
&[0, 0, 0, 0],
52
TextureFormat::Bgra8UnormSrgb,
53
RenderAssetUsages::default(),
54
);
55
// You need to set these texture usage flags in order to use the image as a render target
56
image.texture_descriptor.usage =
57
TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
58
59
let image_handle = images.add(image);
60
61
// Light
62
commands.spawn(DirectionalLight::default());
63
64
let texture_camera = commands
65
.spawn((
66
Camera2d,
67
Camera {
68
// render before the "main pass" camera
69
order: -1,
70
target: RenderTarget::Image(image_handle.clone().into()),
71
..default()
72
},
73
))
74
.id();
75
76
commands
77
.spawn((
78
Node {
79
// Cover the whole image
80
width: percent(100),
81
height: percent(100),
82
flex_direction: FlexDirection::Column,
83
justify_content: JustifyContent::Center,
84
align_items: AlignItems::Center,
85
..default()
86
},
87
BackgroundColor(GRAY.into()),
88
UiTargetCamera(texture_camera),
89
))
90
.with_children(|parent| {
91
parent
92
.spawn((
93
Node {
94
position_type: PositionType::Absolute,
95
width: Val::Auto,
96
height: Val::Auto,
97
align_items: AlignItems::Center,
98
padding: UiRect::all(Val::Px(20.)),
99
..default()
100
},
101
BorderRadius::all(Val::Px(10.)),
102
BackgroundColor(BLUE.into()),
103
))
104
.observe(
105
|pointer: On<Pointer<Drag>>, mut nodes: Query<(&mut Node, &ComputedNode)>| {
106
let (mut node, computed) = nodes.get_mut(pointer.entity()).unwrap();
107
node.left =
108
Val::Px(pointer.pointer_location.position.x - computed.size.x / 2.0);
109
node.top = Val::Px(pointer.pointer_location.position.y - 50.0);
110
},
111
)
112
.observe(
113
|pointer: On<Pointer<Over>>, mut colors: Query<&mut BackgroundColor>| {
114
colors.get_mut(pointer.entity()).unwrap().0 = RED.into();
115
},
116
)
117
.observe(
118
|pointer: On<Pointer<Out>>, mut colors: Query<&mut BackgroundColor>| {
119
colors.get_mut(pointer.entity()).unwrap().0 = BLUE.into();
120
},
121
)
122
.with_children(|parent| {
123
parent.spawn((
124
Text::new("Drag Me!"),
125
TextFont {
126
font_size: 40.0,
127
..default()
128
},
129
TextColor::WHITE,
130
));
131
});
132
});
133
134
let mesh_handle = meshes.add(Cuboid::default());
135
136
// This material has the texture that has been rendered.
137
let material_handle = materials.add(StandardMaterial {
138
base_color_texture: Some(image_handle),
139
reflectance: 0.02,
140
unlit: false,
141
..default()
142
});
143
144
// Cube with material containing the rendered UI texture.
145
commands.spawn((
146
Mesh3d(mesh_handle),
147
MeshMaterial3d(material_handle),
148
Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(PI)),
149
Cube,
150
));
151
152
// The main pass camera.
153
commands.spawn((
154
Camera3d::default(),
155
Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
156
));
157
158
commands.spawn(CUBE_POINTER_ID);
159
}
160
161
const ROTATION_SPEED: f32 = 0.1;
162
163
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Cube>>) {
164
for mut transform in &mut query {
165
transform.rotate_x(1.0 * time.delta_secs() * ROTATION_SPEED);
166
transform.rotate_y(0.7 * time.delta_secs() * ROTATION_SPEED);
167
}
168
}
169
170
/// Because bevy has no way to know how to map a mouse input to the UI texture, we need to write a
171
/// system that tells it there is a pointer on the UI texture. We cast a ray into the scene and find
172
/// the UV (2D texture) coordinates of the raycast hit. This UV coordinate is effectively the same
173
/// as a pointer coordinate on a 2D UI rect.
174
fn drive_diegetic_pointer(
175
mut cursor_last: Local<Vec2>,
176
mut raycast: MeshRayCast,
177
rays: Res<RayMap>,
178
cubes: Query<&Mesh3d, With<Cube>>,
179
ui_camera: Query<&Camera, With<Camera2d>>,
180
primary_window: Query<Entity, With<PrimaryWindow>>,
181
windows: Query<(Entity, &Window)>,
182
images: Res<Assets<Image>>,
183
manual_texture_views: Res<ManualTextureViews>,
184
mut window_events: EventReader<WindowEvent>,
185
mut pointer_input: EventWriter<PointerInput>,
186
) -> Result {
187
// Get the size of the texture, so we can convert from dimensionless UV coordinates that span
188
// from 0 to 1, to pixel coordinates.
189
let target = ui_camera
190
.single()?
191
.target
192
.normalize(primary_window.single().ok())
193
.unwrap();
194
let target_info = target
195
.get_render_target_info(windows, &images, &manual_texture_views)
196
.unwrap();
197
let size = target_info.physical_size.as_vec2();
198
199
// Find raycast hits and update the virtual pointer.
200
let raycast_settings = MeshRayCastSettings {
201
visibility: RayCastVisibility::VisibleInView,
202
filter: &|entity| cubes.contains(entity),
203
early_exit_test: &|_| false,
204
};
205
for (_id, ray) in rays.iter() {
206
for (_cube, hit) in raycast.cast_ray(*ray, &raycast_settings) {
207
let position = size * hit.uv.unwrap();
208
if position != *cursor_last {
209
pointer_input.write(PointerInput::new(
210
CUBE_POINTER_ID,
211
Location {
212
target: target.clone(),
213
position,
214
},
215
PointerAction::Move {
216
delta: position - *cursor_last,
217
},
218
));
219
*cursor_last = position;
220
}
221
}
222
}
223
224
// Pipe pointer button presses to the virtual pointer on the UI texture.
225
for window_event in window_events.read() {
226
if let WindowEvent::MouseButtonInput(input) = window_event {
227
let button = match input.button {
228
MouseButton::Left => PointerButton::Primary,
229
MouseButton::Right => PointerButton::Secondary,
230
MouseButton::Middle => PointerButton::Middle,
231
_ => continue,
232
};
233
let action = match input.state {
234
ButtonState::Pressed => PointerAction::Press(button),
235
ButtonState::Released => PointerAction::Release(button),
236
};
237
pointer_input.write(PointerInput::new(
238
CUBE_POINTER_ID,
239
Location {
240
target: target.clone(),
241
position: *cursor_last,
242
},
243
action,
244
));
245
}
246
}
247
248
Ok(())
249
}
250
251