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

[opengl].[#2.GLSL] 02. Shader 사용 기반 코드 작성

by 바른생활머시마 2020. 8. 9.
728x90
반응형

Shader를 사용하기 위한 준비를 해 보겠습니다.

참고가 될 만한 Reference를 잠시 찾아봤는데, 옛 생각이 나서 예전에 많이 보던 사이트에 한번 가봤습니다.

 

NeHe gamedev

https://nehe.gamedev.net/

 

NeHe Productions - Everything OpenGL

by gregsidelnikov, posted at May 7, 2017, 6:02 p.m. Actually, this post is not about how I made MK7 in OpenGL, but what the results were. I am currently porting it to WebGL as well. The level design geometry is so simple, that it might work in 4K. But we'l

nehe.gamedev.net

사이트는 접속 되는데, 마지막 글이 2017년 글이네요. PC에서 직접 OpenGL을 사용하는 개발이 많이 자취를 감춰가면서 이러한 사이트의 운명(?)도 함께 하는 것 같습니다. 저 처럼, 그렇게 덩그러니 남아있는 흔적을 견디지 못해서 다 지워버리는 사람도 있고, 이렇게 남겨둬서 누군가에게 또 도움을 주는 사람도 있고. 도움이 되는 정보라면 그대로 남겨두는게 좋을 것 같네요.

 이 사이트의 Tutorial은 매우 인기가 있었습니다. 그래서, 'NeHe tutorial style'이라는 말이 사용 될 정도였으니까요. 아직 그 tutorial들은 여전히 남아 있는데, 이미지가 좀 깨진 곳도 보이네요. 

 

Lighthouse3D

https://www.lighthouse3d.com/

 

Lighthouse3d.com

Mostly about 3D graphics

www.lighthouse3d.com

이 사이트에도 대단히 좋은 강좌가 많았습니다. 특히, GLSL에 대한 공부도 이 곳 강좌를 보면서 했었답니다. 여기도 2017년 이후로 새 글이 없네요.

 

제가 참고했던 강좌도 여전히 있고,

http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/

 

GLSL 1.2 Tutorial

In this tutorial shader programming using GLSL 1.2 will be covered. Although many parts are now considered deprecated, the essence of GLSL has remained constant. Besides, this tutorial will continu…

www.lighthouse3d.com

새로운 강좌도 하나 있네요. GLSL을 자세히 공부 해 보시겠다면, 여기 보시는 것도 좋은 선택이 되겠습니다.

http://www.lighthouse3d.com/tutorials/glsl-tutorial/

 

GLSL Tutorial – Core

This is the updated version of GLSL tutorial. Only the core version is dealt in here. For compatibility features please go to the original GLSL 1.2 tutorial. This tutorial does not intend to replac…

www.lighthouse3d.com

1. Shader code 로딩 환경

 Shader code는 application을 빌드하는 시점에 함께 빌드되지 않습니다. 실행하는 applicatoin이 shader code를 빌드해서 실행을 시킵니다. 이 때 Shader code는 문자열로 처리되며, 문자열을 준비하는 방법은 Shader의 영역이 아닙니다. 즉, 어떤 방법으로 Shader code 문자열을 준비했는가는 Shader의 영역이 아니므로, 구동되는 환경에 맞게, 간편하고 효과적인 방법을 사용하면 됩니다.  applicatoin 소스 코드 내에 하드 코딩 된 문자열로 쉐이더 코드를 정의 해 둬도 되고, 외뷔의 쉐이더 코드 파일을 읽어들여서 사용해도 됩니다. 

 위에서 말씀 드린, lighthouse3d의 GLSL 1.2 tutorial에서는 text file을 읽어서 빌드 할 수 있는 코드를 제공했었고, 그 방법을 쓰면 shader code만을 별도의 소스 코드 처럼 관리하고 볼 수 있으며, 특히나 오류 발생 시 위치 정보를 정확히 알 수 있어서 유용했습니다. 그래서, 그 방법을 적용 해 보겠습니다.

 

char* 문자열로 파일 내용을 전달하거나, 그 반대로 char*으로 전달 된 문자열을 파일로 저장하는 두 개의 함수로 되어 있습니다.  fopen, fread 등이 보안 이슈로 _s가 붙은 함수로 변경 된 것이 있고, 인자 타입을 char*에서 const char*으로 수정했습니다. 이전에는 이런 warning들을 그냥 무시하는 경우가 많았는데, 이런 것들이 나중에 큰 버그의 원인이 되는 경우가 많더라구요. 가급적 warning도 다 수정 해 주는 습관이 좋은 것 같아요.

 

// File IO
char *textFileRead(const char *fn) {


	FILE *fp;
	char *content = NULL;

	int count=0;

	if (fn != NULL) {

	  if (fopen_s(&fp, fn, "rt")==0) {
      
      fseek(fp, 0, SEEK_END);
      count = ftell(fp);
      rewind(fp);
			if (count > 0) {
				content = (char *)malloc(sizeof(char) * (count+1));
				count = fread_s(content, sizeof(char)*(count + 1),sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}
	return content;
}

int textFileWrite(const char *fn, const char *s) {

	FILE *fp;
	int status = 0;

	if (fn != NULL) {
		if (fopen_s(&fp, fn, "r+t") == 0) {
			if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
				status = 1;
			fclose(fp);
		}
	}
	return(status);
}

정상적으로 작동하는지 확인하기 위해, 파일에서 읽은 후 콘솔창으로 출력 해 보겠습니다.

 

먼저, 두 개의 shader file을 프로젝트에 추가하고, 빌드에서 제외 설정을 합니다. 이렇게 하면, VS에서 함께 볼 수 있어서 다른 에디터 없이 편리하게 수정 할 수 있습니다.

 

테스트 코드는 아래와 같습니다.

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(300, 300);
	glutCreateWindow("OpenGL");
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	glutDisplayFunc(display);
	glutReshapeFunc(changeSize);
	glutIdleFunc(display);

	initGLEW();
	initGL();

	char* vs = NULL, * fs = NULL, * fs2 = NULL;
	vs = textFileRead("minimal.vert");
	fs = textFileRead("minimal.frag");

	std::cout << vs << std::endl;
	std::cout << fs << std::endl;


    glutMainLoop();
    return 0;
}

결과는 아래와 같이 shader code가 잘 출력 됩니다.

 

 

 

 

2. Shader code 사용

 Shader를 사용하려면, Shader를 생성하고, 그 소스 문자열을 넘겨준 후, 컴파일하여, 프로그램을 만들어 붙인 후, 그 프로그램을 연결하여 사용하도록 설정합니다. 각 요소들이 의미하는 것이 무엇인지는 lighthouse3d의 강좌 등 자세한 내용을 따로 참고하도록 하고, 단어의 일반적인 의미로 대강의 뜻을 이해하고 사용 먼저 해 보도록 하겠습니다.

 이 절차를 구현한 코드는 아래와 같습니다.

 

GLuint v,f,p;
void setShaders()
{
	char *vs = NULL,*fs = NULL,*fs2 = NULL;

	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);

	vs = textFileRead("minimal.vert");
	fs = textFileRead("minimal.frag");

	const char * vv = vs;
	const char * ff = fs;

	glShaderSource(v, 1, &vv,NULL);
	glShaderSource(f, 1, &ff,NULL);

	free(vs);free(fs);

	glCompileShader(v);
	glCompileShader(f);

	p = glCreateProgram();
	glAttachShader(p,v);
	glAttachShader(p,f);

	glLinkProgram(p);

	glUseProgram(p);
}

Shader를 처음 하면, 기존의 application 코드와 같은 방법으로 디버깅을 할 수 없어서 좀 많이 어렵습니다. 그래도, 어느 정도 오류에 대한 참고 정보는 얻을 수 있는 방법이 있습니다. opengl에러/ 쉐이더로그/ 프로그램로그를 출력하는 함수를 사용합니다.(lighthouse3d 샘플에서 얻을 수 있는 코드들입니다.)

#define printOpenGLError() printOglError(__FILE__, __LINE__)
int printOglError(char *file, int line)
{
    GLenum glErr;
    int    retCode = 0;

    glErr = glGetError();
    while (glErr != GL_NO_ERROR)
    {
        printf("glError in file %s @ line %d: %s\n", file, line, gluErrorString(glErr));
        retCode = 1;
        glErr = glGetError();
    }
    return retCode;
}

void printShaderInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
        free(infoLog);
    }
}

void printProgramInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
        free(infoLog);
    }
}

3. minimal shader code

Vertex Shader는 원래 vertex 위치에 원래 색상을 그대로 전달하도록 하고,

Fragment Shader는 전달 된 색상을 그대로 전달하는 최소의 코드를 사용하겠습니다.

// minimal vertex shader
// www.lighthouse3d.com
void main()
{
	gl_Position = gl_Vertex;
	gl_FrontColor = gl_Color;
}



// minimal fragment shader
// www.lighthouse3d.com
void main()
{
	gl_FragColor = gl_Color;
}

 이렇게 준비한 후, Shader 관련 코드를 실행하면, 아래와 같이 Shader가 적용 된 결과를 볼 수 있습니다.

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(300, 300);
	glutCreateWindow("OpenGL");
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	glutDisplayFunc(display);
	glutReshapeFunc(changeSize);
	glutIdleFunc(display);

	initGLEW();
	setShaders();

	initGL();

    glutMainLoop();
    return 0;
}

 

반응형

 

 

 

 

 

우리가 적용 해 두었던, 조명 효과도, animation도 다 적용되지 않고 있습니다.

Shader를 쓴다고 하면, 조명은 이제 Shader에서 직접 계산해야 합니다. (에????? 왜 그런 불편한???... 싶은 생각이 드는데, 결과물을 보면 훨씬 남는 장사입니다.). animation도 vertex 좌표를 바꾼 것이 아니고, modelview matrix를 수정하는 방식으로 적용했기 때문에 나타나지 않습니다. 만약, animation 을 직접 vertex 좌표를 수정하는 방법으로 구현했다면 그대로 적용 되었을 것입니다. 다행히, model view matrix는 vertex 위치나 색상처럼 shader에서 참조 할 수 있는 예약 된 변수가 있습니다. 이를 반영한 vertex shader는 아래와 같고, 그 결과 animation은 그대로 적용 되어 있습니다.

 

 

