-----------------------------------------------------------------
Light casters
우리가 이제까지 사용했던 모든 빛은 공간에서 하나의 점인 단일 source로부터 왔었다. 그것은 좋은 결과를 주지만, 현실 세계에서는 각각 다르게 행동하는 여러가지 종류의 빛이 있다. 객체에게 빛을 던지는 광원은 light caster라고 불려진다. 이 튜토리얼에서 우리는 light casters의 다른 종류들에 대해 이야기 할 것이다. 다른 광원들을 재현하는 것을 배우는 것은 아직 너의 환경을 더 부유하게 하는 너의 toolbox의 다른 tool이다.
우리는 처음에 방향이 있는 빛을 이야기하고, 그러고나서 우리가 이전에 가졌던 것의 확장인 point light를 다루고, 마지막으로 spotlight를 다룰 것이다. 다음 튜토리얼에서 우리는 이러한 다른 빛의 여러 종류들을 한 scene에 합칠 것이다.
Directional Light
광원이 멀리 있을 때, 광원으로부터 오는 광선은 서로에게 거의 평행하다. 그것은 모든 광선들이 같은 방향으로부터 오는 것처럼 보인다. 그 오브젝트 또는 보는 사람이 어디에 있는지 상관없이. 한 광원이 무한하게 멀리가도록 모델링 될 때, 그것은 directional light라고 불려진다. 왜냐하면 모든 광선들은 같은 방향을 갖기 때문이다; 그것은 광원의 위치와 독립적이다.
directional light source의 좋은 예는 우리가 알듯이 태양이다. 태양은 무한히 우리로부터 멀리있는것은 아니지만, 그것은 꽤 멀리 있어서, 우리는 그것이 조명 계산에 있어서 무한히 멀리 있다고 인지할 수 있다. 태양으로 부터 오는 모든 광선들은 우리가 다음의 이미지에서 볼 수 있듯이 평행한 광선으로 모델링 된다.
모든 광선들이 평행하기 때문에, 각 오브젝트가 광원의 위치와 어떻게 관련있는지는 중요하지 않다. 왜냐하면 빛의 방향은 scene에서 각 오브젝트에게 모두 같기 떄문이다. 빛의 방향 벡터가 갖게 되기 때문에, 그 조명 계산들은 scene에서 각 오브젝트에 대해 비슷할 것이다.
우리는 위치 벡터 대신에 빛의 방향벡터를 정의하여 direction light같은 것을 모델링 할 수 있다. 쉐이더 계산은 대개 우리가 빛의 position vector로 lightDir를 계산하는 대신에, 빛의 direction vector를 사용하는 이번만을 제외하고 같게 남아 있다.
struct Light { // vec3 position; // No logner necessary when using directional lights. vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; } ... void main() { vec3 lightDir = normalize(-light.direction); }
우리가 처음에 light.direction vector를 음수화 시킨 것을 주목해라. 우리가 이제까지 사용했던 조명 계산은 빛의 방향이 fragment에서 광원쪽으로 향하는 방향이 되도록 기대하지만, 사람들은 일반적으로 광원으로 부터 가리키는 global direction으로서 directional light를 명시하기를 선호한다. 그러므로 우리는 그것의 방향을 바꾸기 위해서 global light direction vector를 음수화 해야만 한다; 그것은 이제 광원쪽으로 가리키는 direction vector이다. 또한, input vector가 단위 vector라고 가정하는 것은 현명하지 않기 때문에, 그 벡터를 확실히 표준화하려고 해라.
최종 lightDir 벡터는 그러고나서 diffuse와 specular computations에서 쓰인 저번 처럼, 사용된다.
directional light가 모든 다양한 오브젝트들에게 같은 효과를 갖는다는 것을 확실히 증명하기 위해, 우리는 Coordinate systems 튜토리얼의 끝에 있는 container party scene에 다시 방문한다. 너가 그 party를 놓친 경우에, 우리는 처음에 10개의 다른 위치를 정의했었고, 컨테이너마다 다른 model matrix를 만들었었다. 그리고 거기에서 각 model matrix는 적절한 local-to-world transformation을 포함한다:
또한, 실제로 광원의 방향을 명시하는 것을 잊지말아라. (우리가 방향을 광원으로부터의 방향으로서 정의했다는 것에 주목해라; 너는 빠르게 빛의 방향이 아래로 향하고 있음을 알 수 있다):
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
Green Box!
우리는 지금 잠시동안 빛의 위치와 방향벡터를 넘기고 있지만, 몇몇 사람들은 모든 벡터들을 vec4에 정의하여 유지하는 것을 선호한다. position vectors를 vec4에 정의할 때, w 요소를 1.0으로 설정하는 것이 중요하다. 그래야, translation과 projections이 적절히 적용된다. 그러나, 방향 벡터를 vec4로 정의할 때, 우리는 translations이 효과를 갖기를 원치 않는다. (그것들은 방향을 나타내기 때문이고, 아무 것도 아니다) 그래서, 우리는 그 w 요소를 0;0으로 정의한다.
방향 벡터들은 그러고나서 다음과 같이 표현된다: vec4(0.2f, 1.0f, 0.3f, 0.0f). 이것은 또한 light 타입에 대한 쉬운 체크로서 기능을 한다: 너는 우리가 light의 position vector를 가졌는지 알기 위해 w요소가 1.0인지 체크할 수 있고, w가 0.0을 가진다면, 우리는 빛의 방향 벡터를 가진 것이다. 그래서 그것을 기반으로 계산을 조절한다:
if(lightVector.w == 0.0) // note : be careful for floating point errors
// do directional light calculations
else if(lightVector.w == 1.0)
// do light calculations using the light's position (like last tutorial)
재미있는 사실: 이것은 실제로 옛날의 OpenGL이 광원이 directional light인지 positional light인지를 결정하고 그것을 기반으로 조명을 조정하는 방식이다.
만약 너가 이제 그 프로그램을 컴파일 하고, scene을 돌아다니다보면, 모든 오브젝트에게 빛을 던지는 태양같은 광원이 있는 것처럼 보일 것이다. 너는 diffuse와 specular components들이 모두 하늘 어딘가에 광원이 있는 것처럼 작동하는 것을 볼 수 있는가? 그것은 이것 처럼 보일 것이다:
너는 프로그램의 전체 소스코드를 여기에서 볼 수 있다.
Point lights
Directional lights는 전체 scene을 비추는 global lights에 훌륭하다. 그러나 directional light를 제쳐두고서, 우리는 보통 또한 scene의 도처에 있는 몇 가지 point lights를 원한다. point light는 세계 어딘가에서 주어진 위치와 함께 모든 방향으로 빛을 비추는 광원이다. 그리고 거기에서 광선은 거리에 따라 옅어진다. point light로서 작동하는 light caster로서 전구와 횃불을 생각해보아라.
이전의 튜토리얼에서, 우리는 항상 (간단한) point light로 작업하고 있었다. 우리는 주어진 light position으로부터 모든 방향으로 빛을 뿌리는(scatter) 주어진 위치의 light source를 가졌었다. 그러나, 우리가 정의했던 그 광원은 결코 옅어지지 않는 광선을 재현했다. 그래서 그것은 광원이 extremely(끝없이) 강한것 처럼 보인다. 대부분의 3D simulation에서, 우리는 전체 scene이 아닌 광원에 가까운 지역만을 비추는 한 광원을 재현하고 싶다.
만약 너가 이전 튜토리얼에서 lighting scene에 10개의 컨테이너들을 추가했다면, 너는 뒤에 있는 컨테이너가 항상 램프의 앞에 있는 컨테이너 처럼 같은 강도로 비춰진다는 것을 눈치챌 것이다; 거리에 따라 빛을 줄이는 것을 정의한 공식은 없다. 우리는 뒤에 있는 컨테이너가 오직 약하게 빛나기를 원한다. 광원에 가까운 컨테이너들에 비교해서.
Attenuation(감쇠)
광선이 움직이는 거리에 따라서 빛의 강도를 줄이는 것은 일반적으로 attenuation라고 불려진다. 거리에 따라 빛의 강도를 줄이는 한 가지 방법은 linear equation을 사용하는 것이다. 그러한 방정식은 선형으로 거리에 따라 빛의 강도를 줄일 것이고, 따라서 어떤 거리에 있는 오브젝트들이 확실히 덜 빛나도록 할 것이다. 그러나, 그러한 선형 함수들은 가짜처럼 보이는 경향이 있다. 실제 세계에서, 빛들은 일반적으로 가까이에서 꽤 밝게 있지만, 빛의 강도는 처음에 빠르게 줄어들고, 나머지 빛의 강도는 더 느리게 거리에 따라 감소한다. 따라서 우리는 빛의 강도를 줄이는 다른 공식이 필요하다.
운 좋게도, 몇몇 똑똑한 사람들이 우리를 위해 이것을 알아냈다. 다음의 공식은 광원에 대한 fragment의 거리를 기반으로 attenuation value를 계산한다. 그리고 나중에 우리는 이것을 광원의 intensity vector에 곱한다:
여기에서 d는 fragment에서 광원까지의 거리를 나타낸다. 그리고나서, attenuation value를 계산하기 위해, 우리는 3개의 (설정할 수 있는) 항을 정의한다: 상수항 K_c, 1차항 K_l, 그리고 2차항 K_q.
- 상수항은 보통 1.0으로 유지되는데, 이것은 주로 최종 분모가 결코 1보다 작아지지 않기 위해서이다. 왜냐하면 그렇지 않으면 어떤 거리에 따라 빛의 강도를 증가시키기 때문이다. 그리고 이것은 우리가 찾는 효과가 아니다.
- 1차항은 선형으로 강도를 줄이는 distance value값과 곱해진다.
- 2차항은 거리의 제곱으로 곱해지고, 광원에 대한 강도의 2차(quadratic) 감소를 설정한다. 2차항은 거리가 작을 때 1차항에 비해 덜 영향을 미칠 것이지만, 거리가 커질 수록, 일차항보다 더 영향이 클 것이다.
2차항 때문에, 그 빛은 대개 거리가 2차항이 1차항을 넘을만큼 충분히 클 때 까지, 선형성으로 줄어들것이고, 그러고나서 빛의 강도는 더욱 빠르게 줄어들 것이다. 최종 효과는 그 빛은 가까운 범위 일 때 꽤 강렬하지만, 거리에 따라 그것의 밝기를 빠르게 잃고, 결국에 좀 더 느린 속도로 밝기를 잃는다. 다음의 그래프는 거리가 100일 때 attenuation이 같는 효과를 보여준다.
너는 거리가 작을 때 빛이 가장 강한 강도를 갖는 것을 볼 수 있다. 그러나 거리가 늘어나자마자, 그것의 강도는 크게 감소하고 거리가 100인 주변에서 0의 강도에 느리게 도달한다. 이것은 정확히 우리가 원하는 것이다.
Choosing the right values
그러나 우리는 그러한 3개의 항을 무엇으로 설정해야 하나? 올바른 값을 설정하는 것은 많은 요소들에 따라 다르다: 환경, 빛이 cover하기 원하는 거리, 빛의 종류 등등. 대부분의 경우에, 그것은 간단히 경험의 문제이고, 적절히 수정하는 정도이다. 다음의 테이블은 특정한 반경(distance)를 cover하는 현실적인(어느정도의) 광원을 재현하기 위해 이러한 항들이 취할 수 있는 값들을 보여준다. 첫 번째 열은 주어진 항과함께 빛이 cover할 거리를 명시한다. 이러한 값들은 Ogre3D의 위키를 통한 대부분의 빛을 위한 좋은 시작점이다.
너도 볼 수 있듯이, 상수항 K_c는 모든 경우에 1.0으로 유지된다. 1차항 K_l은 보통 더 큰거리를 다루기 위해서는 꽤 작아지고, 2차항 K_q는 심지어 더욱 작아진다. 너의 구현에서 그것들의 효과를 보기위해서 이 값들을 가지고 실험해 보아라. 우리의 환경에서 32~100의 거리가 일반적으로 대부분의 빛에 대해서 충분하다.
Implementing attenuation
attenuation을 구현하기 위해, 우리는 fragment shader에서 3개의 추가 values들을 필요로 할 것이다: 이름그대로, 공식의 constant, linear and quadratic 항. 이러한 것들은 우리가 이전에 정의한 Light 구조체에서 가장 잘 정의된다. 우리가 이전 튜토리얼에서 했던 것처럼 lightDir를 계산한다는 것에 유의해라. 이전 Directional Light section이 아니라.
struct Light
{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
}
그러고나서 우리는 OpenGL에서 그 항들을 설정한다: 우리는 빛이 50의 거리를 cover하기를 원한다. 그래서 우리는 테이블에 있는 적절한 상수, 1차, 2차항을 설정할 것이다:
lighting_Shader.setFloat("light.constant", 1.0f); lighting_Shader.setFloat("light.linear", 0.09f); lighting_Shader.setFloat("light.quadratic", 0.032f);
fragment shader에서 attenuation을 구현하는 것은 상대적으로 간단핟: 우리는 간단히 공식을 기반으로 attenuation value값을 계산하고, 이것을 ambient, diffuse 그리고 specular components과 곱해준다.
우리는 공식을 쓰기위해, 광원에 대한 거리가 필요하다; 우리가 어떻게 벡터의 길이를 계산했는지 기억하는가? 우리는 fragment와 광원사이의 차이 벡터를 가져와서 거리 항을 얻을 수 있고, 최종 벡터의 길이를 취할 수 있다. 우리는 그 목적을 위해서 GLSL의 내장 length 함수를 사용할 수 있다.
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
그러고나서 우리는 조명계산에서 이 attenuation 값을 ambient, diffuse 그리고 specular 컬러들과함께 곱하여 포함시킨다.
Green Box
우리는 우리는 ambient component들을 홀로 내버려둘 수 있다. 그래서 ambient lighting은 거리에따라 줄어들지 않는다. 그러나 만약 우리가 1개 이상의 광원을 사용하려한다면, 모든 ambient components들은 싸이기 시작할 것이고, 그래서 그러한 경우에 우리는 또한 ambient lighting을 attenuate하고 싶어한다. 간단히 너의 환경에 가장 잘 맞는걸로 가지고 놀아라.
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
만약 너가 프로그램을 실행시킨다면, 이것과 같은 것을 볼 것이다:
너는 지금 당장 앞에 있는 컨테이너들이 가장 가까운 컨테이너들이 가장 밝게 된 것을 볼 수 있다. 뒤에 있는 컨테이너들은 전혀 비춰지지 않는다. 왜냐하면 그것들은 광원으로 부터 멀기 때문이다. 너는 프로그램의 소스코드를 여기에서 볼 수 있다.
point light는 따라서 설정가능한 위치와 그것의 조명 계싼에 적용되는 attenuation이 있는 광원이다. 그러나 우리의 조명 무기고를 위한 또 다른 유형의 빛이 있다.
Spotlight
우리가 다룰 빛의 마지막 종류는 spotlight이다. spotlight는 모든 방향으로 광선을 쏘는 대신에, 특정한 방향으로만 그 광선들을 쏘는 환경 어딘가에 위치한 광원이다. 그 결과는 그 스팟라이트의 방향의 어떤 반경내에 있는 물체들만이 비춰지고, 모든 다른 것은 어둡게 된다. 한 스팟라이트의 좋은 예시는 가로등 또는 후레쉬이다.
OpenGL에서의 spotlight는 world-space position, 방향, spotlight의 반경을 명시하는 cutoff angle로 나타내어진다. 각 fragment에 대해, 우리는 fragment가 spotlight의 cutoff 방향 사이에 있는지를 (따라서 그것의 원뿔안에 )계산한다. 그리고 만약 그렇다면, 우리는 fragment를 그에 따라 비춘다. 다음의 이미지는 spotlight가 어떻게 작동하는지에 대한 아이디어를 너에게 준다.
- LightDir : fragment에서 광원을 가리키는 벡터
- SpotDir : spotlight가 가리키는 방향
- Phi Φ : spotlight의 반경을 명시하는 cutoff angle. 이 각 밖의 모든 것은 spotlight에 의해 밝아 지지 않는다.
- Theta Θ : LightDir 벡터와 SpotDir 벡터 사이의 각. Θ값은 spotlight안에 있기위해 Φ값보다 더 작아야 한다.
그래서 우리가 기본적으로 할 필요가 있는 것은 LightDir 벡터와 SpotDir의 벡터의 내적을 계산하고 이것을 cutoff angle Φ와 비교한다. 너가 어느정도 spotlight가ㅣ 무엇인지에 대해 이해했으니, 우리는 flashlight 형태의 것을 하나 만들어 볼 것이다.
Flashlight
flashlight는 보는 사람의 position에 위치한 spotlight이고 보통 플레이어의 관점으로부터 똑바로 앞으로 aimed된다. 기본적으로 flashlight는 일반적인 spotlight이지만, 그것의 위치와 방향이 플레이어의 위치와 방향을 기반으로 업데이트 된다.
그래서, 우리가 fragment shader를 위해 필요한 값들은 spotlight의 위치 벡터 (빛의 방향 벡터를 계산하기 위해), spotlight의 방향벡터 그리고 cutoff angle이다. 우리는 이 값들을 Light 구조체에 저장할 수 있다:
struct Light
{
vec3 postion;
vec3 direction;
float cutoff // for spotlight
vec3 ambient;
vec3 diffuse;
vec3 specular;
// for attenuation
float constant;
float linear;
float quadratic;
};
다음으로 우리는 그 쉐이더들에 적절한 값들을 넘긴다:
lighting_Shader.setVec3("light.position", camera.Position); lighting_Shader.setVec3("light.direction", camera.Front); lighting_Shader.setFloat("light.cutoff", glm::cos(glm::radians(12.5f)));
너도 볼 수 있듯이, 우리는 cutoff value에 대해 한 각을 설정하는게 아니라, 한 각을 기반으로한 코사인 값을 계산하고 그 코사인 값을 fragment shader에 넘긴다. 이것을 하는 이유는 fragment shader에서 우리는 LightDir과 SpotDir 벡터 사이에 내적을 계산할 것이고, 그 내적은 각도가 아닌 cosine value를 반환한다. 그래서 우리는 직접적으로 각도와 cosine value를 비교할 수 없다. 각을 얻기 위해서, 우리는 그러고나서 비싼 연산인 내적의 결과의 역코사인을 계산해야만 한다. 그래서 성능을 아끼기 위해, 우리는 주어진 cutoff angle의 코사인 값을 계산하고 이 결과를 fragment shader에 넘긴다. 두 각들이 이제 코사인으로 나타내지기 때문에, 우리는 직접적으로 그것들을 비싼 연산 없이 비교할 수 있다.
이제 해야할 남은 것은 theta Θ값을 계산하고 이것을 cutoff Φ값과 비교하는 것이다. 이것은 우리가 spotlight 안에 있는지 밖에 있는지를 결정하기 위해서이다:
float theta = dot(lightDir, normalize(-light.direction)); if(theta > light.cutoff) { // do lighting calculations } else // else, use ambient light so scene isn't completely dark outside the spotlight color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
우리는 처음에 lightDir과 음수화된 direction vector사이의 내적을 계산한다. (우리는 벡터가 광원을 향하기를 바라기 때문에 음수화 되었다) 모든 관련된 벡터들을 표준화하도록 해라.
Green Box
너는 if문에 왜 < 기호대신에 >기호가 있는지 궁금할지도 모른다. theta는 spotlight안에 있기 위해서, 빛의 cutoff value보다 작아야 되지 않았는가? 그것은 맞지만, angle values들이 코사인 값으로 나타내어졌다는 것을 잊지말라. 각이 0인 것은 코사인 값으로 1이고, 반면에 90도인 각은 코사인 값으로 0.0이다. 너가 여기에서 보듯이:
너는 이제 코사인 값이 1.0에 가까울 수록 그것의 각이 작다는 것을 알 수 있다. 이제 왜 theta 가 cutoff value보다 클 필요가 있는지가 이해된다. cutoff value는 현재 0.9978과 동일한 12.5의 cosine으로 설정되었고, 그래서 0.9979과 1.0 사이의 코사인 theta 값은 fragment에서 spotlight안에서 비쳐지도록 하는 결과를 만들어 낼 것이다.
프로그램을 작동시키는 것은 직접적으로 spotlight의 원뿔 안에 있는 프래그먼트들만을 비추도록 하는 스팟라이트를 만든다. 그것은 이것처럼 보일 것이다:
너는 여기에서 전체 소스코드를 볼 수 있다.
그것은 근데 약간 가짜같아 보인다, 대개 스팟라이트가 딱딱한 엣지를 가지고 있기 때문이다. fargment가 그 스팟라이트 원뿔의 엣지에 도달하는 곳에서, 그것은 좋은 부드러운 옅어짐(nice smooth fade) 대신에, 완전히 없어져 버린다. 현실적인 spotlight는 그 빛을 그것의 엣지 주변에서 점차적으로 줄인다.
Smooth/Soft edges
smoothly-edged된 스팟라이트 효과를 만들기 위해서, 우리는 inner and outer cone을 가진 spotlight를 재현하고 싶어한다. 우리는 이전 섹션에서 정의된 cone으로서의 inner cone을 설정할 수 있지만, 우리는 또한 inner cone에서 outer cone의 edges로 갈수록 빛을 점차적으로 희미하게하는 outer cone을 원한다.
outer cone을 만들기 위해서 우리는 간단히 spotlight의 방향 벡터와 outer cone의 벡터 (그것의 반경과 동일한) 사이의 각을 나타내는 또 다른 코사인 값을 정의한다. 그러고나서 만약 한 fragment가 inner cone과 outer cone 사이에 있다면, 그것은 0.0과 1.0 사이의 강도값을 계산해야 한다. 만약 fragment가 inner cone안에 있으면, 그것의 강도는 1.0과 같고, outer cone 밖에 있다면 0.0과 같다.
다음의 공식을 사용해서 우리는 그러한 값을 계산할 수 있다.
여기에서 epsilon은 inner (Φ) cone과 outer cone(𝛄, gamma) 사이의 코사인 차이이다.
(ϵ = ɸ - 𝛄). 그 결과 I 값은 현재 fragment에서의 spotlight 강도가 된다.
이 공식이 어떻게 작동하는지 시각화하는것은 조금 어렵다. 그래서 몇 가지 간단한 값들 가지고 시도해보자:
너도 볼 수 있듯이, 우리는 기본적으로 Θ값을 기반으로 outer cosine과 inner cosine사이에서 interpolating을 한다. 만약 너가 여전히 무엇이 일어나고 있는지 모르겠다면 걱정말라, 너는 간단히 공식을 당연시 여기고, 여기에서 너가 나이를 더 먹거나 현명해졌을 때 여기로 돌아올 수 있다.
(inner cone의 각은 outer cone의 각보다 작다. 그래서 inner cone영역에서는 빛의 강도가 무조건 1.0이 되게하고. inter cone과 outer cone사이에서는 0.0 ~ 1.0으로 되어 점점 희미하게 만들어버린다. 그리고 outer cone밖으로 나가게되면, clamp하여 0.0으로 만든다.)
우리는 엣지 부근에서 이제 spotlight밖으로 나갈 때 음수이거나 inner cone 내부에 있을 때 1.0 이상인 강도 값을 가진다. 만약 우리가 적절히 그 값을 clamp한다면, 우리는 fragment shader에서 더이상 if-else문을 필요로 하지 않는다. 그리고 우리는 간단히 계산된 강도 값을 light components에 곱한다:
// smooth the spotlight float theta = dot(lightDir, normalize(-light.direction)); float epsilon = light.cutoff - light.outercutoff; float spot_intensity = clamp((theta - light.outercutoff) / epsilon, 0.0, 1.0); // we'll leave ambient unaffected so we always have a little light diffuse *= attenuation * spot_intensity; specular *= attenuation * spot_intensity;
우리가 그것의 첫 번째 인자를 0.0과 1.0사이로 clamp시키는 clamp 함수를 사용한다는 것을 주목해라. 이것은 강도 값이 [0,1]의 간격밖으로 나가지 않게 할 것이다.
너가 Light struct에 outerCutOff 값을 추가하고 프로그램에서 그것의 uniform value를 설정하는 것을 확실히 해라. 다음의 이미지에 대해, 12.5인 inner cutoff angle과 17.5인 outer cutoff angle이 사용되었다:
확실히 이전의 smooth를 적용한 것보다 edge에서 옅어지는게 느껴진다. |
아, 이게 더 낫다. inner and outer cutoff angle가지고 놀아보아라. 그리고 너의 필요에 더욱 잘 맞는 spotlight를 만들려고해라. 너는 여기에서 프로그램의 소스코드를 볼 수 있다.
* View Space Implementation for a smooth spotlight
#version 330 core struct Material { sampler2D diffuse; sampler2D specular; sampler2D emission; float shininess; }; struct Light { vec3 position; vec3 direction; float cutoff; // for spotlight float outercutoff; vec3 ambient; vec3 diffuse; vec3 specular; // for attenuation float constant; float linear; float quadratic; }; in vec2 TexCoords; in vec3 Normal; in vec3 FragPos; in vec3 LightPos; out vec4 FragColor; uniform Material material; uniform Light light; // Lighting in 'View Space', trying to implement a spotlight void main() { // Common Factors vec3 lightDir = normalize(-FragPos); // from fragment to light source vec3 surface_norm = normalize(Normal); vec3 viewDir = normalize(-FragPos); vec3 reflectDir = reflect(-lightDir, surface_norm); // change the direction into from light source to fragment // attenuation float distance = length(-FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); // smooth the spotlight float theta = dot(lightDir, normalize(vec3(0.0, 0.0, 1.0))); // converts camera direction (0,0,-1) to (0,0,1) from fragment. float epsilon = light.cutoff - light.outercutoff; float spot_intensity = clamp((theta - light.outercutoff) / epsilon, 0.0, 1.0); // Ambient Lighting vec3 ambient = vec3(texture(material.diffuse, TexCoords)) * light.ambient; // Diffuse Lighting float diff = max(dot(surface_norm, lightDir), 0.0); vec3 diffuse = diff * vec3(texture(material.diffuse, TexCoords)) * light.diffuse; // Specular Lighting float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = spec * vec3(texture(material.specular, TexCoords)) * light.specular; ambient *= attenuation; diffuse *= attenuation * spot_intensity; specular *= attenuation * spot_intensity; // Result vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); }
이러한 lamp의 flashlight/spotlight 타입은 공포 게임에 완벽하다 그리고 directional and point lights로 조합되면, 그 환경은 정말 빛나기 시작할 것이다. 다음 튜토리얼에서, 우리는 이제까지 다룬 모든 조명들과 tricks들을 합칠 것이다.
Exercises
- 모든 다른 light types들과 그것들의 fragment shader들을 가지고 실험해보아라. <instead of>를 사용하거나 해서 몇 몇 벡터들을 뒤집어보아라. 다른 시각적 결과들을설명하도록 하여라.
attenuation components들을 낮추어 더 먼 거리를 다룰 수 있도록 했다.
attenuation - Distance 20, Linear 0.22, Quadratic 0.20 inner cutoff 1.5, outer cutoff 12.5 |
댓글 없음:
댓글 쓰기