momodudu.zip

#7 Lighting - 조명효과, 퐁 조명 모델(Phong lighting model) 본문

Graphics/OpenGL

#7 Lighting - 조명효과, 퐁 조명 모델(Phong lighting model)

ally10 2019. 9. 27. 13:21

이번 포스팅은 그래픽스에서 아주 기초적이고 간단한편이지만, 널리 쓰이는 퐁 조명 모델과 조명 적용에 관한 전반적인 설명이다.

 

퐁 조명은 총 4가지 항의 합으로 이루어진다. Diffuse, Specular, Ambient, Emissive light이다.

Diffuse는 난반사를 의미하고, Specaular는 정반사, Ambient는 간접조명, Emissive light는 자체 발산광이라고 보면 된다.

 

<Diffuse>

난반사를 의미하며, 광원으로부터 점P까지의 벡터를 l, 점 P의 normal을 n으로 표현한다. 두 벡터 사이의 각이 작으면 작을수록 점 P는 더 많은 조명을 받게 될것이다. 즉, 벡터 l과 n이 정규화 되어있다면 점 P로 들어오는 빛의 양은 두 벡터의 내적값이라고 정의할 수 있다. 대신 조명을 받거나, 받지 않거나 둘중 하나이고 -값은 정의될 수 없으므로 사이각이 둔각인 경우를 대비하여 아래와 같이 조명의 양을 정의한다.

s는 광원의 RGB색상, m은 물체 자체의 diffuse 계수라고 볼 수 있다. 별도로 구하는 식이 있는건 아니고, 상수값이라고 생각하면 된다.

 

<Specualr>

광원으로부터 들어오는 빛에서 반사된 빛 벡터인 r, 그리고 p를 보는 카메라벡터 v가 추가되었다. specualr항은 정반사되어 나온 빛이 카메라로 향했을 때, 점 P에 대해 하이라이트가 얼마나 들어올지를 결정하는 항이다. 즉, 카메라와 반사벡터의 관계를 정의한다. 위 그림에서 카메라벡터 v와 반사벡터 r사이의 각이 0이 된다면, 하이라이트는 최대가 된다. r 역시 l과 v의 내적으로 구한다. 유도 과정이 있는데, 이건 생략...

 

sh는 shineness수치이다. 위에서 설명했듯이, r과 v 사이각이 0이라면 highlight는 최대가 될것이다. 이 내적값은 1이 될것이고 shineness수치에 관계없이 항상 1의 값을 유지하게 된다. s와 m도 위에 Diffuse에 설명한것과 마찬가지다. 

 

 

<Ambient>

Ambient는 말그대로 간접조명을 의미한다. 광원과 상관없이 주변으로부터 들어오는 빛이다. 즉, 모든 source로부터 모든 방향을 따라서 P로 들어오므로 normal이나 camera벡터와는 전혀 상관이 없는 독립적인 값들이다. 따라서 아래의 식처럼 정의된다.

 

<Emissive Light>

자체 발산광이다. 즉 순수하게 material에 의해서만 결정이 되므로 아래와 같이 정의된다.

 

위의 네가지 항을 총합해보면, Phong 조명 모델의 식은 아래와 같이 정의할 수 있다.

 

<Per vertex lighting/Per fragment lighting>

이러한 수식을 적용할 땐 버텍스별로 버텍스쉐이더에서 정의할 수 있을것이고, fragment 별로 fragment shader별로 정의할 수 있다. 상황에따라 적절한걸 사용하면 되는데, per vertex의 경우 메쉬의 복잡도가 적을수록 조명 품질이 떨어진다. 당연할 수 밖에 없는게, 삼각형을 이루는 3개의 정점에 대해서 조명을 적용했을때와 삼각형을 이루는 모든 fragment들에 적용했을때 품질이 다를 수 밖에 없다. 즉, vertex별 조명을 적용하려면 메쉬의 복잡도가 높아야한다.

 

per fragment의 경우, vertex shader로 전달된 노말을 그대로 전달받게 되는데, rasterizer단계에서 이 노말이 scan conversion, 즉 선형 보간이 프래그먼트별로 이뤄지면서 노말이 더 디테일해진다. 이 노말을 기반으로 조명 작업을 하게 되는 것이다. 고급 렌더링 엔진에서는 per fragment 를 자주 사용한다.

 

