moved fp camera code to extras, then added a directional light to the renderer

This commit is contained in:
Peter Li 2025-04-26 19:32:55 -07:00
parent 19e4bf629a
commit ccab1182e1
20 changed files with 268 additions and 134 deletions

View File

@ -99,7 +99,7 @@ pub const AddProgramOptions = struct {
imports: []const Build.Module.Import = &.{},
};
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Step.Compile {
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Module {
const b = self.b;
const exe = self.nw_builder.addExecutable(.{
@ -199,7 +199,7 @@ pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Step.C
b.getInstallStep().dependOn(self.nw_builder.getInstallStep());
return exe;
return mod;
}
pub fn createGameOptions(b: *std.Build) *std.Build.Step.Options {
@ -235,6 +235,11 @@ pub fn createGameOptions(b: *std.Build) *std.Build.Step.Options {
return opts;
}
pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void {
const dep = self.b.dependency(moduleName, .{ .target = self.target, .optimize = self.optimize });
mod.addImport(moduleName, dep.module(moduleName));
}
// ========= standalone build instance =======
// maybe it should be an engine launcher or something..
pub fn build(b: *std.Build) void {

View File

@ -353,18 +353,6 @@ pub const Entity = struct {
}
};
pub const EcsComponentInterface = p2.MakeInterface("EcsComponentInterfaceVTable", struct {
container: ?EcsContainerRef = null,
pub fn Implement(comptime T: type) @This() {
_ = T;
const Impl = struct {};
_ = Impl;
return .{};
}
});
pub const EcsContainerInterface = p2.EcsContainerInterface;
pub const EcsContainerRef = p2.Reference(EcsContainerInterface);
pub fn makeEcsContainerRef(ptr: anytype) EcsContainerRef {

View File

@ -575,8 +575,16 @@ pub const Rotation = struct {
};
}
pub fn forward(self: *@This()) Vectorf {
return self.rotateVector(.{ .z = 1 });
pub fn up(self: @This()) Vectorf {
return self.rotateVector(Vectorf.Up);
}
pub fn forward(self: @This()) Vectorf {
return self.rotateVector(Vectorf.Forward);
}
pub fn right(self: @This()) Vectorf {
return self.rotateVector(Vectorf.Right);
}
pub fn rotateVector(self: @This(), other: Vectorf) Vectorf {

View File

@ -3,6 +3,7 @@ pub const Impl = struct {
device: *gpu.GPUDevice = undefined,
drawData: [*c]ig.DrawData = undefined,
rendererDebug: bool = true,
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
@ -70,9 +71,14 @@ pub const Impl = struct {
}
}
pub fn preTick(_: *@This(), _: f64) core.EngineDataEventError!void {
pub fn preTick(self: *@This(), _: f64) core.EngineDataEventError!void {
c.Imgui_SDL3_NewFrame();
c.igNewFrame();
if (ig.begin("renderer debug", &self.rendererDebug, .{})) {
_ = ig.sliderFloat("directional light yaw", &rend.context().directionalLightYaw, 0, 360, null, .{});
ig.end();
}
}
// renderer plugin

View File

@ -5,6 +5,8 @@ cbuffer Uniforms : register(b0, space3)
{
float4 viewPos;
float4 lightPosition;
float4 directionalLight;
float4 directionalLightColor;
float time;
};
@ -66,6 +68,23 @@ float3 BlinnPhong(float3 normal, float3 fragPos, float3 lightPos, float3 lightCo
}
float3 DirectionalLight(float3 normal, float3 fragPos)
{
float3 lightDir = directionalLight.xyz;
float diff = max(dot(lightDir, normal), 0.0);
float3 diffuse = diff * directionalLightColor.xyz;
// specular parameter
float3 viewDir = normalize(fragPos - viewPos.xyz);
float3 reflectDir = reflect(-lightDir, normal);
float spec = 0.0;
float3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0), 30.0);
float3 specular = spec * directionalLightColor.xyz * 2;
return diffuse;
}
float4 main(
float2 UV : TEXCOORD0,
float3 WorldPos: TEXCOORD1,
@ -83,11 +102,11 @@ float4 main(
float3 lightPos = lightPosition.xyz; //+ float3(0, 20, 0);
float3 lightColor = float3(0.8, 0.8, 0.5);
float3 ambient = lerp(float3(0.01, 0.01, 0.003), float3(0.004, 0.004, 0.06), dot(Normal, float3(0.0,1.0,0.0)) ) * 0.001;
float3 ambient = lerp(float3(0.01, 0.01, 0.003), float3(0.004, 0.004, 0.06), dot(Normal, float3(0.0,1.0,0.0)) ) * 5;
float3 col = s.xyz;
float3 c2 = col * ambient + col * BlinnPhong(Normal, WorldPos, lightPos, lightColor);
float3 c2 = col * ambient + col * BlinnPhong(Normal, WorldPos, lightPos, lightColor) + col * DirectionalLight(Normal, WorldPos);
float4 rv = float4(pow(c2, 1.0 / 2.2), alpha);

View File

@ -19,10 +19,20 @@
"type" : "vec4",
"offset" : 16
},
{
"name" : "directionalLight",
"type" : "vec4",
"offset" : 32
},
{
"name" : "directionalLightColor",
"type" : "vec4",
"offset" : 48
},
{
"name" : "time",
"type" : "float",
"offset" : 32
"offset" : 64
}
]
}
@ -71,7 +81,7 @@
{
"type" : "_11",
"name" : "type.Uniforms",
"block_size" : 36,
"block_size" : 68,
"set" : 3,
"binding" : 0
}

View File

@ -47,6 +47,10 @@ pub const Renderer = struct {
debugDrawSys: *DebugDrawSystem = undefined,
directionalLightYaw: f32 = 0.0,
directionalLightColor: core.colors.Color = .{ .r = 0.8, .g = 0.8, .b = 0.9 },
directionalLightDir: core.Vectorf = core.Vectorf.new(0.5, 0.25, -1).normalize(),
// transients DO NOT TOUCH
swapchainTexture: ?*gpu.GPUTexture = undefined,
@ -83,6 +87,7 @@ pub const Renderer = struct {
try self.discoverFormats();
core.engine_log("sgpu device created, using shader format: {s}", .{self.shaderSuffix});
core.engine_log("using renderer... scientist", .{});
try self.createMeshPipeline();
try self.createBuffers();
@ -366,9 +371,12 @@ pub const Renderer = struct {
if (self.activeCamera) |cam| {
position = cam.finalPos;
}
const resolved = core.Rotation.eulerY(core.radians(self.directionalLightYaw)).rotateVector(self.directionalLightDir);
ptr.viewPos = @bitCast(position.toZm());
ptr.lightPosition = @bitCast(self.lightPosition.toZm());
ptr.directionalLight = @bitCast(resolved.toZm());
ptr.directionalLightColor = @bitCast(self.directionalLightColor);
cmd.pushGPUFragmentUniformData(0, &data, @sizeOf(lit_mesh_frag.Uniforms));
}
}

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mod = b.addModule("gameExtras", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/gameExtras.zig"),
});
const dep = b.dependency("Backlog", .{ .target = target, .optimize = optimize });
mod.addImport("Backlog", dep.module("Backlog"));
}

View File

@ -0,0 +1,11 @@
.{
.name = .gameExtras,
.version = "0.0.0",
.dependencies = .{
.Backlog = .{ .path = "../../" },
},
.paths = .{
"",
},
.fingerprint = 0x993cf5f438b86d4a,
}

View File

@ -0,0 +1,5 @@
# extras
These are sample implementations of various common functionality that's meant to be in your game not really part of the engine.
To get access to these extras

View File

@ -0,0 +1,98 @@
// generic first person camera,
//
//
allocator: std.mem.Allocator,
camera: core.Entity = undefined,
cameraComponent: *rend.CameraComponent = undefined,
// mouse sensitivty, measured in degrees per pixel
sensitivity: f32 = 5.0,
yaw: f32 = 0.0,
pitch: f32 = 0.0,
mouseMovement: core.Vector2f = .{},
mouseLookEnabled: bool = true,
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
self.camera = try core.createEntity();
_ = self.camera.addComponent(core.Scene).?;
self.cameraComponent = self.camera.addComponent(rend.CameraComponent).?;
core.engine_logs("[GameExtras] FpCamera created");
return self;
}
pub fn activateCamera(self: *@This()) void {
rend.setActiveCamera(self.cameraComponent);
}
pub fn resolve(self: *@This()) void {
self.cameraComponent.resolve();
}
pub fn getForward(self: *@This()) core.Vectorf {
return self.cameraComponent.forward();
}
pub fn getForwardXZ(self: *@This()) core.Vectorf {
const rotation = core.Rotation.eulerY(core.radians(self.yaw));
return rotation.rotateVector(core.Vectorf.Forward);
}
pub fn getRightXZ(self: *@This()) core.Vectorf {
const rotation = core.Rotation.eulerY(core.radians(self.yaw));
return rotation.rotateVector(core.Vectorf.Right);
}
pub fn setPosition(self: *@This(), position: core.Vectorf) void {
if (self.camera.fetch(core.Scene)) |scene| {
scene.setPosition(position);
}
}
pub fn getPosition(self: *@This()) core.Vectorf {
return self.camera.fetch(core.Scene).?.getPosition();
}
// movement value can be sourced from a mouse binding that has enableMouseRelative() on
pub fn addMouseMovement(self: *@This(), movement: core.Vector2f) void {
if (self.mouseLookEnabled) {
self.mouseMovement = self.mouseMovement.add(movement);
}
}
pub fn tick(self: *@This(), dt: f64) void {
_ = dt;
const mouseMovement = self.mouseMovement;
self.mouseMovement = .{};
self.yaw += core.radians(mouseMovement.x * self.sensitivity);
self.pitch -= core.radians(mouseMovement.y * self.sensitivity);
if (self.camera.fetch(core.Scene)) |scene| {
const posRot = scene.getPosRot();
self.pitch = std.math.clamp(self.pitch, -90.0, 90.0);
self.cameraComponent.pitch = core.radians(self.pitch);
posRot.rotation = core.Rotation.eulerY(core.radians(self.yaw));
}
}
pub fn destroy(self: *@This()) void {
core.engine_logs("[GameExtras] FpCamera destroyed");
self.allocator.destroy(self);
}
const std = @import("std");
const backlog = @import("Backlog");
const core = backlog.core;
const rend = backlog.rend;

View File

@ -0,0 +1 @@
pub const FpCamera = @import("FpCamera.zig");

0
extras/readme.md Normal file
View File

View File

@ -11,9 +11,11 @@ pub fn build(b: *std.Build) void {
.backlogRoot = "../",
});
_ = blbuild.addProgram(.{
const sampleGame = blbuild.addProgram(.{
.name = "sampleGame",
.desc = "sdl3 project sample",
.root_source_file = b.path("sampleGame/main.zig"),
});
blbuild.addExtraModule(sampleGame, "gameExtras");
}

View File

@ -14,6 +14,7 @@
// engine libraries
.Backlog = .{ .path = "../" },
.SpirvReflect = .{ .path = "../lib/spirv-reflect-zig" },
.gameExtras = .{.path = "../extras/gameExtras"}
},
.paths = .{
"",

View File

@ -7,6 +7,8 @@ struct type_Uniforms
{
float4 viewPos;
float4 lightPosition;
float4 directionalLight;
float4 directionalLightColor;
float time;
};
@ -25,48 +27,48 @@ struct main0_in
fragment main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], texture2d<float> Texture [[texture(0)]], sampler Sampler [[sampler(0)]])
{
main0_out out = {};
float4 _59 = Texture.sample(Sampler, in.in_var_TEXCOORD0);
float _60 = _59.w;
if (_60 < 0.00999999977648258209228515625)
float4 _61 = Texture.sample(Sampler, in.in_var_TEXCOORD0);
float _62 = _61.w;
if (_62 < 0.00999999977648258209228515625)
{
discard_fragment();
}
float3 _117;
float3 _119;
do
{
float3 _74 = Uniforms.lightPosition.xyz - in.in_var_TEXCOORD1;
float _75 = length(_74);
float _76 = _75 * _75;
float _82;
if (_75 > 2.0)
float3 _76 = Uniforms.lightPosition.xyz - in.in_var_TEXCOORD1;
float _77 = length(_76);
float _78 = _77 * _77;
float _84;
if (_77 > 2.0)
{
_82 = 0.5 / _76;
_84 = 0.5 / _78;
}
else
{
_82 = 1.0 / _76;
_84 = 1.0 / _78;
}
float _87;
if (_75 > 4.0)
float _89;
if (_77 > 4.0)
{
_87 = _82 * 0.5;
_89 = _84 * 0.5;
}
else
{
_87 = _82;
_89 = _84;
}
float _89 = (_75 > 15.0) ? 0.0 : _87;
float _91 = (_89 > 1.0) ? 1.0 : _89;
float _91 = (_77 > 15.0) ? 0.0 : _89;
float _93 = (_91 > 1.0) ? 1.0 : _91;
if (length(in.in_var_TEXCOORD2) < 0.100000001490116119384765625)
{
_117 = (in.in_var_TEXCOORD1 * float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5)) * _91;
_119 = (in.in_var_TEXCOORD1 * float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5)) * _93;
break;
}
float3 _98 = fast::normalize(_74);
_117 = ((float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5) * precise::max(dot(_98, in.in_var_TEXCOORD2), 0.0)) * _91) + (((float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5) * powr(precise::max(dot(in.in_var_TEXCOORD2, fast::normalize(_98 + fast::normalize(in.in_var_TEXCOORD1 - Uniforms.viewPos.xyz))), 0.0), 30.0)) * 2.0) * _91);
float3 _100 = fast::normalize(_76);
_119 = ((float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5) * precise::max(dot(_100, in.in_var_TEXCOORD2), 0.0)) * _93) + (((float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5) * powr(precise::max(dot(in.in_var_TEXCOORD2, fast::normalize(_100 + fast::normalize(in.in_var_TEXCOORD1 - Uniforms.viewPos.xyz))), 0.0), 30.0)) * 2.0) * _93);
break;
} while(false);
out.out_var_SV_Target0 = float4(powr(_59.xyz * ((mix(float3(0.00999999977648258209228515625, 0.00999999977648258209228515625, 0.0030000000260770320892333984375), float3(0.0040000001899898052215576171875, 0.0040000001899898052215576171875, 0.0599999986588954925537109375), float3(in.in_var_TEXCOORD2.y)) * 0.001000000047497451305389404296875) + _117), float3(0.4545454680919647216796875)), _60);
out.out_var_SV_Target0 = float4(powr(_61.xyz * (((mix(float3(0.00999999977648258209228515625, 0.00999999977648258209228515625, 0.0030000000260770320892333984375), float3(0.0040000001899898052215576171875, 0.0040000001899898052215576171875, 0.0599999986588954925537109375), float3(in.in_var_TEXCOORD2.y)) * 5.0) + _119) + (Uniforms.directionalLightColor.xyz * precise::max(dot(Uniforms.directionalLight.xyz, in.in_var_TEXCOORD2), 0.0))), float3(0.4545454680919647216796875)), _62);
return out;
}

