Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/render_ui_to_texture.rs
9351 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
..default()
71
},
72
RenderTarget::Image(image_handle.clone().into()),
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: auto(),
96
height: auto(),
97
align_items: AlignItems::Center,
98
padding: UiRect::all(px(20.)),
99
border_radius: BorderRadius::all(px(10.)),
100
..default()
101
},
102
BackgroundColor(BLUE.into()),
103
))
104
.observe(
105
|drag: On<Pointer<Drag>>, mut nodes: Query<(&mut Node, &ComputedNode)>| {
106
let (mut node, computed) = nodes.get_mut(drag.entity).unwrap();
107
node.left = px(drag.pointer_location.position.x - computed.size.x / 2.0);
108
node.top = px(drag.pointer_location.position.y - 50.0);
109
},
110
)
111
.observe(
112
|over: On<Pointer<Over>>, mut colors: Query<&mut BackgroundColor>| {
113
colors.get_mut(over.entity).unwrap().0 = RED.into();
114
},
115
)
116
.observe(
117
|out: On<Pointer<Out>>, mut colors: Query<&mut BackgroundColor>| {
118
colors.get_mut(out.entity).unwrap().0 = BLUE.into();
119
},
120
)
121
.with_children(|parent| {
122
parent.spawn((
123
Text::new("Drag Me!"),
124
TextFont {
125
font_size: FontSize::Px(40.0),
126
..default()
127
},
128
TextColor::WHITE,
129
));
130
});
131
});
132
133
let mesh_handle = meshes.add(Cuboid::default());
134
135
// This material has the texture that has been rendered.
136
let material_handle = materials.add(StandardMaterial {
137
base_color_texture: Some(image_handle),
138
reflectance: 0.02,
139
unlit: false,
140
..default()
141
});
142
143
// Cube with material containing the rendered UI texture.
144
commands.spawn((
145
Mesh3d(mesh_handle),
146
MeshMaterial3d(material_handle),
147
Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(PI)),
148
Cube,
149
));
150
151
// The main pass camera.
152
commands.spawn((
153
Camera3d::default(),
154
Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
155
));
156
157
commands.spawn(CUBE_POINTER_ID);
158
}
159
160
const ROTATION_SPEED: f32 = 0.1;
161
162
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Cube>>) {
163
for mut transform in &mut query {
164
transform.rotate_x(1.0 * time.delta_secs() * ROTATION_SPEED);
165
transform.rotate_y(0.7 * time.delta_secs() * ROTATION_SPEED);
166
}
167
}
168
169
/// Because bevy has no way to know how to map a mouse input to the UI texture, we need to write a
170
/// system that tells it there is a pointer on the UI texture. We cast a ray into the scene and find
171
/// the UV (2D texture) coordinates of the raycast hit. This UV coordinate is effectively the same
172
/// as a pointer coordinate on a 2D UI rect.
173
fn drive_diegetic_pointer(
174
mut cursor_last: Local<Vec2>,
175
mut raycast: MeshRayCast,
176
rays: Res<RayMap>,
177
cubes: Query<&Mesh3d, With<Cube>>,
178
ui_camera: Query<&RenderTarget, With<Camera2d>>,
179
primary_window: Query<Entity, With<PrimaryWindow>>,
180
windows: Query<(Entity, &Window)>,
181
images: Res<Assets<Image>>,
182
manual_texture_views: Res<ManualTextureViews>,
183
mut window_events: MessageReader<WindowEvent>,
184
mut pointer_inputs: MessageWriter<PointerInput>,
185
) -> Result {
186
// Get the size of the texture, so we can convert from dimensionless UV coordinates that span
187
// from 0 to 1, to pixel coordinates.
188
let target = ui_camera
189
.single()?
190
.normalize(primary_window.single().ok())
191
.unwrap();
192
let target_info = target
193
.get_render_target_info(windows, &images, &manual_texture_views)
194
.unwrap();
195
let size = target_info.physical_size.as_vec2();
196
197
// Find raycast hits and update the virtual pointer.
198
let raycast_settings = MeshRayCastSettings {
199
visibility: RayCastVisibility::VisibleInView,
200
filter: &|entity| cubes.contains(entity),
201
early_exit_test: &|_| false,
202
};
203
for (_id, ray) in rays.iter() {
204
for (_cube, hit) in raycast.cast_ray(*ray, &raycast_settings) {
205
let position = size * hit.uv.unwrap();
206
if position != *cursor_last {
207
pointer_inputs.write(PointerInput::new(
208
CUBE_POINTER_ID,
209
Location {
210
target: target.clone(),
211
position,
212
},
213
PointerAction::Move {
214
delta: position - *cursor_last,
215
},
216
));
217
*cursor_last = position;
218
}
219
}
220
}
221
222
// Pipe pointer button presses to the virtual pointer on the UI texture.
223
for window_event in window_events.read() {
224
if let WindowEvent::MouseButtonInput(input) = window_event {
225
let button = match input.button {
226
MouseButton::Left => PointerButton::Primary,
227
MouseButton::Right => PointerButton::Secondary,
228
MouseButton::Middle => PointerButton::Middle,
229
_ => continue,
230
};
231
let action = match input.state {
232
ButtonState::Pressed => PointerAction::Press(button),
233
ButtonState::Released => PointerAction::Release(button),
234
};
235
pointer_inputs.write(PointerInput::new(
236
CUBE_POINTER_ID,
237
Location {
238
target: target.clone(),
239
position: *cursor_last,
240
},
241
action,
242
));
243
}
244
}
245
246
Ok(())
247
}
248
249