<실제로 적용해보기>

1) Diffuse

일단 Diffuse부터 적용해보자. Diffuse항에서 필요한건 점P에 대한 normal과, 광원으로부터 점 P로 들어오는 벡터다. normal의 경우 직접 쉐이더로 전달해주면 되고, 우리는 앞에서 봤던 광원벡터에 대한 계산을 먼저 진행한다.

 

먼저 버텍스 쉐이더이다. 우리는 프래그먼트별 조명을 적용할것이므로, 버텍스쉐이더에서는 정점간의 벡터만 정의해서 프레그먼트쉐이더로 넘겨주면 된다.

 

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 wVertex;
layout(location = 1) in vec2 wTexCoord;
layout(location = 2) in vec3 wNormal;

uniform mat4 uMVPMat;
uniform mat4 uMVMat;
uniform mat4 uMMat;
uniform mat4 uVMat;
uniform sampler2D uSampler;
uniform vec3 uLightPos_worldSpace;

out vec2 texCoord;

out vec3 pos_worldSpace;
out vec3 eyeDir_cameraSpace;
out vec3 lightDir_cameraSpace;
out vec3 normal_cameraSpace;

void main(){

    gl_Position = uMVPMat*vec4(wVertex,1.0);
	
	pos_worldSpace = (uMMat*vec4(wVertex,1.0)).xyz;
	
	vec3 pos_cameraSpace = (uMVMat * vec4(wVertex,1.0)).xyz;
	eyeDir_cameraSpace = vec3(0.0) - pos_cameraSpace;
	
	vec3 lightPos_cameraSpace = (uVMat*vec4(uLightPos_worldSpace, 1.0)).xyz;
	lightDir_cameraSpace = lightPos_cameraSpace+eyeDir_cameraSpace;
	
	normal_cameraSpace = (uMVMat* vec4(wNormal,0)).xyz;
	
	texCoord = wTexCoord;
}

 vertex shader가 조금 복잡해졌는데... 한줄한줄 분석해보자.

먼저 첫번째줄은 원래와 똑같이 clipping space의 vertex를 넘긴다.

 

	pos_worldSpace = (uMMat*vec4(wVertex,1.0)).xyz;

world space의 pos를 따로 구하기위해, uniform 변수로 Model matrix만 전달했다.

wVertex는 model space로 정의되어 있으므로, world space로 바꿔주기 위해 model matrix를 곱해준다.

 

	vec3 pos_cameraSpace = (uMVMat * vec4(wVertex,1.0)).xyz;
	eyeDir_cameraSpace = vec3(0.0) - pos_cameraSpace;

이제 camera 시점에서의 pos를 구하기 위한 작업이다. camera space는 View*Model matrix의 결과값이므로, mdoel space의 vertex에 유니폼으로 따로 전달해준 MV Matrix를 곱해준다.

이렇게 되면 camera space에서의 vertex위치를 구할 수 있는데, 우리는 카메라 시야에서 봤을 때 vertex를 원하는 것이므로 camera space에서 at, 즉 원점으로 정의된 eye에서 vertex position을 빼준다.

 

	vec3 lightPos_cameraSpace = (mVMat*vec4(uLightPos_worldSpace, 1.0)).xyz;
	lightDir_cameraSpace = lightPos_cameraSpace+eyeDir_cameraSpace;

이제 광원에 대한 정의이다. 광원의 위치는 uniform 변수로 전달해줄것이며, world space로 정의한다. 물론 model 로 정의해서 Model matrix를 곱해줘도 상관없다. Camera space에서의 광원을 정의하기 위해 MV Mat을 곱하고, 광원벡터 l을 구하기위해 광원벡터의 camera space 위치 + eyedirection vector를 더해주었다.

이는 카메라 space는 카메라의 eye가 원점으로 정의되어있고, 위에서 eye_direction vector는 정점으로 가는값이므로, 이 값은 light pos에서 정점으로 가는 벡터가 될 것이다. 

 

	normal_cameraSpace = (uMVMat* vec4(wNormal,0)).xyz;

