본문 바로가기
공허의 유산/표현의 자유

[opengl].[#2.GLSL] 12. 조명 정보 구조체와 재질 정보 구조체

by 바른생활머시마 2023. 2. 5.
728x90
반응형

앞에서 쉐이더를 이용하여 멋지게 Toon shading을 구현 해 보았습니다.

https://learn-and-give.tistory.com/59

 

[opengl].[#2.GLSL] 11. Fragment Shader를 이용한 조명의 구현(Diffuse/Toon shading)

앞에서 Vertex shader를 이용하여 Diffuse 조명을 구현 해 보았습니다. https://learn-and-give.tistory.com/58 [opengl].[#2.GLSL] 10. Vertex Shader를 이용한 조명의 구현(Diffuse) 앞에서 Shader 없이, Vertex의 밀도를 높히는

learn-and-give.tistory.com

 

조명의 위치는 uniform 변수로 전달했었습니다.조명도 gl_Color이나 gl_Normal처럼 매우 핵심적인 값이니 넘겨줄만하다 싶었는데 실제로 조명과 관련 된 값을 전달 해 주는 방법이 있습니다.

 

 

struct gl_LightSourceParameters

조명과 관련 된 여러가지 구조체들이 있습니다. 조명에 의한 효과는 재질에 영향을 받기 때문에 재질 또한 같은 방식으로 파라미터 전달이 가능합니다.이러한 구조체는 내부적으로 uniform으로 선언되어 있기 때문에, 그냥 쓰면 됩니다.

         struct gl_LightSourceParameters {
                 vec4 ambient;
                 vec4 diffuse;
                 vec4 specular;
                 vec4 position;
                 vec4 halfVector;
                 vec3 spotDirection;
                 float spotExponent;
                 float spotCutoff; // (range: [0.0,90.0], 180.0)
                 float spotCosCutoff; // (range: [1.0,0.0],-1.0)
                 float constantAttenuation;
                 float linearAttenuation;
                 float quadraticAttenuation;
         };
        
         uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];
        
         struct gl_LightModelParameters {
                 vec4 ambient;
         };
        
         uniform gl_LightModelParameters gl_LightModel;
 
         struct gl_MaterialParameters {
                 vec4 emission;  
                 vec4 ambient;   
                 vec4 diffuse;   
                 vec4 specular;  
                 float shininess;
         };
         uniform gl_MaterialParameters gl_FrontMaterial;
         uniform gl_MaterialParameters gl_BackMaterial;

위 구조체에 대한 내용 중, 조명의 개수가 gl_MaxLights로 정해져 있는 것을 볼 수 있는데, 이것은 Fixed rendering pipeline에서 지원되는 조명의 개수를 말하는 것입니다. 즉, glEnable(GL_LIGHTn)과 같은 명령에서 사용되는 조명의 개수를 말합니다. 기존에 쉐이더 없이 구현되어 있던 조명을 쉐이더로 옮길 경우에는 이 구조체가 유용하게 사용 될 수 있을 것입니다.그러나, 조명의 개수가 저 최대 개수보다 많거나, 새로 쉐이더로 조명을 구현해야 한다면, Fixed rendering pipeline에서 조명에 대한 코드도 필요하고, 쉐이더의 조명 코드도 필요하니, 이중 관리가 되어 좀 더 불편하지 않을까 생각 됩니다.(개인적인 의견)

 

 어느 한 쪽이 절대적으로 옳은 것은 아니니, 상황에 따라 적합한 방법을 사용하는 것이 좋겠습니다. 일단, 이 구조체를 사용해서 조명을 구현하려면 기존의 방식으로 조명에 대한 파라미터 설정을 해야 합니다. 기존의 방식이라면, glLightfv 등으로 조명의 파라미터를 설정하는 것을 말합니다.  

 

 

 

일단, 어떻게 쓰는지 한번 살펴보면, 각 방식의 장단점을 좀 더 쉽게 이해 할 수 있을 것 같습니다.

앞에서 구현한 Toon shading 코드에서 조명의 위치를 이 구조체를 통해서 받도록, Fragment shader를 다음과 같이 작성 할 수 있습니다.

float intensity;
//uniform vec3 lightDir;   //ß 더 이상 필요하지 않음.
varying vec3 normal;
void main()
{
         //intensity = dot(normalize(lightDir),normalize(normal));  // lightDir 사용시
         intensity = dot(vec3(gl_LightSource[0].position),normalize(normal));
         vec4 color;
         if (intensity > 0.95)         color = vec4(0.5,0.5,1.0,1.0);
         else if (intensity > 0.5)    color = vec4(0.3,0.3,0.6,1.0);
         else if (intensity > 0.25)   color = vec4(0.2,0.2,0.4,1.0);
         else                        color = vec4(0.1,0.1,0.2,1.0);        
         gl_FragColor = color;
}

이렇게 하고 실행을 해보면....

이상한 결과가 나왔습니다. 왜 이럴까요????

바로 조명의 위치를 설정해주는 코드가 없기 때문입니다. 앞에서 쉐이더로 조명을 구현하기 위해 기존의 조명 파라미터 설정하는 코드를 다 주석처리 했기 때문에, 조명의 위치 정보가 없어서 기본 위치로 조명이 계산 된 것 같습니다.

 

조명 설정을 해주던 함수인 LightInit() 함수를 아래와 같이 수정합니다.  일단, 필수적인 것만 최소로 사용하도록 해보았습니다. 

void LightInit()
{
	// Specify Black as the clear color
	glClearColor(0.0f, .0f, .0f, 0.0f);
	// Specify the back of the buffer as clear depth
	glClearDepth(1.0f);
	// Enable Depth Testing
	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_LIGHTING);
	//glEnable(GL_COLOR_MATERIAL);
	//glFrontFace(GL_CCW);
	//glShadeModel(GL_SMOOTH);

	float lpos[4] = { 1.f, 1.f, 1.f, 0.f };	
	//float amb[4] = { 0.f, 0.f, 0.f, 1.f };
	//float dif[4] = { 0.5f, 0.5f, 0.5f, 1.f };
	//float spc[4] = { 1.f, 1.f, 1.f, 1.f };

	glLightfv(GL_LIGHT0, GL_POSITION, lpos);
	//glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
	//glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
	//glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
	//glEnable(GL_LIGHT0);
}

