first open gl base

This commit is contained in:
Victor Vobis 2026-01-13 10:21:02 +01:00
commit 0cf7d5eb43
19 changed files with 8973 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
OUT_DIR=./out

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1835
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "proj"
version = "0.1.0"
edition = "2024"
[dependencies]
dotenvy = "0.15.7"
gl = "0.14.0"
glutin = "0.32.3"
glutin-winit = "0.5.0"
raw-window-handle = "0.6.2"
winit = "0.30.12"
[build-dependencies]
dotenvy = "0.15.7"
gl_generator = "0.14"
cfg_aliases = "0.2.1"

View File

@ -0,0 +1,114 @@
newmtl mat0
Ka 0.73 0.41 0.78
Kd 0.50 0.14 0.58
newmtl mat1
Ka 0.61 0.15 0.69
Kd 0.34 0.02 0.44
newmtl mat2
Ka 0.40 0.23 0.72
Kd 0.13 0.04 0.49
newmtl mat3
Ka 0.50 0.87 0.92
Kd 0.22 0.74 0.83
newmtl mat4
Ka 0.00 0.74 0.83
Kd 0.00 0.52 0.66
newmtl mat5
Ka 0.01 0.61 0.90
Kd 0.00 0.34 0.79
newmtl mat6
Ka 0.97 0.73 0.82
Kd 0.94 0.50 0.65
newmtl mat7
Ka 0.94 0.38 0.57
Kd 0.87 0.12 0.29
newmtl mat8
Ka 0.96 0.26 0.21
Kd 0.91 0.05 0.03
newmtl mat9
Ka 0.55 0.76 0.29
Kd 0.27 0.55 0.07
newmtl mat10
Ka 0.30 0.69 0.31
Kd 0.07 0.44 0.08
newmtl mat11
Ka 0.00 0.59 0.53
Kd 0.00 0.31 0.25
newmtl mat12
Ka 1.00 0.92 0.23
Kd 1.00 0.83 0.04
newmtl mat13
Ka 1.00 0.60 0.00
Kd 1.00 0.33 0.00
newmtl mat14
Ka 1.00 0.34 0.13
Kd 1.00 0.09 0.01
newmtl mat15
Ka 0.81 0.85 0.86
Kd 0.63 0.70 0.72
newmtl mat16
Ka 0.47 0.56 0.61
Kd 0.19 0.28 0.34
newmtl mat17
Ka 0.27 0.35 0.39
Kd 0.06 0.10 0.13
newmtl mat18
Ka 1.00 0.80 0.53
Kd 1.00 0.61 0.25
newmtl mat19
Ka 0.87 0.60 0.27
Kd 0.74 0.33 0.06
newmtl mat20
Ka 0.47 0.33 0.28
Kd 0.19 0.09 0.06
newmtl mat21
Ka 1.00 1.00 1.00
Kd 1.00 1.00 1.00
newmtl mat22
Ka 0.62 0.62 0.62
Kd 0.35 0.35 0.35
newmtl mat23
Ka 0.10 0.10 0.10
Kd 0.01 0.01 0.01
newmtl mat24
Ka 0.58 0.65 1.00
Kd 0.83 0.89 0.87
Ks 1 1 1
illum 4
Ns 300
d 0.4
Ni 1.5
newmtl mat25
Ka 1.00 0.65 0.67
Kd 0.83 0.89 0.87
Ks 1 1 1
illum 4
Ns 300
d 0.4
Ni 1.5

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
v -0.5 -0.5 1.0
v 0.0 0.5 1.0
v 0.5 -0.5 1.0

41
build.rs Normal file
View File

@ -0,0 +1,41 @@
use std::env;
use std::fs::File;
use std::path::PathBuf;
use cfg_aliases::cfg_aliases;
use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator};
fn main() {
// XXX this is taken from glutin/build.rs.
dotenvy::dotenv().ok();
// Setup alias to reduce `cfg` boilerplate.
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_family = "wasm" },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
apple: { any(ios_platform, macos_platform) },
free_unix: { all(unix, not(apple), not(android_platform)) },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(wasm_platform)) },
wayland_platform: { all(feature = "wayland", free_unix, not(wasm_platform)) },
// Backends.
egl_backend: { all(feature = "egl", any(windows, unix), not(apple), not(wasm_platform)) },
glx_backend: { all(feature = "glx", x11_platform, not(wasm_platform)) },
wgl_backend: { all(feature = "wgl", windows, not(wasm_platform)) },
cgl_backend: { all(macos_platform, not(wasm_platform)) },
}
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
println!("cargo:rerun-if-changed=build.rs");
let mut file = File::create(dest.join("gl_bindings.rs")).unwrap();
Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
.write_bindings(StructGenerator, &mut file)
.unwrap();
}

