반응형

OpenGL에서 텍스처를 사용하는 방법을 설명합니다.

이번 포스팅에서는 삼각형에 텍스처를 입히는 경우를 설명하고 다음 번에는 사각형에 텍스처 입히는 경우와 텍스처 방법 관련 옵션에 대해 설명할 예정입니다. 



1. 삼각형 오브젝트에 텍스쳐 이미지 매핑

    1.1. defineVertexArrayObject 함수

    1.2. initShaderProgram 함수

    1.3. CreateTexture 함수

    1.4. main 함수

 

2. 전체 소스 코드

 

3. 참고한 곳






텍스처는 보통 마인크래프트의 스킨처럼 2D 이미지로  3D 오브젝트에 세부적인 모습을 추가하기 위해 사용됩니다.

오른쪽에 있는 이미지를 3D로 만들어진 사람 오브젝트의 표면에 입혀주어 왼쪽처럼 만드는 겁니다. 

 

http://www.planetminecraft.com/skin/until-dawn-minecraft-skins---jessica/ 



텍스처를 사용하는 다른 이유는 버텍스(vertex)가 많아지는 경우 vertex마다 color 속성을 주면 오버헤드가 발생하기 때문입니다.



포스팅에 있는 코드를 컴파일하기 위해서는 다음 포스팅에 소개되어 있는 GLFW 및 GLEW 라이브러리 설정이 되어 있어야 합니다.

 

Visual Studio 2023에 OpenGL 개발 환경 만들기 ( GLFW / GLEW )

https://webnautes.tistory.com/2085





1. 삼각형 오브젝트에 텍스쳐 이미지 매핑

 

아래 포스팅에 마지막에 있는 삼각형을 그리는 전체 소스 코드를 텍스처를 사용하는 코드로 수정하였습니다. 

 

Modern OpenGL 강좌 - 삼각형 그리기

https://webnautes.tistory.com/2087




앞에서 그렸던 삼각형에 다음 이미지를 텍스쳐로 사용합니다.




실행결과 다음처럼 삼각형에 이미지가 입혀지게 됩니다. 




OpenGL에는 이미지 파일 불러오기 관련 API가 없기 때문에 별도의 라이브러리를 사용해야 합니다.   

본 포스팅에서는 FreeImage를 사용했습니다. 

 

http://freeimage.sourceforge.net/download.html 에서  FreeImage 바이너리를 다운로드 받습니다.

 




압축을 풀어서 FreeImage3180Win32Win64\FreeImage\Dist\x64에 있는 파일을 각각 다음 경로들에 복사해줍니다.

파일 이름 복사 경로 
FreeImage.h OpenGL-Example\OpenGL-Example\include
FreeImage.lib OpenGL-Example\OpenGL-Example\lib
FreeImage.dll  OpenGL-Example\OpenGL-Example





다음과 같이 헤더파일과 라이브러리를 코드에 추가 해줍니다. 




텍스처 좌표를 사용하여 텍스처 색상을 가져 오는 것을 샘플링이라고합니다.

 

텍스처를 삼각형에 매핑하려면 삼각형의 각 vertex가 텍스쳐 이미지의 어느 부분에 해당하는지 알려줄 필요가 있습니다. 

이를 위해 각 vertex는 샘플링될 텍스처 이미지의 부분을 지정하기 위한 텍스쳐 좌표(texture coordinate)를 가지고 있어야 합니다. 

vertex를 제외한 나머지 부분들에 대해서는 보간(interpolation)이 수행됩니다.

 

2D 텍스쳐의 경우 텍스처 좌표의 범위는 x축, y축 방향으로 각각 0에서 1사이입니다.

텍스 좌표계는 다음과 같습니다..

 



픽셀 좌표를 텍스처 좌표로 변환하려면, 픽셀 좌표 x는 텍스처의 너비로 나누어주고 픽셀 좌표 y는 텍스처의 높이로 나누어주면 됩니다.




1.1. defineVertexArrayObject 함수

vertex 데이터로 사용될 텍스처 좌표를 추가합니다. 

텍스처 좌표는 텍스처 이미지 상의 좌표로 픽셀 단위가 아니라,  0에서 1 사이의 값을 갖습니다. 




매핑시킬 이미지에 텍스처 좌표를 적어보면 다음 처럼 됩니다. 

텍스처 이미지의 왼쪽 아래에 대한 텍스처 좌표가 (0,0)이고 텍스처 이미지의 오른쪽 위에 대한 텍스처 좌표가 (1,1)입니다.  

 




