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

[opengl].[#2.GLSL] 16. Texturing 기초

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

제법 멋진 Spot light까지 쉐이더로 구현 해 보았습니다.

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

 

[opengl].[#2.GLSL] 15. Spot light

Directional light를 살펴보다가 Positional light, 즉, 점광원을 살펴봤습니다. https://learn-and-give.tistory.com/62 [opengl].[#2.GLSL] 14. Positional light 앞에서 Ambient+Specular light의 구현을 해보았습니다. https://learn-and-g

learn-and-give.tistory.com

 

이제 텍스쳐링을 해 보겠습니다.

일단 쉐이더 없는 텍스쳐링을 먼저 구현 해 보겠습니다.

텍스쳐로 사용 할 이미지 파일을 읽어서 텍스쳐로 쓸 수 있는 형태로 가공 하는 코드가 필요하고,

텍스쳐 관련 설정을 해주는 코드도 필요하고,

텍스쳐가 입혀진 형상을 그려주는 코드도 필요하겠네요.

 

하나씩 해보겠습니다.

 

 

텍스쳐가 입혀 질 육면체

 정육면체에 텍스쳐를 입힐 것인데, 한 변의 길이에 대한 정보만 주면, Vertex 좌표나 텍스쳐 좌표는 다 정의 할 수 있습니다. 이런 준비를 해보겠습니다.

 아래 c는 육면체의 각 꼭지점이고, t는 텍스쳐 좌표입니다.

#define CUBE_LEN	0.5f

float c0[3] = { -CUBE_LEN,-CUBE_LEN,CUBE_LEN};
float c1[3] = { CUBE_LEN,-CUBE_LEN,CUBE_LEN};
float c2[3] = { CUBE_LEN,CUBE_LEN,CUBE_LEN};
float c3[3] = { -CUBE_LEN,CUBE_LEN,CUBE_LEN};
float c4[3] = { -CUBE_LEN,-CUBE_LEN,-CUBE_LEN};
float c5[3] = { CUBE_LEN,-CUBE_LEN,-CUBE_LEN};
float c6[3] = { CUBE_LEN,CUBE_LEN,-CUBE_LEN};
float c7[3] = { -CUBE_LEN,CUBE_LEN,-CUBE_LEN};

float t0[2] = { 0.f,0.f};
float t1[2] = { 1.f,0.f};
float t2[2] = { 1.f,1.f};
float t3[2] = { 0.f,1.f};

 

육면체를 그리는 코드는 아래와 같습니다.

육면체의 한 면에 할당 될 법선을 정하고, 이어서 각 Vertex의 텍스쳐 좌표와 vertex 좌표를 설정하는 형식으로 그립니다.

한 면은 두 개의 삼각형으로 그려집니다.

//GLuint  textures[2];
void	DrawCube()
{
	glBegin(GL_TRIANGLES);
	//+Z
		glNormal3f(0.f,0.f,1.f);
		glTexCoord2fv(t0);			glVertex3fv(c0);
		glTexCoord2fv(t1);			glVertex3fv(c1);
		glTexCoord2fv(t2);			glVertex3fv(c2);
		
		glTexCoord2fv(t0);			glVertex3fv(c0);
		glTexCoord2fv(t2);			glVertex3fv(c2);
		glTexCoord2fv(t3);			glVertex3fv(c3);
	//-Z
		glNormal3f(0.f,0.f,-1.f);
		glTexCoord2fv(t0);			glVertex3fv(c5);
		glTexCoord2fv(t1);			glVertex3fv(c4);
		glTexCoord2fv(t2);			glVertex3fv(c7);
		
		glTexCoord2fv(t0);			glVertex3fv(c5);
		glTexCoord2fv(t2);			glVertex3fv(c7);
		glTexCoord2fv(t3);			glVertex3fv(c6);
	//+X
		glNormal3f(1.f,0.f,0.f);
		glTexCoord2fv(t0);			glVertex3fv(c1);
		glTexCoord2fv(t1);			glVertex3fv(c5);
		glTexCoord2fv(t2);			glVertex3fv(c6);
		
		glTexCoord2fv(t0);			glVertex3fv(c1);
		glTexCoord2fv(t2);			glVertex3fv(c6);
		glTexCoord2fv(t3);			glVertex3fv(c2);
	//-X
		glNormal3f(-1.f,0.f,0.f);
		glTexCoord2fv(t3);			glVertex3fv(c7);
		glTexCoord2fv(t0);			glVertex3fv(c4);
		glTexCoord2fv(t1);			glVertex3fv(c0);
		
		glTexCoord2fv(t2);			glVertex3fv(c3);
		glTexCoord2fv(t3);			glVertex3fv(c7);
		glTexCoord2fv(t1);			glVertex3fv(c0);
	//+Y
		glNormal3f(0.f,1.f,0.f);
		glTexCoord2fv(t0);			glVertex3fv(c2);
		glTexCoord2fv(t1);			glVertex3fv(c6);
		glTexCoord2fv(t2);			glVertex3fv(c7);
		
		glTexCoord2fv(t0);			glVertex3fv(c2);
		glTexCoord2fv(t2);			glVertex3fv(c7);
		glTexCoord2fv(t3);			glVertex3fv(c3);
	//-Y
		glNormal3f(0.f,-1.f,0.f);
		glTexCoord2fv(t1);			glVertex3fv(c5);
		glTexCoord2fv(t2);			glVertex3fv(c1);
		glTexCoord2fv(t3);			glVertex3fv(c0);
		
		glTexCoord2fv(t0);			glVertex3fv(c4);
		glTexCoord2fv(t1);			glVertex3fv(c5);
		glTexCoord2fv(t3);			glVertex3fv(c0);

	glEnd();
}

 

조명은 앞에서 사용했던 spot light를 그대로 뒀는데 아래와 같이 나오네요. 

 

 

 

텍스쳐 이미지 처리

 

텍스쳐로 사용 할 이미지 파일을 읽어야 하는데, TGA 포멧을 써서 해보겠습니다. 이 글 자체가 예전에 써뒀던 글이라 예전 코드를 정리하면서 쓰고 있다보니 그 때는 투명한 채널 정보를 사용 하기 위해서는 TGA를 썼기 때문에 TGA를 쓰게 되네요. 요즘은 png 많이 쓰죠~.

 

먼저 TAG를 읽어서 텍스쳐에 사용 할 정보들을 정리해서 저장 할 구조체를 정의 합니다.

/////////////////////////// TEXTURE TGA 
typedef struct									// Create A Structure
{
	GLubyte	*imageData;							// Image Data (Up To 32 Bits)
	GLuint	bpp;								// Image Color Depth In Bits Per Pixel.
	GLuint	width;								// Image Width
	GLuint	height;								// Image Height
	GLuint	texID;								// Texture ID Used To Select A Texture
} TextureImage;

TextureImage textures[2];							// Storage For 2 Textures

텍스쳐는 하나만 쓸 것이라 하나만 있으면 되는데, 나중에 하나 더 쓸 예정이라 겸사 겸사 두개 만들어 둡니다.

 

TAG 파일을 읽어서 저 구조체를 채울 TGA 로더도 아래와 같이 작성 합니다. 요즘은 이런거 직접 구현하는 일 별로 없겠죠?? 하긴...OpenGL 자체를 직접 다룰 일이 별로 없을 것 같네요.

bool LoadTGA(TextureImage *texture, char *filename)				// Loads A TGA File Into Memory
{    
	GLubyte		TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};		// Uncompressed TGA Header
	GLubyte		TGAcompare[12];						// Used To Compare TGA Header
	GLubyte		header[6];						// First 6 Useful Bytes From The Header
	GLuint		bytesPerPixel;						// Holds Number Of Bytes Per Pixel Used In The TGA File
	GLuint		imageSize;						// Used To Store The Image Size When Setting Aside Ram
	GLuint		temp;							// Temporary Variable
	GLuint		type=GL_RGBA;						// Set The Default GL Mode To RBGA (32 BPP)

	FILE *file = fopen(filename, "rb");					// Open The TGA File

	if(	file==NULL ||							// Does File Even Exist?
		fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||	// Are There 12 Bytes To Read?
		memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 ||		// Does The Header Match What We Want?
		fread(header,1,sizeof(header),file)!=sizeof(header))		// If So Read Next 6 Header Bytes
	{
		if (file == NULL)						// Does The File Even Exist? *Added Jim Strong*
			return FALSE;						// Return False
		else								// Otherwise
		{
			fclose(file);						// If Anything Failed, Close The File
			return FALSE;						// Return False
		}
	}

	texture->width  = header[1] * 256 + header[0];				// Determine The TGA Width	(highbyte*256+lowbyte)
	texture->height = header[3] * 256 + header[2];				// Determine The TGA Height	(highbyte*256+lowbyte)
    
 	if(	texture->width	<=0 ||						// Is The Width Less Than Or Equal To Zero
		texture->height	<=0 ||						// Is The Height Less Than Or Equal To Zero
		(header[4]!=24 && header[4]!=32))				// Is The TGA 24 or 32 Bit?
	{
		fclose(file);							// If Anything Failed, Close The File
		return FALSE;							// Return False
	}

	texture->bpp	= header[4];						// Grab The TGA's Bits Per Pixel (24 or 32)
	bytesPerPixel	= texture->bpp/8;					// Divide By 8 To Get The Bytes Per Pixel
	imageSize		= texture->width*texture->height*bytesPerPixel;	// Calculate The Memory Required For The TGA Data

	texture->imageData=(GLubyte *)malloc(imageSize);			// Reserve Memory To Hold The TGA Data

	if(	texture->imageData==NULL ||					// Does The Storage Memory Exist?
		fread(texture->imageData, 1, imageSize, file)!=imageSize)	// Does The Image Size Match The Memory Reserved?
	{
		if(texture->imageData!=NULL)					// Was Image Data Loaded
			free(texture->imageData);				// If So, Release The Image Data

		fclose(file);							// Close The File
		return FALSE;							// Return False
	}

	for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)			// Loop Through The Image Data
	{									// Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
		temp=texture->imageData[i];					// Temporarily Store The Value At Image Data 'i'
		texture->imageData[i] = texture->imageData[i + 2];		// Set The 1st Byte To The Value Of The 3rd Byte
		texture->imageData[i + 2] = temp;				// Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
	}

	fclose (file);								// Close The File

	// Build A Texture From The Data
	glGenTextures(1, &(texture->texID));					// Generate OpenGL texture IDs

	glBindTexture(GL_TEXTURE_2D, texture->texID);				// Bind Our Texture
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear Filtered
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Filtered
	
	if (texture[0].bpp==24)							// Was The TGA 24 Bits
	{
		type=GL_RGB;							// If So Set The 'type' To GL_RGB
	}

	glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

	return true;								// Texture Building Went Ok, Return True
}

 