196
src/app/app.rs Normal file
View File

@ -0,0 +1,196 @@
use std::num::NonZero;
use glutin::config::{ ConfigTemplateBuilder, GetGlConfig };
use glutin::context::PossiblyCurrentContext;
use glutin::display::GetGlDisplay;
use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext};
use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface};
use glutin_winit::{DisplayBuilder, GlWindow};
use winit::application::ApplicationHandler;
use winit::event::KeyEvent;
use winit::keyboard::{Key, NamedKey};
use winit::window::{ Window, WindowAttributes };
use winit::{event, window};
use crate::app::gl::{gl_config_picker, gl_create_context};
use crate::app::renderer::Renderer;
use crate::models::ModelData;
enum GlDisplayCreationState {
Builder(Box<DisplayBuilder>),
Init
}
struct AppState {
gl_surface: Surface<WindowSurface>,
window: Window,
}
pub struct App {
gl_display: GlDisplayCreationState,
gl_context: Option<PossiblyCurrentContext>,
template: ConfigTemplateBuilder,
renderer: Option<Renderer>,
current_scene: Option<String>,
scene_data: ModelData,
state: Option<AppState>,
}
impl App {
pub fn new(scene_data: ModelData) -> Self {
let template = ConfigTemplateBuilder::new()
.with_alpha_size(8)
.with_transparency(cfg!(target_os = "macos"));
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes()));
Self {
template,
gl_display: GlDisplayCreationState::Builder(Box::new(display_builder)),
gl_context: None,
renderer: None,
state: None,
scene_data,
current_scene: None,
}
}
pub fn set_current_scene(&mut self, current_scene: &str) {
self.current_scene = Some(current_scene.to_string());
}
}
pub fn window_attributes() -> WindowAttributes {
window::Window::default_attributes()
.with_transparent(false)
.with_title("Test Window")
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let (window, gl_config) = match &self.gl_display {
GlDisplayCreationState::Builder(builder) => {
let (window, gl_config) = match builder.clone().build(
event_loop,
self.template.clone(),
gl_config_picker
) {
Ok((window, gl_config)) => (window.unwrap(), gl_config),
Err(e) => {
panic!("Encountered Error {:#?}", e);
}
};
self.gl_display = GlDisplayCreationState::Init;
self.gl_context = Some(gl_create_context(&window, &gl_config).treat_as_possibly_current());
(window, gl_config)
},
GlDisplayCreationState::Init => {
let gl_config = self.gl_context.as_ref().unwrap().config();
match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) {
Ok(window) => (window, gl_config),
Err(e) => {
panic!("Todo: Handle error for {:#?}", e);
}
}
},
};
let attrs = window
.build_surface_attributes(Default::default())
.expect("Failed to build surface attributes");
let gl_surface = unsafe {
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
};
let gl_context = self.gl_context.as_ref().unwrap();
gl_context.make_current(&gl_surface).unwrap();
if self.renderer.is_none() {
let mut renderer = Renderer::new(&gl_context.display());
renderer.upload_scene_data(&self.scene_data);
println!("Built Renderer");
self.renderer.get_or_insert_with(|| renderer);
}
if let Err(res) = gl_surface.set_swap_interval(
gl_context,
SwapInterval::Wait(NonZero::new(1).unwrap())
) {
eprintln!("Error setting vsync: {:?}", res)
}
assert!(self.state.replace(AppState { gl_surface, window }).is_none());
}
fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
println!("Android window removed");
self.state = None;
self.gl_context = Some(
self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current()
);
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: window::WindowId,
event: event::WindowEvent,
) {
match event {
event::WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => {
if let Some(AppState { gl_surface, window: _ }) = &self.state {
let gl_context = self.gl_context.as_ref().unwrap();
gl_surface.resize(
gl_context,
NonZero::new(size.width).unwrap(),
NonZero::new(size.height).unwrap()
);
let renderer = self.renderer.as_ref().unwrap();
renderer.resize(size.width as i32, size.height as i32);
}
},
event::WindowEvent::CloseRequested
| event::WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. } => {
event_loop.exit();
}
_ => ()
}
}
fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
let _gl_display = self.gl_context.take().unwrap().display();
self.state = None;
#[cfg(egl_backend)]
#[allow(irrefutable_let_patterns)]
if let glutin::display::Display::Egl(display) = _gl_display {
unsafe {
display.terminate();
}
}
}
fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
if let Some(AppState { gl_surface, window }) = self.state.as_ref() {
let gl_context = self.gl_context.as_ref().unwrap();
let renderer = self.renderer.as_ref().unwrap();
if let Some(current_scene) = &self.current_scene {
renderer.render(current_scene);
}
window.request_redraw();
gl_surface.swap_buffers(gl_context).unwrap();
}
}
}

