Fully implemented lighting
Two types of light sources are currently supported: directional lights and point lights. A scene can have only one directional light and up to 1024 point lights.
This commit is contained in:
parent
214317e0bf
commit
503ed33aec
7 changed files with 181 additions and 48 deletions
|
|
@ -1,39 +1,80 @@
|
|||
#version 450
|
||||
|
||||
#define MAX_POINT_LIGHTS 1024
|
||||
|
||||
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(location = 2) in vec3 Normal;
|
||||
layout(location = 3) in vec3 FragPos;
|
||||
layout(location = 4) in vec2 TexCoords;
|
||||
|
||||
layout (binding = 2) uniform LightUniform {
|
||||
vec3 pos;
|
||||
} lightPos;
|
||||
layout (binding = 2) uniform DirectionalLight {
|
||||
vec3 direction;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
} directional_light;
|
||||
layout (binding = 5) uniform PointLight {
|
||||
vec3 position;
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
vec3 data;
|
||||
|
||||
} point_lights[MAX_POINT_LIGHTS];
|
||||
|
||||
layout (binding = 3) uniform ViewUniform {
|
||||
vec3 pos;
|
||||
} viewPos;
|
||||
|
||||
layout(push_constant) uniform pc {
|
||||
int light_count;
|
||||
} pushConstants;
|
||||
|
||||
layout (set = 1, binding = 0) uniform sampler2D textureSampler;
|
||||
layout (set = 1, binding = 1) uniform sampler2D diffuseSampler;
|
||||
|
||||
void main() {
|
||||
vec3 lightDiffuse = vec3(0.5, 0.5, 0.5);
|
||||
vec3 lightAmbient = vec3(0.2, 0.2, 0.2);
|
||||
vec3 lightSpecular = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 lightDir = normalize(lightPos.pos - FragPos);
|
||||
float diff = max(dot(norm, lightDir), 0.0);
|
||||
vec3 diffuse = lightDiffuse * diff * vec3(texture(textureSampler, TexCoords));
|
||||
vec3 ambient = lightAmbient * vec3(texture(textureSampler, TexCoords));
|
||||
|
||||
vec3 viewDir = normalize(viewPos.pos - FragPos);
|
||||
vec3 reflectDir = reflect(-lightDir, norm);
|
||||
vec3 calc_directional_light(vec3 normal, vec3 viewDir) {
|
||||
vec3 lightDir = normalize(-directional_light.direction);
|
||||
float diff = max(dot(normal, lightDir), 0.0);
|
||||
vec3 reflectDir = reflect(-lightDir, normal);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 2);
|
||||
vec3 specular = lightSpecular * spec * vec3(texture(diffuseSampler, TexCoords));
|
||||
//vec3 specular = lightSpecular * spec * vec3(1.0, 1.0, 1.0);
|
||||
|
||||
vec3 result = (ambient + diffuse + specular);
|
||||
outColor = vec4(result, 1.0);
|
||||
vec3 ambient = directional_light.ambient * vec3(texture(textureSampler, TexCoords));
|
||||
vec3 diffuse = directional_light.diffuse * diff * vec3(texture(textureSampler , TexCoords));
|
||||
vec3 specular = directional_light.specular * spec * vec3(texture(diffuseSampler, TexCoords));
|
||||
return (ambient + diffuse + specular);
|
||||
}
|
||||
|
||||
vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir) {
|
||||
float constant = point_lights[index].data[0];
|
||||
float linear = point_lights[index].data[1];
|
||||
float quadratic = point_lights[index].data[2];
|
||||
|
||||
vec3 lightDir = normalize(point_lights[index].position - fragPos);
|
||||
float diff = max(dot(normal, lightDir), 0.0);
|
||||
vec3 reflectDir = reflect(-lightDir, normal);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 2);
|
||||
float distance = length(point_lights[index].position - fragPos);
|
||||
float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
|
||||
vec3 ambient = point_lights[index].ambient * vec3(texture(textureSampler, TexCoords));
|
||||
vec3 diffuse = point_lights[index].diffuse * diff * vec3(texture(textureSampler, TexCoords));
|
||||
vec3 specular = point_lights[index].specular * spec * vec3(texture(diffuseSampler, TexCoords));
|
||||
ambient *= attenuation;
|
||||
diffuse *= attenuation;
|
||||
specular *= attenuation;
|
||||
return (ambient + diffuse + specular);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 viewDir = normalize(viewPos.pos - FragPos);
|
||||
|
||||
vec3 result = calc_directional_light(norm, viewDir);
|
||||
for(int i = 0; i < pushConstants.light_count; i++)
|
||||
result += calc_point_light(i, norm, FragPos, viewDir);
|
||||
|
||||
outColor = vec4(result, 1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,6 +314,11 @@ pub fn initShader(self: Self, comptime name: []const u8) !c.VkShaderModule {
|
|||
return shader_module;
|
||||
}
|
||||
|
||||
pub fn pushConstant(self: Self, pipeline: vk.GraphicsPipeline, stage: u32, offset: u32, size: u32, data: [*c]u8, frame: usize) void {
|
||||
_ = stage;
|
||||
c.vkCmdPushConstants(self.command_buffers[frame], pipeline.layout, c.VK_SHADER_STAGE_FRAGMENT_BIT, offset, size, data);
|
||||
}
|
||||
|
||||
pub fn deinitShader(self: Self, shader: c.VkShaderModule) void {
|
||||
c.vkDestroyShaderModule(self.handle, shader, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ const c = vk.c;
|
|||
const math = @import("math");
|
||||
const gltf = @import("gltf.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const rendering = @import("rendering.zig");
|
||||
const lights = rendering.lights;
|
||||
|
||||
const max_point_lights = 1024;
|
||||
|
||||
layout: c.VkPipelineLayout,
|
||||
handle: c.VkPipeline,
|
||||
|
|
@ -17,7 +21,6 @@ descriptor_pool: c.VkDescriptorPool,
|
|||
descriptor_set: c.VkDescriptorSet,
|
||||
descriptor_set_layout: c.VkDescriptorSetLayout,
|
||||
projection_buffer: vk.Buffer,
|
||||
light_buffer: vk.Buffer,
|
||||
view_buffer: vk.Buffer,
|
||||
view_memory: [*c]u8,
|
||||
transform_memory: [*c]u8,
|
||||
|
|
@ -25,7 +28,8 @@ view_pos_memory: [*c]u8,
|
|||
texture_sampler: vk.Sampler,
|
||||
diffuse_sampler: vk.Sampler,
|
||||
textures: std.ArrayList(c.VkDescriptorSet),
|
||||
light_pos: [*]f32,
|
||||
directional_light: *lights.DirectionalLight,
|
||||
point_lights: []lights.PointLight,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
|
@ -304,13 +308,20 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
.stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT,
|
||||
};
|
||||
|
||||
const light_binding = c.VkDescriptorSetLayoutBinding{
|
||||
const directional_light_binding = c.VkDescriptorSetLayoutBinding{
|
||||
.binding = 2,
|
||||
.descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
};
|
||||
|
||||
const point_lights_binding = c.VkDescriptorSetLayoutBinding{
|
||||
.binding = 5,
|
||||
.descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.descriptorCount = max_point_lights,
|
||||
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
};
|
||||
|
||||
const view_pos_binding = c.VkDescriptorSetLayoutBinding{
|
||||
.binding = 3,
|
||||
.descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
|
|
@ -332,7 +343,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
};
|
||||
|
||||
const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, transform_binding, light_binding, view_pos_binding};
|
||||
const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, transform_binding, directional_light_binding, point_lights_binding, view_pos_binding};
|
||||
const texture_bindings = [_]c.VkDescriptorSetLayoutBinding{texture_sampler_binding, diffuse_sampler_binding};
|
||||
|
||||
var descriptor_set_layout: c.VkDescriptorSetLayout = undefined;
|
||||
|
|
@ -340,7 +351,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
|
||||
const descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{
|
||||
.sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = 5,
|
||||
.bindingCount = 6,
|
||||
.pBindings = bindings[0..].ptr,
|
||||
};
|
||||
|
||||
|
|
@ -355,12 +366,18 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
|
||||
var set_layouts = [_]c.VkDescriptorSetLayout{descriptor_set_layout, texture_descriptor_set_layout};
|
||||
|
||||
const range: c.VkPushConstantRange = .{
|
||||
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.offset = 0,
|
||||
.size = 4,
|
||||
};
|
||||
|
||||
const layout_info: c.VkPipelineLayoutCreateInfo = .{
|
||||
.sType = c.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = 2,
|
||||
.pSetLayouts = set_layouts[0..].ptr,
|
||||
.pushConstantRangeCount = 0,
|
||||
.pPushConstantRanges = null,
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &range,
|
||||
};
|
||||
|
||||
var layout: c.VkPipelineLayout = undefined;
|
||||
|
|
@ -392,7 +409,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
|
||||
const size = c.VkDescriptorPoolSize{
|
||||
.type = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.descriptorCount = 5,
|
||||
.descriptorCount = 5 + max_point_lights,
|
||||
};
|
||||
|
||||
const sampler_size = c.VkDescriptorPoolSize{
|
||||
|
|
@ -519,38 +536,72 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
|
||||
c.vkUpdateDescriptorSets(device.handle, 1, &write_transform_descriptor_set, 0, null);
|
||||
|
||||
const light_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf([3]f32));
|
||||
const directional_light_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(lights.DirectionalLight));
|
||||
|
||||
var light_data: [*c]u8 = undefined;
|
||||
var directional_light_data: [*c]u8 = undefined;
|
||||
|
||||
try vk.mapError(c.vkMapMemory(
|
||||
device.handle,
|
||||
light_buffer.memory,
|
||||
directional_light_buffer.memory,
|
||||
0,
|
||||
light_buffer.size,
|
||||
directional_light_buffer.size,
|
||||
0,
|
||||
@ptrCast(&light_data),
|
||||
@ptrCast(&directional_light_data),
|
||||
));
|
||||
|
||||
const light_pos: [*]f32 = @alignCast(@ptrCast(light_data));
|
||||
const directional_light: *lights.DirectionalLight = @alignCast(@ptrCast(directional_light_data));
|
||||
|
||||
const light_descriptor_buffer_info = c.VkDescriptorBufferInfo{
|
||||
.buffer = light_buffer.handle,
|
||||
const directional_light_descriptor_buffer_info = c.VkDescriptorBufferInfo{
|
||||
.buffer = directional_light_buffer.handle,
|
||||
.offset = 0,
|
||||
.range = light_buffer.size,
|
||||
.range = directional_light_buffer.size,
|
||||
};
|
||||
|
||||
const write_light_descriptor_set = c.VkWriteDescriptorSet{
|
||||
const write_directional_light_descriptor_set = c.VkWriteDescriptorSet{
|
||||
.sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descriptor_set,
|
||||
.dstBinding = 2,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pBufferInfo = &light_descriptor_buffer_info,
|
||||
.pBufferInfo = &directional_light_descriptor_buffer_info,
|
||||
};
|
||||
|
||||
c.vkUpdateDescriptorSets(device.handle, 1, &write_light_descriptor_set, 0, null);
|
||||
c.vkUpdateDescriptorSets(device.handle, 1, &write_directional_light_descriptor_set, 0, null);
|
||||
|
||||
const point_lights_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(lights.PointLight) * max_point_lights);
|
||||
|
||||
var point_lights_data: [*c]u8 = undefined;
|
||||
|
||||
try vk.mapError(c.vkMapMemory(
|
||||
device.handle,
|
||||
point_lights_buffer.memory,
|
||||
0,
|
||||
point_lights_buffer.size,
|
||||
0,
|
||||
@ptrCast(&point_lights_data),
|
||||
));
|
||||
|
||||
const point_lights: []lights.PointLight = @as([*]lights.PointLight, @alignCast(@ptrCast(point_lights_data)))[0..max_point_lights];
|
||||
|
||||
var point_lights_descriptor_buffer_info: [max_point_lights]c.VkDescriptorBufferInfo = undefined;
|
||||
for (0..max_point_lights) |i| {
|
||||
point_lights_descriptor_buffer_info[i].buffer = point_lights_buffer.handle;
|
||||
point_lights_descriptor_buffer_info[i].offset = @sizeOf(lights.PointLight) * i;
|
||||
point_lights_descriptor_buffer_info[i].range = @sizeOf(lights.PointLight);
|
||||
}
|
||||
|
||||
const write_point_lights_descriptor_set = c.VkWriteDescriptorSet{
|
||||
.sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descriptor_set,
|
||||
.dstBinding = 5,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = max_point_lights,
|
||||
.descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pBufferInfo = @ptrCast(point_lights_descriptor_buffer_info[0..].ptr),
|
||||
};
|
||||
|
||||
c.vkUpdateDescriptorSets(device.handle, 1, &write_point_lights_descriptor_set, 0, null);
|
||||
|
||||
const view_pos_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf([3]f32));
|
||||
|
||||
|
|
@ -593,15 +644,15 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
|
|||
.projection_buffer = projection_buffer,
|
||||
.view_buffer = view_buffer,
|
||||
.view_memory = view_data,
|
||||
.light_buffer = light_buffer,
|
||||
.view_pos_memory = view_pos_data,
|
||||
.transform_memory = transform_data,
|
||||
.texture_sampler = try vk.Sampler.init(device),
|
||||
.diffuse_sampler = try vk.Sampler.init(device),
|
||||
.textures = std.ArrayList(c.VkDescriptorSet).init(allocator),
|
||||
.light_pos = light_pos,
|
||||
.vertex_buffer = vertex_buffer,
|
||||
.index_buffer = index_buffer,
|
||||
.directional_light = directional_light,
|
||||
.point_lights = point_lights,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ graphics_pipeline: vk.GraphicsPipeline,
|
|||
current_frame: u32,
|
||||
mesh: Mesh,
|
||||
transform: math.Transform,
|
||||
transform2: math.Transform,
|
||||
previous_time: std.time.Instant,
|
||||
|
||||
pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_handle: vk.c.VkSurfaceKHR) !Renderer {
|
||||
|
|
@ -52,9 +53,26 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand
|
|||
|
||||
_ = try graphics_pipeline.addTexture(device, texture, diffuse);
|
||||
|
||||
graphics_pipeline.light_pos[0] = -10.0;
|
||||
graphics_pipeline.light_pos[1] = 0.0;
|
||||
graphics_pipeline.light_pos[2] = 0.0;
|
||||
graphics_pipeline.directional_light.direction = .{-0.2, -1.0, -0.3};
|
||||
graphics_pipeline.directional_light.ambient = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.directional_light.diffuse = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.directional_light.specular = .{0.5, 0.5, 0.5};
|
||||
|
||||
graphics_pipeline.point_lights[0].position = .{0.0, 3.0, 3.0};
|
||||
graphics_pipeline.point_lights[0].data[0] = 1.0;
|
||||
graphics_pipeline.point_lights[0].data[1] = 0.0014;
|
||||
graphics_pipeline.point_lights[0].data[2] = 0.000007;
|
||||
graphics_pipeline.point_lights[0].ambient = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.point_lights[0].diffuse = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.point_lights[0].specular = .{1.0, 1.0, 1.0};
|
||||
|
||||
graphics_pipeline.point_lights[1].position = .{-1.0, 0.0, 0.0};
|
||||
graphics_pipeline.point_lights[1].data[0] = 1.0;
|
||||
graphics_pipeline.point_lights[1].data[1] = 0.09;
|
||||
graphics_pipeline.point_lights[1].data[2] = 0.032;
|
||||
graphics_pipeline.point_lights[1].ambient = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.point_lights[1].diffuse = .{0.5, 0.5, 0.5};
|
||||
graphics_pipeline.point_lights[1].specular = .{1.0, 1.0, 1.0};
|
||||
|
||||
return Renderer{
|
||||
.instance = instance,
|
||||
|
|
@ -66,6 +84,7 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand
|
|||
.graphics_pipeline = graphics_pipeline,
|
||||
.current_frame = 0,
|
||||
.transform = math.Transform.init(.{0.0, 0.0, 0.0}, .{1.0, 1.0, 1.0}, .{0.0, 0.0, 0.0}),
|
||||
.transform2 = math.Transform.init(.{0.0, 3.0, 3.0}, .{0.5, 0.5, 0.5}, .{0.0, 0.0, 0.0}),
|
||||
.previous_time = try std.time.Instant.now(),
|
||||
.mesh = mesh,
|
||||
};
|
||||
|
|
@ -97,7 +116,7 @@ pub fn render(pool: *ecs.Pool) anyerror!void {
|
|||
view_pos[1] = camera.position[1];
|
||||
view_pos[2] = camera.position[2];
|
||||
|
||||
renderer.transform.rotate(math.rad(10) * delta_time, .{0.0, 1.0, 0.0});
|
||||
renderer.transform.rotate(math.rad(15) * delta_time, .{0.0, 1.0, 0.0});
|
||||
|
||||
const transform_memory = renderer.graphics_pipeline.transform_memory;
|
||||
@memcpy(transform_memory[0..(@sizeOf(math.Transform)-@sizeOf(math.Quaternion))], std.mem.asBytes(&renderer.transform)[0..(@sizeOf(math.Transform)-@sizeOf(math.Quaternion))]);
|
||||
|
|
@ -111,6 +130,8 @@ pub fn render(pool: *ecs.Pool) anyerror!void {
|
|||
renderer.device.bindVertexBuffer(renderer.graphics_pipeline.vertex_buffer, renderer.current_frame);
|
||||
renderer.device.bindIndexBuffer(renderer.graphics_pipeline.index_buffer, renderer.current_frame);
|
||||
renderer.device.bindDescriptorSets(renderer.graphics_pipeline, renderer.current_frame, 0);
|
||||
var lights: u32 = 2;
|
||||
renderer.device.pushConstant(renderer.graphics_pipeline, 0, 0, 4, @ptrCast(&lights), renderer.current_frame);
|
||||
renderer.device.draw(renderer.mesh.index_count, renderer.current_frame, renderer.mesh);
|
||||
renderer.render_pass.end(renderer.device, renderer.current_frame);
|
||||
try renderer.device.endCommand(renderer.current_frame);
|
||||
|
|
|
|||
14
src/rendering/lights.zig
Normal file
14
src/rendering/lights.zig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
pub const DirectionalLight = extern struct {
|
||||
direction: [3]f32 align(16),
|
||||
ambient: [3]f32 align(16),
|
||||
diffuse: [3]f32 align(16),
|
||||
specular: [3]f32 align(16),
|
||||
};
|
||||
|
||||
pub const PointLight = extern struct {
|
||||
position: [3]f32 align(16),
|
||||
ambient: [3]f32 align(16),
|
||||
diffuse: [3]f32 align(16),
|
||||
specular: [3]f32 align(16),
|
||||
data: [3]f32 align(16),
|
||||
};
|
||||
|
|
@ -2,3 +2,4 @@ pub const Mesh = @import("Mesh.zig");
|
|||
pub const vk = @import("vulkan.zig");
|
||||
pub const Camera = @import("Camera.zig");
|
||||
pub const Renderer = @import("Renderer.zig");
|
||||
pub const lights = @import("lights.zig");
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ fn init_mods() void {
|
|||
export fn sideros_init(init: api.GameInit) callconv(.c) void {
|
||||
resources = .{
|
||||
.camera = .{
|
||||
.position = .{ -5.0, 5.0, -5.0 },
|
||||
.position = .{ 0.0, 5.0, -5.0 },
|
||||
.target = .{ 0.0, 0.0, 0.0 },
|
||||
},
|
||||
.renderer = undefined,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue