* Computer Graphics Programming in OpenGL with C++ 책을 참고하였습니다.
* 책을 번역한 것이 아닌, 제가 독학 후 책을 참고하여 설명하는 게시물입니다. 따라서 책에 없는 부연 설명이 있기도 하며, 의역 또는 오역, 오개념이 있을 수 있습니다. 피드백은 댓글을 남겨주세요.
* 영어 용어를 최대한 한국어로 번역하지 않습니다. 처음부터 코드에서 사용되는, 또는 원서나 인터넷에서 사용되는 보편적 용어를 사용하여 개념을 잡는 것을 추천드립니다.
궁극적으로 우리는 보통 하나 이상의 정점을 활용해 개체를 생성하는 것을 목표로 합니다. 따라서 우선은 3개의 정점으로 삼각형을 그리는 간단한 예제부터 실행해보도록 하죠.
지난 [Program 2.2]를 약간만 수정하면 됩니다. 1) vertex 쉐이더를 수정하여 파이프라인의 다음 단계로 3개의 다른 정점이 넘어가게 합니다. 2) glDrawArrays() 함수를 수정하여 우리가 3개의 정점을 사용할 것을 설정해줍니다.
glDrawArray() 함수 안에 GL_POINTS 대신 GL_TRIANGLES를 넣어주고 정점을 3개로 설정해주면, vertex 쉐이더가 3번 실행됩니다. vertex 쉐이더가 실행될 때마다, gl_VertexID가 자동적으로 증가합니다. (0부터 시작) gl_VertexID 값을 테스트하여, 쉐이더는 실행되는 3번 각각 다른 점을 출력하게 됩니다. 그다음, 채워진 삼각형을 출력하기 위해 각각의 정점은 rasterization 단계를 거치는 것, 기억나시나요?
[Program 2.5 Drawing a Triangle]
// main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>
#define numVAOs 1
GLuint renderingProgram;
GLuint vao[numVAOs];
using namespace std;
void printShaderLog(GLuint shader) {
int len = 0;
int chWrittn = 0;
char* log;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char*)malloc(len);
glGetShaderInfoLog(shader, len, &chWrittn, log);
cout << "Shader Info Log: " << log << endl;
free(log);
}
}
void printProgramLog(int prog) {
int len = 0;
int chWrittn = 0;
char* log;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char*)malloc(len);
glGetProgramInfoLog(prog, len, &chWrittn, log);
cout << "Program Info Log: " << log << endl;
free(log);
}
}
bool checkOpenGLError() {
bool foundError = false;
int glErr = glGetError();
while (glErr != GL_NO_ERROR) {
cout << "glError: " << glErr << endl;
foundError = true;
glErr = glGetError();
}
return foundError;
}
string readShaderSource(const char *filePath) {
string content = "";
ifstream fileStream(filePath, ios::in);
string line = "";
while (!fileStream.eof()) {
getline(fileStream, line);
content.append(line + "\n");
}
fileStream.close();
return content;
}
GLuint createShaderProgram() {
GLint vertCompiled;
GLint fragCompiled;
GLint linked;
string vertShaderStr = readShaderSource("vertShader.glsl");
string fragShaderStr = readShaderSource("fragShader.glsl");
const char* vertShaderSrc = vertShaderStr.c_str();
const char* fragShaderSrc = fragShaderStr.c_str();
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vShader, 1, &vertShaderSrc, nullptr);
glShaderSource(fShader, 1, &fragShaderSrc, nullptr);
glCompileShader(vShader);
checkOpenGLError();
glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
if (vertCompiled != 1) {
cout << "vertex compilation failed" << endl;
printShaderLog(vShader);
}
glCompileShader(fShader);
checkOpenGLError();
glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
if (fragCompiled != 1) {
cout << "fragment compilation failed" << endl;
printShaderLog(fShader);
}
GLuint vfProgram = glCreateProgram();
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
checkOpenGLError();
glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
if (linked != 1) {
cout << "linking failed" << endl;
printProgramLog(vfProgram);
}
return vfProgram;
}
void init (GLFWwindow* window) {
renderingProgram = createShaderProgram();
glGenVertexArrays(numVAOs, vao);
glBindVertexArray(vao[0]);
}
void display(GLFWwindow* window, double currentTime) {
glUseProgram(renderingProgram);
glPointSize(30.0f);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
int main(void) {
if (!glfwInit()) {exit(EXIT_FAILURE);}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter2 - program5", nullptr, nullptr);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) {exit(EXIT_FAILURE);}
glfwSwapInterval(1);
init(window);
while (!glfwWindowShouldClose(window)) {
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
// vertShader.glsl
#version 410
void main(void) {
if (gl_VertexID==0) gl_Position = vec4(0.25, -0.25, 0.0, 1.0);
else if (gl_VertexID==1) gl_Position = vec4(-0.25, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25, 0.25, 0.0, 1.0);
}
// fragShader.glsl
#version 410
out vec4 color;
void main(void) {
color = vec4(0.0, 0.0, 1.0, 1.0);
}
위 프로그램 main.cpp에서 변경된 부분은 glDrawArrays() 함수가 전부입니다.
// main.cpp display()
//glDrawArrays(GL_POINTS, 0, 1);
glDrawArrays(GL_TRIANGLES, 0, 3);
이 함수가 실행됨에 따라 vertex 쉐이더가 gl_VertexID를 0에서 2까지 증가시키며 3번 실행됩니다.
// vertShader.glsl main()
if (gl_VertexID==0) gl_Position = vec4(0.25, -0.25, 0.0, 1.0);
else if (gl_VertexID==1) gl_Position = vec4(-0.25, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25, 0.25, 0.0, 1.0);
gl_VertexID가 바뀔 때마다 각기 다른 좌표를 gl_Position에 대입합니다. 이 세 가지 다른 값을 가진 gl_Position은 각각 output으로 파이프라인 다음 단계로 넘어가게 됩니다.
자, 이렇게 만든 삼각형에 애니메이팅을 적용해 봅시다. 우리의 OpenGL/C++ 프로그램은 반복적으로 화면을 렌더했던 것을 기억해봅시다. 시간에 따라 다른 값을 출력한다면, real time으로 (실시간으로) 장면에 변화를 줄 수 있습니다.
각각의 렌더링을 frame(프레임)이라고 부릅니다. display() 함수의 호출의 빈번도는 frame rate (프레임 주기)가 되죠. 프레임을 컨트롤하기 위해, 우리는 이전 프레임으로부터 경과된 시간을 사용하여 로직을 구상합니다. 이를 위해서 display() 함수에 currentTime이라는 파라미터를 포함시켜야 합니다.
[Program 2.6 Simple Animation Example]
// main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>
#define numVAOs 1
GLuint renderingProgram;
GLuint vao[numVAOs];
using namespace std;
void printShaderLog(GLuint shader) {
// 수정사항 없음
}
void printProgramLog(int prog) {
// 수정사항 없음
}
bool checkOpenGLError() {
// 수정사항 없음
}
string readShaderSource(const char *filePath) {
// 수정사항 없음
}
GLuint createShaderProgram() {
// 수정사항 없음
}
void init (GLFWwindow* window) {
// 수정사항 없음
}
float x = 0.0f;
float inc = 0.0f;
void display(GLFWwindow* window, double currentTime) {
glClear(GL_DEPTH_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(renderingProgram);
x += inc;
if (x>1.0f) inc = -0.01f;
if (x<-1.0f) inc = 0.01f;
GLuint offsetLoc = glGetUniformLocation(renderingProgram, "offset");
glProgramUniform1f(renderingProgram, offsetLoc, x);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
int main(void) {
// 수정사항 없음
}
// vertShader.glsl
#version 410
uniform float offset;
void main(void) {
if (gl_VertexID==0) gl_Position = vec4(0.25 + offset, -0.25, 0.0, 1.0);
else if (gl_VertexID==1) gl_Position = vec4(-0.25 + offset, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25 + offset, 0.25, 0.0, 1.0);
}
// fragShader.glsl
// 수정사항 없음
// main.cpp
float x = 0.0f;
void display(GLFWwindow* window, double currentTime) {
// ...
x += inc;
// ...
}
display()함수는 삼각형의 X 좌표 위치를 offset(상대적 위치 설정)하기 위해 변수 x를 가지고 있습니다. 이 예제에서 x는 시간의 경과에 따라서가 아닌, display()display() 함수가 실행됨에 따라서 변화합니다. 즉 프레임에 따라 변화됩니다.
// main.cpp display()
if (x>1.0f) inc = -0.01f;
if (x<-1.0f) inc = 0.01f;
x 값이 1.0과 -1.0에 도달할 때마다 방향이 변화됩니다.
다음은 offset이라는 uniform 변수에 변수 x의 값을 넘기는 방법입니다. 책에서는 나중에 설명하지만, 여기서 잠깐 간단히 설명하고 넘어가도록 하겠습니다.
// vertShader.glsl
uniform float offset;
vertex 쉐이더에서 offset이라는 이름을 가진 float형 uniform 변수를 선언합니다.
// main.cpp display()
GLuint offsetLoc = glGetUniformLocation(renderingProgram, "offset");
glGetUniformLocation은 renderingProgram에서 "offset"이라는 이름을 가진 uniform 변수의 주소를 반환합니다. offsetLoc이라는 unsigned int 변수에 대입되죠.
// main.cpp display()
glProgramUniform1f(renderingProgram, offsetLoc, x);
glProgramUniform1f는 renderingProgram에 있는 offsetLoc 주소에 해당하는 uniform 변수에 float x 값을 대입합니다.
// vertShader.glsl main()
if (gl_VertexID==0) gl_Position = vec4(0.25 + offset, -0.25, 0.0, 1.0);
else if (gl_VertexID==1) gl_Position = vec4(-0.25 + offset, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25 + offset, 0.25, 0.0, 1.0);
그 후에는 vertex 쉐이더에서 offset 변수를 사용할 수 있습니다.
// main.cpp display()
glClear(GL_DEPTH_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
display() 함수의 시작 부분에는 glClear() 함수를 넣어 depth 버퍼와 color 버퍼를 초기화시켜야 합니다. 그래야 display() 함수가 호출되어 새 프레임이 렌더될 때마다 누적 결과가 아닌 새로 렌더된 결과만 출력됩니다. glClearColor()는 color 버퍼를 초기화할 색상을 정해줍니다. 또한, 더 복잡한 3D 장면을 그리기 위해 깊이 비교가 이전 깊이 데이터의 영향을 받지 않도록 각 프레임의 depth 버퍼도 초기화해야 합니다.
지금까지 새로운 OpenGL 메소드들을 배워옴에 따라, main.cpp가 복잡하고 무거워졌습니다. 따라서 자주 (거의 항상) 쓰이는 메소드들을 별도의 파일로 분리하여 모둘화를 진행하는 것을 추천드립니다. 또 앞으로 새로 정의될 클래스들도 cpp 파일과 h 파일로 구분하도록 하겠습니다.
메소드, 즉 재사용될 함수를 Utils.cpp와 Utils.h로 먼저 모듈화 해봅시다. [Program 2.2]의 오류 검출 함수들, [Program 2.3]의 GLSL 쉐이더 프로그램을 읽기 위한 함수들을 옮기도록 할게요. (특히 쉐이더를 읽는 함수들을 분리하는 것은 나중에 파이프라인 쉐이더의 가능한 각각 조합에 대해 createShaderProgram() 함수를 오버로드 정의하는 데에 적합합니다.)
GLuint Utils::createShaderProgram(const char* vp, const char* fp)
GLuint Utils::createShaderProgram(const char* vp, const char* gp, const char* fp)
GLuint Utils::createShaderProgram(const char* vp, const char* tCS, const char* tES, const char *fp)
GLuint Utils::createShaderProgram(const char* vp, const char* tCS, const char* tES, const char *gp, const char *fp)
위 오버로딩들은 vertex 쉐이더 (vp), tessellation (tCS, tES), geometry 쉐이더 (gp), fragment 쉐이더 (fp)를 조합하여 설정하게 해 줍니다. 아래와 같이 사용하면 됩니다.
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
위 호출은 파라미터로 주소가 제공된 쉐이더들을 포함하는 쉐이더 파이프라인 프로그램을 컴파일하고 연결하여 변수 renderingProgram에 대입합니다.
책에서 제공하는 Utils.h, Utils.cpp 파일의 내용은 다음과 같습니다.
// Utils.h
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <SOIL2/SOIL2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
class Utils
{
private:
static std::string readShaderFile(const char *filePath);
static void printShaderLog(GLuint shader);
static void printProgramLog(int prog);
static GLuint prepareShader(int shaderTYPE, const char *shaderPath);
static int finalizeShaderProgram(GLuint sprogram);
public:
Utils();
static bool checkOpenGLError();
static GLuint createShaderProgram(const char *vp, const char *fp);
static GLuint createShaderProgram(const char *vp, const char *gp, const char *fp);
static GLuint createShaderProgram(const char *vp, const char *tCS, const char* tES, const char *fp);
static GLuint createShaderProgram(const char *vp, const char *tCS, const char* tES, char *gp, const char *fp);
static GLuint loadTexture(const char *texImagePath);
static GLuint loadCubeMap(const char *mapDir);
static float* goldAmbient();
static float* goldDiffuse();
static float* goldSpecular();
static float goldShininess();
static float* silverAmbient();
static float* silverDiffuse();
static float* silverSpecular();
static float silverShininess();
static float* bronzeAmbient();
static float* bronzeDiffuse();
static float* bronzeSpecular();
static float bronzeShininess();
};
// Utils.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
//#include <SOIL2/SOIL2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp> // glm::value_ptr
#include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Utils.h"
using namespace std;
Utils::Utils() {}
string Utils::readShaderFile(const char *filePath) {
string content;
ifstream fileStream(filePath, ios::in);
string line = "";
while (!fileStream.eof()) {
getline(fileStream, line);
content.append(line + "\n");
}
fileStream.close();
return content;
}
bool Utils::checkOpenGLError() {
bool foundError = false;
int glErr = glGetError();
while (glErr != GL_NO_ERROR) {
cout << "glError: " << glErr << endl;
foundError = true;
glErr = glGetError();
}
return foundError;
}
void Utils::printShaderLog(GLuint shader) {
int len = 0;
int chWrittn = 0;
char *log;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char *)malloc(len);
glGetShaderInfoLog(shader, len, &chWrittn, log);
cout << "Shader Info Log: " << log << endl;
free(log);
}
}
GLuint Utils::prepareShader(int shaderTYPE, const char *shaderPath)
{
GLint shaderCompiled;
string shaderStr = readShaderFile(shaderPath);
const char *shaderSrc = shaderStr.c_str();
GLuint shaderRef = glCreateShader(shaderTYPE);
glShaderSource(shaderRef, 1, &shaderSrc, NULL);
glCompileShader(shaderRef);
checkOpenGLError();
glGetShaderiv(shaderRef, GL_COMPILE_STATUS, &shaderCompiled);
if (shaderCompiled != 1)
{
if (shaderTYPE == 35633) cout << "Vertex ";
if (shaderTYPE == 36488) cout << "Tess Control ";
if (shaderTYPE == 36487) cout << "Tess Eval ";
if (shaderTYPE == 36313) cout << "Geometry ";
if (shaderTYPE == 35632) cout << "Fragment ";
cout << "shader compilation error." << endl;
printShaderLog(shaderRef);
}
return shaderRef;
}
void Utils::printProgramLog(int prog) {
int len = 0;
int chWrittn = 0;
char *log;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char *)malloc(len);
glGetProgramInfoLog(prog, len, &chWrittn, log);
cout << "Program Info Log: " << log << endl;
free(log);
}
}
int Utils::finalizeShaderProgram(GLuint sprogram)
{
GLint linked;
glLinkProgram(sprogram);
checkOpenGLError();
glGetProgramiv(sprogram, GL_LINK_STATUS, &linked);
if (linked != 1)
{
cout << "linking failed" << endl;
printProgramLog(sprogram);
}
return sprogram;
}
GLuint Utils::createShaderProgram(const char *vp, const char *fp) {
GLuint vShader = prepareShader(GL_VERTEX_SHADER, vp);
GLuint fShader = prepareShader(GL_FRAGMENT_SHADER, fp);
GLuint vfprogram = glCreateProgram();
glAttachShader(vfprogram, vShader);
glAttachShader(vfprogram, fShader);
finalizeShaderProgram(vfprogram);
return vfprogram;
}
GLuint Utils::createShaderProgram(const char *vp, const char *gp, const char *fp) {
GLuint vShader = prepareShader(GL_VERTEX_SHADER, vp);
GLuint gShader = prepareShader(GL_GEOMETRY_SHADER, gp);
GLuint fShader = prepareShader(GL_FRAGMENT_SHADER, fp);
GLuint vgfprogram = glCreateProgram();
glAttachShader(vgfprogram, vShader);
glAttachShader(vgfprogram, gShader);
glAttachShader(vgfprogram, fShader);
finalizeShaderProgram(vgfprogram);
return vgfprogram;
}
GLuint Utils::createShaderProgram(const char *vp, const char *tCS, const char* tES, const char *fp) {
GLuint vShader = prepareShader(GL_VERTEX_SHADER, vp);
GLuint tcShader = prepareShader(GL_TESS_CONTROL_SHADER, tCS);
GLuint teShader = prepareShader(GL_TESS_EVALUATION_SHADER, tES);
GLuint fShader = prepareShader(GL_FRAGMENT_SHADER, fp);
GLuint vtfprogram = glCreateProgram();
glAttachShader(vtfprogram, vShader);
glAttachShader(vtfprogram, tcShader);
glAttachShader(vtfprogram, teShader);
glAttachShader(vtfprogram, fShader);
finalizeShaderProgram(vtfprogram);
return vtfprogram;
}
GLuint Utils::createShaderProgram(const char *vp, const char *tCS, const char* tES, char *gp, const char *fp) {
GLuint vShader = prepareShader(GL_VERTEX_SHADER, vp);
GLuint tcShader = prepareShader(GL_TESS_CONTROL_SHADER, tCS);
GLuint teShader = prepareShader(GL_TESS_EVALUATION_SHADER, tES);
GLuint gShader = prepareShader(GL_GEOMETRY_SHADER, gp);
GLuint fShader = prepareShader(GL_FRAGMENT_SHADER, fp);
GLuint vtgfprogram = glCreateProgram();
glAttachShader(vtgfprogram, vShader);
glAttachShader(vtgfprogram, tcShader);
glAttachShader(vtgfprogram, teShader);
glAttachShader(vtgfprogram, gShader);
glAttachShader(vtgfprogram, fShader);
finalizeShaderProgram(vtgfprogram);
return vtgfprogram;
}
GLuint Utils::loadCubeMap(const char *mapDir) {
GLuint textureRef;
string xp = mapDir; xp = xp + "/xp.jpg";
string xn = mapDir; xn = xn + "/xn.jpg";
string yp = mapDir; yp = yp + "/yp.jpg";
string yn = mapDir; yn = yn + "/yn.jpg";
string zp = mapDir; zp = zp + "/zp.jpg";
string zn = mapDir; zn = zn + "/zn.jpg";
textureRef = SOIL_load_OGL_cubemap(xp.c_str(), xn.c_str(), yp.c_str(), yn.c_str(), zp.c_str(), zn.c_str(),
SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS);
if (textureRef == 0) cout << "didnt find cube map image file" << endl;
glBindTexture(GL_TEXTURE_CUBE_MAP, textureRef);
reduce seams
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
return textureRef;
}
GLuint Utils::loadTexture(const char *texImagePath)
{
GLuint textureRef;
textureRef = SOIL_load_OGL_texture(texImagePath, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y);
if (textureRef == 0) cout << "didnt find texture file " << texImagePath << endl;
// ----- mipmap/anisotropic section
glBindTexture(GL_TEXTURE_2D, textureRef);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) {
GLfloat anisoset = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoset);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoset);
}
// ----- end of mipmap/anisotropic section
return textureRef;
}
// GOLD material - ambient, diffuse, specular, and shininess
float* Utils::goldAmbient() { static float a[4] = { 0.2473f, 0.1995f, 0.0745f, 1 }; return (float*)a; }
float* Utils::goldDiffuse() { static float a[4] = { 0.7516f, 0.6065f, 0.2265f, 1 }; return (float*)a; }
float* Utils::goldSpecular() { static float a[4] = { 0.6283f, 0.5559f, 0.3661f, 1 }; return (float*)a; }
float Utils::goldShininess() { return 51.2f; }
// SILVER material - ambient, diffuse, specular, and shininess
float* Utils::silverAmbient() { static float a[4] = { 0.1923f, 0.1923f, 0.1923f, 1 }; return (float*)a; }
float* Utils::silverDiffuse() { static float a[4] = { 0.5075f, 0.5075f, 0.5075f, 1 }; return (float*)a; }
float* Utils::silverSpecular() { static float a[4] = { 0.5083f, 0.5083f, 0.5083f, 1 }; return (float*)a; }
float Utils::silverShininess() { return 51.2f; }
// BRONZE material - ambient, diffuse, specular, and shininess
float* Utils::bronzeAmbient() { static float a[4] = { 0.2125f, 0.1275f, 0.0540f, 1 }; return (float*)a; }
float* Utils::bronzeDiffuse() { static float a[4] = { 0.7140f, 0.4284f, 0.1814f, 1 }; return (float*)a; }
float* Utils::bronzeSpecular() { static float a[4] = { 0.3936f, 0.2719f, 0.1667f, 1 }; return (float*)a; }
float Utils::bronzeShininess() { return 25.6f; }
'College Study > OpenGL' 카테고리의 다른 글
[그래픽스] 기초 수학 (2) - Transformation Matrices (변환 행렬) (0) | 2020.10.15 |
---|---|
[그래픽스] 기초 수학 (1) - 좌표계, 점, 행렬 (0) | 2020.10.15 |
[그래픽스] 파일에서 GLSL 소스코드 읽기 (0) | 2020.06.08 |
[그래픽스] OpenGL과 GLSL의 오류 검출 (0) | 2020.06.08 |
[그래픽스] Shader (쉐이더) (0) | 2020.06.04 |
댓글