53
src/app/gl.rs Normal file
View File

@ -0,0 +1,53 @@
#![allow(clippy::all)]
#![allow(unsafe_op_in_unsafe_fn)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
pub use Gles2 as Gl;
use glutin::config::{Config, GlConfig};
use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, Version};
use glutin::display::GetGlDisplay;
use glutin::prelude::GlDisplay;
use raw_window_handle::HasWindowHandle;
use winit::window::Window;
pub fn gl_config_picker(configs: Box<dyn Iterator<Item = Config> + '_>) -> Config {
configs
.reduce(|accum, config| {
let transparency_check = config.supports_transparency().unwrap_or(false)
& !accum.supports_transparency().unwrap_or(false);
if transparency_check || config.num_samples() > accum.num_samples() {
config
} else {
accum
}
})
.unwrap()
}
pub fn gl_create_context(window: &Window, gl_config: &Config) -> NotCurrentContext {
let raw_window_handle = window.window_handle().ok().map(|wh| wh.as_raw());
let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);
let fallback_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(None))
.build(raw_window_handle);
let legacy_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1))))
.build(raw_window_handle);
let gl_display = gl_config.display();
unsafe {
gl_display.create_context(gl_config, &context_attributes).unwrap_or_else(|_| {
gl_display.create_context(gl_config, &fallback_context_attributes).unwrap_or_else(|_| {
gl_display
.create_context(gl_config, &legacy_context_attributes)
.expect("Failed to create context")
})
})
}
}

208
src/app/renderer.rs Normal file
View File

