|
| 1 | +//! This example demonstrates drawing a coloured cube using indexed rendering. |
| 2 | +
|
| 3 | +#![feature(allocator_api)] |
| 4 | + |
| 5 | +use citro3d::macros::include_shader; |
| 6 | +use citro3d::math::{ |
| 7 | + AspectRatio, ClipPlanes, CoordinateOrientation, FVec3, Matrix4, Projection, StereoDisplacement, |
| 8 | +}; |
| 9 | +use citro3d::render::ClearFlags; |
| 10 | +use citro3d::{attrib, buffer, render, shader, texenv}; |
| 11 | +use ctru::prelude::*; |
| 12 | +use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; |
| 13 | + |
| 14 | +#[repr(C)] |
| 15 | +#[derive(Copy, Clone)] |
| 16 | +struct Vec3 { |
| 17 | + x: f32, |
| 18 | + y: f32, |
| 19 | + z: f32, |
| 20 | +} |
| 21 | + |
| 22 | +impl Vec3 { |
| 23 | + const fn new(x: f32, y: f32, z: f32) -> Self { |
| 24 | + Self { x, y, z } |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +#[repr(C)] |
| 29 | +#[derive(Copy, Clone)] |
| 30 | +struct Vertex { |
| 31 | + pos: Vec3, |
| 32 | + color: Vec3, |
| 33 | +} |
| 34 | + |
| 35 | +// borrowed from https://bevyengine.org/examples/3D%20Rendering/generate-custom-mesh/ |
| 36 | +const VERTS: &[[f32; 3]] = &[ |
| 37 | + // top (facing towards +y) |
| 38 | + [-0.5, 0.5, -0.5], // vertex with index 0 |
| 39 | + [0.5, 0.5, -0.5], // vertex with index 1 |
| 40 | + [0.5, 0.5, 0.5], // etc. until 23 |
| 41 | + [-0.5, 0.5, 0.5], |
| 42 | + // bottom (-y) |
| 43 | + [-0.5, -0.5, -0.5], |
| 44 | + [0.5, -0.5, -0.5], |
| 45 | + [0.5, -0.5, 0.5], |
| 46 | + [-0.5, -0.5, 0.5], |
| 47 | + // right (+x) |
| 48 | + [0.5, -0.5, -0.5], |
| 49 | + [0.5, -0.5, 0.5], |
| 50 | + [0.5, 0.5, 0.5], // This vertex is at the same position as vertex with index 2, but they'll have different UV and normal |
| 51 | + [0.5, 0.5, -0.5], |
| 52 | + // left (-x) |
| 53 | + [-0.5, -0.5, -0.5], |
| 54 | + [-0.5, -0.5, 0.5], |
| 55 | + [-0.5, 0.5, 0.5], |
| 56 | + [-0.5, 0.5, -0.5], |
| 57 | + // back (+z) |
| 58 | + [-0.5, -0.5, 0.5], |
| 59 | + [-0.5, 0.5, 0.5], |
| 60 | + [0.5, 0.5, 0.5], |
| 61 | + [0.5, -0.5, 0.5], |
| 62 | + // forward (-z) |
| 63 | + [-0.5, -0.5, -0.5], |
| 64 | + [-0.5, 0.5, -0.5], |
| 65 | + [0.5, 0.5, -0.5], |
| 66 | + [0.5, -0.5, -0.5], |
| 67 | +]; |
| 68 | + |
| 69 | +static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica"); |
| 70 | +const CLEAR_COLOR: u32 = 0x68_B0_D8_FF; |
| 71 | + |
| 72 | +fn main() { |
| 73 | + let mut soc = Soc::new().expect("failed to get SOC"); |
| 74 | + drop(soc.redirect_to_3dslink(true, true)); |
| 75 | + |
| 76 | + let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); |
| 77 | + let mut hid = Hid::new().expect("Couldn't obtain HID controller"); |
| 78 | + let apt = Apt::new().expect("Couldn't obtain APT controller"); |
| 79 | + |
| 80 | + let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D"); |
| 81 | + |
| 82 | + let top_screen = TopScreen3D::from(&gfx.top_screen); |
| 83 | + |
| 84 | + let (mut top_left, mut top_right) = top_screen.split_mut(); |
| 85 | + |
| 86 | + let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); |
| 87 | + let mut top_left_target = instance |
| 88 | + .render_target(width, height, top_left, None) |
| 89 | + .expect("failed to create render target"); |
| 90 | + |
| 91 | + let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); |
| 92 | + let mut top_right_target = instance |
| 93 | + .render_target(width, height, top_right, None) |
| 94 | + .expect("failed to create render target"); |
| 95 | + |
| 96 | + let mut bottom_screen = gfx.bottom_screen.borrow_mut(); |
| 97 | + let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); |
| 98 | + |
| 99 | + let mut bottom_target = instance |
| 100 | + .render_target(width, height, bottom_screen, None) |
| 101 | + .expect("failed to create bottom screen render target"); |
| 102 | + |
| 103 | + let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); |
| 104 | + let vertex_shader = shader.get(0).unwrap(); |
| 105 | + |
| 106 | + let program = shader::Program::new(vertex_shader).unwrap(); |
| 107 | + instance.bind_program(&program); |
| 108 | + let mut vbo_data = Vec::with_capacity_in(VERTS.len(), ctru::linear::LinearAllocator); |
| 109 | + for vert in VERTS.iter().enumerate().map(|(i, v)| Vertex { |
| 110 | + pos: Vec3 { |
| 111 | + x: v[0], |
| 112 | + y: v[1], |
| 113 | + z: v[2], |
| 114 | + }, |
| 115 | + color: { |
| 116 | + // Give each vertex a slightly different color just to highlight edges/corners |
| 117 | + let value = i as f32 / VERTS.len() as f32; |
| 118 | + Vec3::new(1.0, 0.7 * value, 0.5) |
| 119 | + }, |
| 120 | + }) { |
| 121 | + vbo_data.push(vert); |
| 122 | + } |
| 123 | + |
| 124 | + let attr_info = build_attrib_info(); |
| 125 | + |
| 126 | + let mut buf_info = buffer::Info::new(); |
| 127 | + let vbo_slice = buf_info.add(&vbo_data, &attr_info).unwrap(); |
| 128 | + |
| 129 | + // Configure the first fragment shading substage to just pass through the vertex color |
| 130 | + // See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight |
| 131 | + let stage0 = texenv::Stage::new(0).unwrap(); |
| 132 | + instance |
| 133 | + .texenv(stage0) |
| 134 | + .src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None) |
| 135 | + .func(texenv::Mode::BOTH, texenv::CombineFunc::Replace); |
| 136 | + |
| 137 | + let projection_uniform_idx = program.get_uniform("projection").unwrap(); |
| 138 | + let camera_transform = Matrix4::looking_at( |
| 139 | + FVec3::new(1.8, 1.8, 1.8), |
| 140 | + FVec3::new(0.0, 0.0, 0.0), |
| 141 | + FVec3::new(0.0, 1.0, 0.0), |
| 142 | + CoordinateOrientation::RightHanded, |
| 143 | + ); |
| 144 | + let indices: &[u8] = &[ |
| 145 | + 0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side. |
| 146 | + 4, 5, 7, 5, 6, 7, // bottom (-y) |
| 147 | + 8, 11, 9, 9, 11, 10, // right (+x) |
| 148 | + 12, 13, 15, 13, 14, 15, // left (-x) |
| 149 | + 16, 19, 17, 17, 19, 18, // back (+z) |
| 150 | + 20, 21, 23, 21, 22, 23, // forward (-z) |
| 151 | + ]; |
| 152 | + let index_buffer = vbo_slice.index_buffer(indices).unwrap(); |
| 153 | + |
| 154 | + while apt.main_loop() { |
| 155 | + hid.scan_input(); |
| 156 | + |
| 157 | + if hid.keys_down().contains(KeyPad::START) { |
| 158 | + break; |
| 159 | + } |
| 160 | + |
| 161 | + instance.render_frame_with(|instance| { |
| 162 | + let mut render_to = |target: &mut render::Target, projection| { |
| 163 | + target.clear(ClearFlags::ALL, CLEAR_COLOR, 0); |
| 164 | + |
| 165 | + instance |
| 166 | + .select_render_target(target) |
| 167 | + .expect("failed to set render target"); |
| 168 | + |
| 169 | + instance.bind_vertex_uniform(projection_uniform_idx, projection * camera_transform); |
| 170 | + |
| 171 | + instance.set_attr_info(&attr_info); |
| 172 | + unsafe { |
| 173 | + instance.draw_elements(buffer::Primitive::Triangles, vbo_slice, &index_buffer); |
| 174 | + } |
| 175 | + }; |
| 176 | + |
| 177 | + let Projections { |
| 178 | + left_eye, |
| 179 | + right_eye, |
| 180 | + center, |
| 181 | + } = calculate_projections(); |
| 182 | + |
| 183 | + render_to(&mut top_left_target, &left_eye); |
| 184 | + render_to(&mut top_right_target, &right_eye); |
| 185 | + render_to(&mut bottom_target, ¢er); |
| 186 | + }); |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +fn build_attrib_info() -> attrib::Info { |
| 191 | + // Configure attributes for use with the vertex shader |
| 192 | + let mut attr_info = attrib::Info::new(); |
| 193 | + |
| 194 | + let reg0 = attrib::Register::new(0).unwrap(); |
| 195 | + let reg1 = attrib::Register::new(1).unwrap(); |
| 196 | + |
| 197 | + attr_info |
| 198 | + .add_loader(reg0, attrib::Format::Float, 3) |
| 199 | + .unwrap(); |
| 200 | + |
| 201 | + attr_info |
| 202 | + .add_loader(reg1, attrib::Format::Float, 3) |
| 203 | + .unwrap(); |
| 204 | + |
| 205 | + attr_info |
| 206 | +} |
| 207 | + |
| 208 | +struct Projections { |
| 209 | + left_eye: Matrix4, |
| 210 | + right_eye: Matrix4, |
| 211 | + center: Matrix4, |
| 212 | +} |
| 213 | + |
| 214 | +fn calculate_projections() -> Projections { |
| 215 | + // TODO: it would be cool to allow playing around with these parameters on |
| 216 | + // the fly with D-pad, etc. |
| 217 | + let slider_val = ctru::os::current_3d_slider_state(); |
| 218 | + let interocular_distance = slider_val / 2.0; |
| 219 | + |
| 220 | + let vertical_fov = 40.0_f32.to_radians(); |
| 221 | + let screen_depth = 2.0; |
| 222 | + |
| 223 | + let clip_planes = ClipPlanes { |
| 224 | + near: 0.01, |
| 225 | + far: 100.0, |
| 226 | + }; |
| 227 | + |
| 228 | + let (left, right) = StereoDisplacement::new(interocular_distance, screen_depth); |
| 229 | + |
| 230 | + let (left_eye, right_eye) = |
| 231 | + Projection::perspective(vertical_fov, AspectRatio::TopScreen, clip_planes) |
| 232 | + .stereo_matrices(left, right); |
| 233 | + |
| 234 | + let center = |
| 235 | + Projection::perspective(vertical_fov, AspectRatio::BottomScreen, clip_planes).into(); |
| 236 | + |
| 237 | + Projections { |
| 238 | + left_eye, |
| 239 | + right_eye, |
| 240 | + center, |
| 241 | + } |
| 242 | +} |
0 commit comments