From aaa9f3e3ab7e9df5d32fb413fd63d63dbf81631f Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Fri, 2 May 2025 23:40:12 +0400 Subject: [PATCH] Start working on graphics --- assets/shaders/light_ps.glsl | 22 +++++++++++ assets/shaders/lit_ps.glsl | 37 ++++++++++++++++++ assets/shaders/lit_vs.glsl | 31 +++++++++++++++ build_hot_reload.sh | 2 +- game/assets/assets.odin | 76 ++++++++++++++++++++++++++++++++---- game/game.odin | 32 ++++++++++++++- game/render/render.odin | 34 ++++++++++++++++ 7 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 assets/shaders/light_ps.glsl create mode 100644 assets/shaders/lit_ps.glsl create mode 100644 assets/shaders/lit_vs.glsl create mode 100644 game/render/render.odin diff --git a/assets/shaders/light_ps.glsl b/assets/shaders/light_ps.glsl new file mode 100644 index 0000000..beb3b9d --- /dev/null +++ b/assets/shaders/light_ps.glsl @@ -0,0 +1,22 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// in vec3 worldPosition; +// in vec3 worldNormal; + +// Input uniform values +// uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// NOTE: Add your custom variables here + +void main() +{ + finalColor = colDiffuse*fragColor; +} diff --git a/assets/shaders/lit_ps.glsl b/assets/shaders/lit_ps.glsl new file mode 100644 index 0000000..e674637 --- /dev/null +++ b/assets/shaders/lit_ps.glsl @@ -0,0 +1,37 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// in vec3 worldPosition; +in vec3 worldNormal; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +uniform vec3 ambient; +uniform vec3 lightDir; + + +// NOTE: Add your custom variables here + +void main() +{ + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord); + + // final color is the color from the texture + // times the tint color (colDiffuse) + // times the fragment color (interpolated vertex color) + + float NDotL = dot(lightDir, -worldNormal); + float toon = 0.5 * smoothstep(0.66, 0.67, NDotL) + 0.5; + vec3 light = mix(vec3(0), vec3(1), toon); + + finalColor = texelColor*colDiffuse*fragColor * vec4(light, 1); +} diff --git a/assets/shaders/lit_vs.glsl b/assets/shaders/lit_vs.glsl new file mode 100644 index 0000000..26ff8cd --- /dev/null +++ b/assets/shaders/lit_vs.glsl @@ -0,0 +1,31 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; + +// Output vertex attributes (to fragment shader) +out vec2 fragTexCoord; +out vec4 fragColor; + +// NOTE: Add your custom variables here +out vec3 worldPosition; +out vec3 worldNormal; + +void main() +{ + // Send vertex attributes to fragment shader + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + // Calculate final vertex position + gl_Position = mvp*vec4(vertexPosition, 1.0); + worldPosition = (matModel * vec4(vertexPosition, 1.0)).xyz; + worldNormal = mat3(matModel) * vertexNormal; +} diff --git a/build_hot_reload.sh b/build_hot_reload.sh index f0d772d..34c2676 100755 --- a/build_hot_reload.sh +++ b/build_hot_reload.sh @@ -36,7 +36,7 @@ esac # Build the game. echo "Building game$DLL_EXT" -odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed +odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:GLFW_SHARED=true -define:PHYSFS_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed # Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written. mv game_tmp$DLL_EXT game$DLL_EXT diff --git a/game/assets/assets.odin b/game/assets/assets.odin index 1692525..69320c9 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -48,6 +48,7 @@ Curve_2D :: [][2]f32 Asset_Manager :: struct { textures: Asset_Cache(rl.Texture2D), models: Asset_Cache(rl.Model), + shaders: Asset_Cache(Loaded_Shader), curves_1d: Asset_Cache([]f32), curves_2d: Asset_Cache(Curve_2D), bvhs: map[cstring]Loaded_BVH, @@ -59,8 +60,12 @@ Asset_Cache_Entry :: struct($E: typeid) { modtime: i64, } +Asset_Cache_Loader_Payload :: union { + cstring, +} + Asset_Cache_Loader :: struct($E: typeid) { - load: proc(path: cstring) -> (E, bool), + load: proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (E, bool), unload: proc(value: E), } @@ -69,8 +74,37 @@ Asset_Cache :: struct($E: typeid) { loader: Asset_Cache_Loader(E), } + +Shader_Location :: enum { + Ambient, + LightDir, +} +Shader_Location_Set :: bit_set[Shader_Location] +Shader_Location_Array :: [Shader_Location]i32 + +SHADER_LOCATION_NAMES := [Shader_Location]cstring { + .Ambient = "ambient", + .LightDir = "lightDir", +} + +Loaded_Shader :: struct { + shader: rl.Shader, + location_set: Shader_Location_Set, + locations: Shader_Location_Array, +} + +SHADER_LOADER :: Asset_Cache_Loader(Loaded_Shader) { + load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (Loaded_Shader, bool) { + shader := rl.LoadShader(path, payload.(cstring)) + return Loaded_Shader{shader = shader}, rl.IsShaderValid(shader) + }, + unload = proc(shader: Loaded_Shader) { + rl.UnloadShader(shader.shader) + }, +} + MODEL_LOADER :: Asset_Cache_Loader(rl.Model) { - load = proc(path: cstring) -> (rl.Model, bool) { + load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (rl.Model, bool) { model := rl.LoadModel(path) return model, rl.IsModelValid(model) }, @@ -80,7 +114,7 @@ MODEL_LOADER :: Asset_Cache_Loader(rl.Model) { } TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) { - load = proc(path: cstring) -> (rl.Texture2D, bool) { + load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (rl.Texture2D, bool) { texture := rl.LoadTexture(path) return texture, rl.IsTextureValid(texture) }, @@ -90,7 +124,7 @@ TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) { } CURVE_1D_CSV_LOADER :: Asset_Cache_Loader([]f32) { - load = proc(path: cstring) -> ([]f32, bool) { + load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> ([]f32, bool) { data, err := physfs.read_entire_file(string(path), context.temp_allocator) if err != nil { log.errorf("Failed to read curve: %s, %v", path, err) @@ -111,7 +145,7 @@ CURVE_1D_CSV_LOADER :: Asset_Cache_Loader([]f32) { } CURVE_2D_CSV_LOADER :: Asset_Cache_Loader(Curve_2D) { - load = proc(path: cstring) -> (Curve_2D, bool) { + load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (Curve_2D, bool) { data, err := physfs.read_entire_file(string(path), context.temp_allocator) if err != nil { log.errorf("Failed to read curve: %s, %v", path, err) @@ -135,6 +169,9 @@ assetman_init :: proc(assetman: ^Asset_Manager) { assetman.models = { loader = MODEL_LOADER, } + assetman.shaders = { + loader = SHADER_LOADER, + } assetman.textures = { loader = TEXTURE_LOADER, } @@ -156,6 +193,7 @@ Asset_Cache_Result :: enum { assetcache_fetch_or_load :: proc( ac: ^$T/Asset_Cache($E), path: cstring, + payload: Asset_Cache_Loader_Payload = nil, ) -> ( value: E, modtime: i64, @@ -174,7 +212,7 @@ assetcache_fetch_or_load :: proc( } else { // Try to load the new version - new_value, ok := ac.loader.load(path) + new_value, ok := ac.loader.load(path, payload) if ok { result = .Reloaded @@ -197,7 +235,7 @@ assetcache_fetch_or_load :: proc( } else { modtime = physfs.getLastModTime(path) ok: bool - value, ok = ac.loader.load(path) + value, ok = ac.loader.load(path, payload) if ok { ac.cache[path] = { @@ -256,6 +294,29 @@ get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model { return model } +get_shader :: proc( + assetman: ^Asset_Manager, + vs_path: cstring, + ps_path: cstring, + location_set: Shader_Location_Set, +) -> Loaded_Shader { + loaded_shader, _, result := assetcache_fetch_or_load(&assetman.shaders, vs_path, ps_path) + + if location_set > loaded_shader.location_set || result == .Loaded || result == .Reloaded { + loaded_shader.location_set = location_set + loaded_shader.locations = {} + + for location in location_set { + loaded_shader.locations[location] = rl.GetShaderLocation( + loaded_shader.shader, + SHADER_LOCATION_NAMES[location], + ) + } + } + + return loaded_shader +} + null_bvhs: []bvh.BVH get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_BVH { @@ -702,6 +763,7 @@ shutdown :: proc(assetman: ^Asset_Manager) { assetcache_destroy(&assetman.textures) assetcache_destroy(&assetman.models) + assetcache_destroy(&assetman.shaders) assetcache_destroy(&assetman.curves_1d) assetcache_destroy(&assetman.curves_2d) diff --git a/game/game.odin b/game/game.odin index 2b08810..a3f0e29 100644 --- a/game/game.odin +++ b/game/game.odin @@ -26,6 +26,7 @@ import "game:halfedge" import "game:physics" import "game:physics/bvh" import "game:physics/collision" +import "game:render" import "libs:tracy" import "ui" import rl "vendor:raylib" @@ -777,7 +778,9 @@ draw :: proc() { if !g_mem.editor { car_matrix := rl.QuaternionToMatrix(car_body.q) - car_model.transform = car_matrix + car_matrix = + (auto_cast linalg.matrix4_translate_f32(physics.body_get_shape_pos(car_body))) * + car_matrix if !runtime_world.pause { if runtime_world.rewind_simulation { @@ -796,7 +799,30 @@ draw :: proc() { } } - rl.DrawModel(car_model, physics.body_get_shape_pos(car_body), 1, rl.WHITE) + basic_shader := assets.get_shader( + &g_mem.assetman, + "assets/shaders/lit_vs.glsl", + "assets/shaders/lit_ps.glsl", + {.Ambient, .LightDir}, + ) + light_dir := linalg.normalize(rl.Vector3{1, -1, 0}) + ambient := linalg.normalize(rl.Vector3{0.1, 0.1, 0.1}) + rl.SetShaderValue( + basic_shader.shader, + basic_shader.locations[.LightDir], + &light_dir, + .VEC3, + ) + rl.SetShaderValue( + basic_shader.shader, + basic_shader.locations[.Ambient], + &ambient, + .VEC3, + ) + + render.draw_model(car_model, basic_shader.shader, car_matrix) + + render.draw_point_light(0, 2, rl.YELLOW) } { @@ -1158,6 +1184,8 @@ game_memory_size :: proc() -> int { game_hot_reloaded :: proc(mem: rawptr) { g_mem = (^Game_Memory)(mem) + render.init() + g_mem.runtime_world.orbit_camera.distance = 4 } diff --git a/game/render/render.odin b/game/render/render.odin new file mode 100644 index 0000000..d679dbb --- /dev/null +++ b/game/render/render.odin @@ -0,0 +1,34 @@ +package render + +import gl "vendor:OpenGL" +import glfw "vendor:glfw" +import rl "vendor:raylib" +import rlgl "vendor:raylib/rlgl" + +init :: proc() { + gl.load_up_to(3, 3, glfw.gl_set_proc_address) +} + +draw_model :: proc(model: rl.Model, shader: rl.Shader, transform: rl.Matrix) { + model := model + for i in 0 ..< model.materialCount { + model.materials[i].shader = shader + } + model.transform = transform + + rl.DrawModel(model, rl.Vector3{}, 1, rl.WHITE) +} + +draw_point_light :: proc(pos: rl.Vector3, radius: f32, color: rl.Color) { + rlgl.DrawRenderBatchActive() + + gl.StencilFunc(gl.ALWAYS, 1, 0) + defer gl.StencilFunc(gl.ALWAYS, 0, 0) + + rlgl.SetCullFace(.FRONT) + rl.DrawSphere(pos, radius, color) + + rlgl.DrawRenderBatchActive() + rlgl.SetCullFace(.BACK) + // rl.DrawMesh(mesh, rl.LoadMaterialDefault(), transform) +}