@ -0,0 +1,208 @@
use glutin::prelude::GlDisplay;
use crate::app::gl;
use crate::models::{self, Model, ModelData, RenderObject, Scene, ShaderSource};
use std::collections::HashMap;
use std::ops::Deref;
use std::ffi::{ CStr, CString };
fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{
unsafe {
let s = gl.GetString(variant);
(!s.is_null()).then(|| CStr::from_ptr(s.cast()))
}
}
pub struct Renderer {
gl: gl::Gl,
shaders: HashMap<String, gl::types::GLuint>,
scenes: HashMap<String, Scene>
}
impl Renderer {
pub fn new<D: GlDisplay>(gl_display: &D) -> Self {
let gl = gl::Gl::load_with(|symbol| {
let symbol = CString::new(symbol).unwrap();
gl_display.get_proc_address(symbol.as_c_str()).cast()
});
if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) {
println!("Running on {}", renderer.to_string_lossy());
}
if let Some(version) = get_gl_string(&gl, gl::VERSION) {
println!("OpenGL version {}", version.to_string_lossy());
}
if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) {
println!("Shaders version on {}", shaders_version.to_string_lossy());
}
Self {
gl,
shaders: HashMap::new(),
scenes: HashMap::new()
}
}
pub fn upload_scene_data(&mut self, scene_data: &ModelData) {
let objects = scene_data.objects_data
.iter()
.map(|(mesh, shader_src)| {
let shader = self.create_shader(shader_src);
let render_object = self.upload_mesh(mesh);
(render_object, shader)
})
.collect::<Vec<(RenderObject, u32)>>();
println!("Uploading Scene {}", scene_data.name);
self.scenes.insert(scene_data.name.clone(), Model { objects });
}
pub fn upload_mesh(&self, mesh: &models::Mesh) -> RenderObject {
unsafe {
let gl = &self.gl;
let mut vao = std::mem::zeroed();
gl.GenVertexArrays(1, &mut vao);
gl.BindVertexArray(vao);
let mut vbo = std::mem::zeroed();
gl.GenBuffers(1, &mut vbo);
gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
let vertex_data = mesh.vertices
.iter()
.map(|v| {
[v.x, v.y, v.z, 1.0, 0.1, 0.1]
})
.collect::<Vec<[f32; 6]>>();
gl.BufferData(
gl::ARRAY_BUFFER,
(vertex_data.len() * std::mem::size_of::<[f32; 6]>()) as gl::types::GLsizeiptr,
vertex_data.as_ptr() as *const _,
gl::STATIC_DRAW,
);
let pos_attrib = 0;
let color_attrib = 1;
gl.VertexAttribPointer(
pos_attrib as gl::types::GLuint,
3,
gl::FLOAT,
0,
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
std::ptr::null()
);
gl.VertexAttribPointer(
color_attrib as gl::types::GLuint,
3,
gl::FLOAT,
0,
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
(3 * std::mem::size_of::<f32>()) as *const _,
);
gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint);
gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint);
RenderObject {
vao,
vbo,
_ebo: None,
vertex_count: mesh.vertices.len() as i32,
_index_count: None
}
}
}
fn compile_shader(gl: &gl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint {
unsafe {
let shader = gl.CreateShader(shader);
gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null());
gl.CompileShader(shader);
shader
}
}
pub fn create_shader(&self, shader_src: &ShaderSource) -> gl::types::GLuint {
unsafe {
let vertex_shader = Renderer::compile_shader(&self.gl, gl::VERTEX_SHADER, shader_src.vertex_src);
let fragment_shader = Renderer::compile_shader(&self.gl, gl::FRAGMENT_SHADER, shader_src.fragment_src);
let program = self.gl.CreateProgram();
self.gl.AttachShader(program, vertex_shader);
self.gl.AttachShader(program, fragment_shader);
self.gl.LinkProgram(program);
self.gl.DeleteShader(vertex_shader);
self.gl.DeleteShader(fragment_shader);
program
}
}
pub fn add_shader(&mut self, name: &str, shader: gl::types::GLuint) {
if let None = self.shaders.get(name) {
self.shaders.insert(name.to_string(), shader);
}
}
pub fn get_shader(&self, name: &str) -> Option<&gl::types::GLuint> {
self.shaders.get(name)
}
pub fn remove_shader(&mut self, name: &str) {
self.shaders.remove(name);
}
pub fn resize(&self, width: i32, height: i32) {
unsafe {
self.gl.Viewport(0, 0, width, height);
}
}
pub fn draw_object(&self, obj: &RenderObject, shader: gl::types::GLuint) {
unsafe {
self.UseProgram(shader);
self.BindBuffer(gl::ARRAY_BUFFER, obj.vbo);
self.BindVertexArray(obj.vao);
self.DrawArrays(gl::TRIANGLES, 0, obj.vertex_count);
}
}
pub fn render(&self, current_scene: &String) {
unsafe {
self.ClearColor(1.0, 1.0, 1.0, 0.9);
self.Clear(gl::COLOR_BUFFER_BIT);
}
if let Some(scene) = self.scenes.get(current_scene) {
for model in scene.models {
self.draw_object(obj, *shader);
}
} else {
eprintln!("Unknown Scene Requested: {}", current_scene);
}
}
}
impl Deref for Renderer {
type Target = gl::Gl;
fn deref(&self) -> &Self::Target {
&self.gl
}
}
impl Drop for Renderer {
fn drop(&mut self) {
unsafe {
self.shaders.values().for_each(|shader| self.gl.DeleteProgram(*shader));
}
}
}

27
src/lib.rs Normal file
View File

@ -0,0 +1,27 @@
pub mod models {
pub mod mesh;
pub use mesh::*;
pub mod vertex;
pub use vertex::*;
pub mod model;
pub use model::*;
pub mod scene;
pub use scene::*;
pub mod material;
pub use material::*;
}
pub mod app {
pub mod renderer;
pub mod app;
pub mod gl;
}
pub mod shaders {
pub mod shaders;
pub use shaders::*;
}

33
src/main.rs Normal file
View File

@ -0,0 +1,33 @@
use proj::{app::app::App, models::{Mesh, ModelData, ShaderSource}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}};
use winit::event_loop::EventLoop;
const TRIANGLE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
fn main() {
dotenvy::dotenv().ok();
let event_loop = EventLoop::new()
.expect("Should be able to have Event Loop");
let mut mesh = Mesh::new();
if let Err(e) = mesh.load_obj(TRIANGLE_OBJ_PATH) {
panic!("{}", e);
}
let shader_src = ShaderSource {
vertex_src: VERTEX_SHADER_SOURCE,
fragment_src: FRAGMENT_SHADER_SOURCE
};
let scene_data = ModelData {
name: "home".to_string(),
objects_data: vec![(mesh, shader_src)]
};
let mut app = App::new(scene_data);
app.set_current_scene("home");
let event_res = event_loop.run_app(&mut app);
println!("{:#?}", event_res);
}

18
src/models/material.rs Normal file
View File