결과는 기존과 동일하게 나옵니다.

 여기서 하나 눈여겨 볼 것이 있습니다. 쉐이더 외부 코드에서 glEnable(GL_LIGHTING)도 하지 않았고, glEnable(GL_LIGHT0)도 하지 않았습니다. 즉, 조명의 활성화에 대한 설정은 조명 파라미터 설정과 무관하며, 활성화 되어 있지 않더라도 파라미터 설정은 그대로 작동되는 것 같습니다. 

  이후에는 이 구조체를 이용하여 파라미터를 전달/사용하도록 하겠습니다.

 

 

 

재질 정보가 반영 된 Diffuse 조명 구현

 

 Diffuse 조명은 앞에서 구현한 Toon shader에서 step function 부분만 제거하면 됩니다. 이 때 한 면의 밝기는 빛이 조사되는 면의 방향과 입사 광의 방향에 의해 결정됩니다. 반사광이 영향을 주지 않는 것은, Diffuse 조명 모델이 표면에서 난반사가 일어나는 모델이므로 입사 된 빛이 모든 방향으로 골고루 퍼져 나가기 때문에 입사되는 방향에만 영향을 받는 것입니다. 이 때 면의 방향과 입사광의 각도에 의해 결정되는 모델에는 렘버트의 코사인 법칙’이 적용 됩니다.

 

 

여기서 L은 조명의 방향이며, N은 법선의 방향입니다. 

Ld,Md Diffuse 속성에 대한 조명과 재질의 특성값을 나타냅니다. 이전의 예에서는 제가 푸른색 계통의 색상을 코드 상에 직접 반영했었는데,  원래 이러한 값은 조명과 재질에 의해 결정이 되는 값입니다. 푸른색 상자에 빨간색 조명이 비추면 두 개의 색상이 섞인 색으로 보여지는 것이 그런 예 입니다.

 

코드를 한번 작성 해 보겠습니다. 

Diffuse 조명의 경우, Vertex shader에서만 구현을 해도 어지간히 괜찮은 느낌이 났었습니다. 그래서, Fragment shader에서는 그냥 입력 받은 색을 그대로 출력하도록 하고, Vertex shader에서 필요한 코드를 아래와 같이 작성 해 보겠습니다.

 

Vertex Shader

void main()
{
         vec3 normal, lightDir;
         vec4 diffuse;
         float NdotL;
         normal = normalize(gl_NormalMatrix * gl_Normal);
         lightDir = normalize(vec3(gl_LightSource[0].position));
         NdotL = max(dot(normal, lightDir), 0.0);
         diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
         gl_FrontColor =  NdotL * diffuse;
         gl_Position = ftransform();
}

 Fragment Shader

void main()
{
         gl_FragColor = gl_Color;
}