다음 이미지는 텍스처 좌표가 삼각형에 어떻게 매핑되는 지를 보여줍니다. 

삼각형에 대해 3개의 텍스처 좌표를 지정하고 있습니다.

텍스처 이미지는 사각형인데 오브젝트는 삼각형이라 텍스처 이미지의 일부만 사용하여 매핑이 이루어지게 됩니다. 

 

삼각형 왼쪽 아래 vertex를 텍스처 이미지의 왼쪽 아래로 매핑시키기 위해  해당 vertex를 위한 텍스처 좌표로 (0,0)을 사용하고 있습니다. 

마찬가지로 삼각형 오른쪽 아래 vertex를 텍스처 이미지의 오른쪽 아래로 매핑시키기 위해 vertex를 위한 텍스처 좌표로 (1,0)을 사용하고 있습니다. 

삼각형 상단에 있는 vertex를 위해서는 텍스처 좌표 (0.5,1.0)을 사용합니다.

 

 




텍스처 좌표를 GPU 메모리에 업로드합니다. 

전역 변수 triangleTextureCoordinateBufferObjectID를 선언해주고..

defineVertexArrayObject 함수에  텍스처 좌표를 위한 Vertex Buffer Object(VBO)를 생성하여 데이터를 복사해줍니다.

GLuint trianglePositionVertexBufferObjectID, triangleColorVertexBufferObjectID;
GLuint triangleTextureCoordinateBufferObjectID;

 

//#2
//Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.
glGenBuffers(1, &trianglePositionVertexBufferObjectID);
glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

glGenBuffers(1, &triangleColorVertexBufferObjectID);
glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

glGenBuffers(1, &triangleTextureCoordinateBufferObjectID);
glBindBuffer(GL_ARRAY_BUFFER, triangleTextureCoordinateBufferObjectID);
glBufferData(GL_ARRAY_BUFFER, sizeof(textureCoordinate), textureCoordinate, GL_STATIC_DRAW);




defineVertexArrayObject 함수에 다음 코드를 추가하여 Vertex Shader의 textureCoordinateAttribute 속성 변수에서 VBO에 있는 데이터를 가져갈 수 있도록 해줍니다.

 



1.2. initShaderProgram 함수

Shader 코드에 텍스처 관련 코드를 추가합니다. 

 

vertex shader에서는 입력변수 textureCoordinateAttribute의 값을 출력변수 passTextureCoordinateAttribute에 대입하여 다음 단계로 넘겨줍니다.

기존에 넘겨주던 컬러 정보관련 코드들은 아래처럼 주석처리해줍니다.



fragment shader에서는 passTextureCoordinateAttribute를 입력변수로 지정하며, 기존 컬러 정보인 passColorAttribute 대신에 fragment_shader의 출력변수에 대입해줍니다.

결과적으로 삼각형에 컬러 대신에 텍스처 이미지를 매핑해주는 겁니다.




더 진행하기 전에 uniform 변수에 대해 알아야 합니다. 지금까지 shader 코드에서 사용한 변수들은 attribute 변수였습니다.  

 

두 변수의 차이를 적어보면 다음과 같습니다.

attribute 변수는 각 vertex 마다 다른 값을 가질 수 있지만 uniform 변수는 여러 vertex가 같은 값을 유지합니다. 예를 들어, 삼각형 전체의 색을 설정하려면 uniform 변수를 사용해야 하며, 삼각형의 각 vertex를 다른 색으로 설정하려면 attribute 변수를 사용해야 합니다.

 

attribute 변수의 값은 수정이 가능하지만 uniform 변수의 값은 상수로 취급되기 때문에 수정할 수 없습니다.

 

attribute 변수는 반드시 vertex shader의 입력으로 먼저 사용해야 하며 필요시 fragment shader에 전달해서 사용해야 합니다.  uniform 변수는 모든 shader에서 접근하여 사용가능합니다.



vertex shader에 3개의 vertex에 대한 텍스처 좌표만 전달하는데 이 값들은 fragment shader에 전달되면서  삼각형 전체에 해당되는 모든 텍스처 좌표를  계산합니다.



passTextureCoordinateAttribute는 attribute 변수입니다.삼각형의 각 vertex는 서로 다른 텍스처 좌표를 갖기 때문입니다.

uniform 키워드는 tex가 uniform 변수임을 나타냅니다. 삼각형의 모든 vertex가 같은 텍스처를 갖게 만들거라서 uniform변수를 사용합니다. sampler2D는 변수 타입으로, 2D 텍스쳐를 저장하게 됨을 의미합니다.