이제 저 함수를 이용하여 이미지 파일을 읽어 들인 후, Texture로 등록하는 과정을 처리할 InitTexture 함수를 만듭니다. Extension을 체크하는 부분은 필요 할 때 쓰면 됩니다. 

void InitTexture()
{
	bool result;
	result = LoadTGA(&textures[0], (char*)("box_tga.tga"));

    /*
	GLubyte str[1024];

	char* extensions;
	extensions = strdup((char*)glGetString(GL_EXTENSIONS));			// Fetch Extension String
	int len = strlen(extensions);
	for (int i = 0; i < len; i++)										// Separate It By Newline Instead Of Blank
		if (extensions[i] == ' ') extensions[i] = '\n';
	printf("%s", extensions);
    */

	glEnable(GL_TEXTURE_2D);

	// TEXTURE-UNIT #0		
	//glActiveTextureARB(GL_TEXTURE0_ARB);
	glBindTexture(GL_TEXTURE_2D, textures[0].texID);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
	glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);
}

이 함수도 조명/재질 초기화 하는 함수를 호출하는 부분에서 이어서 호출 하도록 합니다.

void initGL()
{
	LightInit();
	InitTexture();  // <----

	//Modelview and projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
	glMatrixMode(GL_MODELVIEW);
}

