mirror of
https://github.com/bailwillharr/engine.git
synced 2024-09-21 04:51:18 +00:00
get normal maps working
This commit is contained in:
parent
b58d343c8d
commit
33ee5aa321
@ -47,6 +47,11 @@ struct GraphicsSettings {
|
||||
MSAALevel msaa_level;
|
||||
};
|
||||
|
||||
enum class ImageFormat {
|
||||
kLinear,
|
||||
kSRGB,
|
||||
};
|
||||
|
||||
enum class ShaderType {
|
||||
kVertex,
|
||||
kFragment,
|
||||
|
@ -102,7 +102,7 @@ class GFXDevice {
|
||||
|
||||
void DestroyBuffer(const gfx::Buffer* buffer);
|
||||
|
||||
gfx::Image* CreateImage(uint32_t w, uint32_t h, const void* image_data);
|
||||
gfx::Image* CreateImage(uint32_t w, uint32_t h, gfx::ImageFormat input_format, const void* image_data);
|
||||
|
||||
void DestroyImage(const gfx::Image* image);
|
||||
|
||||
|
@ -17,7 +17,7 @@ class Texture {
|
||||
kAnisotropic,
|
||||
};
|
||||
|
||||
Texture(Renderer* renderer, const uint8_t* bitmap, int width, int height, Filtering filtering);
|
||||
Texture(Renderer* renderer, const uint8_t* bitmap, int width, int height, Filtering filtering, bool srgb);
|
||||
|
||||
~Texture();
|
||||
Texture(const Texture&) = delete;
|
||||
@ -32,7 +32,7 @@ class Texture {
|
||||
const gfx::Sampler* sampler_; // not owned by Texture, owned by Renderer
|
||||
};
|
||||
|
||||
std::unique_ptr<Texture> LoadTextureFromFile(const std::string& path, Texture::Filtering filtering, Renderer* renderer);
|
||||
std::unique_ptr<Texture> LoadTextureFromFile(const std::string& path, Texture::Filtering filtering, Renderer* renderer, bool srgb = true);
|
||||
|
||||
} // namespace engine
|
||||
|
||||
|
@ -5,12 +5,10 @@ layout(set = 2, binding = 1) uniform sampler2D materialSetNormalSampler;
|
||||
layout(set = 2, binding = 2) uniform sampler2D materialSetOcclusionSampler;
|
||||
layout(set = 2, binding = 3) uniform sampler2D materialSetMetallicRoughnessSampler;
|
||||
|
||||
layout(location = 0) in vec3 fragPos;
|
||||
layout(location = 1) in vec3 fragNorm;
|
||||
layout(location = 2) in vec2 fragUV;
|
||||
layout(location = 3) in vec3 fragViewPos;
|
||||
layout(location = 4) in vec3 fragLightPos;
|
||||
layout(location = 5) in mat3 fragTBN;
|
||||
layout(location = 0) in vec2 fragUV;
|
||||
layout(location = 1) in vec3 fragPosTangentSpace;
|
||||
layout(location = 2) in vec3 fragViewPosTangentSpace;
|
||||
layout(location = 3) in vec3 fragLightPosTangentSpace;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
@ -25,10 +23,11 @@ void main() {
|
||||
vec3 baseColor = vec3(texture(materialSetAlbedoSampler, fragUV));
|
||||
|
||||
vec3 norm = vec3(texture(materialSetNormalSampler, fragUV));
|
||||
//norm.y = 1.0 - norm.y;
|
||||
norm = normalize(norm * 2.0 - 1.0);
|
||||
|
||||
vec3 lightDir = fragTBN * normalize(fragLightPos - fragPos);
|
||||
vec3 viewDir = fragTBN * normalize(fragViewPos - fragPos);
|
||||
vec3 lightDir = normalize(fragLightPosTangentSpace - fragPosTangentSpace);
|
||||
vec3 viewDir = normalize(fragViewPosTangentSpace - fragPosTangentSpace);
|
||||
|
||||
vec3 diffuse = max(dot(norm, lightDir), 0.0) * lightColor;
|
||||
vec3 ambient = ambientColor * ambientStrength;
|
||||
@ -36,7 +35,7 @@ void main() {
|
||||
float spec = pow(max(dot(norm, halfwayDir), 0.0), 32.0);
|
||||
vec3 specular = spec * lightColor;
|
||||
|
||||
vec3 lighting = min(diffuse + ambient + specular, 1.0);
|
||||
outColor = min( ( vec4(baseColor, 1.0) ) * vec4(lighting + emission, 1.0), vec4(1.0));
|
||||
vec3 lighting = diffuse + ambient + specular;
|
||||
outColor = vec4(min(baseColor * (lighting + emission), 1.0), 1.0);
|
||||
}
|
||||
|
||||
|
@ -17,27 +17,24 @@ layout(location = 1) in vec3 inNorm;
|
||||
layout(location = 2) in vec4 inTangent;
|
||||
layout(location = 3) in vec2 inUV;
|
||||
|
||||
layout(location = 0) out vec3 fragPos;
|
||||
layout(location = 1) out vec3 fragNorm;
|
||||
layout(location = 2) out vec2 fragUV;
|
||||
layout(location = 3) out vec3 fragViewPos;
|
||||
layout(location = 4) out vec3 fragLightPos;
|
||||
layout(location = 5) out mat3 fragTBN;
|
||||
layout(location = 0) out vec2 fragUV;
|
||||
layout(location = 1) out vec3 fragPosTangentSpace;
|
||||
layout(location = 2) out vec3 fragViewPosTangentSpace;
|
||||
layout(location = 3) out vec3 fragLightPosTangentSpace;
|
||||
|
||||
void main() {
|
||||
gl_Position = globalSetUniformBuffer.proj * frameSetUniformBuffer.view * constants.model * vec4(inPosition, 1.0);
|
||||
vec4 worldPosition = constants.model * vec4(inPosition, 1.0);
|
||||
gl_Position = globalSetUniformBuffer.proj * frameSetUniformBuffer.view * worldPosition;
|
||||
|
||||
mat3 normalMatrix = mat3(transpose(inverse(constants.model)));
|
||||
vec3 T = normalize(vec3(constants.model * vec4(inTangent.xyz, 0.0)));
|
||||
vec3 N = normalize(vec3(constants.model * vec4(inNorm, 0.0)));
|
||||
vec3 B = cross(T, N) * inTangent.w;
|
||||
mat3 worldToTangentSpace = transpose(mat3(T, B, N));
|
||||
|
||||
fragPos = vec3(constants.model * vec4(inPosition, 1.0));
|
||||
fragNorm = normalize(normalMatrix * inNorm);
|
||||
fragUV = inUV;
|
||||
fragViewPos = vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0));
|
||||
fragLightPos = vec3(2000.0, -2000.0, 2000.0);
|
||||
|
||||
vec3 T = normalize(normalMatrix * inTangent.xyz);
|
||||
vec3 B = cross(fragNorm, T) * inTangent.w; // from unity docs, w flips the binormal
|
||||
fragTBN = transpose(mat3(T, B, fragNorm));
|
||||
fragPosTangentSpace = worldToTangentSpace * vec3(worldPosition);
|
||||
fragViewPosTangentSpace = worldToTangentSpace * vec3(inverse(frameSetUniformBuffer.view) * vec4(0.0, 0.0, 0.0, 1.0));
|
||||
fragLightPosTangentSpace = worldToTangentSpace * vec3(59.0, -20.0, 10.0);
|
||||
|
||||
gl_Position.y *= -1.0;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ void main() {
|
||||
fragNorm = mat3(transpose(inverse(frameSetUniformBuffer.view * constants.model))) * inNorm;
|
||||
fragUV = inUV;
|
||||
|
||||
vec3 lightPos = vec3(2000.0, -2000.0, 2000.0);
|
||||
vec3 lightPos = vec3(59.0, -20.0, 10.0);
|
||||
fragLightPos = vec3(frameSetUniformBuffer.view * vec4(lightPos, 1.0));
|
||||
|
||||
gl_Position.y *= -1.0;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
|
||||
namespace engine {
|
||||
|
||||
Texture::Texture(Renderer* renderer, const uint8_t* bitmap, int width, int height, Filtering filtering) : gfx_(renderer->GetDevice())
|
||||
Texture::Texture(Renderer* renderer, const uint8_t* bitmap, int width, int height, Filtering filtering, bool srgb) : gfx_(renderer->GetDevice())
|
||||
{
|
||||
gfx::SamplerInfo samplerInfo{};
|
||||
|
||||
@ -40,7 +40,12 @@ Texture::Texture(Renderer* renderer, const uint8_t* bitmap, int width, int heigh
|
||||
renderer->samplers.insert(std::make_pair(samplerInfo, gfx_->CreateSampler(samplerInfo)));
|
||||
}
|
||||
|
||||
image_ = gfx_->CreateImage(width, height, bitmap);
|
||||
gfx::ImageFormat format = gfx::ImageFormat::kLinear;
|
||||
if (srgb) {
|
||||
format = gfx::ImageFormat::kSRGB;
|
||||
}
|
||||
|
||||
image_ = gfx_->CreateImage(width, height, format, bitmap);
|
||||
sampler_ = renderer->samplers.at(samplerInfo);
|
||||
|
||||
LOG_DEBUG("Created texture: width: {}, height: {}", width, height);
|
||||
@ -52,11 +57,11 @@ Texture::~Texture()
|
||||
gfx_->DestroyImage(image_);
|
||||
}
|
||||
|
||||
std::unique_ptr<Texture> LoadTextureFromFile(const std::string& path, Texture::Filtering filtering, Renderer* renderer)
|
||||
std::unique_ptr<Texture> LoadTextureFromFile(const std::string& path, Texture::Filtering filtering, Renderer* renderer, bool srgb)
|
||||
{
|
||||
int width, height;
|
||||
auto bitmap = util::ReadImageFile(path, width, height);
|
||||
return std::make_unique<Texture>(renderer, bitmap->data(), width, height, filtering);
|
||||
return std::make_unique<Texture>(renderer, bitmap->data(), width, height, filtering, srgb);
|
||||
}
|
||||
|
||||
} // namespace engine
|
BIN
test/res/models/cube/WallsMaterialbasecolortexture.png
Normal file
BIN
test/res/models/cube/WallsMaterialbasecolortexture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 MiB |
BIN
test/res/models/cube/cube.dae
(Stored with Git LFS)
Normal file
BIN
test/res/models/cube/cube.dae
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
test/res/textures/emptynormal.png
Normal file
BIN
test/res/textures/emptynormal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 B |
@ -164,33 +164,37 @@ void PlayGame(GameSettings settings)
|
||||
scene2->AddComponent<CameraControllerComponent>(camera);
|
||||
}
|
||||
|
||||
{ /* house */
|
||||
engine::Entity floor = engine::util::LoadMeshFromFile(scene2, app.GetResourcePath("models/cobble_house/cobble_house.dae"), true);
|
||||
}
|
||||
|
||||
{ /* axes */
|
||||
engine::util::LoadMeshFromFile(scene2, app.GetResourcePath("models/MY_AXES.dae"), true);
|
||||
}
|
||||
|
||||
{ /* a wall */
|
||||
engine::Entity wall = scene2->CreateEntity("wall", 0, glm::vec3{50.0f, 0.0f, 0.0f});
|
||||
auto wall_renderable = scene2->AddComponent<engine::MeshRenderableComponent>(wall);
|
||||
{ /* a cube with a normal map */
|
||||
engine::Entity wall2 = scene2->CreateEntity("wall2", 0, glm::vec3{ 60.0f, 0.0f, 0.0f });
|
||||
auto wall_renderable = scene2->AddComponent<engine::MeshRenderableComponent>(wall2);
|
||||
wall_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 8.0f, 8.0f, 8.0f);
|
||||
wall_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.fancy"));
|
||||
|
||||
std::shared_ptr<engine::Texture> albedo_texture =
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/brickwall_albedo.jpg"), engine::Texture::Filtering::kTrilinear, app.renderer());
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/brickwall_albedo.jpg"), engine::Texture::Filtering::kBilinear, app.renderer());
|
||||
std::shared_ptr<engine::Texture> normal_texture =
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/testnormal.png"), engine::Texture::Filtering::kTrilinear, app.renderer());
|
||||
engine::LoadTextureFromFile(app.GetResourcePath("textures/brickwall_normal.jpg"), engine::Texture::Filtering::kBilinear, app.renderer(), false);
|
||||
|
||||
wall_renderable->material->SetAlbedoTexture(app.GetResource<engine::Texture>("builtin.white"));
|
||||
wall_renderable->material->SetAlbedoTexture(albedo_texture);
|
||||
wall_renderable->material->SetNormalTexture(normal_texture);
|
||||
|
||||
auto custom = scene2->AddComponent<engine::CustomComponent>(wall);
|
||||
auto custom = scene2->AddComponent<engine::CustomComponent>(wall2);
|
||||
custom->onInit = []() {};
|
||||
custom->onUpdate = [&](float dt) {
|
||||
//scene2->GetComponent<engine::TransformComponent>(wall)->rotation *= glm::angleAxis(dt, glm::normalize(glm::vec3{2.0f, 1.0f, 1.0f}));
|
||||
};
|
||||
scene2->GetComponent<engine::TransformComponent>(wall2)->rotation *= glm::angleAxis(dt * 0.2f, glm::normalize(glm::vec3{ 0.3f, 2.1f, 1.0f }));
|
||||
};
|
||||
}
|
||||
|
||||
{ /* light */
|
||||
engine::Entity light = scene2->CreateEntity("light", 0, glm::vec3{59.0f, -20.0f, 10.0f});
|
||||
auto wall_renderable = scene2->AddComponent<engine::MeshRenderableComponent>(light);
|
||||
wall_renderable->mesh = GenSphereMesh(app.renderer()->GetDevice(), 0.5f, 16, false, true);
|
||||
wall_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.standard"));
|
||||
wall_renderable->material->SetAlbedoTexture(app.GetResource<engine::Texture>("builtin.white"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,26 +93,26 @@ std::unique_ptr<engine::Mesh> GenCuboidMesh(engine::GFXDevice* gfx, float x, flo
|
||||
v.push_back({{x, 0.0f, z}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{0.0f, 0.0f, z}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
// back
|
||||
v.push_back({{x, y, z}, {0.0f, 1.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, z}, {0.0f, 1.0f, 0.0f}, {}, {tiling, 0.0f}});
|
||||
v.push_back({{x, y, z}, {0.0f, 1.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, y, z}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, z}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{x, y, z}, {0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
// left
|
||||
v.push_back({{0.0f, y, z}, {-1.0f, 0.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {-1.0f, 0.0f, 0.0f}, {}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, z}, {-1.0f, 0.0f, 0.0f}, {}, {tiling, 0.0f}});
|
||||
v.push_back({{0.0f, y, z}, {-1.0f, 0.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{0.0f, y, z}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, z}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{0.0f, y, z}, {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
// right
|
||||
v.push_back({{x, 0.0f, z}, {1.0f, 0.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {}, {0.0f, tiling}});
|
||||
v.push_back({{x, y, 0.0f}, {1.0f, 0.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{x, y, 0.0f}, {1.0f, 0.0f, 0.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{x, y, z}, {1.0f, 0.0f, 0.0f}, {}, {tiling, 0.0f}});
|
||||
v.push_back({{x, 0.0f, z}, {1.0f, 0.0f, 0.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, 0.0f, z}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, tiling}});
|
||||
v.push_back({{x, y, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{x, y, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{x, y, z}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{x, 0.0f, z}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
// top
|
||||
v.push_back({{0.0f, y, z}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
v.push_back({{0.0f, 0.0f, z}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, tiling}});
|
||||
@ -121,12 +121,12 @@ std::unique_ptr<engine::Mesh> GenCuboidMesh(engine::GFXDevice* gfx, float x, flo
|
||||
v.push_back({{x, y, z}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{0.0f, y, z}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
// bottom
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {tiling, 0.0f}});
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
v.push_back({{x, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, tiling}});
|
||||
v.push_back({{0.0f, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {tiling, 0.0f}});
|
||||
v.push_back({{x, y, 0.0f}, {0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}});
|
||||
|
||||
if (wind_inside) {
|
||||
for (size_t i = 0; i < v.size(); i += 3) {
|
||||
|
Loading…
Reference in New Issue
Block a user