1.3. CreateTexture 함수

 

텍스처로 사용할 이미지를 FreeImage 라이브러리를 이용하여 로드하여 FIBITMAP으로 변환하고, 텍스처 객체에서 사용하도록 합니다. 



이미지 데이터를 저장할 메모리를 할당합니다.

GLuint tempTextureID;
glGenTextures(1, &tempTextureID);
glBindTexture(GL_TEXTURE_2D, tempTextureID);




이미지 데이터를 축소하거나 확대해서 텍스처로 사용할 경우에 사용되는 보간법을 지정합니다. 여기선 선형 보간법(linear interpolation)을 지정하고 있습니다..

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);




이미지 데이터를 텍스처로 사용합니다.



마지막으로 텍스처를 사용하기 전까지 언바인딩 해둡니다

glBindTexture(GL_TEXTURE_2D, 0);




1.4. main 함수

"tex" uniform 변수에 텍스처를 전달하고 렌더링에 사용할 텍스처를 바인딩해줍니다.

//FreeImage 라이브러리를 사용하여 이미지를 로드하고 텍스쳐를 생성합니다.
GLuint texureId = CreateTexture("number-board.jpg");

//"tex" uniform 변수에 텍스처를 전달한다.
glUniform1i(glGetUniformLocation(triangleShaderProgramID, "tex"), 0);


//렌더링에 사용할 텍스처를 바인딩한다.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texureId);



텍스처를 바로 shader로 전달할 수 없습니다. 

GLSL에서는 glGenTexture 함수로 생성된 텍스처 객체 대신에 텍스처 유닛을 사용하여 텍스처를 액세스 하기 때문입니다.  

텍스처를 텍스처 유닛(=texture image unit)에 바인딩하고 나서, 텍스처 유닛의 인덱스를 shader에 전달해야 합니다. 

주의할 점은 디바이스에 따라 사용할 수 있는 텍스처 유닛의 개수가 제한되어 있습니다.



텍스처 객체를 텍스처 유닛 0에 바인딩합니다.

glGetUniformLocation 함수를 호출하여 program 객체로부터 "tex" uniform 변수의 위치를 반환받습니다.

glUniform1i 함수를 호출하여 fragment shader의 uniform 변수인 tex에 텍스처를 전달합니다.

glUniform1i(glGetUniformLocation(triangleShaderProgramID, "tex"), 0);



glActiveTexture 함수를 호출하여 활성화할 텍스처 유닛으로 텍스처 유닛 0을 지정합니다.

렌더링에 사용할 텍스처 ID를 바인딩합니다. 텍스처 ID는 glGenTextures함수 호출로 얻은 텍스처 객체의 ID입니다.

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texureId);

 



2. 전체 소스 코드

#include "include/GL/glew.h"		
#include "include/GLFW/glfw3.h" 
#include "include/FreeImage.h"
#include <iostream>

#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "lib/glew32.lib")
#pragma comment(lib, "lib/glfw3.lib")
#pragma comment(lib, "lib/FreeImage.lib")


using namespace std;

int framebufferWidth, framebufferHeight;
GLuint triangleVertexArrayObject;
GLuint triangleShaderProgramID;
GLuint trianglePositionVertexBufferObjectID, triangleColorVertexBufferObjectID;
GLuint triangleTextureCoordinateBufferObjectID;