자, 이제 이렇게 하면 모든 코드가 준비 되었는데요, 이 상태에서 실행을 하면....지금까지 텍스쳐링을 위해 준비한 것이 전혀 나오지 않습니다.

쉐이더가 설정되어 있기 때문에, 쉐이더에는 텍스쳐링 처리가 되어 있지 않기 때문입니다. 그럼, 텍스쳐링을 할 준비가 잘 되었는지 쉐이더 설정을 하지 않고, fixed rendering pipeline으로 확인 해 보겠습니다. 조명과 마찬가지로 할꺼면 다해야 되는거죠. ^^

 

동일한 코드에서 쉐이더 활성화만 다르게 하여 결과를 비교 해 보는 영상입니다.

 

자, 그럼 이걸 쉐이더로 어떻게 그리게 할 것인가~~~~

일단 조명에 대한 내용은 지금 필요 없으니, 기존 쉐이더 코드에서 조명은 다 지우고, 텍스쳐링 부분만 작성 해 보겠습니다.

 

Vertex Shader

특별히 할 것 없습니다. 그냥 Vertex의 위치와 Texture 좌표만 잘 넘겨주면 됩니다. 여기서 입력 텍스쳐 좌표가 MultiTexCoord0입니다. 다중 텍스쳐를 사용 할 수 있는 기반이라는 것을 볼 수 있고, 그 중 0번째 텍스쳐 좌표라는 것을 알 수 있습니다. 

