Start working on graphics

This commit is contained in:
sergeypdev 2025-05-02 23:40:12 +04:00
parent 1ea89fb2c9
commit aaa9f3e3ab
7 changed files with 224 additions and 10 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -36,7 +36,7 @@ esac
# Build the game. # Build the game.
echo "Building game$DLL_EXT" 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. # 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 mv game_tmp$DLL_EXT game$DLL_EXT

View File

@ -48,6 +48,7 @@ Curve_2D :: [][2]f32
Asset_Manager :: struct { Asset_Manager :: struct {
textures: Asset_Cache(rl.Texture2D), textures: Asset_Cache(rl.Texture2D),
models: Asset_Cache(rl.Model), models: Asset_Cache(rl.Model),
shaders: Asset_Cache(Loaded_Shader),
curves_1d: Asset_Cache([]f32), curves_1d: Asset_Cache([]f32),
curves_2d: Asset_Cache(Curve_2D), curves_2d: Asset_Cache(Curve_2D),
bvhs: map[cstring]Loaded_BVH, bvhs: map[cstring]Loaded_BVH,
@ -59,8 +60,12 @@ Asset_Cache_Entry :: struct($E: typeid) {
modtime: i64, modtime: i64,
} }
Asset_Cache_Loader_Payload :: union {
cstring,
}
Asset_Cache_Loader :: struct($E: typeid) { 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), unload: proc(value: E),
} }
@ -69,8 +74,37 @@ Asset_Cache :: struct($E: typeid) {
loader: Asset_Cache_Loader(E), 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) { 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) model := rl.LoadModel(path)
return model, rl.IsModelValid(model) return model, rl.IsModelValid(model)
}, },
@ -80,7 +114,7 @@ MODEL_LOADER :: Asset_Cache_Loader(rl.Model) {
} }
TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) { 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) texture := rl.LoadTexture(path)
return texture, rl.IsTextureValid(texture) return texture, rl.IsTextureValid(texture)
}, },
@ -90,7 +124,7 @@ TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) {
} }
CURVE_1D_CSV_LOADER :: Asset_Cache_Loader([]f32) { 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) data, err := physfs.read_entire_file(string(path), context.temp_allocator)
if err != nil { if err != nil {
log.errorf("Failed to read curve: %s, %v", path, err) 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) { 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) data, err := physfs.read_entire_file(string(path), context.temp_allocator)
if err != nil { if err != nil {
log.errorf("Failed to read curve: %s, %v", path, err) log.errorf("Failed to read curve: %s, %v", path, err)
@ -135,6 +169,9 @@ assetman_init :: proc(assetman: ^Asset_Manager) {
assetman.models = { assetman.models = {
loader = MODEL_LOADER, loader = MODEL_LOADER,
} }
assetman.shaders = {
loader = SHADER_LOADER,
}
assetman.textures = { assetman.textures = {
loader = TEXTURE_LOADER, loader = TEXTURE_LOADER,
} }
@ -156,6 +193,7 @@ Asset_Cache_Result :: enum {
assetcache_fetch_or_load :: proc( assetcache_fetch_or_load :: proc(
ac: ^$T/Asset_Cache($E), ac: ^$T/Asset_Cache($E),
path: cstring, path: cstring,
payload: Asset_Cache_Loader_Payload = nil,
) -> ( ) -> (
value: E, value: E,
modtime: i64, modtime: i64,
@ -174,7 +212,7 @@ assetcache_fetch_or_load :: proc(
} else { } else {
// Try to load the new version // Try to load the new version
new_value, ok := ac.loader.load(path) new_value, ok := ac.loader.load(path, payload)
if ok { if ok {
result = .Reloaded result = .Reloaded
@ -197,7 +235,7 @@ assetcache_fetch_or_load :: proc(
} else { } else {
modtime = physfs.getLastModTime(path) modtime = physfs.getLastModTime(path)
ok: bool ok: bool
value, ok = ac.loader.load(path) value, ok = ac.loader.load(path, payload)
if ok { if ok {
ac.cache[path] = { ac.cache[path] = {
@ -256,6 +294,29 @@ get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model {
return 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 null_bvhs: []bvh.BVH
get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_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.textures)
assetcache_destroy(&assetman.models) assetcache_destroy(&assetman.models)
assetcache_destroy(&assetman.shaders)
assetcache_destroy(&assetman.curves_1d) assetcache_destroy(&assetman.curves_1d)
assetcache_destroy(&assetman.curves_2d) assetcache_destroy(&assetman.curves_2d)

View File

@ -26,6 +26,7 @@ import "game:halfedge"
import "game:physics" import "game:physics"
import "game:physics/bvh" import "game:physics/bvh"
import "game:physics/collision" import "game:physics/collision"
import "game:render"
import "libs:tracy" import "libs:tracy"
import "ui" import "ui"
import rl "vendor:raylib" import rl "vendor:raylib"
@ -777,7 +778,9 @@ draw :: proc() {
if !g_mem.editor { if !g_mem.editor {
car_matrix := rl.QuaternionToMatrix(car_body.q) 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.pause {
if runtime_world.rewind_simulation { 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) { game_hot_reloaded :: proc(mem: rawptr) {
g_mem = (^Game_Memory)(mem) g_mem = (^Game_Memory)(mem)
render.init()
g_mem.runtime_world.orbit_camera.distance = 4 g_mem.runtime_world.orbit_camera.distance = 4
} }

34
game/render/render.odin Normal file
View File

@ -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)
}