결과는~~

 꼭 우주에서 달을 촬영한 사진 같은 느낌이네요. 하드 코딩 되어 있던 파란색이 사라지니 하얀색으로 표현되었습니다. 

 그렇다고 하얀색을 설정하지도 않았으니, 어떤 기본값이 쓰였겠군요. 무슨 값이 사용 되었을까요??

 

위의 Vertex shader에서 색상을 결정하는 부분은 아래 코드이고, 다른 부분은 그 밝기를 결정하는 부분입니다.

 

 diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;

 

Front material도 설정한 적 없고, Light의 Diffuse 성분도 설정한 적이 없는데...

일단 조명 파라미터는 Position을 위해서 좀 다루었으니, Diffuse 성분 설정을 한번 해 보겠습니다.

 

void LightInit()
{
	glClearColor(0.0f, .0f, .0f, 0.0f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);

	float lpos[4] = { 1.f, 1.f, 1.f, 0.f };	
	float dif[4] = { 1.f, 0.5f, 0.5f, 1.f };

	glLightfv(GL_LIGHT0, GL_POSITION, lpos);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
}

위와 같이 조명의 Diffuse 성분을 밝은 빨간색으로 설정을 하였습니다. 결과는~

화성 사진처럼 보이는데, 코드 그대로 해석을 하자면, 이 결과는 하얀색 구에 빨간색 빛이 비춰진 것이지 빨간색 구가 아닙니다. 

 그렇다면, gl_FrontMaterial.diffuse도 설정하면 될 것 같네요.

 

 

 

Material 설정

OpenGL을 처음 공부 할 때, Material이 매우 번거로운 속성으로 생각 되었습니다. 원하는 색상으로 형상을 그리고 싶은데 해줘야 할 것이 너무 많고, 그럼에도 불구하고 원하는 느낌을 잘 나타내기가 쉽지 않았습니다 .그러다가, OpenGL의 Color Material이라는 옵션을 알고는 거의 그 옵션을 많이 사용하게 되었습니다. 이 옵션은 별로 어려운 것이 아니고, glColor로 설정되는 칼라 값을 재질의 Diffuse값으로 설정 해 주는 것입니다.

 glEnable(GL_LIGHTING)을 하지 않아도 조명의 파라미터가 전달 된 것 처럼, Color material을 설정하지 않아도 glColor로 설정 된 값이 diffuse로 전달되지 않을까 싶지만, 위의 그림을 그리는 코드에는 파란색 색상을 glColor로 설정하는 코드가 이미 적용되어 있었습니다.

void display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//Draw
	glColor3f(0.5f, 0.5f, 1.f);
	DrawSphere(0.8f, 40, 40);
	glFlush();

	glutSwapBuffers();
}

그럼, glColor는 이미 설정되어 있으니, Color material 옵션만 활성화 시켜주도록 하겠습니다.

void LightInit()
{
	glClearColor(0.0f, .0f, .0f, 0.0f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
    
    //COLOR Material 설정
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

	float lpos[4] = { 1.f, 1.f, 1.f, 0.f };	
	float dif[4] = { 1.f, 0.5f, 0.5f, 1.f };

	glLightfv(GL_LIGHT0, GL_POSITION, lpos);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
}

 

결과는....

앗!!! 왜 그대로 나올까요??

 

아래 코드를 적용하면 되는데... 이전에는 되었는데 왜 안될까요? deprecated??

         glEnable(GL_COLOR_MATERIAL);
         glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

 

 Light 성분을 빼고 그려보면 흰색으로 넘어오네요.

void main()
{
         vec3 normal, lightDir;
         vec4 diffuse;
         float NdotL;
         normal = normalize(gl_NormalMatrix * gl_Normal);
         lightDir = normalize(vec3(gl_LightSource[0].position));
         NdotL = max(dot(normal, lightDir), 0.0);
         diffuse = gl_FrontMaterial.diffuse;// * gl_LightSource[0].diffuse;
         gl_FrontColor =  NdotL * diffuse;
         gl_Position = ftransform();
}

 

여러가지 실험/테스트를 많이 해봤는데 잘 해결이 안되네요.

요건 좀 더 조사 해 보고, 여의치 않을 경우, 재질값은 따로 전달하는 것으로 해야겠네요.

 

구글링을 좀 해보니, 역시 쉐이더는 쉐이더로! 이슈였군요.

https://stackoverflow.com/questions/15364454/setting-shininess-for-glsl

 

Setting shininess for glsl

How do i set opengl shininess float, so i can use it in a shader program with gl_FrontMaterial.shininess? I tried this glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100f);, but the highlights are not