//CreateTexture 함수의 원본 코드 출처
//https://r3dux.org/2014/10/how-to-load-an-opengl-texture-using-the-freeimage-library-or-freeimageplus-technically/
//
// Method to load an image into a texture using the freeimageplus library. Returns the texture ID or dies trying.
GLuint CreateTexture(char const* filename)
{
	// Determine the format of the image.
	// Note: The second paramter ('size') is currently unused, and we should use 0 for it.
	FREE_IMAGE_FORMAT format = FreeImage_GetFileType(filename, 0);

	// Image not found? Abort! Without this section we get a 0 by 0 image with 0 bits-per-pixel but we don't abort, which
	// you might find preferable to dumping the user back to the desktop.
	if (format == -1)
	{
		cout << "Could not find image: " << filename << " - Aborting." << endl;
		exit(-1);
	}

	// Found image, but couldn't determine the file format? Try again...
	if (format == FIF_UNKNOWN)
	{
		cout << "Couldn't determine file format - attempting to get from file extension..." << endl;

		// ...by getting the filetype from the filename extension (i.e. .PNG, .GIF etc.)
		// Note: This is slower and more error-prone that getting it from the file itself,
		// also, we can't use the 'U' (unicode) variant of this method as that's Windows only.
		format = FreeImage_GetFIFFromFilename(filename);

		// Check that the plugin has reading capabilities for this format (if it's FIF_UNKNOWN,
		// for example, then it won't have) - if we can't read the file, then we bail out =(
		if (!FreeImage_FIFSupportsReading(format))
		{
			cout << "Detected image format cannot be read!" << endl;
			exit(-1);
		}
	}

	// If we're here we have a known image format, so load the image into a bitap
	FIBITMAP* bitmap = FreeImage_Load(format, filename);

	// How many bits-per-pixel is the source image?
	int bitsPerPixel = FreeImage_GetBPP(bitmap);

	// Convert our image up to 32 bits (8 bits per channel, Red/Green/Blue/Alpha) -
	// but only if the image is not already 32 bits (i.e. 8 bits per channel).
	// Note: ConvertTo32Bits returns a CLONE of the image data - so if we
	// allocate this back to itself without using our bitmap32 intermediate
	// we will LEAK the original bitmap data, and valgrind will show things like this:
	//
	// LEAK SUMMARY:
	//  definitely lost: 24 bytes in 2 blocks
	//  indirectly lost: 1,024,874 bytes in 14 blocks    <--- Ouch.
	//
	// Using our intermediate and cleaning up the initial bitmap data we get:
	//
	// LEAK SUMMARY:
	//  definitely lost: 16 bytes in 1 blocks
	//  indirectly lost: 176 bytes in 4 blocks
	//
	// All above leaks (192 bytes) are caused by XGetDefault (in /usr/lib/libX11.so.6.3.0) - we have no control over this.
	//
	FIBITMAP* bitmap32;
	if (bitsPerPixel == 32)
	{
		cout << "Source image has " << bitsPerPixel << " bits per pixel. Skipping conversion." << endl;
		bitmap32 = bitmap;
	}
	else
	{
		cout << "Source image has " << bitsPerPixel << " bits per pixel. Converting to 32-bit colour." << endl;
		bitmap32 = FreeImage_ConvertTo32Bits(bitmap);
	}

	// Some basic image info - strip it out if you don't care
	int imageWidth = FreeImage_GetWidth(bitmap32);
	int imageHeight = FreeImage_GetHeight(bitmap32);
	cout << "Image: " << filename << " is size: " << imageWidth << "x" << imageHeight << "." << endl;

	// Get a pointer to the texture data as an array of unsigned bytes.
	// Note: At this point bitmap32 ALWAYS holds a 32-bit colour version of our image - so we get our data from that.
	// Also, we don't need to delete or delete[] this textureData because it's not on the heap (so attempting to do
	// so will cause a crash) - just let it go out of scope and the memory will be returned to the stack.
	GLubyte* textureData = FreeImage_GetBits(bitmap32);

	// Generate a texture ID and bind to it
	GLuint tempTextureID;
	glGenTextures(1, &tempTextureID);
	glBindTexture(GL_TEXTURE_2D, tempTextureID);

	// Construct the texture.
	// Note: The 'Data format' is the format of the image data as provided by the image library. FreeImage decodes images into
	// BGR/BGRA format, but we want to work with it in the more common RGBA format, so we specify the 'Internal format' as such.
	glTexImage2D(GL_TEXTURE_2D,    // Type of texture
		0,                // Mipmap level (0 being the top level i.e. full size)
		GL_RGBA,          // Internal format
		imageWidth,       // Width of the texture
		imageHeight,      // Height of the texture,
		0,                // Border in pixels
		GL_BGRA,          // Data format
		GL_UNSIGNED_BYTE, // Type of texture data
		textureData);     // The image data to use for this texture

	// Specify our minification and magnification filters

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);


	glBindTexture(GL_TEXTURE_2D, 0);


	// Check for OpenGL texture creation errors
	GLenum glError = glGetError();
	if (glError)
	{
		cout << "There was an error loading the texture: " << filename << endl;

		switch (glError)
		{
		case GL_INVALID_ENUM:
			cout << "Invalid enum." << endl;
			break;

		case GL_INVALID_VALUE:
			cout << "Invalid value." << endl;
			break;

		case GL_INVALID_OPERATION:
			cout << "Invalid operation." << endl;

		default:
			cout << "Unrecognised GLenum." << endl;
			break;
		}

		cout << "See https://www.opengl.org/sdk/docs/man/html/glTexImage2D.xhtml for further details." << endl;
	}

	// Unload the 32-bit colour bitmap
	FreeImage_Unload(bitmap32);

	// If we had to do a conversion to 32-bit colour, then unload the original
	// non-32-bit-colour version of the image data too. Otherwise, bitmap32 and
	// bitmap point at the same data, and that data's already been free'd, so
	// don't attempt to free it again! (or we'll crash).
	if (bitsPerPixel != 32)
	{
		FreeImage_Unload(bitmap);
	}

	// Finally, return the texture ID
	return tempTextureID;
}