View File

@ -1,32 +0,0 @@
pub const shaderTypes = @import("shaderTypes");
pub const int = shaderTypes.int;
pub const uint = shaderTypes.uint;
pub const vec2 = shaderTypes.vec2;
pub const u8vec4 = shaderTypes.u8vec4;
pub const vec3 = shaderTypes.vec3;
pub const vec4 = shaderTypes.vec4;
pub const mat4 = shaderTypes.mat4;
pub const float = shaderTypes.float;
pub const BufferInfo = shaderTypes.BufferInfo;
pub const Scene = struct {
Model: mat4,
pub const Buffer: BufferInfo = .{ .storage = 0 };
};
pub const Uniforms = struct {
ViewProjection: mat4,
time: float,
pub const Buffer: BufferInfo = .{ .uniform = 0 };
};
pub const LoadArgs = shaderTypes.ShaderLoadArgs{
.num_samplers = 0, // The number of samplers defined in the shader.
.num_storage_textures = 0, // The number of storage textures defined in the shader.
.num_storage_buffers = 1, // The number of storage buffers defined in the shader.
.num_uniform_buffers = 1, // The number of uniform buffers defined in the shader.
};

View File

@ -1,26 +1,19 @@
allocator: std.mem.Allocator,
camera: core.Entity = undefined,
cameraComponent: *rend.CameraComponent = undefined,
cameraSpeed: f32 = 10.0,
moveInput: *core.Axis2dBinding = undefined,
cameraSpeed: f32 = 10.0,
moveVector: core.Vectorf = .{},
mouseMove: core.Vector2f = .{},
rotateAxis: f32 = 0.0,
rotateAxisPitch: f32 = 0.0,
sensitivity: f32 = 5.0,
verticalMove: f32 = 0.0,
centerDist: f32 = 2.0,
centerDistMove: f32 = 2.0,
yaw: f32 = 0.0,
pitch: f32 = 0.0,
mouseLook: bool = true,
showWindow: bool = true,
fpcamera: *FpCamera = undefined,
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
@ -83,10 +76,9 @@ pub fn prepare(self: *@This()) !void {
inp.activate();
}
self.camera = try core.createEntity();
const cameraScene = self.camera.addComponent(core.Scene).?;
cameraScene.setPosition(.{ .z = -15 });
self.cameraComponent = self.camera.addComponent(rend.CameraComponent).?;
self.fpcamera = try FpCamera.create(self.allocator);
self.fpcamera.setPosition(.{ .z = -15, .y = 20 });
self.fpcamera.activateCamera();
{
const lostEmpire = try core.createEntity();
@ -98,13 +90,13 @@ pub fn prepare(self: *@This()) !void {
}
{
const fox = try core.createEntity();
const scene = fox.addComponent(core.Scene).?;
_ = scene;
// scene.setScale();
const mesh = fox.addComponent(rend.MeshComponent).?;
mesh.setMesh("m_fox");
mesh.setTexture("t_fox");
// const fox = try core.createEntity();
// const scene = fox.addComponent(core.Scene).?;
// _ = scene;
// // scene.setScale();
// const mesh = fox.addComponent(rend.MeshComponent).?;
// mesh.setMesh("m_fox");
// mesh.setTexture("t_fox");
}
{
@ -117,17 +109,15 @@ pub fn prepare(self: *@This()) !void {
mesh.setTexture("t_default");
}
rend.setActiveCamera(self.cameraComponent);
const moveInput = try core.Axis2dBinding.create(core.MakeName("movement"));
self.moveInput = try core.Axis2dBinding.create(core.MakeName("movement"));
moveInput.addKey(.w, 1.0, .y);
moveInput.addKey(.s, -1.0, .y);
moveInput.addKey(.d, 1.0, .x);
moveInput.addKey(.a, -1.0, .x);
self.moveInput.addKey(.w, 1.0, .y);
self.moveInput.addKey(.s, -1.0, .y);
self.moveInput.addKey(.d, 1.0, .x);
self.moveInput.addKey(.a, -1.0, .x);
_ = self.moveInput.data.addListener(self, onMove);
self.moveInput.activate();
_ = moveInput.data.addListener(self, onMove);
moveInput.activate();
{
const i = try core.Axis1dBinding.create(core.MakeName("rotPitch"));
@ -173,8 +163,7 @@ pub fn prepare(self: *@This()) !void {
fn onMouseLook(ctx: ?*anyopaque, axis: core.Vector2f) void {
const self: *@This() = @ptrCast(@alignCast(ctx));
if (self.mouseLook)
self.mouseMove = axis;
self.fpcamera.addMouseMovement(axis);
}
fn toggleMouseLook(ctx: ?*anyopaque, action: core.ActionEvent) void {
@ -182,13 +171,16 @@ fn toggleMouseLook(ctx: ?*anyopaque, action: core.ActionEvent) void {
_ = action;
self.mouseLook = !self.mouseLook;
if (!self.mouseLook)
self.showWindow = true;
self.updateMouseLook();
}
fn updateMouseLook(self: *@This()) void {
self.fpcamera.mouseLookEnabled = self.mouseLook;
platform.setMouseRelativeMode(self.mouseLook);
platform.setImguiVisible(!self.mouseLook);
// platform.setImguiVisible(!self.mouseLook);
}
fn onShaderReload(ctx: ?*anyopaque, action: core.ActionEvent) void {
@ -247,42 +239,35 @@ pub fn getMouseMovement(self: *@This()) core.Vector2f {
pub fn tick(self: *@This(), dt: f64) void {
const fdt: f32 = @floatCast(dt);
if (self.camera.fetch(core.Scene)) |scene| {
const vector = core.Vectorf{ .x = self.moveVector.x, .z = self.moveVector.z, .y = self.verticalMove };
const posRot = scene.getPosRot();
self.fpcamera.tick(dt);
self.yaw += self.rotateAxis * fdt * 20;
self.pitch += self.rotateAxisPitch * fdt * 20;
var movement = self.fpcamera.getForwardXZ().fmul(self.moveVector.z * fdt * self.cameraSpeed);
movement = movement.add(self.fpcamera.getRightXZ().fmul(self.moveVector.x * fdt * self.cameraSpeed));
movement = movement.add(.{ .y = self.verticalMove * fdt * self.cameraSpeed });
const pos = self.fpcamera.getPosition().add(movement);
self.fpcamera.setPosition(pos);
self.centerDist += self.centerDistMove * fdt;
const mouseMovement = self.getMouseMovement();
self.fpcamera.resolve();
self.yaw += core.radians(mouseMovement.x * self.sensitivity);
self.pitch -= core.radians(mouseMovement.y * self.sensitivity);
const forward = self.fpcamera.getForward();
const debugCenter = self.fpcamera.getPosition().add(forward.fmul(self.centerDist));
self.pitch = std.math.clamp(self.pitch, -90.0, 90.0);
self.cameraComponent.pitch = core.radians(self.pitch);
posRot.rotation = core.Rotation.eulerY(core.radians(self.yaw));
const movement = posRot.rotation.rotateVector(vector.fmul(self.cameraSpeed * fdt));
core.debugSphere(debugCenter, 0.3, .{});
core.debugSphere(debugCenter, 0.1, .{ .color = .{ .x = 1.0 } });
posRot.position = posRot.position.add(movement);
rend.context().lightPosition = debugCenter;
self.cameraComponent.resolve();
self.centerDist += self.centerDistMove * fdt;
const forward = self.cameraComponent.forward(); //posRot.rotation.forward();
const debugCenter = posRot.position.add(forward.fmul(self.centerDist));
// show a window with the current camera's position
core.debugSphere(debugCenter, 0.3, .{});
core.debugSphere(debugCenter, 0.1, .{ .color = .{ .x = 1.0 } });
rend.context().lightPosition = debugCenter;
if (ig.begin("meh", &self.showWindow, .{})) {
ig.textFmt("x:{d:.03} y:{d:.03} z:{d:.03}", .{ pos.x, pos.y, pos.z }) catch return;
ig.end();
}
var show: bool = true;
ig.showDemoWindow(&show);
}
pub fn deinit(self: *@This()) void {
self.fpcamera.destroy();
self.allocator.destroy(self);
}
@ -296,6 +281,7 @@ pub fn main() anyerror!void {
});
}
const FpCamera = @import("gameExtras").FpCamera;
const std = @import("std");
const backlog = @import("Backlog");
const assets = backlog.assets;