stackoverflow.com

 없애려면 다 없애지 헷갈리게 조명은 살려둔 이유를 모르겠군요. 너무 많이 써서 그런것인지...

 

 

재질 정보를 uniform 변수로 전달하여 적용하기

먼저 재질 정보(Diffuse 색상)를 저장 할 변수를 하나 밖에 만들어 값을 밝은 파란색으로 설정합니다.

float material_diffuse[4] = { 0.5f, 0.5f, 1.f, 1.f };

테스트 할 때 그냥 파란색으로 했더니 결과가 파랗게 나와서 무슨 이상이 있나 했었는데요, 두 벡터가 곱해지는 것이라 조명이나 재질의 어떤 색상 성분이 0이되면 그 성분은 완전히 사라지게 됩니다. 

만약, 조명은 빨강, 재질은 파랑으로 하면, 각 모든 성분이 0이 되어 검은색으로 나옵니다. (재질 표시가 안되서 막 테스트 할 때 검은색으로 나온 적이 있는데 혹시 이 이유???? 그럼 material 정보가 전달되는 경우가 있었다는 말???... 일단 안되는걸로 생각하고 마음의 평안을...)

그리고, 이 값을 uniform 변수로 쉐이더에 넘겨주는 코드를 쉐이더 초기화 코드에 추가합니다.

	loc = glGetUniformLocationARB(program_shader, "material_diffuse");
	glUniform4fARB(loc, material_diffuse[0], material_diffuse[1], material_diffuse[2],material_diffuse[3]);

 

이제 Vertex shader에서 이 값을 사용하여 diffuse 성분을 계산하도록 해줍니다.

uniform vec4 material_diffuse;

void main()
{
         vec3 normal, lightDir;
         vec4 diffuse;
         float NdotL;
         normal = normalize(gl_NormalMatrix * gl_Normal);
         lightDir = normalize(vec3(gl_LightSource[0].position));
         NdotL = max(dot(normal, lightDir), 0.0);
         diffuse = material_diffuse * gl_LightSource[0].diffuse;
         gl_FrontColor =  NdotL * diffuse;
         gl_Position = ftransform();
}

결과는, 멋지게 성공!!!

 

(추가) 아무래도 찜찜해서 이런 저런 테스트를 더 해 봤는데, Color material은 안되지만, glMaterial로 지정한 값은 올바로 전달 되는 것을 확인했습니다. 즉, 재질 설정을 직접 해주는 방식으로 하면, 예약 된 키워드로 ambient 속성이 잘 전달 됩니다. 아래와 같이 재질 코드를 추가하고,

void LightInit()
{
	glClearColor(0.0f, .0f, .0f, 0.0f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_COLOR_MATERIAL);
	//glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	float lpos[4] = { 1.f, 1.f, 1.f, 0.f };	
	float dif[4] = { 1.f, 0.5f, 0.5f, 1.f };

	glLightfv(GL_LIGHT0, GL_POSITION, lpos);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);

	float material_diffuse[4] = { 0.5f, 0.5f, 1.f, 1.f };
	glMaterialfv(GL_FRONT, GL_DIFFUSE, material_diffuse);
}

랜더링 코드에서 glColor를 제거하고,

void display()
{
	//Clear
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	DrawSphere(0.8f, 40, 40);
	glFlush();

	glutSwapBuffers();
}

Vertex shader는 원래처럼 예약 된 구조체를 사용하면 잘 작동합니다.

void main()
{
         vec3 normal, lightDir;
         vec4 diffuse;
         float NdotL;
         normal = normalize(gl_NormalMatrix * gl_Normal);
         lightDir = normalize(vec3(gl_LightSource[0].position));
         NdotL = max(dot(normal, lightDir), 0.0);
         diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
         gl_FrontColor =  NdotL * diffuse;
         gl_Position = ftransform();
}

 

결과는 uniform으로 전달했을 때와 같습니다.

 

다음 시간에는 ambient를 구현 해 보겠습니다.

https://learn-and-give.tistory.com/61

 

[opengl].[#2.GLSL] 13. ambient 조명과 specular 조명

앞에서 Diffuse 조명과 재질을 쉐이더로 구현하는 방법을 살펴봤습니다. [opengl].[#2.GLSL] 12. 조명 정보 구조체와 재질 정보 구조체 (tistory.com) [opengl].[#2.GLSL] 12. 조명 정보 구조체와 재질 정보 구조체

learn-and-give.tistory.com

 

 

 

728x90
반응형

댓글