bool initShaderProgram() {

	//#3
	const GLchar* vertexShaderSource =
		"#version 330 core\n"
		"in vec3 positionAttribute;"
		//"in vec3 colorAttribute;"
		"in vec2 textureCoordinateAttribute;"
		//"out vec3 passColorAttribute;"
		"out vec2 passTextureCoordinateAttribute;"
		"void main()"
		"{"
		"gl_Position = vec4(positionAttribute, 1.0);"
		//"passColorAttribute = colorAttribute;"
		"passTextureCoordinateAttribute = textureCoordinateAttribute;"
		"}";


	//#4
	const GLchar* fragmentShaderSource =
		"#version 330 core\n"
		//"in vec3 passColorAttribute;"
		"in vec2 passTextureCoordinateAttribute;"
		"out vec4 fragmentColor;"
		"uniform sampler2D tex;"
		"void main()"
		"{"
		//컬러만 출력
		//"fragmentColor = vec4(passColorAttribute, 1.0);"
		//텍스처만 출력
		"fragmentColor = texture(tex, passTextureCoordinateAttribute);"
		//텍스처와 컬러 같이 출력
		//"fragmentColor = texture(tex, passTextureCoordinateAttribute)*vec4(passColorAttribute, 1.0); "
		"}";



	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	GLint result;
	GLchar errorLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
	if (!result)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, errorLog);
		cerr << "ERROR: vertex shader 컴파일 실패\n" << errorLog << endl;
		glDeleteShader(vertexShader);
		return false;
	}


	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
	if (!result)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, errorLog);
		cerr << "ERROR: fragment shader 컴파일 실패\n" << errorLog << endl;

		return false;
	}




	//#5
	triangleShaderProgramID = glCreateProgram();

	glAttachShader(triangleShaderProgramID, vertexShader);
	glAttachShader(triangleShaderProgramID, fragmentShader);

	glLinkProgram(triangleShaderProgramID);


	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);


	glGetProgramiv(triangleShaderProgramID, GL_LINK_STATUS, &result);
	if (!result) {
		glGetProgramInfoLog(triangleShaderProgramID, 512, NULL, errorLog);
		cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
		return false;
	}

	return true;
}



bool defineVertexArrayObject() {

	//#1
	//삼각형을 구성하는 vertex 데이터 - position과 color
	float position[] = {
		0.0f,  0.5f, 0.0f, //vertex 1  위 중앙
		0.5f, -0.5f, 0.0f, //vertex 2  오른쪽 아래
		-0.5f, -0.5f, 0.0f //vertex 3  왼쪽 아래
	};

	float color[] = {
		1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
		0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0) 
		0.0f, 0.0f, 1.0f  //vertex 3 : BLUE (0,0,1)
	};

	float textureCoordinate[] = {
		0.5f, 1.0f,  //vertex 1  
		1.0f, 0.0f,  //vertex 2
		0.0f, 0.0f   //vertex 3        
	};



	//#2
	//Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.
	glGenBuffers(1, &trianglePositionVertexBufferObjectID);
	glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
	glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

	glGenBuffers(1, &triangleColorVertexBufferObjectID);
	glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
	glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

	glGenBuffers(1, &triangleTextureCoordinateBufferObjectID);
	glBindBuffer(GL_ARRAY_BUFFER, triangleTextureCoordinateBufferObjectID);
	glBufferData(GL_ARRAY_BUFFER, sizeof(textureCoordinate), textureCoordinate, GL_STATIC_DRAW);


	//#6
	glGenVertexArrays(1, &triangleVertexArrayObject);
	glBindVertexArray(triangleVertexArrayObject);


	GLint positionAttribute = glGetAttribLocation(triangleShaderProgramID, "positionAttribute");
	if (positionAttribute == -1) {
		cerr << "position 속성 설정 실패" << endl;
		return false;
	}
	glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
	glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);

	glEnableVertexAttribArray(positionAttribute);


	/*
	GLint colorAttribute = glGetAttribLocation(triangleShaderProgramID, "colorAttribute");
	if (colorAttribute == -1) {
		cerr << "color 속성 설정 실패" << endl;
		return false;
	}
	glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
	glVertexAttribPointer(colorAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(colorAttribute);
	*/


	GLint textureCoordinateAttribute = glGetAttribLocation(triangleShaderProgramID, "textureCoordinateAttribute");
	if (textureCoordinateAttribute == -1) {
		cerr << "Texture Coordinate 속성 설정 실패" << endl;
		return false;
	}
	glBindBuffer(GL_ARRAY_BUFFER, triangleTextureCoordinateBufferObjectID);
	glVertexAttribPointer(textureCoordinateAttribute, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(textureCoordinateAttribute);


	glBindVertexArray(0);


	return true;
}