@ -0,0 +1,18 @@
#[derive(Debug)]
pub struct Material {
pub ambient: [f32; 3],
pub diffuse: [f32; 3],
pub specular: [f32; 3],
pub shader_name: String,
}
impl Material {
pub fn new() -> Self {
Self {
ambient: Default::default(),
diffuse: Default::default(),
specular: Default::default(),
shader_name: String::new(),
}
}
}

124
src/models/mesh.rs Normal file
View File

@ -0,0 +1,124 @@
use std::collections::HashMap;
use crate::models::Material;
use super::Vertex;
#[derive(Debug)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub material_name: String,
}
const NEW_MATERIAL_DELIMITER: &str = "newmtl ";
fn parse_rgb(values: &str) -> Result<[f32; 3], Box<dyn std::error::Error>> {
let mut rgb = [0.0; 3];
let mut i = 0;
let values = values.split_whitespace();
for v in values {
let parsed = v.parse::<f32>()?;
rgb[i] = parsed;
i += 1;
if i > 2 {
todo!("Implement custom error handling: too many values");
}
};
if i != 3 {
todo!("Implement custom error handling: too few values");
}
Ok(rgb)
}
impl Mesh {
pub fn new() -> Self {
Self {
vertices: vec![],
material_name: String::new(),
}
}
pub fn load_obj(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
for line in content.lines() {
if line.starts_with("v ") {
let coords: Result<Vec<f32>, _> = line
.split_whitespace()
.skip(1)
.take(3)
.map(|s| s.parse::<f32>())
.collect();
match coords {
Ok(v) if v.len() == 3 => {
self.vertices.push(Vertex { x: v[0], y: v[1], z: v[2] })
},
_ => eprintln!("Warning: Invalid vertex line: {}", line)
}
}
}
println!("{:#?}", self.vertices);
Ok(())
}
pub fn load_material(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let mut lines = content.lines().peekable();
while let Some(line) = lines.next() {
let line = line.trim();
if line.is_empty() {
continue ;
}
if line.starts_with(NEW_MATERIAL_DELIMITER) {
let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap();
while let Some(&next_line) = lines.peek() {
let next = next_line.trim();
if next.starts_with(NEW_MATERIAL_DELIMITER) || next.is_empty() {
lines.next();
let mut material = Material::new();
if let Some((key, values)) = next.split_once(' ') {
match key {
"Ka" => {
match parse_rgb(values) {
Ok(v) => material.ka = v,
Err(e) => eprintln!("Error parsing Ka values: {}", e.to_string())
}
},
"Kd" => {
match parse_rgb(values) {
Ok(v) => material.kd = v,
Err(e) => eprintln!("Error parsing Kd values: {}", e.to_string())
}
}
"Ks" => {
match parse_rgb(values) {
Ok(v) => material.ks = v,
Err(e) => eprintln!("Error parsing Ks values: {}", e.to_string())
}
}
_ => { eprintln!("Unknown key: {key}") }
}
}
self.materials.insert(mat_name.to_string(), material);
}
}
}
}
println!("{:#?}", self.vertices);
Ok(())
}
}

23
src/models/model.rs Normal file
View File

@ -0,0 +1,23 @@
use crate::models::Mesh;
pub struct ShaderSource {
pub vertex_src: &'static [u8],
pub fragment_src: &'static [u8],
}
pub struct Model {
pub objects: Vec<RenderObject>
}
pub struct RenderObject {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub _ebo: Option<gl::types::GLuint>,
pub vertex_count: i32,
pub _index_count: Option<i32>,
}
pub struct ModelData {
pub name: String,
pub meshes: Vec<Mesh>
}

8
src/models/scene.rs Normal file
View File

@ -0,0 +1,8 @@
use std::collections::HashMap;
use crate::models::{Material, Model};
pub struct Scene {
pub models: Vec<Model>,
pub materials: HashMap<String, Material>,
}

7
src/models/vertex.rs Normal file
View File

@ -0,0 +1,7 @@
#[derive(Debug)]
pub struct Vertex {
pub x: f32,
pub y: f32,
pub z: f32,
}

26
src/shaders/shaders.rs Normal file
View File

@ -0,0 +1,26 @@
pub const VERTEX_SHADER_SOURCE: &[u8] = b"
#version 100
precision mediump float;
attribute vec3 position;
attribute vec3 color;
varying vec3 v_color;
void main() {
gl_Position = vec4(position, 1.0);
v_color = color;
}
\0";
pub const FRAGMENT_SHADER_SOURCE: &[u8] = b"
#version 100
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color, 1.0);
}
\0";