이제는 GLSL을 이용해서 조명 효과 등 Shader의 맛을 볼 수 있는 구현을 하면 됩니다.

준비하는데 꽤 많은 사전 작업이 필요하죠?? 소스 코드들은 따로 첨부 합니다.

 

wingl_review_glsl02.zip
0.00MB

 

 WebGL이나 OpenGL ES도 플랫폼에 따라 다르긴 하지만 비슷한 과정들을 거쳐야 합니다.

그래서, opengl을 해 봤다고 해도, webGL, opengl es 환경에서 opengl을 바로 구현 할 수가 없기 때문에 다른 플랫폼으로 옮겨갈 때 많은 어려움을 겪게 됩니다. 이제는 다양한 환경, 최소한 모바일/웹은 대략이라도 알고 있어야 되는 시절인 것 같아요.

 

다음에는, 본격적인 진행 전에 잠시 뼈와 살이 되는 잔소리 살짝 하고 갈께요.

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

 

[opengl].[#2.GLSL] 03. Shader, 이걸 누가 설명 해 줬더라면...

나이가 들면서 점점 잔소리가 많아지는 것을 딸과의 대화에서 체감하고 있습니다. 예전에는 몰랐던 것을 이제 알게 되어, 똑같은 실수를 하지 않았으면 하는 바램이 크기 때문이죠. GLSL에서도

learn-and-give.tistory.com

 

728x90
반응형

댓글