void main()
{
	gl_TexCoord[0] = gl_MultiTexCoord0;
	gl_Position = ftransform();
}

 

Fragment Shader

Fragment shader도 별로 작성 할 코드가 많지 않습니다. 여기서는 텍스쳐 데이터에서 색상을 얻어와서 그것을 해당 Pixel의 색상으로 설정 해 주는 코드만 있습니다.

uniform sampler2D tex;

void main()
{
	vec4 color = texture2D(tex,gl_TexCoord[0].st);
	gl_FragColor = color;
}

 

이렇게만 설정하고 실행하면, 아래와 같이 텍스쳐링이 된 결과가 그려집니다. 이렇게 준비가 되면, 쉐이더 설정을 하면 쉐이더에 의해서 그려지는 것이고, 쉐이더 설정을 하지 않으면 고정 파이프 라인으로 그려지게 됩니다.

 

여기까지 GLSL에서의 텍스쳐링을 살펴 보았습니다. 

지금은 이미지만 입혀져 있을 뿐, 조명에 의한 음영 효과는 전혀 없는데 그런 효과도 내도록 해보겠습니다. 이것은 기본적으로 텍스쳐링이 되지 않은 상태와 텍스쳐를 어떻게 섞을 것인가를 통해 결정되는 것으로, 쉐이더 없이도 제공되던 옵션이었습니다. 즉, 텍스쳐링 모드 GL_REPLACE, GL_MODULATE, GL_DECAL 옵션에 따라서 결과가 영향을 받게 됩니다. 이런 내용을 다음 시간에 살펴 보도록 하겠습니다.

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

 

[opengl].[#2.GLSL] 17. Texturing 모드

앞에서 GLSL 기반의 기본적인 Texturing을 살펴 보았습니다. https://learn-and-give.tistory.com/64 [opengl].[#2.GLSL] 16. Texturing 기초 제법 멋진 Spot light까지 쉐이더로 구현 해 보았습니다. https://learn-and-give.tistory.

learn-and-give.tistory.com

 

728x90
반응형

댓글