link : https://learnopengl.com/Lighting/Basic-Lighting
Basic Lighting
현실 세계의 조명은 매우 복잡하며 너무 많은 요소에 따라 달라지므로 제한된 처리 능력으로 계산할 수 없다.
따라서 OpenGL의 조명은 처리가 훨씬 간편하고 비교적 비슷하게 보이는 단순화된 모델을 사용해 현실의 근사치를
기반으로 한다. 이 조명 모델은 우리가 이해할 수 있는 빛의 물리학을 기반으로 한다.
이러한 모델 중 하나는 Phong 조명 모델이라고 한다. Phong 모델의 주요 구성 요소는 주변, 확산 및 반사 조명의
3가지 구성 요소로 구성된다. 아래에서 이러한 조명 구성 요소의 실제 모습을 볼 수 있습니다.
- Ambient lighting : 어둠이 있어도 어딘가에는 세계의 어떤 곳의 빛이 있어 오브젝트가 거의 완전히 어두워지지 않는다.
이것을 시뮬레이트 하기 위해 항상 객체에 약간의 색상을 주는 주변 조명 상수를 사용한다.
- Diffuse lighting : 밝은 물체가 물체에 미치는 방향 충돌을 시뮬레이트한다. 이것은 조명 모델에서 가장 시각적으로
중요한 구성 요소이다. 물체의 일부가 광원을 향할수록 밝아진다.
- Specular lighting : 반짝이는 물체에 나타나는 빛의 밝은 부분을 시뮬레이션한다. 반사 하이라이트는 종종 객체의
색상보다 빛의 색상에 더 기울어진다. 반사 하이라이트는 종종 객체의 색상보다 빛의 색상에 기울어진다.
시각적으로 흥미로운 장면을 만들기 위해서는 최소한 3가지 조명 구성 요소를 시뮬레이트하는 것이 좋다. 가장 간단한 것에서부터 시작하겠다.
Ambient lighting
빛은 보통 하나의 광원에서 오는 것이 아니라 즉시 볼 수 없는 경우에도 우리 주변에 흩어져있는 많은 광원에서 나온다.
빛의 속성 중 하나는 그것이 직접 주변 부근에 있지 않은 지점에 도달하는 많은 방향으로 흩어져 튈 수 있다는 것이다.
따라서 빛은 다른 표면에서 반사할 수 있으며 물체의 조명에 간접적인 영향을 미칠 수 있다. 이것을 고려한 알고리즘을
global illumination algorithm이라고 부르지만 코스트가 높고 복잡하다.
우리는 복잡하고 값 비싼 알고리즘의 팬이 아니기 때문에 ambient lighting이라는 매우 단순한 모델, 즉 주변 조명을
사용해 시작할 것이다. 이전 섹션에서 보았듯이 객체 파편의 최종 결과 색상에 추가하는 작은 상수(밝은) 색상을
사용하므로 직접 광원이 없는 경우에도 항상 흩어져있는 빛이 있는 것처럼 보인다.
주변 조명을 장면에 추가하는 것은 정말 쉽다. 우리는 빛의 색을 취해 그것을 작은 상수 요소로 곱하고
이것을 개체의 색에 곱해 조각의 색으로 사용한다:
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
이제 프로그램을 실행하면 조명의 첫 번째 단계가 객체에 성공적으로 적용되었음을 알 수 있습니다. 물체는 아주 어둡지만
주변 조명이 적용되었기 때문에 완전하지는 않다. (다른 쉐이더를 사용하기 때문에 라이트 큐브는 영향을 받지 않는다)
다음과 같이 보일 것이다:
Diffuse lighting
주변 조명만으로는 가장 흥미로운 결과는 나오지 않지만 확산 조명은 객체에 시각적으로 중요한 영향을 미치기 시작한다.
확산 조명은 광원에서 오는 광선에 조각이 더 가깝게 위치할수록 오브젝트의 밝기가 높아진다. 확산 조명을 보다 잘 이해하려면
다음 이미지를 살펴보아라:
왼쪽에 우리는 물체의 단편을 목표로 하는 광선을 가진 광원을 찾는다. 우리는 광선이 어떤 부분에 닿는지를 측정해야한다.
광선이 물체의 표면에 수직인 경우 빛이 가장 큰 영향을 준다. 광선과 파편 사이의 각을 측정하기 위해 조각의 표면에 수직인
벡터인 법선 벡터 (yellow vector)를 사용한다. 우리는 나중에 그것을 얻을 것이다. 그러면 두 벡터 사이의 각도는 내적을 사용해
쉽게 계산할 수 있다.
변형 튜토리얼에서 두 단위 벡터 사이의 각도가 작을수록 내적 값이 1로 기울어짐을 기억할 수 있다. 두 벡터 사이의 각도가
90도로 되면 내적은 0이 된다. θ도 마찬가지다. θ가 클수록 빛이 조각의 색에 미치는 영향이 적다.
그 결과 생성된 내적 생성물은 조각의 색상에 미치는 빛의 영향을 계산하는데 사용할 수 있는 스칼라를 반환해 빛에 대한 방향에
따라 조각이 다르게 표시된다.
그렇다면 diffuse lighting을 계산하려면 무엇이 필요할까?
- Normal vector : 정점의 표면에 수직인 벡터이다.
- Directed light ray : 빛의 위치와 조각의 위치 사이의 차이. 이 광선을 계산하려면 빛의 위치 벡터와 조각의 위치 벡터가 필요하다.
Normal vectors
법선 벡터는 꼭지점의 표면에 수직인 (단위) 벡터이다. 정점 자체는 아무런 표면도 없기 때문에 주변 정점을 사용해 정점의 표면을
계산해 법선 벡터를 검색한다. 교차곱을 사용해 모든 큐브의 정점에 대한 법선 벡터를 계산하는데 약간의 트릭을 사용할 수 있지만
3D 큐브는 복잡한 모양이 아니므로 간단히 정점 데이터에 수동으로 추가할 수 있다. 업데이트된 정점 데이터 배열은 여기에서
찾을 수 있다. 법선이 실제로 큐브의 평면 표면에 수직인 벡터임을 나타낸다.
정점 배열에 여분의 데이터를 추가했으므로 조명의 정점 쉐이더를 업데이트해야한다.
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
...
이제 각 꼭지점에 법선 벡터를 추가하고 꼭지점 쉐이더를 업데이트했으므로 꼭지점 특성 포인터를 업데이트해야한다.
램프 오브젝트는 정점 데이터에 대해 동일한 정점 배열을 사용하지만 램프 쉐이더에는 새로 추가된 법선 벡터를
사용하지 않는다. 램프의 쉐이더 또는 속성 구성을 업데이트 할 필요는 없지만 새 꼭지점 배열의 크기를 반영하기 위해
적어도 vertexattribpointer를 수정해야한다:
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnable VertexAttribArray (0);
각 꼭지점의 처음 3개의 부동 소수점을 사용하고 마지막 3개의 부동 소수점을 무시하기만 하면 보폭 매개 변수를 float 크기의
6배로 업데이트하면 된다.
모든 조명 계산은 조각 쉐이더에서 수행되므로 법선 벡터를 정점 쉐이더에서 조각 쉐이더로 전달해야한다. 그것을 해보자:
out vec3 Normal;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
Normal = aNormal;
}
남은 것은 조각 쉐이더에서 상응하는 입력 변수를 선언하는 것이다:
in vec3 Normal;
Calculating the diffuse color
이제 우리는 각 꼭지점에 대한 법선 벡터를 가지지만 여전히 빛의 위치 벡터와 단편의 위치 벡터가 필요하다.
광원의 위치는 단 하나의 정적 변수이므로 조각 쉐이더에서 유니폼으로 간단하게 선언할 수 있다.
uniform vec3 lightPos;
그런 다음 게임 루프에서 유니폼을 업데이트한다. 이전 튜토리얼에서 선언한 lightPos 벡터를 광원의 위치로 사용한다:
lightingShader.setVec3("lightPos", lightPos);
그렇다면 우리가 필요한 마지막 조각은 실제 조각의 위치이다. 우리는 world 공간에서 모든 조명 계산을 수행할 것이므로
월드 공간에 있는 정점 위치를 원한다. 꼭지점 위치 속성에 모델 행렬만 곱해 이를 월드 공간 좌표로 변환하면 된다.
이것은 정점 쉐이더에서 쉽게 수행할 수 있다. 그래서 출력 변수를 선언하고 월드 공간 좌표를 계산해보자:
out vec3 FragPos;
out vec3 Normal;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
}
마지막으로 해당 입력 변수를 조각 쉐이더에 추가해라:
in vec3 FragPos;
이제 필요한 모든 변수가 설정되었으므로 조각 쉐이더에서 조명 계산을 시작할 수 있다.
먼저 계산해야 할 것은 광원과 조각의 위치 사이의 방향 벡터이다. light의 방향 벡터는 라이트의 위치 벡터와 조각의
위치 벡터 사이의 차이 벡터이다. 변형 튜토리얼에서 기억할 수 있듯이 두 벡터를 빼서 이 차이를 쉽게 계산할 수 있다.
또한, 모든 관련 벡터가 단위 벡터로 끝나기를 원하므로 일반 방향 벡터와 결과 방향 벡터를 모두 정규화한다.
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
다음으로 우리는 norm과 lightDir 벡터의 내적을 취해 빛이 현재 조각에 미치는 실제 확산 영향을 계산하려고 한다.
결과 값에 빛의 색을 곱해 확산 요소를 얻으므로 두 벡터 사이의 각도가 클수록 확산 요소가 더 어두워진다:
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
두 벡터 사이의 각도가 90도 보다 큰 경우 내적 결과는 실제로 음수가 되고, 결국 음수 확산 구성 요소가 된다.
이러한 이유 때문에 우리는 두 매개 변수 중 가장 높은 값을 반환하는 max 함수를 사용해 확산되는 구성 요소가
절대 음수가 되지 않도록 한다. 네거티브 컬러의 조명은 실제로 정의되지 않으므로, 당신이 그 괴팍한 예술가 중
한 명이라면 제외시키지 않는 것이 좋다.
ambient와 diffuse 구성 요소를 모두 가지게 되었으므로 두 색상을 서로 더한 다음 결과에 객체의 색상을 곱해
결과로 생성된 단편의 출력 색상을 얻는다:
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
응용 프로그램 (및 쉐이더)이 성공적으로 컴파욀되면 다음과 같은 내용이 표시된다:
diffuse lighting을 사용하면 큐브가 실제로 큐브처럼 보이기 시작한다. 머리에 있는 법선 벡터를 시각화하고,
큐브 주위를 이동해 빛의 방향과 각도가 커질수록 조각이 어두워지는 것을 확인해라.
One last thing
현재 우리는 법선 벡터를 정점 쉐이더에서 조각 쉐이더로 직접 전달하고 있다. 그러나 조각 쉐이더에서 수행한 계산은
모두 월드 공간 좌표로 수행되므로 일반적인 벡터를 월드 공간 좌표로 변환하면 안될까? 기본적으로 그렇다.
그러나 모델 행렬에 단순히 곱하는 것만큼 간단하지 않다.
우선, 법선 벡터는 방향 벡터일 뿐이며 공간의 특정 위치를 나타내지 않는다. 또한, 법선 벡터에는 동질의 좌표가
없다. 이것은 번역기 법선 벡터에 영향을 끼치지 않아야 한다는 것을 의미한다. 따라서 법선 벡터에 모델 행렬을
곱하고 싶다면 모델 행렬의 왼쪽 위 3x3 행렬을 취해 행렬의 변환 부분을 제거하고 싶다.
법선 벡터에 적용하려는 유일한 변환은 축척 및 회전 변환이다.
두 번째, 모델 행렬이 비 균일 스케일을 수행하면 법선 벡터가 표면에 수직이 아닌 방식으로 꼭지점이 변경되어
이러한 모델 행렬로 법선 벡터를 변형 할 수 없다. 다음 이미지는 비 균일 스케일링과 같은 모델 행렬이 법선
벡터에 미치는 영향을 보여준다.
우리가 비 균일 스케일을 적용할 때마다 법선 벡터는 더 이상 해당 표면에 수직이 아니기 때문에 조명에 왜곡된다.
이 동작을 수정하는 방법은 법선 벡터에 맞게 조정된 다른 모델 행렬을 사용하는 것이다. 이 행렬을 법선 행렬이라고
부르며 법선 벡터를 잘못 스케일링하는 효과를 제거하기 위해 몇 가지 선형 대수 연산을 사용한다. 이 행렬이 실제로
어떻게 계산되는지 알고 싶다면 다음 기사를 보아라.
정점 쉐이더에서는 모든 행렬 유형에서 작동하는 정점 쉐이더의 역함수와 join 함수를 사용해 이 normal 행렬을
직접 생성할 수 있다. 우리는 또한 행렬은 3x3 행렬에 캐스트해 변환 속성을 잃지 않고 vec3 법선 벡터를 곱할 수
있는지 확인한다:
Normal = mat3(transpose(inverse(model))) * aNormal;
diffuse lighting 섹션에서는 오브젝트 자체에 스케일링 연산을 수행하지 않았기 때문에 조명이 좋았다. 그래서 정규
매트릭스를 사용할 필요가 없었으며 법선을 모델 매트릭스에 곱할 수 있었다. 그러나 비 균일 스케일을 수행하는 경우
법선 벡터에 일반 매트릭스를 곱하는 것이 중요하다.
Specular Lighting
모든 조명 계산으로 고갈되지 않은 경우, Specular 하이라이트를 추가해 Phong 조명 모델을 마무리 할 수 있다.
diffuse lighting과 마찬가지로 Specular(반사) lighting은 조명의 방향 벡터 및 대상의 법선 벡터를 기반으로하지만
이번에는 플레이어가 조각을 보고 있는 방향에서 보는 방향에도 기반한다. Specular lighting은 빛의 반사 특성을
기반으로 한다. 우리가 물체의 표면을 거울이라고 생각하면 표면에 빛이 반사되는 것을 볼 때마다 반사 조명이
가장 강하다. 다음 이미지에서 이 효과를 볼 수 있다:
우리는 법선 벡터 주위로 빛의 방향을 반사해 반사 벡터를 계산한다. 그런 다음 이 반사 벡터와 뷰 방향 사이의 각도
거리를 계산하고, 그 사이의 각도가 가까울수록 반사 조명의 영향이 커진다. 결과는 객체를 통해 반사되는 빛의 방향을
볼 때 약간의 하이라이트를 볼 수 있다는 것이다.
뷰 벡터는 뷰어의 월드 공간 위치와 조각의 위치를 사용해 계산할 수 있는 반사 조명에 필요한 추가 변수 중 하나이다.
그런 다음 우리는 반사의 강도를 계산하고, 이를 밝은 색상으로 곱한 다음 이를 결과 주변 및 확산 구성 요소에 추가한다.
뷰어의 월드 공간 좌표를 얻으려면 카메라 객체의 위치 벡터를 가져온다. 이제 조각 쉐이더에 다른 유니폼을 추가하고 조각
쉐이더에 해당 카메라 위치 벡터를 전달한다:
uniform vec3 viewPos;
lightingShader.setVec3("viewPos", camera.Position);
이제 우리는 모든 필수 변수를 가지므로 정반사 강도를 계산할 수 있다. 우선 우리는 specular 강조 값을 정의해 반투명 강조를
중간 밝음색으로 지정해 너무 많은 영향을 미치지 않도록 한다.
float specularStrength = 0.5;
이 값을 1.0f로 설정하면 coral 큐브에 비해 너무 밝은 반사 성분을 얻을 수 있다. 다음 튜토리얼에서는 이러한 모든 조명 강도를
적절히 설정하는 방법과 조명 강도가 개체에 미치는 영향에 대해 설명한다. 다음으로 우리는 뷰 방향 벡터와 법선 축을 따른 해당
반사 벡터를 계산한다:
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
LightDir 벡터는 무효화된다. reflect 함수는 첫 번째 벡터가 광원에서 조각 위치를 가리킬 것으로 예상하지만 lightDir 벡터는 현재
조각을 광원쪽으로 향하고 있다. 올바른 반사 벡터를 얻으려면 lightDir 벡터를 먼저 negating해 방향을 바꾼다. 두 번째 인수는
법선 벡터를 기대하므로 정규화된 norm 벡터를 제공한다:
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
먼저 뷰 방향과 반사 방향 사이의 내적을 계산한 다음 (음사가 아닌지 확인) 32의 거듭 제곱으로 올린다. 이 32 값은 밝은 영역의
밝기 값이다. 물체의 광택 값이 높을수록 빛을 적절히 반사하는 대신 물체를 산란시키기보다 빛을 반사하기 때문에 하이라이트가
작아진다. 아래에서 다양한 shininess 값의 시각적 영향을 보여주는 이미지를 볼 수 있다:
우리는 specular 구성 요소가 너무 지배적이어서 지수를 32로 유지하기를 원하지 않는다. 남은 유일한 작업은 이를
ambient 및 diffuse 구성 요소에 추가하고 결합된 결과에 객체의 색상을 곱하는 것이다:
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
이제 Phong 조명 모델의 모든 조명 구성 요소를 계산했다. 관점에 따라 다음과 같은 것을 보아야한다:
Phong 조명 모델이 정점 쉐이더에서 구현될 때 Phong 쉐이딩 대신 Gouraud 쉐이딩이라고 불린다. 보간으로 인해 조명이 약간 꺼져 보인다. Phong 음영은 훨씬 매끄러운 조명 결과를 제공한다.
이제 쉐이더가 얼마나 강력한지 알기 시작해야한다. 정보가 거의 없으므로 쉐이더는 조명이 모든 객체의 조각 색상에
어떤 영향을 미치는지 계산할 수 있다. 다음 튜토리얼에서는 조명 모델로 수행할 수 있는 작업에 대해 자세히 설명한다.
** ** ** ** ** ** ** ** ** **
lightPos을 옮겨가면서 조명을 바꾸어봤는데 꽤나 만족스럽게 나온다. 이 튜토리얼에서는 정말
자세한 부분까지 잘 알려줘서 좋다. pos을 변경할때 전역변수로 바꾸고 한 번 해보려고 했는데 자꾸
에러가 떠서 보았더니 mat4를 mat3로 선언했더라.. 실수 자제하자! 다음 튜토리얼도 화이팅
'Game > Graphics' 카테고리의 다른 글
Learn OpenGL - Lighting : Lighting maps (0) | 2018.08.17 |
---|---|
Learn OpenGL - Lighting : Materials (0) | 2018.08.16 |
Learn OpenGL - Lighting : Colors (0) | 2018.08.14 |
Learn OpenGL - Getting started : Camera (0) | 2018.08.12 |
Learn OpenGL - Getting started : Coordinate Systems (0) | 2018.08.10 |