마지막으로 model에서 정의된 노말도 camera space로 바꿔준다. 이제 vertex shader에서 넘겨줄 값은 다 넘겨주었다. 모든 계산은 fragment shader의 몫이다.

#version 330 core

// Ouput data
in vec2 texCoord;

in vec3 pos_worldSpace;
in vec3 eyeDir_cameraSpace;
in vec3 lightDir_cameraSpace;
in vec3 normal_cameraSpace;

out vec3 color;

uniform sampler2D uSampler;
uniform vec3 uLightColor;
uniform float uLightPower;
uniform vec3 uLightPos_worldSpace;

void main()
{
	vec3 mDiffuse = texture(uSampler, texCoord).rgb;
	
	float dist = length(uLightPos_worldSpace-pos_worldSpace);
	
	vec3 n = normalize(normal_cameraSpace);
	vec3 l = normalize(lightDir_cameraSpace);
	
	float cosTheta = clamp(dot(n,l),0,1);	
	
	color = cosTheta*mDiffuse*uLightColor*uLightPower/(dist*dist);

}

위에서 정의한 diffuse항에서 material은 실제 해당 텍스처의 rgb값이라고 보면 된다. 

 

그리고 광원과 점 P의 거리에 반비례하는 컬러값을 갖도록 distance도 구해준다.

normal과 light vector를 각각 normalize하고, 이를 내적하여 clamp로 max(n*l,0)을 구현한다.

그리고 정의한 diffuse처럼 수식으로 컬러값을 넣어주면 된다.

 

Diffuse가 적용된 모습이다. 빛이 잘 비치지 않는 부분은 어둡고, 비치는 부분은 밝다. 광원이 우상단에 있음을 알 수 있다.

 

2) Specular

이제 Specular, 즉 광원에 따른 highlight를 넣어줄것이다.

필요한 값은 모두 vertex shader로 이미 다 전달이 됐고, specular에서 필요한 reflection vector를 계산한다.

 

	// specular
	vec3 v = normalize(eyeDir_cameraSpace);
	vec3 r = reflect(l,n);
	
	float cosAlpha = clamp(dot(v,r), 0,1);
    
    	color = cosTheta*mDiffuse*uLightColor*uLightPower/(dist*dist) +
			pow(cosAlpha,5)*mSpecular*uLightColor*uLightPower/(dist*dist);

 

먼저 카메라 direction vector인 v를 정규화하고, 광원벡터 l과 n의 사이각을 갖는 reflection vector r을 정규화하여 마찬가지로 둘을 내적하고 [0,1]사이로 만든다. 그리고 마지막 output color값에 specular항을 추가한다. 식을 보면 알수있듯이, 위 식에서 정의한 shineness계수를 5로 정의했다.

 

 

Diffuse만 적용 -> Specular까지 적용. 육면체라 티가 잘 안나지만, 구 모델로 highlight 적용 전후를 비교해보면 차이가 꽤 난다.

 

3) Ambient

ambient는 별다른 벡터 계산 없이 임의로 추가해주면 된다. 내 경우 아래와 같이 정의했다.

 

vec3 mAmbient = vec3(0.2)*mDiffuse;
    
color = cosTheta*mDiffuse*uLightColor*uLightPower/(dist*dist) +
		pow(cosAlpha,5)*mSpecular*uLightColor*uLightPower/(dist*dist) +
		mAmbient;

Diffuse -> Diffuse + Sepcular -> Diffuse + Specular + Ambient를 모두 조합한 결과물이다. emissive의 경우 생략한다. ambient와 비슷한 방식으로 직접 컬러값을 지정해서 발산광이 어느정도 되는지 지정해준 후, color에 추가해주면 된다.

'Graphics > OpenGL' 카테고리의 다른 글

#9 Geometry shader  (0) 2022.08.02
#6 Texturing  (0) 2019.09.26
#5 Vertex Processing - 변환 매트릭스  (0) 2019.09.25
#4 vertex array, buffer objects  (0) 2019.09.25
#3 Model/View/Projection Matrix - MVP Matrix glm이용해서 써보기  (0) 2019.09.08