Begin new Asset Pipeline
This commit is contained in:
6
assets/materials/Bricks.json
Normal file
6
assets/materials/Bricks.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"baseColorTexture": "Bricks_BaseColor.png",
|
||||
"metallic": 0,
|
||||
"normalTexture": "Bricks_Normal.png",
|
||||
"roughness": 1
|
||||
}
|
||||
6
assets/materials/Dirt.json
Normal file
6
assets/materials/Dirt.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"baseColorTexture": "Dirt_BaseColor.png",
|
||||
"metallic": 0,
|
||||
"normalTexture": "Dirt_Normal.png",
|
||||
"roughness": 1
|
||||
}
|
||||
6
assets/materials/Gold.json
Normal file
6
assets/materials/Gold.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"baseColorTexture": "Gold_BaseColor.png",
|
||||
"metallic": 1,
|
||||
"normalTexture": "Gold_Normal.png",
|
||||
"roughness": 0
|
||||
}
|
||||
6
assets/materials/Stone.json
Normal file
6
assets/materials/Stone.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"baseColorTexture": "Stone_BaseColor.png",
|
||||
"metallic": 0,
|
||||
"normalTexture": "Stone_Normal.png",
|
||||
"roughness": 1
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
@vs vs_main
|
||||
|
||||
layout(location = 0) in vec3 positionOS;
|
||||
layout(location = 1) in vec2 texCoord;
|
||||
layout(location = 2) in vec3 normalOS;
|
||||
layout(location = 3) in vec4 tangentOS;
|
||||
|
||||
layout(location = 0) out vec3 var_positionVS;
|
||||
layout(location = 1) out vec2 var_texCoord;
|
||||
layout(location = 2) out vec3 var_normalVS;
|
||||
layout(location = 3) out vec3 var_tangentVS;
|
||||
layout(location = 4) out vec3 var_bitangentVS;
|
||||
|
||||
layout(binding = 0) uniform Global_Uniforms_Vertex {
|
||||
mat4 _MatrixWStoVS;
|
||||
mat4 _MatrixVStoCS;
|
||||
};
|
||||
|
||||
layout(binding = 1) uniform Object_Uniforms {
|
||||
mat4 _MatrixOStoWS;
|
||||
mat4 _MatrixOStoWSNormal;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec3 positionWS = (_MatrixOStoWS * vec4(positionOS, 1.0)).xyz;
|
||||
vec3 positionVS = (_MatrixWStoVS * vec4(positionWS, 1.0)).xyz;
|
||||
vec4 positionCS = _MatrixVStoCS * vec4(positionVS, 1.0);
|
||||
|
||||
vec3 normalWS = normalize((_MatrixOStoWSNormal * vec4(normalOS, 0.0)).xyz);
|
||||
vec3 normalVS = normalize((_MatrixWStoVS * vec4(normalWS, 0.0)).xyz);
|
||||
|
||||
vec3 tangentWS = normalize((_MatrixOStoWS * vec4(tangentOS.xyz, 0.0)).xyz);
|
||||
vec3 tangentVS = normalize((_MatrixWStoVS * vec4(tangentWS, 0.0)).xyz);
|
||||
|
||||
vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS));
|
||||
|
||||
gl_Position = positionCS;
|
||||
var_positionVS = positionVS;
|
||||
var_texCoord = texCoord;
|
||||
var_normalVS = normalVS;
|
||||
var_tangentVS = tangentVS;
|
||||
var_bitangentVS = bitangentVS;
|
||||
}
|
||||
@end
|
||||
|
||||
@fs fs_main
|
||||
|
||||
struct Point_Light {
|
||||
vec3 positionWS;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
struct Directional_Light {
|
||||
vec3 directionWS;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 var_positionVS;
|
||||
layout(location = 1) in vec2 var_texCoord;
|
||||
layout(location = 2) in vec3 var_normalVS;
|
||||
layout(location = 3) in vec3 var_tangentVS;
|
||||
layout(location = 4) in vec3 var_bitangentVS;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
layout(binding = 2) uniform Global_Uniforms_Fragment {
|
||||
mat4 _MatrixWStoVS;
|
||||
vec3 _AmbientLight;
|
||||
int _PointLightCount;
|
||||
int _DirectionalLightCount;
|
||||
};
|
||||
|
||||
layout(binding = 3) uniform Material_Uniforms {
|
||||
int _TextureIndex;
|
||||
};
|
||||
|
||||
layout(binding = 0) readonly buffer Point_Lights {
|
||||
Point_Light _PointLights[];
|
||||
};
|
||||
|
||||
layout(binding = 1) readonly buffer Directional_Lights {
|
||||
Directional_Light _DirectionalLights[];
|
||||
};
|
||||
|
||||
layout(binding = 2) uniform texture2DArray _BaseColorTexture;
|
||||
layout(binding = 3) uniform texture2DArray _OcclusionRoughnessMetallicTexture;
|
||||
layout(binding = 4) uniform texture2DArray _NormalTexture;
|
||||
|
||||
layout(binding = 0) uniform sampler _Sampler;
|
||||
|
||||
const float INV_PI = 0.31830987;
|
||||
const float IOR = 1.45;
|
||||
const vec3 DIELECTRIC_F0 = vec3(pow((IOR - 1.0) / (IOR + 1.0), 2.0));
|
||||
const vec3 F90 = vec3(1.0);
|
||||
|
||||
vec3 fresnelSchlick(float dotVH, vec3 f0) {
|
||||
return mix(f0, F90, pow(1.0 - dotVH, 5.0));
|
||||
}
|
||||
|
||||
float visibilityGGX(float dotNL, float dotNV, float alpha) {
|
||||
float alphaSquared = alpha * alpha;
|
||||
|
||||
float vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
|
||||
float lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
|
||||
float GGX = vGGX + lGGX;
|
||||
return mix(0.0, 0.5 / GGX, GGX > 0.0);
|
||||
}
|
||||
|
||||
float distributionGGX(float dotNH, float alpha) {
|
||||
float alphaSquared = alpha * alpha;
|
||||
float tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0;
|
||||
return alphaSquared * INV_PI / (tmp * tmp);
|
||||
}
|
||||
|
||||
vec3 toneMapAcesNarkowicz(vec3 color) {
|
||||
const float A = 2.51;
|
||||
const float B = 0.03;
|
||||
const float C = 2.43;
|
||||
const float D = 0.59;
|
||||
const float E = 0.14;
|
||||
return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 lightOutgoingRadiance(
|
||||
vec3 viewDirectionVS, vec3 normalVS, float dotNV,
|
||||
vec3 baseColor, float alpha, float metallic, vec3 f0,
|
||||
vec3 incomingRadiance, vec3 lightDirectionVS
|
||||
) {
|
||||
vec3 halfVectorVS = normalize(lightDirectionVS + viewDirectionVS);
|
||||
float dotVH = clamp(dot(viewDirectionVS, halfVectorVS), 0.0, 1.0);
|
||||
float dotNH = clamp(dot(normalVS, halfVectorVS), 0.0, 1.0);
|
||||
float dotNL = clamp(dot(normalVS, lightDirectionVS), 0.0, 1.0);
|
||||
|
||||
vec3 fresnel = fresnelSchlick(dotVH, f0);
|
||||
float visibility = visibilityGGX(dotNL, dotNV, alpha);
|
||||
float distribution = distributionGGX(dotNH, alpha);
|
||||
|
||||
vec3 scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI;
|
||||
vec3 reflectedFactor = fresnel * visibility * distribution;
|
||||
|
||||
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
||||
}
|
||||
|
||||
vec4 texture2DArrayAA(texture2DArray tex, vec2 texCoord) {
|
||||
vec2 size = vec2(textureSize(sampler2DArray(tex, _Sampler), 0).xy);
|
||||
vec2 texCoordPX = texCoord * size;
|
||||
vec2 seam = floor(texCoordPX + vec2(0.5));
|
||||
|
||||
texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam;
|
||||
texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5);
|
||||
|
||||
vec3 texCoord3 = vec3(texCoordPX / size, float(_TextureIndex));
|
||||
return texture(sampler2DArray(tex, _Sampler), texCoord3);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 baseColorTexel = texture2DArrayAA(_BaseColorTexture, var_texCoord);
|
||||
vec4 occlusionRoughnessMetallicTexel = texture2DArrayAA(_OcclusionRoughnessMetallicTexture, var_texCoord);
|
||||
vec4 normalTextureTexel = texture2DArrayAA(_NormalTexture, var_texCoord);
|
||||
|
||||
vec3 baseColor = baseColorTexel.rgb;
|
||||
float occlusion = occlusionRoughnessMetallicTexel.r;
|
||||
float roughness = occlusionRoughnessMetallicTexel.g;
|
||||
float metallic = occlusionRoughnessMetallicTexel.b;
|
||||
|
||||
vec3 tangentVS = normalize(var_tangentVS);
|
||||
vec3 bitangentVS = normalize(var_bitangentVS);
|
||||
mat3 matrixTStoVS = mat3(tangentVS, bitangentVS, var_normalVS);
|
||||
vec3 normalTS = normalTextureTexel.xyz * 2.0 - 1.0;
|
||||
vec3 normalVS = normalize(matrixTStoVS * normalTS);
|
||||
|
||||
vec3 positionVS = var_positionVS;
|
||||
vec3 viewDirectionVS = normalize(-positionVS);
|
||||
float dotNV = clamp(dot(normalVS, viewDirectionVS), 0.0, 1.0);
|
||||
float alpha = roughness * roughness;
|
||||
|
||||
vec3 f0 = mix(DIELECTRIC_F0, baseColor, metallic);
|
||||
|
||||
vec3 outgoingRadiance = vec3(0.0);
|
||||
|
||||
for (int i = 0; i < _PointLightCount; i++) {
|
||||
Point_Light light = _PointLights[i];
|
||||
|
||||
vec3 lightPositionVS = (_MatrixWStoVS * vec4(light.positionWS, 1.0)).xyz;
|
||||
vec3 lightDirectionVS = normalize(lightPositionVS - positionVS);
|
||||
float lightDistance = distance(positionVS, lightPositionVS);
|
||||
float lightAttenuation = 1.0 / (lightDistance * lightDistance);
|
||||
vec3 incomingRadiance = light.color * lightAttenuation;
|
||||
|
||||
outgoingRadiance += lightOutgoingRadiance(
|
||||
viewDirectionVS, normalVS, dotNV,
|
||||
baseColor, alpha, metallic, f0,
|
||||
incomingRadiance, lightDirectionVS
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _DirectionalLightCount; i++) {
|
||||
Directional_Light light = _DirectionalLights[i];
|
||||
|
||||
vec3 lightDirectionVS = normalize((_MatrixWStoVS * vec4(-light.directionWS, 0.0)).xyz);
|
||||
vec3 incomingRadiance = light.color;
|
||||
|
||||
outgoingRadiance += lightOutgoingRadiance(
|
||||
viewDirectionVS, normalVS, dotNV,
|
||||
baseColor, alpha, metallic, f0,
|
||||
incomingRadiance, lightDirectionVS
|
||||
);
|
||||
}
|
||||
|
||||
outgoingRadiance += _AmbientLight * baseColor * occlusion;
|
||||
|
||||
vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
||||
vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2));
|
||||
|
||||
fragColor = vec4(toneMappedSrgbColor, 1.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@cs cs_equirectangular_to_cubemap
|
||||
|
||||
layout(binding = 0) uniform Layer_Index {
|
||||
int _LayerIndex;
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform texture2D _EquirectangularTexture;
|
||||
layout(binding = 1, rgba16f) uniform writeonly image2DArray _CubemapImage;
|
||||
|
||||
layout(binding = 0) uniform sampler _EquirectangularSampler;
|
||||
|
||||
const float INV_PI = 0.31830987;
|
||||
const float HALF_INV_PI = 0.15915494;
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
void main() {
|
||||
vec2 size = vec2(imageSize(_CubemapImage).xy);
|
||||
vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size;
|
||||
|
||||
texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1]
|
||||
|
||||
vec3 cubeCoord;
|
||||
if (_LayerIndex == 0) {
|
||||
// Positive X
|
||||
cubeCoord = vec3(1, -texCoord.y, -texCoord.x);
|
||||
} else if (_LayerIndex == 1) {
|
||||
// Negative X
|
||||
cubeCoord = vec3(-1, -texCoord.y, texCoord.x);
|
||||
} else if (_LayerIndex == 2) {
|
||||
// Positive Y
|
||||
cubeCoord = vec3(texCoord.x, 1, texCoord.y);
|
||||
} else if (_LayerIndex == 3) {
|
||||
// Negative Y
|
||||
cubeCoord = vec3(texCoord.x, -1, -texCoord.y);
|
||||
} else if (_LayerIndex == 4) {
|
||||
// Positive Z
|
||||
cubeCoord = vec3(texCoord.x, -texCoord.y, 1);
|
||||
} else if (_LayerIndex == 5) {
|
||||
// Negative Z
|
||||
cubeCoord = vec3(-texCoord.x, -texCoord.y, -1);
|
||||
}
|
||||
|
||||
vec3 cubeDir = normalize(cubeCoord);
|
||||
|
||||
float theta = atan(cubeDir.y, cubeDir.x);
|
||||
float phi = asin(cubeDir.z);
|
||||
|
||||
vec2 equirectCoord = vec2(theta * HALF_INV_PI, phi * INV_PI) + vec2(0.5);
|
||||
|
||||
vec4 irradiance = texture(sampler2D(_EquirectangularTexture, _EquirectangularSampler), equirectCoord);
|
||||
imageStore(_CubemapImage, ivec3(ivec2(gl_GlobalInvocationID.xy), _LayerIndex), irradiance);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@program main vs_main fs_main
|
||||
@program equirectangular_to_cubemap cs_equirectangular_to_cubemap
|
||||
49
assets/shaders/equirect_to_cube.comp
Normal file
49
assets/shaders/equirect_to_cube.comp
Normal file
@@ -0,0 +1,49 @@
|
||||
#version 460
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler;
|
||||
|
||||
layout(set = 1, binding = 0) uniform texture2D _EquirectangularTexture;
|
||||
layout(set = 2, binding = 1, rgba16f) uniform writeonly restrict imageCube _CubemapImage;
|
||||
|
||||
const float INV_PI = 0.31830987;
|
||||
const float HALF_INV_PI = 0.15915494;
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
void main() {
|
||||
vec2 size = vec2(imageSize(_CubemapImage).xy);
|
||||
vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size;
|
||||
uint layerIndex = gl_GlobalInvocationID.z;
|
||||
|
||||
texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1]
|
||||
|
||||
vec3 cubeCoord;
|
||||
if (layerIndex == 0) {
|
||||
// Positive X
|
||||
cubeCoord = vec3(1, -texCoord.y, -texCoord.x);
|
||||
} else if (layerIndex == 1) {
|
||||
// Negative X
|
||||
cubeCoord = vec3(-1, -texCoord.y, texCoord.x);
|
||||
} else if (layerIndex == 2) {
|
||||
// Positive Y
|
||||
cubeCoord = vec3(texCoord.x, 1, texCoord.y);
|
||||
} else if (layerIndex == 3) {
|
||||
// Negative Y
|
||||
cubeCoord = vec3(texCoord.x, -1, -texCoord.y);
|
||||
} else if (layerIndex == 4) {
|
||||
// Positive Z
|
||||
cubeCoord = vec3(texCoord.x, -texCoord.y, 1);
|
||||
} else if (layerIndex == 5) {
|
||||
// Negative Z
|
||||
cubeCoord = vec3(-texCoord.x, -texCoord.y, -1);
|
||||
}
|
||||
|
||||
vec3 cubeDir = normalize(cubeCoord);
|
||||
|
||||
float theta = atan(cubeDir.y, cubeDir.x);
|
||||
float phi = asin(cubeDir.z);
|
||||
|
||||
vec2 equirectCoord = vec2(theta * HALF_INV_PI, phi * INV_PI) + vec2(0.5);
|
||||
|
||||
vec4 irradiance = texture(sampler2D(_EquirectangularTexture, _EquirectangularSampler), equirectCoord);
|
||||
imageStore(_CubemapImage, ivec3(ivec2(gl_GlobalInvocationID.xy), layerIndex), irradiance);
|
||||
}
|
||||
147
assets/shaders/main.frag
Normal file
147
assets/shaders/main.frag
Normal file
@@ -0,0 +1,147 @@
|
||||
#version 460
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
|
||||
in Varyings {
|
||||
layout(location = 0) vec3 positionVS;
|
||||
layout(location = 1) vec2 texCoord;
|
||||
layout(location = 2) vec3 normalVS;
|
||||
layout(location = 3) vec3 tangentVS;
|
||||
layout(location = 4) vec3 bitangentVS;
|
||||
} var;
|
||||
|
||||
#include "main_common.glsl"
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
const float INV_PI = 0.31830987;
|
||||
const float IOR = 1.45;
|
||||
const vec3 F90 = vec3(1.0);
|
||||
|
||||
vec3 fresnelSchlick(float dotVH, vec3 f0) {
|
||||
return mix(f0, F90, pow(1.0 - dotVH, 5.0));
|
||||
}
|
||||
|
||||
float visibilityGGX(float dotNL, float dotNV, float alpha) {
|
||||
float alphaSquared = alpha * alpha;
|
||||
|
||||
float vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
|
||||
float lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
|
||||
float GGX = vGGX + lGGX;
|
||||
return mix(0.0, 0.5 / GGX, GGX > 0.0);
|
||||
}
|
||||
|
||||
float distributionGGX(float dotNH, float alpha) {
|
||||
float alphaSquared = alpha * alpha;
|
||||
float tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0;
|
||||
return alphaSquared * INV_PI / (tmp * tmp);
|
||||
}
|
||||
|
||||
vec3 toneMapAcesNarkowicz(vec3 color) {
|
||||
const float A = 2.51;
|
||||
const float B = 0.03;
|
||||
const float C = 2.43;
|
||||
const float D = 0.59;
|
||||
const float E = 0.14;
|
||||
return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 lightOutgoingRadiance(
|
||||
vec3 viewDirectionVS, vec3 normalVS, float dotNV,
|
||||
vec3 baseColor, float alpha, float metallic, vec3 f0,
|
||||
vec3 incomingRadiance, vec3 lightDirectionVS
|
||||
) {
|
||||
vec3 halfVectorVS = normalize(lightDirectionVS + viewDirectionVS);
|
||||
float dotVH = clamp(dot(viewDirectionVS, halfVectorVS), 0.0, 1.0);
|
||||
float dotNH = clamp(dot(normalVS, halfVectorVS), 0.0, 1.0);
|
||||
float dotNL = clamp(dot(normalVS, lightDirectionVS), 0.0, 1.0);
|
||||
|
||||
vec3 fresnel = fresnelSchlick(dotVH, f0);
|
||||
float visibility = visibilityGGX(dotNL, dotNV, alpha);
|
||||
float distribution = distributionGGX(dotNH, alpha);
|
||||
|
||||
vec3 scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI;
|
||||
vec3 reflectedFactor = fresnel * visibility * distribution;
|
||||
|
||||
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
||||
}
|
||||
|
||||
vec4 texture2DAA(texture2D tex, vec2 texCoord) {
|
||||
vec2 size = vec2(textureSize(sampler2D(tex, _Sampler), 0).xy);
|
||||
vec2 texCoordPX = texCoord * size;
|
||||
vec2 seam = floor(texCoordPX + vec2(0.5));
|
||||
|
||||
texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam;
|
||||
texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5);
|
||||
|
||||
texCoord = texCoordPX / size;
|
||||
return texture(sampler2D(tex, _Sampler), texCoord);
|
||||
}
|
||||
|
||||
#define MATERIAL _Materials[_Object.material]
|
||||
|
||||
void main() {
|
||||
vec4 baseColorTexel = texture2DAA(_Textures[MATERIAL.baseColorTexture], var.texCoord);
|
||||
vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[MATERIAL.occlusionRoughnessMetallicTexture], var.texCoord);
|
||||
vec4 normalTexel = texture2DAA(_Textures[MATERIAL.normalTexture], var.texCoord);
|
||||
vec4 emissiveTexel = texture2DAA(_Textures[MATERIAL.emissiveTexture], var.texCoord);
|
||||
|
||||
vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb;
|
||||
float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0);
|
||||
float roughness = MATERIAL.roughness * occlusionRoughnessMetallicTexel.g;
|
||||
float metallic = MATERIAL.metallic * occlusionRoughnessMetallicTexel.b;
|
||||
vec3 emissive = MATERIAL.emissive * emissiveTexel.rgb;
|
||||
float ior = MATERIAL.ior;
|
||||
|
||||
vec3 tangentVS = normalize(var.tangentVS);
|
||||
vec3 bitangentVS = normalize(var.bitangentVS);
|
||||
mat3 matrixTStoVS = mat3(tangentVS, bitangentVS, var.normalVS);
|
||||
vec3 normalTS = normalTexel.xyz;
|
||||
vec3 normalVS = normalize(matrixTStoVS * normalTS);
|
||||
|
||||
vec3 positionVS = var.positionVS;
|
||||
vec3 viewDirectionVS = normalize(-positionVS);
|
||||
float dotNV = clamp(dot(normalVS, viewDirectionVS), 0.0, 1.0);
|
||||
float alpha = roughness * roughness;
|
||||
|
||||
vec3 f0 = vec3(pow((ior - 1.0) / (ior + 1.0), 2.0));
|
||||
f0 = mix(f0, baseColor, metallic);
|
||||
|
||||
vec3 outgoingRadiance = vec3(0.0);
|
||||
|
||||
for (uint i = 0; i < _PointLights.count; i++) {
|
||||
PointLight light = _PointLights.lights[i];
|
||||
|
||||
vec3 lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz;
|
||||
vec3 lightDirectionVS = normalize(lightPositionVS - positionVS);
|
||||
float lightDistance = distance(positionVS, lightPositionVS);
|
||||
float lightAttenuation = 1.0 / (lightDistance * lightDistance);
|
||||
vec3 incomingRadiance = light.color * lightAttenuation;
|
||||
|
||||
outgoingRadiance += lightOutgoingRadiance(
|
||||
viewDirectionVS, normalVS, dotNV,
|
||||
baseColor, alpha, metallic, f0,
|
||||
incomingRadiance, lightDirectionVS
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _DirectionalLights.count; i++) {
|
||||
DirectionalLight light = _DirectionalLights.lights[i];
|
||||
|
||||
vec3 lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(-light.directionWS, 0.0)).xyz);
|
||||
vec3 incomingRadiance = light.color;
|
||||
|
||||
outgoingRadiance += lightOutgoingRadiance(
|
||||
viewDirectionVS, normalVS, dotNV,
|
||||
baseColor, alpha, metallic, f0,
|
||||
incomingRadiance, lightDirectionVS
|
||||
);
|
||||
}
|
||||
|
||||
outgoingRadiance += _Global.ambientLight * baseColor * occlusion;
|
||||
|
||||
vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
||||
vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2));
|
||||
|
||||
fragColor = vec4(toneMappedSrgbColor, 1.0);
|
||||
}
|
||||
39
assets/shaders/main.vert
Normal file
39
assets/shaders/main.vert
Normal file
@@ -0,0 +1,39 @@
|
||||
#version 460
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
|
||||
layout(location = 0) in vec3 positionOS;
|
||||
layout(location = 1) in vec2 texCoord;
|
||||
layout(location = 2) in vec3 normalOS;
|
||||
layout(location = 3) in vec4 tangentOS;
|
||||
|
||||
out Varyings {
|
||||
layout(location = 0) vec3 positionVS;
|
||||
layout(location = 1) vec2 texCoord;
|
||||
layout(location = 2) vec3 normalVS;
|
||||
layout(location = 3) vec3 tangentVS;
|
||||
layout(location = 4) vec3 bitangentVS;
|
||||
} var;
|
||||
|
||||
#include "main_common.glsl"
|
||||
|
||||
void main() {
|
||||
vec3 positionWS = (_Object.matrixOStoWS * vec4(positionOS, 1.0)).xyz;
|
||||
vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz;
|
||||
vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0);
|
||||
|
||||
vec3 normalWS = normalize((_Object.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz);
|
||||
vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz);
|
||||
|
||||
vec3 tangentWS = normalize((_Object.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz);
|
||||
vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz);
|
||||
|
||||
vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS));
|
||||
|
||||
gl_Position = positionCS;
|
||||
var.positionVS = positionVS;
|
||||
var.texCoord = texCoord;
|
||||
var.normalVS = normalVS;
|
||||
var.tangentVS = tangentVS;
|
||||
var.bitangentVS = bitangentVS;
|
||||
}
|
||||
57
assets/shaders/main_common.glsl
Normal file
57
assets/shaders/main_common.glsl
Normal file
@@ -0,0 +1,57 @@
|
||||
// --- SET 0 --- GLOBAL --------------------------------------------------------
|
||||
|
||||
struct PointLight {
|
||||
vec3 positionWS;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
struct DirectionalLight {
|
||||
vec3 directionWS;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
struct Material {
|
||||
vec3 baseColor;
|
||||
uint baseColorTexture;
|
||||
vec3 emissive;
|
||||
uint emissiveTexture;
|
||||
float ior;
|
||||
float metallic;
|
||||
float normalScale;
|
||||
uint normalTexture;
|
||||
uint occlusionRoughnessMetallicTexture;
|
||||
float occlusionTextureStrength;
|
||||
float roughness;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 0, scalar) uniform GlobalUniforms {
|
||||
mat4 matrixWStoVS;
|
||||
mat4 matrixVStoCS;
|
||||
vec3 ambientLight;
|
||||
} _Global;
|
||||
|
||||
layout(set = 0, binding = 1, scalar) readonly buffer PointLights {
|
||||
uint count;
|
||||
PointLight lights[];
|
||||
} _PointLights;
|
||||
|
||||
layout(set = 0, binding = 2, scalar) readonly buffer DirectionalLights {
|
||||
uint count;
|
||||
DirectionalLight lights[];
|
||||
} _DirectionalLights;
|
||||
|
||||
layout(set = 0, binding = 3) uniform sampler _Sampler;
|
||||
layout(set = 0, binding = 4) uniform texture2D _Textures[];
|
||||
|
||||
layout(set = 0, binding = 5, scalar) readonly buffer Materials {
|
||||
Material _Materials[];
|
||||
};
|
||||
|
||||
// --- SET 1 --- PER OBJECT ----------------------------------------------------
|
||||
|
||||
layout(set = 1, binding = 0, scalar) uniform ObjectUniforms {
|
||||
mat4 matrixOStoWS;
|
||||
mat4 matrixOStoWSNormal;
|
||||
|
||||
uint material;
|
||||
} _Object;
|
||||
BIN
assets/textures/BaseColor.png
(Stored with Git LFS)
BIN
assets/textures/BaseColor.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/textures/Bricks_BaseColor.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Bricks_BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Bricks_Normal.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Bricks_Normal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Dirt_BaseColor.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Dirt_BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Dirt_Normal.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Dirt_Normal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Gold_BaseColor.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Gold_BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Gold_Normal.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Gold_Normal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Normal.png
(Stored with Git LFS)
BIN
assets/textures/Normal.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
BIN
assets/textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/textures/Stone_BaseColor.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Stone_BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Stone_Normal.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Stone_Normal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
119
src/Game.zig
Normal file
119
src/Game.zig
Normal file
@@ -0,0 +1,119 @@
|
||||
const Game = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const glfw = @import("zglfw");
|
||||
|
||||
const math = @import("math.zig");
|
||||
|
||||
const Materials = @import("assets/Materials.zig");
|
||||
const Swapchain = @import("engine/Swapchain.zig");
|
||||
const Iterator3 = math.Iterator3;
|
||||
const Matrix4x4 = math.Matrix4x4;
|
||||
const Quaternion = math.Quaternion;
|
||||
const Vector2 = math.Vector2;
|
||||
const Vector3 = math.Vector3;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
swapchain: *Swapchain,
|
||||
|
||||
materials: Materials,
|
||||
|
||||
camera_position: Vector3 = .init(0, 0, 1.62),
|
||||
camera_pitch: f32 = 0,
|
||||
camera_yaw: f32 = 0,
|
||||
|
||||
input_forwards: bool = false,
|
||||
input_backwards: bool = false,
|
||||
input_left: bool = false,
|
||||
input_right: bool = false,
|
||||
|
||||
const camera_near_plane = 0.1;
|
||||
const camera_vertical_fov_deg = 90.0;
|
||||
const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg;
|
||||
const camera_mouse_sensitivity = 0.0015;
|
||||
|
||||
const max_point_lights = 100;
|
||||
const max_directional_lights = 4;
|
||||
|
||||
const player_speed = 5.0;
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game {
|
||||
var materials = try Materials.init(allocator);
|
||||
errdefer materials.deinit();
|
||||
|
||||
var it = materials.map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
std.debug.print("Material: {s}\n", .{entry.key_ptr.*});
|
||||
std.debug.print("Value: {}\n", .{entry.value_ptr.*});
|
||||
}
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.swapchain = swapchain,
|
||||
.materials = materials,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Game) void {
|
||||
self.materials.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn update(self: *Game, dt: f32) void {
|
||||
var camera_d = Vector2.init(
|
||||
@as(f32, @floatFromInt(@intFromBool(self.input_right))) - @as(f32, @floatFromInt(@intFromBool(self.input_left))),
|
||||
@as(f32, @floatFromInt(@intFromBool(self.input_forwards))) - @as(f32, @floatFromInt(@intFromBool(self.input_backwards))),
|
||||
).rotate(self.camera_yaw).mulScalar(player_speed * dt);
|
||||
|
||||
self.camera_position = Vector3.add(self.camera_position, camera_d.asVector3(0));
|
||||
}
|
||||
|
||||
pub fn onKeyDown(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void {
|
||||
const no_mods = @as(c_int, @bitCast(mods)) == 0;
|
||||
|
||||
if (key_code == .escape and no_mods) {
|
||||
self.swapchain.engine.mode.surface.window.setShouldClose(true);
|
||||
}
|
||||
|
||||
if (key_code == .w) {
|
||||
self.input_forwards = true;
|
||||
self.input_backwards = false;
|
||||
}
|
||||
if (key_code == .s) {
|
||||
self.input_backwards = true;
|
||||
self.input_forwards = false;
|
||||
}
|
||||
if (key_code == .a) {
|
||||
self.input_left = true;
|
||||
self.input_right = false;
|
||||
}
|
||||
if (key_code == .d) {
|
||||
self.input_right = true;
|
||||
self.input_left = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onKeyUp(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void {
|
||||
_ = mods;
|
||||
|
||||
if (key_code == .w) {
|
||||
self.input_forwards = false;
|
||||
}
|
||||
if (key_code == .s) {
|
||||
self.input_backwards = false;
|
||||
}
|
||||
if (key_code == .a) {
|
||||
self.input_left = false;
|
||||
}
|
||||
if (key_code == .d) {
|
||||
self.input_right = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onMouseMove(self: *Game, dx: f32, dy: f32) void {
|
||||
self.camera_pitch -= dy * camera_mouse_sensitivity;
|
||||
self.camera_yaw -= dx * camera_mouse_sensitivity;
|
||||
|
||||
self.camera_pitch = std.math.clamp(self.camera_pitch, -0.5 * std.math.pi, 0.5 * std.math.pi);
|
||||
self.camera_yaw = @mod(self.camera_yaw, 2 * std.math.pi);
|
||||
}
|
||||
11
src/Log.zig
11
src/Log.zig
@@ -1,11 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
level: std.log.Level,
|
||||
message: []const u8,
|
||||
|
||||
pub fn init(level: std.log.Level, message: []const u8) @This() {
|
||||
return .{
|
||||
.level = level,
|
||||
.message = message,
|
||||
};
|
||||
}
|
||||
70
src/assets/Atoms.zig
Normal file
70
src/assets/Atoms.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub const Atoms = @This();
|
||||
const std = @import("std");
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
string_arena_state: std.heap.ArenaAllocator.State,
|
||||
map: Map,
|
||||
array: Array,
|
||||
|
||||
pub const Atom = extern struct {
|
||||
atom: u32,
|
||||
};
|
||||
|
||||
pub const Map = std.StringHashMapUnmanaged(Atom);
|
||||
pub const Array = std.ArrayList([]const u16);
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Atoms {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.string_arena_state = .{},
|
||||
.map = .empty,
|
||||
.next_index = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Atoms) void {
|
||||
self.string_arena_state.promote(std.heap.page_allocator).deinit();
|
||||
self.map.deinit(self.allocator);
|
||||
self.array.deinit(self.allocator);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn getString(self: *const Atoms, atom: Atom) ?[]const u8 {
|
||||
const index: usize = atom.atom;
|
||||
return if (index < self.array.items.len) self.array.items[index] else null;
|
||||
}
|
||||
|
||||
pub fn getAtom(self: *const Atoms, string: []const u8) ?Atom {
|
||||
return self.map.get(string);
|
||||
}
|
||||
|
||||
pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom {
|
||||
const entry = try self.map.getOrPut(self.allocator, string);
|
||||
|
||||
if (entry.found_existing) {
|
||||
return entry.value_ptr.*;
|
||||
} else {
|
||||
errdefer self.map.remove(string);
|
||||
try self.array.ensureUnusedCapacity(self.allocator, 1);
|
||||
const owned_string = try self.toOwnedString(string);
|
||||
|
||||
const index = self.array.items.len;
|
||||
const atom: Atom = .{ .atom = @intCast(index) };
|
||||
|
||||
entry.key_ptr.* = owned_string;
|
||||
entry.value_ptr.* = atom;
|
||||
|
||||
self.array.appendAssumeCapacity(owned_string);
|
||||
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
|
||||
fn toOwnedString(self: *const Atoms, string: []const u8) ![]const u8 {
|
||||
var string_arena = self.string_arena_state.promote(std.heap.page_allocator);
|
||||
defer self.string_arena_state = string_arena.state;
|
||||
|
||||
const allocator = string_arena.allocator();
|
||||
const owned_string = try allocator.dupe(u8, string);
|
||||
return owned_string;
|
||||
}
|
||||
138
src/assets/Materials.zig
Normal file
138
src/assets/Materials.zig
Normal file
@@ -0,0 +1,138 @@
|
||||
const Materials = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Atoms = @import("Atoms.zig");
|
||||
const Engine = @import("../engine/Engine.zig");
|
||||
const Textures = @import("Textures.zig");
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
map: Map,
|
||||
|
||||
pub const Id = extern struct {
|
||||
id: u32,
|
||||
};
|
||||
|
||||
pub const Map = std.AutoHashMapUnmanaged(Atoms.Atom, Id);
|
||||
|
||||
pub const Material = extern struct {
|
||||
base_color: [3]f32,
|
||||
base_color_texture: Textures.Id,
|
||||
emissive: [3]f32,
|
||||
emissive_texture: Textures.Id,
|
||||
ior: f32,
|
||||
metallic: f32,
|
||||
normal_scale: f32,
|
||||
normal_texture: Textures.Id,
|
||||
occlusion_roughness_metallic_texture: Textures.Id,
|
||||
occlusion_texture_strength: f32,
|
||||
roughness: f32,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Materials {
|
||||
var map: Map = .empty;
|
||||
errdefer map.deinit(allocator);
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.map = map,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Materials) void {
|
||||
self.arena.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: *Atoms, key: Atoms.Atom) !Id {
|
||||
const MaterialJson = struct {
|
||||
baseColor: [3]f32 = .{ 1, 1, 1 },
|
||||
baseColorTexture: ?[]const u8 = null,
|
||||
emissive: [3]f32 = .{ 0, 0, 0 },
|
||||
emissiveTexture: ?[]const u8 = null,
|
||||
ior: f32 = 1.45,
|
||||
metallic: f32 = 1,
|
||||
normalScale: f32 = 1,
|
||||
normalTexture: ?[]const u8 = null,
|
||||
occlusionRoughnessMetallicTexture: ?[]const u8 = null,
|
||||
occlusionTextureStrength: f32 = 1,
|
||||
roughness: f32 = 1,
|
||||
};
|
||||
|
||||
const filename = atoms.getString(key).?;
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
|
||||
var dir = try cwd.openDir("assets/materials", .{});
|
||||
defer dir.close();
|
||||
|
||||
// NOTE Buffer size approximated based on expected JSON structure.
|
||||
var buffer: [512]u8 = undefined;
|
||||
|
||||
const file = try dir.openFile(filename, .{});
|
||||
defer file.close();
|
||||
|
||||
var file_reader = file.reader(&buffer);
|
||||
var json_reader: std.json.Reader = .init(self.allocator, &file_reader.interface);
|
||||
defer json_reader.deinit();
|
||||
|
||||
const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, self.allocator, &json_reader, .{
|
||||
.duplicate_field_behavior = .@"error",
|
||||
.ignore_unknown_fields = false,
|
||||
.allocate = .alloc_if_needed,
|
||||
});
|
||||
defer parsed.deinit();
|
||||
|
||||
const material_json = parsed.value;
|
||||
|
||||
const base_color_texture = blk: {
|
||||
if (material_json.baseColorTexture) |name| {
|
||||
const atom = try atoms.getOrPutAtom(name);
|
||||
break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .base_color });
|
||||
} else {
|
||||
break :blk textures.empty_base_color;
|
||||
}
|
||||
};
|
||||
|
||||
const emissive_texture = blk: {
|
||||
if (material_json.emissiveTexture) |name| {
|
||||
const atom = try atoms.getOrPutAtom(name);
|
||||
break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .emissive });
|
||||
} else {
|
||||
break :blk textures.empty_emissive;
|
||||
}
|
||||
};
|
||||
|
||||
const normal_texture = blk: {
|
||||
if (material_json.normalTexture) |name| {
|
||||
const atom = try atoms.getOrPutAtom(name);
|
||||
break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .normal });
|
||||
} else {
|
||||
break :blk textures.empty_normal;
|
||||
}
|
||||
};
|
||||
|
||||
const occlusion_roughness_metallic_texture = blk: {
|
||||
if (material_json.occlusionRoughnessMetallicTexture) |name| {
|
||||
const atom = try atoms.getOrPutAtom(name);
|
||||
break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .occlusion_roughness_metallic });
|
||||
} else {
|
||||
break :blk textures.empty_occlusuion_roughness_metallic;
|
||||
}
|
||||
};
|
||||
|
||||
const material: Material = .{
|
||||
.base_color = material_json.baseColor,
|
||||
.base_color_texture = base_color_texture,
|
||||
.emissive = material_json.emissive,
|
||||
.emissive_texture = emissive_texture,
|
||||
.ior = material_json.ior,
|
||||
.metallic = material_json.metallic,
|
||||
.normal_scale = material_json.normalScale,
|
||||
.normal_texture = normal_texture,
|
||||
.occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture,
|
||||
.occlusion_texture_strength = material_json.occlusionTextureStrength,
|
||||
.roughness = material_json.roughness,
|
||||
};
|
||||
}
|
||||
139
src/assets/Textures.zig
Normal file
139
src/assets/Textures.zig
Normal file
@@ -0,0 +1,139 @@
|
||||
const Textures = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const stbi = @import("zstbi");
|
||||
|
||||
const Atoms = @import("Atoms.zig");
|
||||
const Engine = @import("../engine/Engine.zig");
|
||||
const Texture = @import("../engine/Texture.zig");
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
map: Map,
|
||||
array: Array,
|
||||
|
||||
empty_base_color: Id,
|
||||
empty_emissive: Id,
|
||||
empty_normal: Id,
|
||||
empty_occlusuion_roughness_metallic: Id,
|
||||
|
||||
pub const Id = extern struct {
|
||||
id: u32,
|
||||
};
|
||||
|
||||
pub const Key = struct {
|
||||
atom: Atoms.Atom,
|
||||
usage: Texture.Usage,
|
||||
};
|
||||
|
||||
pub const Map = std.AutoHashMapUnmanaged(Key, Id);
|
||||
pub const Array = std.ArrayList(Texture);
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures {
|
||||
var map: Map = .empty;
|
||||
errdefer map.deinit(allocator);
|
||||
|
||||
var array: Array = try .initCapacity(allocator, 4);
|
||||
errdefer {
|
||||
for (array.items) |texture| {
|
||||
texture.deinit(engine);
|
||||
}
|
||||
array.deinit(allocator);
|
||||
}
|
||||
|
||||
const empty_base_color: Id = .{ .id = @intCast(array.items.len) };
|
||||
const empty_base_color_texture: Texture = try .init(engine, 1, 1, .base_color);
|
||||
array.appendAssumeCapacity(empty_base_color_texture);
|
||||
|
||||
const empty_emissive: Id = .{ .id = @intCast(array.items.len) };
|
||||
const empty_emissive_texture: Texture = try .init(engine, 1, 1, .emissive);
|
||||
array.appendAssumeCapacity(empty_emissive_texture);
|
||||
|
||||
const empty_normal: Id = .{ .id = @intCast(array.items.len) };
|
||||
const empty_normal_texture: Texture = try .init(engine, 1, 1, .normal);
|
||||
array.appendAssumeCapacity(empty_normal_texture);
|
||||
|
||||
const empty_occlusuion_roughness_metallic: Id = .{ .id = @intCast(array.items.len) };
|
||||
const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic);
|
||||
array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture);
|
||||
|
||||
empty_base_color_texture.write([3]u8, &.{.{ 255, 255, 255 }});
|
||||
empty_emissive_texture.write([3]f32, &.{.{ 1.0, 1.0, 1.0 }});
|
||||
empty_normal_texture.write([3]u8, &.{.{ 128, 128, 255 }});
|
||||
empty_occlusuion_roughness_metallic_texture.write([3]u8, &.{.{ 255, 255, 255 }});
|
||||
|
||||
return .{
|
||||
.map = map,
|
||||
.array = array,
|
||||
|
||||
.empty_base_color = empty_base_color,
|
||||
.empty_emissive = empty_emissive,
|
||||
.empty_normal = empty_normal,
|
||||
.empty_occlusuion_roughness_metallic = empty_occlusuion_roughness_metallic,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Textures, allocator: std.mem.Allocator, engine: *Engine) void {
|
||||
for (self.array.items) |texture| {
|
||||
texture.deinit(engine);
|
||||
}
|
||||
self.array.deinit(allocator);
|
||||
|
||||
self.map.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn getTexture(self: *const Textures, id: Id) ?*Texture {
|
||||
const index: usize = id.id;
|
||||
return if (index < self.array.items.len) &self.array.items[index] else null;
|
||||
}
|
||||
|
||||
pub fn getId(self: *const Textures, key: Key) ?Id {
|
||||
return self.map.get(key);
|
||||
}
|
||||
|
||||
pub fn getOrLoadId(self: *Textures, engine: *Engine, key: Key) !Id {
|
||||
const entry = try self.map.getOrPut(self.allocator, key);
|
||||
|
||||
if (entry.found_existing) {
|
||||
return entry.value_ptr.*;
|
||||
} else {
|
||||
errdefer self.map.remove(key);
|
||||
try self.array.ensureUnusedCapacity(self.allocator, 1);
|
||||
const texture = try self.loadTexture(engine, key);
|
||||
|
||||
const id = self.nextId();
|
||||
|
||||
entry.value_ptr.* = id;
|
||||
self.array.appendAssumeCapacity(texture);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
fn loadTexture(self: *Textures, engine: *Engine, key: Key) !Texture {
|
||||
const cwd = std.fs.cwd();
|
||||
|
||||
var dir = try cwd.openDir("assets/textures", .{});
|
||||
defer dir.close();
|
||||
|
||||
const file = try dir.openFile(key.name, .{});
|
||||
defer file.close();
|
||||
|
||||
const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize));
|
||||
defer self.allocator.free(file_buf);
|
||||
|
||||
var img = try stbi.Image.loadFromMemory(file_buf, key.usage.sampleCount());
|
||||
defer img.deinit();
|
||||
std.debug.assert(img.num_components == key.usage.sampleCount());
|
||||
|
||||
var texture: Texture = try .init(engine, img.width, img.height, key.usage);
|
||||
errdefer texture.deinit(engine);
|
||||
|
||||
texture.write(u8, img.data);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
fn nextId(self: *const Textures) Id {
|
||||
const index = self.array.items.len;
|
||||
return .{ .id = @intCast(index) };
|
||||
}
|
||||
@@ -7,7 +7,7 @@ pub const app_version_string = @import("config").version;
|
||||
pub const app_version = std.SemanticVersion.parse(app_version_string) catch unreachable;
|
||||
|
||||
pub const min_framerate = 30.0;
|
||||
pub const min_frametime = 1.0 / min_framerate;
|
||||
pub const max_frametime = 1.0 / min_framerate;
|
||||
|
||||
pub const default_window_width = 1280;
|
||||
pub const default_window_height = 720;
|
||||
|
||||
51
src/engine/IndexBuffer.zig
Normal file
51
src/engine/IndexBuffer.zig
Normal file
@@ -0,0 +1,51 @@
|
||||
const IndexBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
index_count: usize,
|
||||
|
||||
pub fn init(engine: *Engine, index_count: usize) !IndexBuffer {
|
||||
const size = std.math.mul(usize, index_count, @sizeOf(u16)) catch return error.OutOfMemory;
|
||||
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_dst_bit = true,
|
||||
.index_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = .exclusive,
|
||||
}, null);
|
||||
errdefer engine.device.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.index_count = index_count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *IndexBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: IndexBuffer, engine: *Engine, indices: []const u16) !void {
|
||||
std.debug.assert(indices.len == self.index_count);
|
||||
|
||||
const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
}
|
||||
54
src/engine/StagingBuffer.zig
Normal file
54
src/engine/StagingBuffer.zig
Normal file
@@ -0,0 +1,54 @@
|
||||
const StagingBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
|
||||
pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !StagingBuffer {
|
||||
const transfer_queue_family = engine.transfer_queue.allocation.family;
|
||||
|
||||
const single_queue_family = transfer_queue_family == destination_queue_family;
|
||||
const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ transfer_queue_family, destination_queue_family };
|
||||
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = data.len,
|
||||
.usage = .{
|
||||
.transfer_src_bit = true,
|
||||
},
|
||||
.sharing_mode = if (single_queue_family) .exclusive else .concurrent,
|
||||
.p_queue_family_indices = queue_family_indices.ptr,
|
||||
}, null);
|
||||
errdefer engine.device.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(
|
||||
memory_requirements,
|
||||
.{
|
||||
.host_visible_bit = true,
|
||||
.host_coherent_bit = true,
|
||||
},
|
||||
);
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
const mapped_memory: [*]u8 = @ptrCast(try engine.device.mapMemory(memory, 0, data.len, .{}) orelse return error.OutOfMemory);
|
||||
defer engine.device.unmapMemory(memory);
|
||||
@memcpy(mapped_memory, data);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StagingBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
74
src/engine/StorageBuffer.zig
Normal file
74
src/engine/StorageBuffer.zig
Normal file
@@ -0,0 +1,74 @@
|
||||
const StorageBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
prefix_size: usize,
|
||||
element_size: usize,
|
||||
array_offset: usize,
|
||||
array_capacity: usize,
|
||||
|
||||
pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: type, array_capacity: usize) !StorageBuffer {
|
||||
const prefix_size = @sizeOf(PrefixType);
|
||||
const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType));
|
||||
const element_size = @sizeOf(ElementType);
|
||||
|
||||
const array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory;
|
||||
const size = std.math.add(array_offset, array_capacity_in_bytes) catch return error.OutOfMemory;
|
||||
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_dst_bit = true,
|
||||
.storage_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = .exclusive,
|
||||
}, null);
|
||||
errdefer engine.device.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.prefix_size = prefix_size,
|
||||
.element_size = element_size,
|
||||
.array_offset = array_offset,
|
||||
.array_capacity = array_capacity,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StorageBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void {
|
||||
std.debug.assert(elements.len <= self.array_capacity);
|
||||
std.debug.assert(@sizeOf(PrefixType) == self.prefix_size);
|
||||
std.debug.assert(@sizeOf(ElementType) == self.element_size);
|
||||
std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType)));
|
||||
|
||||
const array_size_in_bytes = elements.len * elements.len;
|
||||
const size = self.array_offset + array_size_in_bytes;
|
||||
|
||||
const data = try engine.vk_allocator.allocator.alloc(u8, size);
|
||||
defer engine.vk_allocator.allocator.free(data);
|
||||
|
||||
@memcpy(data[0..@sizeOf(PrefixType)], std.mem.asBytes(&prefix));
|
||||
@memcpy(data[self.array_offset..], std.mem.sliceAsBytes(elements));
|
||||
|
||||
const staging_buffer: StagingBuffer = .init(engine, data, engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
}
|
||||
139
src/engine/Texture.zig
Normal file
139
src/engine/Texture.zig
Normal file
@@ -0,0 +1,139 @@
|
||||
const Texture = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
|
||||
pub const Usage = enum {
|
||||
base_color,
|
||||
normal,
|
||||
occlusion_roughness_metallic,
|
||||
emissive,
|
||||
|
||||
pub fn format(self: Usage) vk.Format {
|
||||
return switch (self) {
|
||||
.base_color => .r8g8b8_srgb,
|
||||
.normal => .r8g8b8_snorm,
|
||||
.occlusion_roughness_metallic => .r8g8b8_unorm,
|
||||
.emissive => .r16g16b16_sfloat,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sampleCount(self: Usage) usize {
|
||||
return switch (self) {
|
||||
.base_color => 3,
|
||||
.normal => 3,
|
||||
.occlusion_roughness_metallic => 3,
|
||||
.emissive => 3,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn SampleType(comptime self: Usage) type {
|
||||
return switch (self) {
|
||||
.base_color => u8,
|
||||
.normal => u8,
|
||||
.occlusion_roughness_metallic => u8,
|
||||
.emissive => f16,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sampleSize(self: Usage) usize {
|
||||
return switch (self) {
|
||||
inline else => |x| @sizeOf(SampleType(x)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn TexelType(comptime self: Usage) type {
|
||||
return [self.sampleCount()]SampleType(self);
|
||||
}
|
||||
|
||||
pub fn texelSize(self: Usage) usize {
|
||||
return switch (self) {
|
||||
inline else => |x| @sizeOf(TexelType(x)),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
image: vk.Image,
|
||||
image_view: vk.ImageView,
|
||||
memory: vk.DeviceMemory,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
usage: Usage,
|
||||
|
||||
pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture {
|
||||
const format: vk.Format = usage.format();
|
||||
|
||||
const image = try engine.device.createImage(&.{
|
||||
.image_type = .@"2d",
|
||||
.format = format,
|
||||
.extent = .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1,
|
||||
},
|
||||
.mip_levels = 1,
|
||||
.array_layers = 1,
|
||||
.samples = .{ .@"1_bit" = true },
|
||||
.tiling = .optimal,
|
||||
.usage = .{
|
||||
.transfer_src_bit = true,
|
||||
.sampled_bit = true,
|
||||
},
|
||||
.sharing_mode = .exclusive,
|
||||
.initial_layout = .undefined,
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyImage(image, &engine.vk_allocator.interface);
|
||||
|
||||
const memory_requirements = engine.device.getImageMemoryRequirements(image);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindImageMemory(image, memory, 0);
|
||||
|
||||
const image_view = try engine.device.createImageView(&.{
|
||||
.image = image,
|
||||
.view_type = .@"2d",
|
||||
.format = format,
|
||||
.components = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_level = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
}, &engine.vk_allocator.interface);
|
||||
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface);
|
||||
|
||||
return .{
|
||||
.image = image,
|
||||
.image_view = image_view,
|
||||
.memory = memory,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Texture, engine: *Engine) void {
|
||||
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface);
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyImage(self.image, &engine.vk_allocator.interface);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: Texture, comptime T: type, data: []const T) void {
|
||||
const bytes_per_texel = self.format.texelSize();
|
||||
const bytes_per_row = self.width * bytes_per_texel;
|
||||
const byte_length = self.height * bytes_per_row;
|
||||
|
||||
std.debug.assert(data.len * @sizeOf(T) == byte_length);
|
||||
}
|
||||
55
src/engine/VertexBuffer.zig
Normal file
55
src/engine/VertexBuffer.zig
Normal file
@@ -0,0 +1,55 @@
|
||||
const VertexBuffer = @This();
|
||||
const std = @import("std");
|
||||
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const Engine = @import("Engine.zig");
|
||||
const StagingBuffer = @import("StagingBuffer.zig");
|
||||
|
||||
buffer: vk.Buffer,
|
||||
memory: vk.DeviceMemory,
|
||||
vertex_size: usize,
|
||||
vertex_count: usize,
|
||||
|
||||
pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !VertexBuffer {
|
||||
const vertex_size = @sizeOf(VertexType);
|
||||
const size = std.math.mul(usize, vertex_count, vertex_size) catch return error.OutOfMemory;
|
||||
|
||||
const buffer = try engine.device.createBuffer(&.{
|
||||
.size = size,
|
||||
.usage = .{
|
||||
.transfer_dst_bit = true,
|
||||
.vertex_buffer_bit = true,
|
||||
},
|
||||
.sharing_mode = .exclusive,
|
||||
}, null);
|
||||
errdefer engine.device.destroyBuffer(buffer);
|
||||
|
||||
const memory_requirements = engine.device.getBufferMemoryRequirements(buffer);
|
||||
const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true });
|
||||
errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface);
|
||||
|
||||
try engine.device.bindBufferMemory(buffer, memory, 0);
|
||||
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.vertex_size = vertex_size,
|
||||
.vertex_count = vertex_count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *VertexBuffer, engine: *Engine) void {
|
||||
engine.device.freeMemory(self.memory, &engine.vk_allocator.interface);
|
||||
engine.device.destroyBuffer(self.buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, vertices: []const VertexType) !void {
|
||||
std.debug.assert(vertices.len == self.vertex_count);
|
||||
std.debug.assert(@sizeOf(VertexType) == self.vertex_size);
|
||||
|
||||
const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family);
|
||||
defer staging_buffer.deinit(engine);
|
||||
}
|
||||
285
src/game.zig
285
src/game.zig
@@ -1,285 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const ig = @import("cimgui");
|
||||
const shaders = @import("shaders");
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const sapp = sokol.app;
|
||||
const sg = sokol.gfx;
|
||||
const sglue = sokol.glue;
|
||||
|
||||
const main = @import("main.zig");
|
||||
const math = @import("math.zig");
|
||||
const tiles = @import("tiles.zig");
|
||||
const samplers = @import("samplers.zig");
|
||||
const skybox = @import("skybox.zig");
|
||||
|
||||
const Iterator3 = math.Iterator3;
|
||||
const Matrix4x4 = math.Matrix4x4;
|
||||
const Quaternion = math.Quaternion;
|
||||
const Vector2 = math.Vector2;
|
||||
const Vector3 = math.Vector3;
|
||||
|
||||
var show_console: bool = false;
|
||||
var show_demo_window: bool = false;
|
||||
|
||||
var point_light_buffer: sg.Buffer = undefined;
|
||||
var directional_light_buffer: sg.Buffer = undefined;
|
||||
|
||||
pub var point_light_buffer_view: sg.View = undefined;
|
||||
pub var directional_light_buffer_view: sg.View = undefined;
|
||||
|
||||
var camera_position: Vector3 = .init(0, 0, 1.62);
|
||||
var camera_pitch: f32 = 0;
|
||||
var camera_yaw: f32 = 0;
|
||||
|
||||
const camera_near_plane = 0.1;
|
||||
const camera_vertical_fov_deg = 90.0;
|
||||
const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg;
|
||||
const camera_mouse_sensitivity = 0.0015;
|
||||
|
||||
const max_point_lights = 100;
|
||||
const max_directional_lights = 4;
|
||||
|
||||
pub fn init() void {
|
||||
point_light_buffer = sg.makeBuffer(.{
|
||||
.size = @sizeOf([max_point_lights]shaders.PointLight),
|
||||
.usage = .{
|
||||
.stream_update = true,
|
||||
.storage_buffer = true,
|
||||
},
|
||||
.label = "PointLight Buffer",
|
||||
});
|
||||
point_light_buffer_view = sg.makeView(.{
|
||||
.storage_buffer = .{ .buffer = point_light_buffer },
|
||||
.label = "PointLight BV",
|
||||
});
|
||||
|
||||
directional_light_buffer = sg.makeBuffer(.{
|
||||
.size = @sizeOf([max_directional_lights]shaders.DirectionalLight),
|
||||
.usage = .{
|
||||
.stream_update = true,
|
||||
.storage_buffer = true,
|
||||
},
|
||||
.label = "DirectionalLight Buffer",
|
||||
});
|
||||
directional_light_buffer_view = sg.makeView(.{
|
||||
.storage_buffer = .{ .buffer = directional_light_buffer },
|
||||
.label = "DirectionalLight BV",
|
||||
});
|
||||
|
||||
tiles.init();
|
||||
skybox.init();
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
skybox.deinit();
|
||||
tiles.deinit();
|
||||
|
||||
sg.destroyView(directional_light_buffer_view);
|
||||
sg.destroyBuffer(directional_light_buffer);
|
||||
sg.destroyView(point_light_buffer_view);
|
||||
sg.destroyBuffer(point_light_buffer);
|
||||
|
||||
samplers.deinit();
|
||||
}
|
||||
|
||||
pub fn update(dt: f32) void {
|
||||
var camera_d = Vector2.init(
|
||||
@as(f32, @floatFromInt(@intFromBool(input_right))) - @as(f32, @floatFromInt(@intFromBool(input_left))),
|
||||
@as(f32, @floatFromInt(@intFromBool(input_forwards))) - @as(f32, @floatFromInt(@intFromBool(input_backwards))),
|
||||
).rotate(camera_yaw).mulScalar(player_speed * dt);
|
||||
|
||||
camera_position = Vector3.add(camera_position, camera_d.asVector3(0));
|
||||
|
||||
const framebuffer_size = Vector2.init(sapp.widthf(), sapp.heightf());
|
||||
|
||||
if (show_console) {
|
||||
showConsole();
|
||||
}
|
||||
|
||||
if (show_demo_window) {
|
||||
ig.igShowDemoWindow(&show_demo_window);
|
||||
}
|
||||
|
||||
sg.beginPass(.{
|
||||
.action = blk: {
|
||||
var ret: sg.PassAction = .{};
|
||||
|
||||
ret.colors[0] = .{
|
||||
.load_action = .CLEAR,
|
||||
.store_action = .STORE,
|
||||
.clear_value = .{ .r = 0, .g = 0, .b = 0, .a = 1 },
|
||||
};
|
||||
|
||||
ret.depth = .{
|
||||
.load_action = .CLEAR,
|
||||
.store_action = .STORE,
|
||||
.clear_value = 0,
|
||||
};
|
||||
|
||||
break :blk ret;
|
||||
},
|
||||
.swapchain = sglue.swapchain(),
|
||||
});
|
||||
|
||||
tiles.bind();
|
||||
|
||||
const camera_rotation = Quaternion.mulQuaternion(
|
||||
.initRotationXY(camera_yaw),
|
||||
.initRotationYZ(camera_pitch),
|
||||
);
|
||||
const camera_aspect_ratio = framebuffer_size.getX() / framebuffer_size.getY();
|
||||
const camera_yscale = 1.0 / @tan(camera_half_vertical_fov_rad);
|
||||
const camera_xscale = camera_yscale / camera_aspect_ratio;
|
||||
|
||||
const matrix_ws_to_vs = Matrix4x4.mulMatrix(
|
||||
Matrix4x4.initRotation(camera_rotation.conjugate()),
|
||||
Matrix4x4.initTranslation(camera_position.negate()),
|
||||
);
|
||||
// zig fmt: off
|
||||
const matrix_vs_to_cs = Matrix4x4.init(
|
||||
camera_xscale, 0, 0, 0,
|
||||
0, 0, camera_near_plane, 1,
|
||||
0, camera_yscale, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
);
|
||||
// zig fmt: on
|
||||
const ambient_light = Vector3.init(0.3, 0.3, 0.3);
|
||||
|
||||
sg.applyUniforms(shaders.UB_Global_Uniforms_Vertex, sg.asRange(&shaders.GlobalUniformsVertex{
|
||||
._MatrixWStoVS = matrix_ws_to_vs.asArray(),
|
||||
._MatrixVStoCS = matrix_vs_to_cs.asArray(),
|
||||
}));
|
||||
|
||||
const point_lights: []const shaders.PointLight = &.{
|
||||
.{
|
||||
.positionWS = .{ 0, 0, 1 },
|
||||
.color = .{ 10, 10, 10 },
|
||||
},
|
||||
.{
|
||||
.positionWS = .{ -10, 10, 1 },
|
||||
.color = .{ 5, 0, 0 },
|
||||
},
|
||||
.{
|
||||
.positionWS = .{ 10, 10, 1 },
|
||||
.color = .{ 0, 0, 5 },
|
||||
},
|
||||
.{
|
||||
.positionWS = .{ -10, -10, 1 },
|
||||
.color = .{ 0, 5, 0 },
|
||||
},
|
||||
.{
|
||||
.positionWS = .{ 10, -10, 1 },
|
||||
.color = .{ 5, 5, 0 },
|
||||
},
|
||||
};
|
||||
|
||||
const directional_lights: []const shaders.DirectionalLight = &.{
|
||||
.{
|
||||
.directionWS = .{ 0, 0, -1 },
|
||||
.color = .{ 0.2, 0.2, 0.2 },
|
||||
},
|
||||
};
|
||||
|
||||
sg.updateBuffer(point_light_buffer, sg.asRange(point_lights));
|
||||
sg.updateBuffer(directional_light_buffer, sg.asRange(directional_lights));
|
||||
|
||||
sg.applyUniforms(shaders.UB_Global_Uniforms_Fragment, sg.asRange(&shaders.GlobalUniformsFragment{
|
||||
._MatrixWStoVS = matrix_ws_to_vs.asArray(),
|
||||
._AmbientLight = ambient_light.asArray(),
|
||||
._PointLightCount = @intCast(point_lights.len),
|
||||
._DirectionalLightCount = @intCast(directional_lights.len),
|
||||
}));
|
||||
|
||||
var it = Iterator3.init(.init(-8, -8, 0), .init(8, 8, 0), .one);
|
||||
var tile_index: u32 = 0;
|
||||
while (it.next()) |pos| : (tile_index = (tile_index + 1) % 16) {
|
||||
tiles.draw(pos, .identity, tile_index);
|
||||
}
|
||||
|
||||
sg.endPass();
|
||||
}
|
||||
|
||||
var input_forwards: bool = false;
|
||||
var input_backwards: bool = false;
|
||||
var input_left: bool = false;
|
||||
var input_right: bool = false;
|
||||
const player_speed = 5.0;
|
||||
|
||||
pub fn onKeyDown(key_code: sapp.Keycode, mods: u32) void {
|
||||
const no_mods = mods == 0;
|
||||
|
||||
if (key_code == .ESCAPE and no_mods) {
|
||||
sapp.requestQuit();
|
||||
}
|
||||
|
||||
if (key_code == .GRAVE_ACCENT and no_mods) {
|
||||
show_console = !show_console;
|
||||
}
|
||||
|
||||
if (key_code == .F1 and no_mods) {
|
||||
show_demo_window = true;
|
||||
}
|
||||
|
||||
if (key_code == .W) {
|
||||
input_forwards = true;
|
||||
input_backwards = false;
|
||||
}
|
||||
if (key_code == .S) {
|
||||
input_backwards = true;
|
||||
input_forwards = false;
|
||||
}
|
||||
if (key_code == .A) {
|
||||
input_left = true;
|
||||
input_right = false;
|
||||
}
|
||||
if (key_code == .D) {
|
||||
input_right = true;
|
||||
input_left = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onKeyUp(key_code: sapp.Keycode, mods: u32) void {
|
||||
_ = mods;
|
||||
|
||||
if (key_code == .W) {
|
||||
input_forwards = false;
|
||||
}
|
||||
if (key_code == .S) {
|
||||
input_backwards = false;
|
||||
}
|
||||
if (key_code == .A) {
|
||||
input_left = false;
|
||||
}
|
||||
if (key_code == .D) {
|
||||
input_right = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onMouseMove(dx: f32, dy: f32) void {
|
||||
camera_pitch -= dy * camera_mouse_sensitivity;
|
||||
camera_yaw -= dx * camera_mouse_sensitivity;
|
||||
|
||||
camera_pitch = std.math.clamp(camera_pitch, -0.5 * std.math.pi, 0.5 * std.math.pi);
|
||||
camera_yaw = @mod(camera_yaw, 2 * std.math.pi);
|
||||
}
|
||||
|
||||
fn showConsole() void {
|
||||
const display_size = ig.igGetIO().*.DisplaySize;
|
||||
ig.igSetNextWindowPos(.{ .x = 0, .y = 0 }, 0);
|
||||
ig.igSetNextWindowSize(.{ .x = display_size.x, .y = main.min_window_height }, ig.ImGuiCond_Once);
|
||||
if (ig.igBegin("Console", null, ig.ImGuiWindowFlags_NoTitleBar | ig.ImGuiWindowFlags_NoResize | ig.ImGuiWindowFlags_NoMove | ig.ImGuiWindowFlags_NoCollapse)) {
|
||||
for (main.logs.items) |log| {
|
||||
ig.igPushStyleColorImVec4(ig.ImGuiCol_Text, switch (log.level) {
|
||||
.err => .{ .x = 1, .y = 0, .z = 0, .w = 1 },
|
||||
.warn => .{ .x = 1, .y = 1, .z = 0, .w = 1 },
|
||||
.info => .{ .x = 1, .y = 1, .z = 1, .w = 1 },
|
||||
.debug => .{ .x = 0.5, .y = 0.5, .z = 0.5, .w = 1 },
|
||||
});
|
||||
ig.igTextUnformattedEx(log.message.ptr, log.message.ptr + log.message.len);
|
||||
ig.igPopStyleColor();
|
||||
}
|
||||
ig.igEnd();
|
||||
}
|
||||
}
|
||||
14
src/main.zig
14
src/main.zig
@@ -5,10 +5,10 @@ const stbi = @import("zstbi");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const c = @import("const.zig");
|
||||
const game = @import("game.zig");
|
||||
|
||||
const Engine = @import("engine/Engine.zig");
|
||||
const Swapchain = @import("engine/Swapchain.zig");
|
||||
const Game = @import("Game.zig");
|
||||
|
||||
pub var allocator: std.mem.Allocator = undefined;
|
||||
pub var temp_allocator: std.mem.Allocator = undefined;
|
||||
@@ -54,12 +54,18 @@ pub fn main() !void {
|
||||
var swapchain = try Swapchain.init(&engine);
|
||||
defer swapchain.deinit();
|
||||
|
||||
//game.init();
|
||||
//defer game.deinit();
|
||||
var game = try Game.init(allocator, &swapchain);
|
||||
defer game.deinit();
|
||||
|
||||
var t1 = glfw.getTime();
|
||||
while (!window.shouldClose()) {
|
||||
glfw.pollEvents();
|
||||
//game.update(dt);
|
||||
|
||||
const t2 = glfw.getTime();
|
||||
const dt: f32 = @floatCast(t2 - t1);
|
||||
t1 = t2;
|
||||
|
||||
game.update(dt);
|
||||
std.Thread.sleep(1 * std.time.ns_per_ms);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user