- 광원(light source)
- 재질(material)
위 두가지를 거쳐 눈에 색이 도달하는 과정 (매우 복잡한 물리적 현상)
가장 대표적인 Local illumination model
- 빛과 물체간의 색상 결정을 3가지로 나눠서 표현
- ambient(주변광), diffuse(분산광), specular(반사광) 이 세가지의 빛을 더하여 최종 색상을 결정
Ambient Light
- 주변광
- 빛의 방향, 물체 표변의 방향, 시선 방향과 아무 상관 없이 물체가 기본적으로 받는 빛
- 상수값으로 처리한다.
Diffuse Light
- 분산광
- 빛이 물체 표면에 부딪혔을 때, 모든 방향으로 고르게 퍼지는 빛, 즉 시선의 방향과는 상관없이 빛의 방향과 물체 표면의 방향에 따라 결정된다.
- Diffuse = dot(ligth, normal)
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 transform;
out vec3 normal;
out vec2 texCoord;
void main() {
gl_Position = transform * vec4(aPos, 1.0);
normal = aNormal;
texCoord = aTexCoord;
}
#version 330 core
in vec3 normal;
in vec2 texCoord;
out vec4 fragColor;
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform float ambientStrength;
void main() {
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
fragColor = vec4(result, 1.0);
}
- lightColor로 빛의 색상을, objectColor로 물체의 색상을 나타낸다.
- ambientStrength만큼 전체 밝기를 높여준다
vec3 ambient = ambientStrength * lightColor;
ShaderPtr vertShader = Shader::CreateFromFile("./shader/lighting.vs", GL_VERTEX_SHADER);
ShaderPtr fragShader = Shader::CreateFromFile("./shader/lighting.fs", GL_FRAGMENT_SHADER);
void SetUniform(const std::string& name, int value) const;
void SetUniform(const std::string& name, float value) const; // 추가된 항목
void SetUniform(const std::string& name, const glm::vec3& value) const; // 추가된 항목
void SetUniform(const std::string& name, const glm::mat4& value) const;
void Program::SetUniform(const std::string& name, float value) const {
auto loc = glGetUniformLocation(m_program, name.c_str());
glUniform1f(loc, value);
}
void Program::SetUniform(const std::string& name, const glm::vec3& value) const {
auto loc = glGetUniformLocation(m_program, name.c_str());
glUniform3fv(loc, 1, glm::value_ptr(value));
}
// light parameter
glm::vec3 m_lightColor { glm::vec3(1.0f, 1.0f, 1.0f) };
glm::vec3 m_objectColor { glm::vec3(1.0f, 0.5f, 0.0f) };
float m_ambientStrength { 0.1f };
if (ImGui::Begin("ui window"))
{
if (ImGui::CollapsingHeader("light"))
{
ImGui::ColorEdit3("light color", glm::value_ptr(m_lightColor));
ImGui::ColorEdit3("object color", glm::value_ptr(m_objectColor));
ImGui::SliderFloat("ambient strength", &m_ambientStrength, 0.0f, 1.0f);
}
//...
ImGui::End();
m_program->Use(); // 추가된 항목
m_program->SetUniform("lightColor", m_lightColor); // 추가된 항목
m_program->SetUniform("objectColor", m_objectColor); // 추가된 항목
m_program->SetUniform("ambientStrength", m_ambientStrength); // 추가된 항목
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
};
m_vertexBuffer = Buffer::CreateWithData(
GL_ARRAY_BUFFER, GL_STATIC_DRAW,
vertices, sizeof(float) * 8 * 6 * 4);
m_vertexLayout->SetAttrib(0, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 8, 0);
m_vertexLayout->SetAttrib(1, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 8, sizeof(float) * 3);
m_vertexLayout->SetAttrib(2, 2, GL_FLOAT, GL_FALSE,
sizeof(float) * 8, sizeof(float) * 6);
layout (location = 2) in vec2 aTexCoord;
uniform mat4 transform;
uniform mat4 modelTransform; // 추가된 항목
out vec3 normal; // 추가된 항목
out vec2 texCoord;
out vec3 position; // 추가된 항목
void main() {
gl_Position = transform * vec4(aPos, 1.0);
normal = (transpose(inverse(modelTransform)) * vec4(aNormal, 0.0)).xyz; // 추가된 항목
texCoord = aTexCoord;
position = (modelTransform * vec4(aPos, 1.0)).xyz; // 추가된 항목
}
- gl_Position외에도 position을 계산하여 fragment shader에 넘기는 이유
- gl_Position은 perspective transform이 적용되어 canonical space상의 좌표값으로 전환됨
- diffuse값을 계산하려면 world space상에서의 좌표값이 필요
- normal도 같은 이유로 model transform만 적용
- normal에 modelTransform의 inverse transform을 적용하는 이유
- 점이 아닌 벡터의 경우 이렇게 해야 제대로 변환된 값을 계산할 수 있다.
- matrix의 inverse transform은 모든 점에서 동일하므로 보통 별도의 uniform으로 입력한다.normal = (transpose(inverse(modelTransform)) * vec4(aNormal, 0.0)).xyz;
보통 이 코드는 uniform mat4 inverseTransModelTransform; 이라고 별도의 uniform을 만들어 입력해 주는데 이 이유는 위와 같은 방식으로 할 경우 컴퓨터기 일일히 계산해야 하므로써 연산에 부담이 증가하게 된다. 어짜피 계산의 결과는 같으므로 별도의 uniform을 만들어 값을 바로 대입시켜주는 것이다.
#version 330 core
in vec3 normal;
in vec2 texCoord;
in vec3 position; // 추가된 항목
out vec4 fragColor;
uniform vec3 lightPos; // 추가된 항목
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform float ambientStrength;
void main() {
vec3 ambient = ambientStrength * lightColor;
//빛의 방향
vec3 lightDir = normalize(lightPos - position); // 추가된 항목
// 법선 방향
vec3 pixelNorm = normalize(normal); // 추가된 항목
// -(마이너스)이하의 값은 필요가 없기 때문에 max() 사용
// 그 값에 빛의 색(lightColor)을 곱해준다.
vec3 diffuse = max(dot(pixelNorm, lightDir), 0.0) * lightColor; // 추가된 항목
vec3 result = (ambient + diffuse) * objectColor; // 추가된 항목
fragColor = vec4(result, 1.0);
}
- lightDir = normalize(lightPos - position);
- (빛의 방향) = (광원의 위치) - (각 픽셀의 3D위치);- normal을 다시 normalize 하는 이유
- vertex shader에서 계산된 normal은 rasterization되는 과정에서 선형 보간이 진행됨
- unit vector간의 선형 보간 결과는 unit vector보장을 못하기 때문에 normalization 해주어야 한다.
// animation
bool m_animation { true };
// clear color
glm::vec4 m_clearColor { glm::vec4(0.1f, 0.2f, 0.3f, 0.0f) };
// light parameter
glm::vec3 m_lightPos { glm::vec3(3.0f, 3.0f, 3.0f) };
glm::vec3 m_lightColor { glm::vec3(1.0f, 1.0f, 1.0f) };
glm::vec3 m_objectColor { glm::vec3(1.0f, 0.5f, 0.0f) };
float m_ambientStrength { 0.1f };
if (ImGui::Begin("ui window"))
{
if (ImGui::CollapsingHeader("light", ImGuiTreeNodeFlags_DefaultOpen)) // 추가된 항목
{
ImGui::DragFloat3("light pos", glm::value_ptr(m_lightPos), 0.01f); // 추가된 항목
ImGui::ColorEdit3("light color", glm::value_ptr(m_lightColor));
ImGui::ColorEdit3("object color", glm::value_ptr(m_objectColor));
ImGui::SliderFloat("ambient strength", &m_ambientStrength, 0.0f, 1.0f);
}
ImGui::Checkbox("animation", &m_animation); // 추가된 항목
if (ImGui::ColorEdit4("clear color", glm::value_ptr(m_clearColor)))
{
glClearColor(m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a);
}
m_program->SetUniform("lightPos", m_lightPos); // 추가된 항목
m_program->SetUniform("lightColor", m_lightColor);
m_program->SetUniform("objectColor", m_objectColor);
m_program->SetUniform("ambientStrength", m_ambientStrength);
for (size_t i = 0; i < cubePositions.size(); i++){
auto& pos = cubePositions[i];
auto model = glm::translate(glm::mat4(1.0f), pos);
auto angle = glm::radians((float)glfwGetTime() * 120.0f + 20.0f * (float)i); // 추가된 항목
model = glm::rotate(model, m_animation ? angle : 0.0f, glm::vec3(1.0f, 0.5f, 0.0f)); // 추가된 항목
auto transform = projection * view * model;
m_program->SetUniform("transform", transform);
m_program->SetUniform("modelTransform", model); // 추가된 항목
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
auto lightModelTransform =
glm::translate(glm::mat4(1.0), m_lightPos) *
glm::scale(glm::mat4(1.0), glm::vec3(0.1f));
m_program->Use();
m_program->SetUniform("lightPos", m_lightPos);
m_program->SetUniform("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
m_program->SetUniform("objectColor", glm::vec3(1.0f, 1.0f, 1.0f));
m_program->SetUniform("ambientStrength", 1.0f);
m_program->SetUniform("transform", projection * view * lightModelTransform);
m_program->SetUniform("modelTransform", lightModelTransform);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
#version 330 core
in vec3 normal;
in vec2 texCoord;
in vec3 position;
out vec4 fragColor;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform float ambientStrength;
uniform float specularStrength; // 추가된 항목
uniform float specularShininess; // 추가된 항목
uniform vec3 viewPos; // 추가된 항목
void main() {
vec3 ambient = ambientStrength * lightColor;
vec3 lightDir = normalize(lightPos - position);
vec3 pixelNorm = normalize(normal);
vec3 diffuse = max(dot(pixelNorm, lightDir), 0.0) * lightColor;
vec3 viewDir = normalize(viewPos - position); // 추가된 항목
vec3 reflectDir = reflect(-lightDir, pixelNorm); // 추가된 항목
float spec = pow(max(dot(viewDir, reflectDir), 0.0), specularShininess); // 추가된 항목
vec3 specular = specularStrength * spec * lightColor; // 추가된 항목
vec3 result = (ambient + diffuse + specular) * objectColor; // 추가된 항목
fragColor = vec4(result, 1.0);
}
reflect(light, normal) : light벡터 방향의 광선이 normal벡터 방향의 표면에 부딪혔을 때 반사되는 벡터를 출력하는 OpenGL내장 함수이다. (입사하는 방향벡터와 픽셀, 면의 Normal Vector를 주면 반사되는 방향벡터를 리턴)
- 현재 카메라의 world space상의 좌표와 픽셀 좌표간의 차를 통해 viewDir(시선벡터)을 계산한다.
- spec : reflectDir과 viewDir간의 내적을 통해 반사광을 많이 보는 정도를 계산, 0~1 사이의 값을 리턴한다.
- specularStrength : 반사광의 정도를 조절
- specularShininess : 반사광의 면적을 조절
- specularShininess 값에 따른 specular 하이라이트의 변화
// light parameter
glm::vec3 m_lightPos { glm::vec3(3.0f, 3.0f, 3.0f) };
glm::vec3 m_lightColor { glm::vec3(1.0f, 1.0f, 1.0f) };
glm::vec3 m_objectColor { glm::vec3(1.0f, 0.5f, 0.0f) };
float m_ambientStrength { 0.1f };
float m_specularStrength { 0.5f }; // 추가된 항목
float m_specularShininess { 32.0f }; // 추가된 항목
ImGui::SliderFloat("ambient strength", &m_ambientStrength, 0.0f, 1.0f);
ImGui::SliderFloat("specular strength", &m_specularStrength, 0.0f, 1.0f); // 추가된 항목
ImGui::DragFloat("specular shininess", &m_specularShininess, 1.0f, 1.0f, 256.0f); // 추가된 항목
m_program->SetUniform("viewPos", m_cameraPos); // 추가된 항목
m_program->SetUniform("lightPos", m_lightPos);
m_program->SetUniform("lightColor", m_lightColor);
m_program->SetUniform("objectColor", m_objectColor);
m_program->SetUniform("ambientStrength", m_ambientStrength);
m_program->SetUniform("specularStrength", m_specularStrength); // 추가된 항목
m_program->SetUniform("specularShininess", m_specularShininess); // 추가된 항목
#version 330 core
in vec3 normal;
in vec2 texCoord;
in vec3 position;
out vec4 fragColor;
uniform vec3 viewPos;
struct Light
{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
struct Material
{
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
void main() {
vec3 ambient = material.ambient * light.ambient;
vec3 lightDir = normalize(light.position - position);
vec3 pixelNorm = normalize(normal);
float diff = max(dot(pixelNorm, lightDir), 0.0);
vec3 diffuse = diff * material.diffuse * light.diffuse;
vec3 viewDir = normalize(viewPos - position);
vec3 reflectDir = reflect(-lightDir, pixelNorm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = spec * material.specular * light.specular;
vec3 result = ambient + diffuse + specular;
fragColor = vec4(result, 1.0);
}
// light parameter
struct Light {
glm::vec3 position { glm::vec3(3.0f, 3.0f, 3.0f) };
glm::vec3 ambient { glm::vec3(0.1f, 0.1f, 0.1f) };
glm::vec3 diffuse { glm::vec3(0.5f, 0.5f, 0.5f) };
glm::vec3 specular { glm::vec3(1.0f, 1.0f, 1.0f) };
};
Light m_light;
// material parameter
struct Material {
glm::vec3 ambient { glm::vec3(1.0f, 0.5f, 0.3f) };
glm::vec3 diffuse { glm::vec3(1.0f, 0.5f, 0.3f) };
glm::vec3 specular { glm::vec3(0.5f, 0.5f, 0.5f) };
float shininess { 32.0f };
};
Material m_material;
if (ImGui::Begin("ui window")) {
if (ImGui::CollapsingHeader("light", ImGuiTreeNodeFlags_DefaultOpen)) // 추가된 항목
{
ImGui::DragFloat3("l.position", glm::value_ptr(m_light.position), 0.01f); // 추가된 항목
ImGui::ColorEdit3("l.ambient", glm::value_ptr(m_light.ambient)); // 추가된 항목
ImGui::ColorEdit3("l.diffuse", glm::value_ptr(m_light.diffuse)); // 추가된 항목
ImGui::ColorEdit3("l.specular", glm::value_ptr(m_light.specular)); // 추가된 항목
}
if (ImGui::CollapsingHeader("material", ImGuiTreeNodeFlags_DefaultOpen)) // 추가된 항목
{
ImGui::ColorEdit3("m.ambient", glm::value_ptr(m_material.ambient)); // 추가된 항목
ImGui::ColorEdit3("m.diffuse", glm::value_ptr(m_material.diffuse)); // 추가된 항목
ImGui::ColorEdit3("m.specular", glm::value_ptr(m_material.specular)); // 추가된 항목
ImGui::DragFloat("m.shininess", &m_material.shininess, 1.0f, 1.0f, 256.0f); // 추가된 항목
}
//...
}
m_program->SetUniform("viewPos", m_cameraPos);
m_program->SetUniform("light.position", m_light.position); // 추가된 항목
m_program->SetUniform("light.ambient", m_light.ambient); // 추가된 항목
m_program->SetUniform("light.diffuse", m_light.diffuse); // 추가된 항목
m_program->SetUniform("light.specular", m_light.specular); // 추가된 항목
m_program->SetUniform("material.ambient", m_material.ambient); // 추가된 항목
m_program->SetUniform("material.diffuse", m_material.diffuse); // 추가된 항목
m_program->SetUniform("material.specular", m_material.specular); // 추가된 항목
m_program->SetUniform("material.shininess", m_material.shininess); // 추가된 항목
이전 코드들을 리펙토링해준다.
정리 , src / context.h 작성
CLASS_PTR(Program)
class Program {
public:
static ProgramUPtr Create(const std::vector<ShaderPtr>& shaders);
static ProgramUPtr Create(const std::string& vertShaderFilename, const std::string& fragShaderFilename);
// ...
void SetUniform(const std::string& name, const glm::vec4& value) const;
// ...
ProgramUPtr Program::Create(const std::string& vertShaderFilename, const std::string& fragShaderFilename)
{
ShaderPtr vs = Shader::CreateFromFile(vertShaderFilename, GL_VERTEX_SHADER);
ShaderPtr fs = Shader::CreateFromFile(fragShaderFilename, GL_FRAGMENT_SHADER);
if (!vs || !fs)
return nullptr;
return std::move(Create({vs, fs}));
}
void Program::SetUniform(const std::string& name, const glm::vec4& value) const
{
auto loc = glGetUniformLocation(m_program, name.c_str());
glUniform4fv(loc, 1, glm::value_ptr(value));
}
private:
Context() {}
bool Init();
ProgramUPtr m_program;
ProgramUPtr m_simpleProgram; // 추가된 항목
m_simpleProgram = Program::Create("./shader/simple.vs", "./shader/simple.fs");
if (!m_simpleProgram)
return false;
m_program = Program::Create("./shader/lighting.vs", "./shader/lighting.fs");
if (!m_program)
return false;
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 transform;
void main() {
gl_Position = transform * vec4(aPos, 1.0);
}
#version 330 core
uniform vec4 color;
out vec4 fragColor;
void main() {
fragColor = color;
}
auto lightModelTransform = glm::translate(glm::mat4(1.0), m_light.position) * glm::scale(glm::mat4(1.0), glm::vec3(0.1f));
m_simpleProgram->Use(); // 추가된 항목
m_simpleProgram->SetUniform("color", glm::vec4(m_light.ambient + m_light.diffuse, 1.0f)); // 추가된 항목
m_simpleProgram->SetUniform("transform", projection * view * lightModelTransform);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0); // 추가된 항목
m_program->Use(); // 추가된 항목
struct Material {
sampler2D diffuse; // 추가된 항목
vec3 specular;
float shininess;
};
uniform Material material;
void main() {
vec3 texColor = texture2D(material.diffuse, texCoord).xyz; // 추가된 항목
vec3 ambient = texColor * light.ambient; // 추가된 항목
vec3 lightDir = normalize(light.position - position);
vec3 pixelNorm = normalize(normal);
float diff = max(dot(pixelNorm, lightDir), 0.0);
vec3 diffuse = diff * texColor * light.diffuse; // 추가된 항목
// material parameter
struct Material {
TextureUPtr diffuse; // 추가된 항목
glm::vec3 specular { glm::vec3(0.5f, 0.5f, 0.5f) };
float shininess { 32.0f };
};
Material m_material;
m_material.diffuse = Texture::CreateFromImage(Image::Load("./image/container2.png").get());
m_program->Use();
m_program->SetUniform("viewPos", m_cameraPos);
m_program->SetUniform("light.position", m_light.position);
m_program->SetUniform("light.ambient", m_light.ambient);
m_program->SetUniform("light.diffuse", m_light.diffuse);
m_program->SetUniform("light.specular", m_light.specular);
m_program->SetUniform("material.diffuse", 0); // 추가된 항목
m_program->SetUniform("material.specular", m_material.specular);
m_program->SetUniform("material.shininess", m_material.shininess);
glActiveTexture(GL_TEXTURE0); // 추가된 항목
m_material.diffuse->Bind(); // 추가된 항목
동일한 형태의 specular를 적용하고 있기 때문에 생기는 문제이다.
- specular color도 texture map으로 대체하면 해결된다.
spceular map을 읽어오고 적용하도록 코드 수정
수정 , shader / lighting.fs 작성
struct Material {
sampler2D diffuse;
sampler2D specular; // 추가된 항목
float shininess;
};
void main()
{
//...
vec3 specColor = texture2D(material.specular, texCoord).xyz; // 추가된 항목
vec3 viewDir = normalize(viewPos - position);
vec3 reflectDir = reflect(-lightDir, pixelNorm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = spec * specColor * light.specular; // 추가된 항목
//...
}
// ... in Context declaration
struct Material {
TextureUPtr diffuse;
TextureUPtr specular; // 추가된 항목
float shininess { 32.0f };
};
m_material.diffuse = Texture::CreateFromImage(Image::Load("./image/container2.png").get());
m_material.specular = Texture::CreateFromImage(Image::Load("./image/container2_specular.png").get()); // 추가된 항목
m_program->Use();
m_program->SetUniform("viewPos", m_cameraPos);
m_program->SetUniform("light.position", m_light.position);
m_program->SetUniform("light.ambient", m_light.ambient);
m_program->SetUniform("light.diffuse", m_light.diffuse);
m_program->SetUniform("light.specular", m_light.specular);
m_program->SetUniform("material.diffuse", 0);
m_program->SetUniform("material.specular", 1); // 추가된 항목
m_program->SetUniform("material.shininess", m_material.shininess);
glActiveTexture(GL_TEXTURE0);
m_material.diffuse->Bind();
glActiveTexture(GL_TEXTURE1); // 추가된 항목
m_material.specular->Bind(); // 추가된 항목