void framebufferSizeCallback(GLFWwindow* window, int width, int height)
{
	//처음 2개의 파라미터는 viewport rectangle의 왼쪽 아래 좌표
	//다음 2개의 파라미터는 viewport의 너비와 높이이다.
	//framebuffer의 width와 height를 가져와 glViewport에서 사용한다.
	glViewport(0, 0, width, height);

	framebufferWidth = width;
	framebufferHeight = height;
}



void errorCallback(int errorCode, const char* errorDescription)
{
	cerr << "Error: " << errorDescription << endl;
}



void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GLFW_TRUE);
}




int main()
{

	glfwSetErrorCallback(errorCallback);


	if (!glfwInit()) {

		cerr << "Error: GLFW 초기화 실패" << endl;
		std::exit(EXIT_FAILURE);
	}



	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	//glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
	glfwWindowHint(GLFW_SAMPLES, 4);


	GLFWwindow* window = glfwCreateWindow(
		800,
		600,
		"OpenGL Example",
		NULL, NULL);
	if (!window) {

		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}


	glfwMakeContextCurrent(window);


	glfwSetKeyCallback(window, keyCallback);
	glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);



	glewExperimental = GL_TRUE;
	GLenum errorCode = glewInit();
	if (GLEW_OK != errorCode) {

		cerr << "Error: GLEW 초기화 실패 - " << glewGetErrorString(errorCode) << endl;

		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}



	if (!GLEW_VERSION_3_3) {

		cerr << "Error: OpenGL 3.3 API is not available." << endl;

		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}


	cout << "OpenGL version: " << glGetString(GL_VERSION) << endl;
	cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
	cout << "Vendor: " << glGetString(GL_VENDOR) << endl;
	cout << "Renderer: " << glGetString(GL_RENDERER) << endl;




	if (!initShaderProgram()) {

		cerr << "Error: Shader Program 생성 실패" << endl;

		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}



	if (!defineVertexArrayObject()) {

		cerr << "Error: Shader Program 생성 실패" << endl;

		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}





	glfwSwapInterval(1);



	double lastTime = glfwGetTime();
	int numOfFrames = 0;
	int count = 0;





	glUseProgram(triangleShaderProgramID);
	glBindVertexArray(triangleVertexArrayObject);


	GLuint texureId = CreateTexture("number-board.jpg");

	glUniform1i(glGetUniformLocation(triangleShaderProgramID, "tex"), 0);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texureId);


	while (!glfwWindowShouldClose(window)) {


		double currentTime = glfwGetTime();
		numOfFrames++;
		if (currentTime - lastTime >= 1.0) {

			printf("%f ms/frame  %d fps \n", 1000.0 / double(numOfFrames), numOfFrames);
			numOfFrames = 0;
			lastTime = currentTime;
		}



		glClearColor(0, 0, 0, 1);
		glClear(GL_COLOR_BUFFER_BIT);


		glDrawArrays(GL_TRIANGLES, 0, 3);


		count++;

		glfwSwapBuffers(window);
		glfwPollEvents();

	}


	glUseProgram(0);
	glBindVertexArray(0);


	glDeleteProgram(triangleShaderProgramID);
	glDeleteBuffers(1, &trianglePositionVertexBufferObjectID);
	glDeleteBuffers(1, &triangleColorVertexBufferObjectID);
	glDeleteBuffers(1, &triangleTextureCoordinateBufferObjectID);
	glDeleteVertexArrays(1, &triangleVertexArrayObject);
	glfwTerminate();

	std::exit(EXIT_SUCCESS);
}

3. 참고한 곳

https://learnopengl.com/#!Getting-started/Textures 

 

https://open.gl/textures

 












반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts