Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions include/DeferredPipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ struct DeferredPipelineResources {
gsl_lite::not_null<std::shared_ptr<Shader>> pointLightShader;
gsl_lite::not_null<std::shared_ptr<Shader>> spotLightShader;
gsl_lite::not_null<std::shared_ptr<Shader>> directionalLightShader;
gsl_lite::not_null<std::shared_ptr<Material>> geometryMaterial;
gsl_lite::not_null<std::shared_ptr<Material>> stencilMaterial;
gsl_lite::not_null<std::shared_ptr<Material>> pointLightMaterial;
gsl_lite::not_null<std::shared_ptr<Material>> spotLightMaterial;
Expand Down Expand Up @@ -56,7 +55,6 @@ class DeferredPipeline {
MeshShaderBindings stencilBindings_;
MeshShaderBindings pointLightBindings_;
MeshShaderBindings spotLightBindings_;
gsl_lite::not_null<std::shared_ptr<Material>> geometryMaterial_;
gsl_lite::not_null<std::shared_ptr<Material>> stencilMaterial_;
gsl_lite::not_null<std::shared_ptr<Material>> pointLightMaterial_;
gsl_lite::not_null<std::shared_ptr<Material>> spotLightMaterial_;
Expand Down
2 changes: 1 addition & 1 deletion include/Main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Application {
std::shared_ptr<Shader> DSDirectionalLightShader, DSSpotLightShader,
DSStencilPassShader;
std::shared_ptr<Material> mainMaterial;
std::shared_ptr<Material> DSGeometryPassMaterial, DSPointLightMaterial;
std::shared_ptr<Material> DSPointLightMaterial;
std::shared_ptr<Material> DSDirectionalLightMaterial, DSSpotLightMaterial,
DSStencilPassMaterial;
std::shared_ptr<GBuffer> gBuffer1;
Expand Down
1 change: 1 addition & 0 deletions include/Material.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Material {
explicit Material(gsl_lite::not_null<std::shared_ptr<Shader>> shader);
virtual ~Material();
void Use(float drawOpacity = 1.0F) const;
void UseWithShader(Shader& shader, float drawOpacity = 1.0F) const;
void SetColorTexture(std::shared_ptr<Texture2D> _colorMap);
void SetNormalTexture(std::shared_ptr<Texture2D> _normalMap);
void SetSpecularTexture(std::shared_ptr<Texture2D> _specularMap);
Expand Down
3 changes: 2 additions & 1 deletion include/Mesh.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class Mesh : public MaterialObject, public Transformable {
void Render(const Camera& cam) override;
void Draw(const Transform& transform, const Material& material,
const MeshShaderBindings& bindings, float opacity = 1.0F,
GLuint instanceBuffer = 0, GLsizei instanceCount = 1) const;
GLuint instanceBuffer = 0, GLsizei instanceCount = 1,
Shader* passShader = nullptr) const;

private:
int faceCount_{};
Expand Down
6 changes: 3 additions & 3 deletions include/MeshInstanceRenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ class MeshInstanceRenderer {
MeshInstanceRenderer(const MeshInstanceRenderer&) = delete;
MeshInstanceRenderer& operator=(const MeshInstanceRenderer&) = delete;

// A pass material overrides all instance materials (e.g. the G-buffer
// pass).
// A pass shader overrides shader selection while each command retains its
// surface material and textures.
void Render(std::span<const MeshRenderCommand> commands,
const MeshShaderBindings& bindings,
const Material* passMaterial = nullptr);
Shader* passShader = nullptr);

private:
struct InstanceData {
Expand Down
3 changes: 1 addition & 2 deletions src/DeferredPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ DeferredPipeline::DeferredPipeline(DeferredPipelineResources resources)
stencilBindings_(MeshShaderBindings::FromShader(*stencilShader_)),
pointLightBindings_(MeshShaderBindings::FromShader(*pointLightShader_)),
spotLightBindings_(MeshShaderBindings::FromShader(*spotLightShader_)),
geometryMaterial_(std::move(resources.geometryMaterial)),
stencilMaterial_(std::move(resources.stencilMaterial)),
pointLightMaterial_(std::move(resources.pointLightMaterial)),
spotLightMaterial_(std::move(resources.spotLightMaterial)),
Expand Down Expand Up @@ -112,7 +111,7 @@ void DeferredPipeline::GeometryPass(const SceneView& scene) {
glm::value_ptr(matrix));

instanceRenderer_.Render(scene.queue.GetOpaqueMeshes(), geometryBindings_,
geometryMaterial_.get());
geometryShader_.get());
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);
}
Expand Down
23 changes: 23 additions & 0 deletions src/Material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@ void Material::Use(float drawOpacity) const {
}
}

void Material::UseWithShader(Shader& shader, float drawOpacity) const {
shader.Use();
colorMap->Bind(GL_TEXTURE0);
glUniform1i(shader.GetUniformLocation("colTexSampler"), 0);
normalMap->Bind(GL_TEXTURE1);
glUniform1i(shader.GetUniformLocation("normTexSampler"), 1);
specularMap->Bind(GL_TEXTURE2);
glUniform1i(shader.GetUniformLocation("specTexSampler"), 2);
shadowMap->Bind(GL_TEXTURE3);
glUniform1i(shader.GetUniformLocation("shadowTexSampler"), 3);
glUniform1f(shader.GetUniformLocation("opacity"), drawOpacity);

for (GLint unit = 4; unit < max_texture_units; ++unit) {
const auto textureIndex = static_cast<std::size_t>(unit);
if (texturesID.at(textureIndex) == 0) continue;
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, texturesID.at(textureIndex));
glUniform1i(
shader.GetUniformLocation("gSampler" + ConvertToString(unit)),
unit);
}
}

void Material::SetColorTexture(std::shared_ptr<Texture2D> _colorMap) {
colorMap = std::move(_colorMap);
}
Expand Down
8 changes: 6 additions & 2 deletions src/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,15 @@ void Mesh::Render(const Camera& /*cam*/) {

void Mesh::Draw(const Transform& transform, const Material& material,
const MeshShaderBindings& bindings, float opacity,
GLuint instanceBuffer, GLsizei instanceCount) const {
GLuint instanceBuffer, GLsizei instanceCount,
Shader* passShader) const {
const glm::mat4 modelMatrix = transform.ModelMatrix();
const glm::mat4 normalMatrix = transform.NormalMatrix();

material.Use(opacity);
if (passShader != nullptr)
material.UseWithShader(*passShader, opacity);
else
material.Use(opacity);
glUniform1i(bindings.instanced, instanceBuffer != 0);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
Expand Down
23 changes: 10 additions & 13 deletions src/MeshInstanceRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,31 @@ MeshInstanceRenderer::~MeshInstanceRenderer() {

void MeshInstanceRenderer::Render(std::span<const MeshRenderCommand> commands,
const MeshShaderBindings& bindings,
const Material* passMaterial) {
Shader* passShader) {
static_assert(sizeof(InstanceData) == 2 * sizeof(glm::mat4));
sorted_.clear();
for (const auto& command : commands) {
if (passMaterial != nullptr || command.material != nullptr)
sorted_.push_back(&command);
if (command.material != nullptr) sorted_.push_back(&command);
}
const auto materialFor = [passMaterial](const MeshRenderCommand* command) {
return passMaterial != nullptr ? passMaterial : command->material.get();
};
std::ranges::stable_sort(sorted_, [&](const auto* a, const auto* b) {
if (&a->mesh.get() != &b->mesh.get())
return std::less<const Mesh*>{}(&a->mesh.get(), &b->mesh.get());
return std::less<const Material*>{}(materialFor(a), materialFor(b));
return std::less<const Material*>{}(a->material.get(),
b->material.get());
});
for (std::size_t begin = 0; begin < sorted_.size();) {
std::size_t end = begin + 1;
while (end < sorted_.size() &&
&sorted_[end]->mesh.get() == &sorted_[begin]->mesh.get() &&
materialFor(sorted_[end]) == materialFor(sorted_[begin]))
sorted_[end]->material == sorted_[begin]->material)
++end;
const auto& first = *sorted_[begin];
const Material& material = *materialFor(&first);
const Material& material = *first.material;
if (end - begin == 1 || bindings.instanceWorld < 0 ||
bindings.instanceRotation < 0 || bindings.instanced < 0) {
for (std::size_t index = begin; index < end; ++index)
first.mesh.get().Draw(sorted_[index]->transform, material,
bindings);
bindings, 1.0F, 0, 1, passShader);
} else {
instances_.clear();
for (std::size_t index = begin; index < end; ++index) {
Expand All @@ -65,9 +62,9 @@ void MeshInstanceRenderer::Render(std::span<const MeshRenderCommand> commands,
static_cast<GLsizeiptr>(instances_.size() *
sizeof(InstanceData)),
instances_.data(), GL_STREAM_DRAW);
first.mesh.get().Draw(first.transform, material, bindings, 1.0F,
buffer_,
static_cast<GLsizei>(instances_.size()));
first.mesh.get().Draw(
first.transform, material, bindings, 1.0F, buffer_,
static_cast<GLsizei>(instances_.size()), passShader);
}
begin = end;
}
Expand Down
6 changes: 0 additions & 6 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,6 @@ int Application::InitScene(GLFWwindow& window) {
{{"shaders/DSGeometryPass.vsh", ShaderType::Vertex},
{"shaders/DSGeometryPass.fsh", ShaderType::Fragment}});
}
// load material
{
DSGeometryPassMaterial = std::make_shared<Material>(
gsl_lite::not_null{DSGeometryPassShader});
}
}

// light shaders
Expand Down Expand Up @@ -577,7 +572,6 @@ int Application::InitScene(GLFWwindow& window) {
.spotLightShader = gsl_lite::not_null{DSSpotLightShader},
.directionalLightShader =
gsl_lite::not_null{DSDirectionalLightShader},
.geometryMaterial = gsl_lite::not_null{DSGeometryPassMaterial},
.stencilMaterial = gsl_lite::not_null{DSStencilPassMaterial},
.pointLightMaterial = gsl_lite::not_null{DSPointLightMaterial},
.spotLightMaterial = gsl_lite::not_null{DSSpotLightMaterial},
Expand Down
109 changes: 109 additions & 0 deletions tests/OpenGLRenderTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ std::shared_ptr<Shader> LoadShader(const std::string& vertex,
return shader;
}

std::shared_ptr<Texture2D> SolidTexture(
const std::array<std::uint8_t, 4>& color) {
GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
color.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return std::make_shared<Texture2D>(texture);
}

class OpenGLRender : public testing::Test {
protected:
void SetUp() override {
Expand Down Expand Up @@ -439,6 +451,103 @@ TEST_F(OpenGLRender, InstancedAndIndividualTransformsProduceMatchingImages) {
}
}

TEST_F(OpenGLRender, PassShaderPreservesPerInstanceSurfaceMaterials) {
constexpr auto surfaceVertex = R"(#version 330
in vec3 s_vPosition;
void main() { gl_Position = vec4(s_vPosition, 1.0); })";
constexpr auto surfaceFragment = R"(#version 330
out vec4 color;
void main() { color = vec4(1.0); })";
auto surfaceShader = std::make_shared<Shader>();
surfaceShader->AddShader(surfaceVertex, ShaderType::Vertex);
surfaceShader->AddShader(surfaceFragment, ShaderType::Fragment);
surfaceShader->Link();

constexpr auto passFragment = R"(#version 330
in vec3 fN;
in vec3 Tangent;
in vec2 UV;
uniform float attributeWeight;
uniform sampler2D colTexSampler;
uniform sampler2D normTexSampler;
uniform sampler2D specTexSampler;
out vec4 color;
void main() {
vec3 surface = vec3(texture(colTexSampler, UV).r,
texture(normTexSampler, UV).g,
texture(specTexSampler, UV).b);
color = vec4(surface + attributeWeight * (fN + Tangent), 1.0);
})";
auto passShader = std::make_shared<Shader>();
passShader->AddShader(ReadFile(Asset("shaders/DSGeometryPass.vsh")),
ShaderType::Vertex);
passShader->AddShader(passFragment, ShaderType::Fragment);
passShader->Link();

auto leftMaterial =
std::make_shared<Material>(gsl_lite::not_null{surfaceShader});
auto rightMaterial =
std::make_shared<Material>(gsl_lite::not_null{surfaceShader});
const auto leftTexture = SolidTexture({51, 76, 102, 255});
const auto rightTexture = SolidTexture({204, 178, 153, 255});
for (const auto& [material, texture] :
{std::pair{leftMaterial, leftTexture},
std::pair{rightMaterial, rightTexture}}) {
material->SetColorTexture(texture);
material->SetNormalTexture(texture);
material->SetSpecularTexture(texture);
}

Mesh mesh(gsl_lite::not_null{leftMaterial},
Asset("models/cube2x2x2.obj").string());
RenderQueue queue;
for (const float y : {-0.5F, 0.5F}) {
queue.SubmitOpaqueMesh(
mesh,
Transform{.position = {-0.8F, y, -3.0F}, .scale = glm::vec3(0.35F)},
leftMaterial);
queue.SubmitOpaqueMesh(
mesh,
Transform{.position = {0.8F, y, -3.0F}, .scale = glm::vec3(0.35F)},
rightMaterial);
}

RenderTarget target;
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
GL_FRAMEBUFFER_COMPLETE);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
passShader->Use();
const glm::mat4 projection =
glm::orthoRH_NO(-2.0F, 2.0F, -2.0F, 2.0F, 0.1F, 10.0F);
glUniformMatrix4fv(passShader->GetUniformLocation("gVC"), 1, GL_FALSE,
glm::value_ptr(projection));
glUniform1f(passShader->GetUniformLocation("attributeWeight"), 0.0F);
MeshInstanceRenderer renderer;
renderer.Render(queue.GetOpaqueMeshes(),
MeshShaderBindings::FromShader(*passShader),
passShader.get());

const Image image = target.Read();
const auto pixel = [&image](int x, int y, int channel) {
return image.at(
static_cast<std::size_t>((y * kSize + x) * 4 + channel));
};
for (const int y : {48, 80}) {
EXPECT_NEAR(pixel(38, y, 0), 51, 1);
EXPECT_NEAR(pixel(38, y, 1), 76, 1);
EXPECT_NEAR(pixel(38, y, 2), 102, 1);
EXPECT_NEAR(pixel(90, y, 0), 204, 1);
EXPECT_NEAR(pixel(90, y, 1), 178, 1);
EXPECT_NEAR(pixel(90, y, 2), 153, 1);
}
EXPECT_EQ(glGetError(), GL_NO_ERROR);
}

TEST_F(OpenGLRender, GBufferCreationAndResizeAreComplete) {
GBuffer buffer(kSize, kSize);
EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),
Expand Down
Loading