Для более реалистичного изображения нужны свет и тени, а значит – новые шейдеры. Мы применим Phong shading model.
Начнем с простого, заливка сплошный цветом.
1. Vertex shader.
Скопируем нижеприведенный код в Текстовый редактор и сохраним как txt файл в
C:\CPP\engine\dt\shaders\phong_ucolor_v.txt
#version 320 es
precision lowp float;
uniform mat4 uMVP; // transform matrix (Model-View-Projection)
uniform mat3 uMV3x3; // Model-View matrix (for calculating normals into eye space)
in vec3 aPos; // position attribute (x,y,z)
in vec3 aNormal; // normal attribute (x,y,z)
out vec3 vNormal; // varying normal (to pass to fragment shader)
void main(void) {
gl_Position = uMVP * vec4(aPos, 1.0);
// Transform the normal's orientation into eye space.
vNormal = uMV3x3 * aNormal;
}
2. Fragment shader.
Скопируем нижеприведенный код в Текстовый редактор и сохраним как txt файл в
C:\CPP\engine\dt\shaders\phong_ucolor_f.txt
#version 320 es
precision lowp float;
out vec4 FragColor; //output pixel color
in vec3 vNormal; //normal passed from rasterizer
uniform vec4 uColor;
uniform float uAlphaFactor; //for semi-transparency
uniform float uAmbient;
uniform float uSpecularIntencity;
uniform float uSpecularMinDot;
uniform float uSpecularPowerOf;
uniform vec3 uVectorToLight;
uniform vec3 uHalfVector;
void main(void) {
vec4 outColor = uColor;
vec3 vNormalNormal = normalize(vNormal);
if(uAmbient<1.0){
// Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
// pointing in the same direction then it will get max illumination.
float directionalLightIntencity = dot(vNormalNormal, uVectorToLight);
// count ambient component
directionalLightIntencity += uAmbient;
if(directionalLightIntencity < uAmbient)
directionalLightIntencity = uAmbient;
// Multiply the color by the lightIntencity illumination level to get final output color.
outColor = outColor * directionalLightIntencity;
}
if(uSpecularIntencity>0.0){
//specular light
// INTENSITY OF THE SPECULAR LIGHT
// DOT PRODUCT OF NORMAL VECTOR AND THE HALF VECTOR TO THE POWER OF THE SPECULAR HARDNESS
float dotProduct = dot(vNormalNormal, uHalfVector);
if(dotProduct>uSpecularMinDot){
float specularIntencity = pow(dotProduct, uSpecularPowerOf) * uSpecularIntencity;
if(specularIntencity > uSpecularIntencity)
specularIntencity = uSpecularIntencity;
outColor += specularIntencity;
}
}
outColor.a = uAlphaFactor;
FragColor = outColor;
}
Код сопровожден комментами. Надеюсь, достаточно понятными.
Куча новых переменных. Давайте позаботимся о них.
Windows
3. Запускаем VS. Открываем C:\CPP\a997modeler\p_windows\p_windows.sln.
Во-первых, класс Shader. Нужно добавить locations для новых переменных шейдера, в частности для:
- l_aNormal – vertex normal attribute (нормали)
- l_uVectorToLight – направление на свет, требуется для расчета освещенности
- l_uHalfVector – требуется для расчета бликов (specular light)
Новые переменные Material-а, относящиеся к свету:
- l_uAmbient
- l_uSpecularIntencity
- l_uSpecularMinDot
- l_uSpecularPowerOf
- Плюс l_uMV3x3 – для Model-View матрицы для расчета нормалей
- Плюс номер новой шейдер-программы spN_phong_ucolor.
- Плюс новая функция fillLocations() .
- Остальные новые переменные зарезервированы для последжующих глав.
4. Заменим Shader.h код на:
#pragma once
#include "platform.h"
#include <string>
#include <vector>
class Shader
{
public:
//Shader program's individual descriptor:
unsigned int GLid = -1; // GL shader id
//common variables, "l_" for "location"
//attributes
int l_aPos; //attribute position (3D coordinates)
int l_aTuv; //attribute TUV (texture coordinates)
int l_aTuv2; //attribute TUV (texture coordinates for normal map)
int l_aNormal; //attribute normal (3D vector)
int l_aTangent; //for normal map
int l_aBinormal; //for normal map
//uniforms
int l_uMVP; // transform matrix (Model-View-Projection)
int l_uMV3x3; // Model-View matrix for normals
int l_uVectorToLight; //required for light
int l_uHalfVector; //required for specular light
//material's properties
int l_uColor;
int l_uTex0; //texture id
int l_uTex1mask; //transparency map
int l_uTex2nm; //normal map
int l_uTex3; //texture id
int l_uTex1alphaChannelN; //alpha channel for mask
int l_uTex1alphaNegative; //alpha channel negative
int l_uTex0translateChannelN; //translate tex0 to tex3 by channelN.
int l_uAlphaFactor; //for semi-transparency
int l_uAlphaBlending; //for semi-transparency
//light:
int l_uAmbient; //ambient light
//specular light parameters
int l_uSpecularIntencity;
int l_uSpecularMinDot;
int l_uSpecularPowerOf;
//end of descriptor
//static array (vector) of all loaded shaders
static std::vector<Shader*> shaders;
//common shader programs ("spN" - shader program number)
static int spN_flat_ucolor;
static int spN_flat_tex;
static int spN_phong_ucolor;
public:
static int loadShaders();
static int loadShader(std::string filePathVertexS, std::string filePathFragmentS);
static int cleanUp();
static unsigned int getGLid(int shN) { return shaders.at(shN)->GLid; };
static int linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS);
static int compileShader(const char* filePath, GLenum shaderType);
static int shaderErrorCheck(int shaderId, std::string ref);
static int programErrorCheck(int programId, std::string ref);
static int fillLocations(Shader* pSh);
};
Теперь надо добавить загрузку новой шейдер-программы в Shader::loadShaders(). А в Shader::loadShader()->fillLocations() – заполнение локаций новых переменных.
5. Заменим Shader.cpp код на:
#include "Shader.h"
#include "platform.h"
#include "utils.h"
extern std::string filesRoot;
//static array (vector) of all loaded shaders
std::vector<Shader*> Shader::shaders;
//common shader programs ("spN" - shader program number)
int Shader::spN_flat_ucolor = -1;
int Shader::spN_flat_tex = -1;
int Shader::spN_phong_ucolor = -1;
int Shader::loadShaders() {
spN_flat_ucolor = loadShader("/dt/shaders/flat_ucolor_v.txt", "/dt/shaders/flat_ucolor_f.txt");
spN_flat_tex = loadShader("/dt/shaders/flat_tex_v.txt", "/dt/shaders/flat_tex_f.txt");
spN_phong_ucolor = loadShader("/dt/shaders/phong_ucolor_v.txt", "/dt/shaders/phong_ucolor_f.txt");
return 1;
}
int Shader::loadShader(std::string filePathVertexS, std::string filePathFragmentS) {
//create shader object
Shader* pSh = new Shader();
shaders.push_back(pSh);
pSh->GLid = linkShaderProgram((filesRoot + filePathVertexS).c_str(), (filesRoot + filePathFragmentS).c_str());
//common variables. If not presented, = -1;
fillLocations(pSh);
return (shaders.size() - 1);
}
int Shader::fillLocations(Shader* pSh) {
//common variables. If not presented, = -1;
//attributes
pSh->l_aPos = glGetAttribLocation(pSh->GLid, "aPos"); //attribute position (3D coordinates)
pSh->l_aNormal = glGetAttribLocation(pSh->GLid, "aNormal"); //attribute normal (3D vector)
pSh->l_aTangent = glGetAttribLocation(pSh->GLid, "aTangent"); //for normal map
pSh->l_aBinormal = glGetAttribLocation(pSh->GLid, "aBinormal"); //for normal map
pSh->l_aTuv = glGetAttribLocation(pSh->GLid, "aTuv"); //attribute TUV (texture coordinates)
pSh->l_aTuv2 = glGetAttribLocation(pSh->GLid, "aTuv2"); //attribute TUV (texture coordinates)
//uniforms
pSh->l_uMVP = glGetUniformLocation(pSh->GLid, "uMVP"); // transform matrix (Model-View-Projection)
pSh->l_uMV3x3 = glGetUniformLocation(pSh->GLid, "uMV3x3"); // Model-View matrix for normals
pSh->l_uVectorToLight = glGetUniformLocation(pSh->GLid, "uVectorToLight"); //
pSh->l_uHalfVector = glGetUniformLocation(pSh->GLid, "uHalfVector"); // required for specular light
//material's properties
pSh->l_uColor = glGetUniformLocation(pSh->GLid, "uColor");
pSh->l_uTex0 = glGetUniformLocation(pSh->GLid, "uTex0"); //texture id
pSh->l_uTex1mask = glGetUniformLocation(pSh->GLid, "uTex1mask"); //texture id
pSh->l_uTex2nm = glGetUniformLocation(pSh->GLid, "uTex2nm"); //texture id
pSh->l_uTex3 = glGetUniformLocation(pSh->GLid, "uTex3"); //texture id
pSh->l_uTex1alphaChannelN = glGetUniformLocation(pSh->GLid, "uTex1alphaChannelN");
pSh->l_uTex1alphaNegative = glGetUniformLocation(pSh->GLid, "uTex1alphaNegative");
pSh->l_uTex0translateChannelN = glGetUniformLocation(pSh->GLid, "uTex0translateChannelN");
pSh->l_uAlphaFactor = glGetUniformLocation(pSh->GLid, "uAlphaFactor"); // for semi-transparency
pSh->l_uAlphaBlending = glGetUniformLocation(pSh->GLid, "uAlphaBlending"); // for semi-transparency
pSh->l_uAmbient = glGetUniformLocation(pSh->GLid, "uAmbient"); // ambient light
pSh->l_uSpecularIntencity = glGetUniformLocation(pSh->GLid, "uSpecularIntencity"); //
pSh->l_uSpecularMinDot = glGetUniformLocation(pSh->GLid, "uSpecularMinDot"); //
pSh->l_uSpecularPowerOf = glGetUniformLocation(pSh->GLid, "uSpecularPowerOf"); //
return 1;
}
int Shader::cleanUp() {
int shadersN = shaders.size();
if (shadersN < 1)
return -1;
glUseProgram(0);
for (int i = 0; i < shadersN; i++) {
Shader* pSh = shaders.at(i);
glDeleteProgram(pSh->GLid);
delete pSh;
}
shaders.clear();
return 1;
}
GLchar infoLog[1024];
int logLength;
int Shader::shaderErrorCheck(int shaderId, std::string ref) {
//use after glCompileShader()
if (checkGLerrors(ref) > 0)
return -1;
glGetShaderInfoLog(shaderId, 1024, &logLength, infoLog);
if (logLength == 0)
return 0;
mylog("%s shader infoLog:\n%s\n", ref.c_str(), infoLog);
return -1;
}
int Shader::programErrorCheck(int programId, std::string ref) {
//use after glLinkProgram()
if (checkGLerrors(ref) > 0)
return -1;
glGetProgramInfoLog(programId, 1024, &logLength, infoLog);
if (logLength == 0)
return 0;
mylog("%s program infoLog:\n%s\n", ref.c_str(), infoLog);
return -1;
}
int Shader::compileShader(const char* filePath, GLenum shaderType) {
int shaderId = glCreateShader(shaderType);
FILE* pFile;
myFopen_s(&pFile, filePath, "rt");
if (pFile != NULL)
{
// obtain file size:
fseek(pFile, 0, SEEK_END);
int fSize = ftell(pFile);
rewind(pFile);
// size obtained, create buffer
char* shaderSource = new char[fSize + 1];
fSize = fread(shaderSource, 1, fSize, pFile);
shaderSource[fSize] = 0;
fclose(pFile);
// source code loaded, compile
glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
//myglErrorCheck("glShaderSource");
glCompileShader(shaderId);
if (shaderErrorCheck(shaderId, "glCompileShader") < 0)
return -1;
delete[] shaderSource;
}
else {
mylog("ERROR loading %s\n", filePath);
return -1;
}
return shaderId;
}
int Shader::linkShaderProgram(const char* filePathVertexS, const char* filePathFragmentS) {
int vertexShaderId = compileShader(filePathVertexS, GL_VERTEX_SHADER);
int fragmentShaderId = compileShader(filePathFragmentS, GL_FRAGMENT_SHADER);
int programId = glCreateProgram();
glAttachShader(programId, vertexShaderId);
glAttachShader(programId, fragmentShaderId);
glLinkProgram(programId);
if (programErrorCheck(programId, "glLinkProgram") < 0)
return -1;
//don't need shaders any longer - detach and delete them
glDetachShader(programId, vertexShaderId);
glDetachShader(programId, fragmentShaderId);
glDeleteShader(vertexShaderId);
glDeleteShader(fragmentShaderId);
return programId;
}
В DrawJob у нас теперь новые атрибуты.
Еще в executeDrawJob() – новые параметры и юниформы, относящиеся к вычислениям освещенности.
6. Заменим DrawJob.h код на:
#pragma once
#include "Material.h"
#include <vector>
struct AttribRef //attribute reference/description
{
unsigned int glVBOid = 0; //buffer object id
int offset = 0; //variable's offset inside of VBO's element
int stride = 0; //Buffer's element size in bytes
};
class DrawJob
{
public:
Material mt;
int pointsN = 0; //N of points to draw
unsigned int glVAOid = 0; //will hold data stream attributes mapping/positions
unsigned int glEBOid = 0; //Element Buffer Object (vertex indices)
//common attributes
AttribRef aPos;
AttribRef aNormal;
AttribRef aTuv;
AttribRef aTuv2; //for normal map
AttribRef aTangent; //for normal map
AttribRef aBinormal; //for normal map
//static arrays (vectors) of all loaded DrawJobs, VBO ids
static std::vector<DrawJob*> drawJobs;
static std::vector<unsigned int> buffersIds;
public:
DrawJob();
virtual ~DrawJob(); //destructor
static int cleanUp();
static int newBufferId();
int buildVAO() { return buildVAOforShader(this, mt.shaderN); };
static int buildVAOforShader(DrawJob* pDJ, int shaderN);
static int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);
virtual int setDesirableOffsets(int* pStride, int shaderN, int VBOid) { return setDesirableOffsetsForSingleVBO(this, pStride, shaderN, VBOid); };
static int setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid);
int execute(float* uMVP, float* uMV, float* dir2light, float* halfVector, Material* pMt) { return executeDrawJob(this, uMVP, uMV, dir2light, halfVector, pMt); };
static int executeDrawJob(DrawJob* pDJ, float* uMVP, float* uMV, float* gir2light, float* halfVector, Material* pMt);
};
Надо отразить новые атрибуты в DrawJob::setDesirableOffsetsForSingleVBO(), DrawJob::buildVAOforShader() и в DrawJob::executeDrawJob().
7. Заменим DrawJob.cpp код на:
#include "DrawJob.h"
#include "platform.h"
#include "utils.h"
#include "Shader.h"
#include "Texture.h"
//static arrays (vectors) of all loaded DrawJobs, VBO ids
std::vector<DrawJob*> DrawJob::drawJobs;
std::vector<unsigned int> DrawJob::buffersIds;
DrawJob::DrawJob() {
drawJobs.push_back(this);
}
DrawJob::~DrawJob() {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
if (glVAOid > 0)
glDeleteVertexArrays(1, &glVAOid);
}
int DrawJob::newBufferId() {
unsigned int bufferId;
glGenBuffers(1, &bufferId);
buffersIds.push_back(bufferId);
return (int)bufferId;
}
unsigned int activeVBOid;
int DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {
//delete VAO if exists already
if (pDJ->glVAOid > 0) {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteVertexArrays(1, &(pDJ->glVAOid));
}
glGenVertexArrays(1, &pDJ->glVAOid);
glBindVertexArray(pDJ->glVAOid);
//open shader descriptor to access variables locations
Shader* pShader = Shader::shaders.at(pDJ->mt.shaderN);
activeVBOid = 0;
attachAttribute(pShader->l_aPos, 3, &pDJ->aPos);
attachAttribute(pShader->l_aNormal, 3, &pDJ->aNormal);
attachAttribute(pShader->l_aTuv, 2, &pDJ->aTuv);
attachAttribute(pShader->l_aTuv2, 2, &pDJ->aTuv2); //for normal map
attachAttribute(pShader->l_aTangent, 3, &pDJ->aTangent); //for normal map
attachAttribute(pShader->l_aBinormal, 3, &pDJ->aBinormal); //for normal map
if (pDJ->glEBOid > 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return 1;
}
int DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {
if (varLocationInShader < 0)
return 0; //not used in this shader
if (pAR->glVBOid == 0) {
mylog("ERROR in DrawJob::attachAttribute, nk such attribute/VBO\n");
return -1;
}
glEnableVertexAttribArray(varLocationInShader);
if (activeVBOid != pAR->glVBOid) {
activeVBOid = pAR->glVBOid;
//attach input stream data
glBindBuffer(GL_ARRAY_BUFFER, activeVBOid);
}
glVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR->stride, (void*)(long)pAR->offset);
return 1;
}
int DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, float* uMV3x3, float* dir2light, float* halfVector, Material* pMt) {
if (pMt == NULL)
pMt = &(pDJ->mt);
glBindVertexArray(pDJ->glVAOid);
Shader* pShader = Shader::shaders.at(pMt->shaderN);
glUseProgram(pShader->GLid);
//input uniforms
glUniformMatrix4fv(pShader->l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);
glUniformMatrix3fv(pShader->l_uMV3x3, 1, GL_FALSE, (const GLfloat*)uMV3x3);
if (pShader->l_uVectorToLight >= 0)
glUniform3fv(pShader->l_uVectorToLight, 1, (const GLfloat*)dir2light);
if (pShader->l_uHalfVector >= 0)
glUniform3fv(pShader->l_uHalfVector, 1, (const GLfloat*)halfVector);
//attach textures
if (pShader->l_uTex0 >= 0) {
int textureId = Texture::getGLid(pMt->uTex0);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
glUniform1i(pShader->l_uTex0, 0);
}
if (pShader->l_uTex1mask >= 0) {
int textureId = Texture::getGLid(pMt->uTex1mask);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE1); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 1.
glUniform1i(pShader->l_uTex1mask, 1);
}
if (pShader->l_uTex2nm >= 0) {
int textureId = Texture::getGLid(pMt->uTex2nm);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE2); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 2.
glUniform1i(pShader->l_uTex2nm, 2);
}
if (pShader->l_uTex0translateChannelN >= 0) {
glUniform1i(pShader->l_uTex0translateChannelN, pMt->uTex0translateChannelN);
if (pShader->l_uTex3 >= 0 && pMt->uTex3 >= 0) {
int textureId = Texture::getGLid(pMt->uTex3);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE3); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 3.
glUniform1i(pShader->l_uTex3, 3);
}
}
//material uniforms
if (pShader->l_uTex1alphaChannelN >= 0)
glUniform1i(pShader->l_uTex1alphaChannelN, pMt->uTex1alphaChannelN);
if (pShader->l_uTex1alphaNegative >= 0)
glUniform1i(pShader->l_uTex1alphaNegative, pMt->uTex1alphaNegative);
if (pShader->l_uColor >= 0)
glUniform4fv(pShader->l_uColor, 1, pMt->uColor.forGL());
if (pShader->l_uAlphaFactor >= 0)
glUniform1f(pShader->l_uAlphaFactor, pMt->uAlphaFactor);
if (pShader->l_uAlphaBlending >= 0)
glUniform1i(pShader->l_uAlphaBlending, pMt->uAlphaBlending);
if (pShader->l_uAmbient >= 0)
glUniform1f(pShader->l_uAmbient, pMt->uAmbient);
if (pShader->l_uSpecularIntencity >= 0)
glUniform1f(pShader->l_uSpecularIntencity, pMt->uSpecularIntencity);
if (pShader->l_uSpecularMinDot >= 0)
glUniform1f(pShader->l_uSpecularMinDot, pMt->uSpecularMinDot);
if (pShader->l_uSpecularPowerOf >= 0)
glUniform1f(pShader->l_uSpecularPowerOf, pMt->uSpecularPowerOf);
//adjust render settings
if (pShader->l_uAlphaBlending >= 0 && pMt->uAlphaBlending > 0) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
glDisable(GL_BLEND);
//execute
if (pDJ->glEBOid == 0) {
glDrawArrays(pMt->primitiveType, 0, pDJ->pointsN);
}
else { //use EBO
glDrawElements(pMt->primitiveType, pDJ->pointsN, GL_UNSIGNED_SHORT, 0);
}
glBindVertexArray(0);
return 1;
}
int DrawJob::cleanUp() {
int itemsN = drawJobs.size();
//delete all drawJobs
for (int i = 0; i < itemsN; i++) {
DrawJob* pDJ = drawJobs.at(i);
delete pDJ;
}
drawJobs.clear();
//delete Buffers
itemsN = buffersIds.size();
//delete all buffers
for (int i = 0; i < itemsN; i++) {
unsigned int id = buffersIds.at(i);
glDeleteBuffers(1, &id);
}
buffersIds.clear();
return 1;
}
int DrawJob::setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid) {
//sets desirable offsets and stride according to given shader needs
//assuming that we have 1 single VBO
Shader* pSh = Shader::shaders.at(shaderN);
int stride = 0;
pDJ->aPos.offset = 0; //attribute o_aPos, always 0
stride += sizeof(float) * 3; //aPos size - 3 floats (x,y,z)
if (pSh->l_aNormal >= 0) { //attribute normal
pDJ->aNormal.offset = stride;
stride += sizeof(float) * 3;
}
if (pSh->l_aTuv >= 0) { //attribute TUV (texture coordinates)
pDJ->aTuv.offset = stride; //attribute TUV (texture coordinates)
stride += sizeof(float) * 2;
}
if (pSh->l_aTuv2 >= 0) { //for normal map
pDJ->aTuv2.offset = stride;
stride += sizeof(float) * 2;
}
if (pSh->l_aTangent >= 0) { //for normal map
pDJ->aTangent.offset = stride;
stride += sizeof(float) * 3;
}
if (pSh->l_aBinormal >= 0) { //for normal map
pDJ->aBinormal.offset = stride;
stride += sizeof(float) * 3;
}
*pStride = stride;
//add stride and VBOid to all attributes
AttribRef* pAR = NULL;
pAR = &pDJ->aPos; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aNormal; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aTuv; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aTuv2; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aTangent; pAR->glVBOid = VBOid; pAR->stride = stride;
pAR = &pDJ->aBinormal; pAR->glVBOid = VBOid; pAR->stride = stride;
return 1;
}
Параметры и юниформы относящиеся к материалу разместим в Material.h.
8. Заменим Material.h код на:
#pragma once
#include "MyColor.h"
class Material
{
public:
int shaderN = -1;
int primitiveType = GL_TRIANGLES;
MyColor uColor;
int uTex0 = -1;
int uTex1mask = -1;
int uTex2nm = -1;
int uTex3 = -1;
int uTex1alphaChannelN = 3; //default - alpha channel for mask
int uTex1alphaNegative = 0; //default - alpha channel not negative
int uTex0translateChannelN = -1; //translate tex0 to tex3 by channelN. Default -1 - don't translate
int uAlphaBlending = 0; //for semi-transparency
float uAlphaFactor = 1; //for semi-transparency
float uAmbient = 0.4f; //ambient light
//specular light parameters
float uSpecularIntencity = 0.8f;
float uSpecularMinDot = 0.95f;
float uSpecularPowerOf = 20.0f;
};
Также надо добавить новые атрибуты в ModelBuilder::buildSingleDrawJob().
9. Заменим ModelBuilder.cpp код на:
#include "ModelBuilder.h"
#include "platform.h"
#include "utils.h"
#include "DrawJob.h"
#include "Shader.h"
extern float degrees2radians;
ModelBuilder::~ModelBuilder() {
//clear all vectors
int itemsN = vertices.size();
for (int i = 0; i < itemsN; i++)
delete vertices.at(i);
vertices.clear();
itemsN = triangles.size();
for (int i = 0; i < itemsN; i++)
delete triangles.at(i);
triangles.clear();
itemsN = vShapesStack.size();
for (int i = 0; i < itemsN; i++)
delete vShapesStack.at(i);
vShapesStack.clear();
itemsN = groupsStack.size();
for (int i = 0; i < itemsN; i++)
delete groupsStack.at(i);
groupsStack.clear();
itemsN = materialsList.size();
for (int i = 0; i < itemsN; i++)
delete materialsList.at(i);
materialsList.clear();
subjNumbersList.clear();
}
int ModelBuilder::useSubjN(ModelBuilder* pMB, int subjN) {
pMB->usingSubjN = subjN;
int itemsN = pMB->subjNumbersList.size();
bool newN = true;
if (itemsN > 0)
for (int i = 0; i < itemsN; i++)
if (pMB->subjNumbersList.at(i) == subjN) {
newN = false;
break;
}
if (newN)
pMB->subjNumbersList.push_back(subjN);
return subjN;
}
int ModelBuilder::useMaterial(ModelBuilder* pMB, Material* pMT) {
int itemsN = pMB->materialsList.size();
if (itemsN > 0)
for (int i = 0; i < itemsN; i++)
if (memcmp(pMB->materialsList.at(i), pMT, sizeof(Material)) == 0) {
pMB->usingMaterialN = i;
return i;
}
//if here - add new material to the list
pMB->usingMaterialN = itemsN;
//create a copy of new Material and add to the list
Material* pMTnew = new Material(*pMT);
pMB->materialsList.push_back(pMTnew);
return itemsN;
}
int ModelBuilder::buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
//this code is for simple box
VirtualShape vs; //face VS,
mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
//rotate desirable side to face us.
if (applyTo.find("front") == 0) {
//Side <front> is facing us as is.
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[1];
//define how to move/place generated face back to the VirtualShape
//just shift closer to us by length/2
mat4x4_translate(transformMatrix, 0, 0, pVS->whl[2] / 2);
}
else if (applyTo.find("back") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[1];
//rotate 180 degrees around Y and shift farther from us by half-length
mat4x4_translate(transformMatrix, 0, 0, -pVS->whl[2] / 2);
mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 180);
}
else if (applyTo.find("left") == 0) {
vs.whl[0] = pVS->whl[2]; //width = original length
vs.whl[1] = pVS->whl[1];
//rotate -90 degrees around Y (CW) and shift half-width to the left
mat4x4_translate(transformMatrix, -pVS->whl[0] / 2, 0, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 90);
}
else if (applyTo.find("right") == 0) {
vs.whl[0] = pVS->whl[2]; //width = original length
vs.whl[1] = pVS->whl[1];
//rotate +90 degrees around Y (CCW) and shift half-width to the right
mat4x4_translate(transformMatrix, pVS->whl[0] / 2, 0, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 90);
}
else if (applyTo.find("top") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[2]; //height = original length
//rotate -90 degrees around X (CW) and 180 around Y, and shift half-height up
mat4x4_translate(transformMatrix, 0, pVS->whl[1] / 2, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 180);
mat4x4_rotate_X(transformMatrix, transformMatrix, -degrees2radians * 90);
}
else if (applyTo.find("bottom") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[2]; //height = original length
//rotate 90 around X (CCW) and shift half-height down
mat4x4_translate(transformMatrix, 0, -pVS->whl[1] / 2, 0);
mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * 90);
}
startGroup(pMB);
//create vertices
int sectionsX = 1;
int sectionsY = 1;
int pointsX = sectionsX + 1;
int pointsY = sectionsY + 1;
float stepX = vs.whl[0] / sectionsX;
float stepY = vs.whl[1] / sectionsY;
float kY = vs.whl[1] / 2;
for (int iy = 0; iy < pointsY; iy++) {
float kX = -vs.whl[0] / 2;
for (int ix = 0; ix < pointsX; ix++) {
int nSE = addVertex(pMB, kX, kY, 0, 0, 0, 1); //vertex number on south-east
if (iy > 0 && ix > 0) {
//add 2 triangles
int nSW = nSE - 1; //vertex number south-west
int nNE = nSE - pointsX; //north-east
int nNW = nSW - pointsX; //north-west
add2triangles(pMB, nNW, nNE, nSW, nSE, iy + ix);
}
kX += stepX;
}
kY -= stepY;
}
//move face to it's place (apply transform matrix)
int vertsN = pMB->vertices.size();
for (int i = pMB->pCurrentGroup->fromVertexN; i < vertsN; i++) {
Vertex01* pVX = pMB->vertices.at(i);
mat4x4_mul_vec4plus(pVX->aPos, transformMatrix, pVX->aPos, 1);
mat4x4_mul_vec4plus(pVX->aNormal, transformMatrix, pVX->aNormal, 0);
}
endGroup(pMB);
return 1;
}
int ModelBuilder::add2triangles(ModelBuilder* pMB, int nNW, int nNE, int nSW, int nSE, int n) {
//indexes: NorthWest, NorthEast, SouthWest,SouthEast
if (n % 2 == 0) { //even number
addTriangle(pMB, nNW, nSW, nNE);
addTriangle(pMB, nNE, nSW, nSE);
}
else { //odd number
addTriangle(pMB, nNW, nSE, nNE);
addTriangle(pMB, nNW, nSW, nSE);
}
return pMB->triangles.size() - 1;
}
int ModelBuilder::addTriangle(ModelBuilder* pMB, int i0, int i1, int i2) {
Triangle01* pTR = new Triangle01();
pMB->triangles.push_back(pTR);
pTR->idx[0] = i0;
pTR->idx[1] = i1;
pTR->idx[2] = i2;
pTR->subjN = pMB->usingSubjN;
pTR->materialN = pMB->usingMaterialN;
return pMB->triangles.size() - 1;
}
void ModelBuilder::startGroup(ModelBuilder* pMB) {
pMB->pCurrentGroup = new Group01();
pMB->groupsStack.push_back(pMB->pCurrentGroup);
pMB->pCurrentGroup->fromVertexN = pMB->vertices.size();
pMB->pCurrentGroup->fromTriangleN = pMB->triangles.size();
}
void ModelBuilder::endGroup(ModelBuilder* pMB) {
delete pMB->pCurrentGroup;
pMB->pCurrentGroup = pMB->groupsStack.back();
pMB->groupsStack.pop_back();
}
int ModelBuilder::addVertex(ModelBuilder* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {
Vertex01* pVX = new Vertex01();
pMB->vertices.push_back(pVX);
pVX->aPos[0] = kx;
pVX->aPos[1] = ky;
pVX->aPos[2] = kz;
//normal
pVX->aNormal[0] = nx;
pVX->aNormal[1] = ny;
pVX->aNormal[2] = nz;
pVX->subjN = pMB->usingSubjN;
pVX->materialN = pMB->usingMaterialN;
return pMB->vertices.size() - 1;
}
int ModelBuilder::buildDrawJobs(ModelBuilder* pMB, std::vector<GameSubj*> gameSubjs) {
int totalSubjsN = pMB->subjNumbersList.size();
if (totalSubjsN < 1) {
pMB->subjNumbersList.push_back(-1);
totalSubjsN = 1;
}
int totalMaterialsN = pMB->materialsList.size();
if (totalSubjsN < 2 && totalMaterialsN < 2) {
//simple single DrawJob
Material* pMT = pMB->materialsList.at(0);
GameSubj* pGS = NULL;
int gsN = pMB->subjNumbersList.at(0);
if (gsN >= 0)
pGS = gameSubjs.at(gsN);
if (pGS != NULL)
pGS->djStartN = DrawJob::drawJobs.size();
buildSingleDrawJob(pMT, pMB->vertices, pMB->triangles);
if (pGS != NULL)
pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
return 1;
}
int totalVertsN = pMB->vertices.size();
int totalTrianglesN = pMB->triangles.size();
//clear flags
for (int vN = 0; vN < totalVertsN; vN++) {
Vertex01* pVX = pMB->vertices.at(vN);
pVX->flag = 0;
}
for (int tN = 0; tN < totalTrianglesN; tN++) {
Triangle01* pTR = pMB->triangles.at(tN);
pTR->flag = 0;
}
int addedDJs = 0;
for (int sN = 0; sN < totalSubjsN; sN++) {
GameSubj* pGS = NULL;
int gsN = pMB->subjNumbersList.at(sN);
if (gsN >= 0)
pGS = gameSubjs.at(gsN);
if (pGS != NULL)
pGS->djStartN = DrawJob::drawJobs.size();
for (int mtN = 0; mtN < totalMaterialsN; mtN++) {
Material* pMT = pMB->materialsList.at(mtN);
std::vector<Vertex01*> useVertices;
std::vector<Triangle01*> useTriangles;
for (int vN = 0; vN < totalVertsN; vN++) {
Vertex01* pVX = pMB->vertices.at(vN);
if (pVX->flag != 0)
continue;
if (pVX->subjN != gsN)
continue;
if (pVX->materialN != mtN)
continue;
//if here - make a copy
Vertex01* pVX2 = new Vertex01(*pVX);
useVertices.push_back(pVX2);
pVX2->altN = vN;
pVX->flag = 1;
if (pVX->endOfSequence > 0) {
rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
buildSingleDrawJob(pMT, useVertices, useTriangles);
addedDJs++;
//clear and proceed to next sequence
int useVerticesN = useVertices.size();
for (int i = 0; i < useVerticesN; i++)
delete useVertices.at(i);
useVertices.clear();
}
}
int useVerticesN = useVertices.size();
if (useVerticesN < 1)
continue; //to next material
//pick triangles
for (int tN = 0; tN < totalTrianglesN; tN++) {
Triangle01* pTR = pMB->triangles.at(tN);
if (pTR->flag != 0)
continue;
if (pTR->subjN != gsN)
continue;
if (pTR->materialN != mtN)
continue;
//if here - make a copy
Triangle01* pTR2 = new Triangle01(*pTR);
useTriangles.push_back(pTR2);
pTR->flag = 1;
}
rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
buildSingleDrawJob(pMT, useVertices, useTriangles);
addedDJs++;
//clear all for next material
for (int i = 0; i < useVerticesN; i++)
delete useVertices.at(i);
useVertices.clear();
int useTrianglesN = useTriangles.size();
for (int i = 0; i < useTrianglesN; i++)
delete useTriangles.at(i);
useTriangles.clear();
}
if (pGS != NULL)
pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
}
return addedDJs;
}
int ModelBuilder::buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
int totalVertsN = useVertices.size();
if (totalVertsN < 1)
return 1;
DrawJob* pDJ = new DrawJob();
//copy material to DJ
memcpy(&pDJ->mt, pMT, sizeof(Material));
//calculate VBO element size (stride) and variables offsets in VBO
int VBOid = DrawJob::newBufferId();
int stride = 0;
pDJ->setDesirableOffsets(&stride, pDJ->mt.shaderN, VBOid);
//create an array for VBO
int bufferSize = totalVertsN * stride;
float* vertsBuffer = new float[bufferSize];
//fill vertsBuffer
Shader* pSh = Shader::shaders.at(pDJ->mt.shaderN);
int floatSize = sizeof(float);
for (int vN = 0; vN < totalVertsN; vN++) {
Vertex01* pVX = useVertices.at(vN);
int idx = vN * stride / floatSize;
//pick data from vertex and move to the buffer
memcpy(&vertsBuffer[idx + pDJ->aPos.offset / floatSize], pVX->aPos, 3 * floatSize);
if (pSh->l_aNormal >= 0) //normal
memcpy(&vertsBuffer[idx + pDJ->aNormal.offset / floatSize], pVX->aNormal, 3 * floatSize);
if (pSh->l_aTuv >= 0) //attribute TUV (texture coordinates)
memcpy(&vertsBuffer[idx + pDJ->aTuv.offset / floatSize], pVX->aTuv, 2 * floatSize);
if (pSh->l_aTuv2 >= 0) //attribute TUV2 (normal maps)
memcpy(&vertsBuffer[idx + pDJ->aTuv2.offset / floatSize], pVX->aTuv2, 2 * floatSize);
if (pSh->l_aTangent >= 0)
memcpy(&vertsBuffer[idx + pDJ->aTangent.offset / floatSize], pVX->aTangent, 3 * floatSize);
if (pSh->l_aBinormal >= 0)
memcpy(&vertsBuffer[idx + pDJ->aBinormal.offset / floatSize], pVX->aBinormal, 3 * floatSize);
}
//buffer is ready, create VBO
glBindBuffer(GL_ARRAY_BUFFER, VBOid);
glBufferData(GL_ARRAY_BUFFER, bufferSize * floatSize, vertsBuffer, GL_STATIC_DRAW);
delete[] vertsBuffer;
pDJ->pointsN = totalVertsN;
int totalTrianglesN = useTriangles.size();
if (totalTrianglesN > 0) {
//create EBO
int totalIndexesN = totalTrianglesN * 3;
//create buffer
GLushort* indexBuffer = new GLushort[totalIndexesN];
for (int tN = 0; tN < totalTrianglesN; tN++) {
Triangle01* pTR = useTriangles[tN];
int idx = tN * 3;
indexBuffer[idx + 0] = (GLushort)pTR->idx[0];
indexBuffer[idx + 1] = (GLushort)pTR->idx[1];
indexBuffer[idx + 2] = (GLushort)pTR->idx[2];
}
//buffer is ready, create IBO
pDJ->glEBOid = DrawJob::newBufferId();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexesN * sizeof(GLushort), indexBuffer, GL_STATIC_DRAW);
delete[] indexBuffer;
pDJ->pointsN = totalIndexesN;
}
//create and fill vertex attributes array (VAO)
pDJ->buildVAO();
return 1;
}
int ModelBuilder::rearrangeArraysForDrawJob(ModelBuilder* pMB, std::vector<Vertex01*> allVertices, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
int totalTrianglesN = useTriangles.size();
if (totalTrianglesN < 1)
return 0;
int totalVerticesN = useVertices.size();
//save new vertices order in original vertices array
//since triangles indices refer to original vertices order
for (int i = 0; i < totalVerticesN; i++) {
Vertex01* pVX1 = useVertices.at(i);
Vertex01* pVX0 = allVertices.at(pVX1->altN);
pVX0->altN = i;
}
//replace triangle original indices by new numbers saved in original vertices altN
for (int tN = 0; tN < totalTrianglesN; tN++) {
Triangle01* pTR = useTriangles.at(tN);
for (int i = 0; i < 3; i++) {
Vertex01* pVX0 = allVertices.at(pTR->idx[i]);
pTR->idx[i] = pVX0->altN;
}
}
return 1;
}
В TheGame.h – новая переменная dirToMainLight.
10. Заменим TheGame.h код на:
#pragma once
#include <vector>
#include "GameSubj.h"
#include "Camera.h"
class TheGame
{
public:
int screenSize[2];
float screenRatio;
bool bExitGame;
Camera mainCamera;
float dirToMainLight[4] = { 1,1,1,0 };
//static arrays (vectors) of active GameSubjs
static std::vector<GameSubj*> gameSubjs;
public:
int run();
int getReady();
int drawFrame();
int cleanUp();
int onScreenResize(int width, int height);
};
В функции TheGame::getReady() нужно задать Main Light. Одиночный направленный источник света (Directional single-source light) – вполне достаточно для достаточно реалистичного изображения. Разместим его (источник) за нашим левым плечом. Тогда направление к свету, dirToMainLight, будет нормализованным {-1,1,1}.
В функции TheGame::drawFrame() мы расчитаем uHalfVector как направление между dirToMainLight и направлением на камеру.
В главном цикле мы теперь будем считать не только 4х4 матрицу mMVP (Model-View-Projection) для расчета 3D координат, но ТАКЖЕ и 3х3 матрицу mMV3x3 (3×3 Model-View) для расчета нормалей.
Ну и конечно, при создании кубика мы выберем новый spN_phong_ucolor шейдер.
11. Заменим TheGame.cpp код на:
#include "TheGame.h"
#include "platform.h"
#include "utils.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
#include "ModelBuilder.h"
extern std::string filesRoot;
std::vector<GameSubj*> TheGame::gameSubjs;
int TheGame::getReady() {
bExitGame = false;
Shader::loadShaders();
glEnable(GL_CULL_FACE);
//=== create box ========================
GameSubj* pGS = new GameSubj();
gameSubjs.push_back(pGS);
pGS->name.assign("box1");
pGS->ownCoords.setPosition(0, 0, 0);
pGS->ownCoords.setDegrees(0, 0, 0);
pGS->ownSpeed.setDegrees(0,1,0);
ModelBuilder* pMB = new ModelBuilder();
pMB->useSubjN(gameSubjs.size() - 1);
//define VirtualShape
VirtualShape vs;
vs.type.assign("box");
vs.whl[0] = 100;
vs.whl[1] = 200;
vs.whl[2] = 400;
Material mt;
//define material - flat red
mt.shaderN = Shader::spN_phong_ucolor;
mt.primitiveType = GL_TRIANGLES;
mt.uColor.setRGBA(255, 0, 0,255); //red
pMB->useMaterial(&mt);
pMB->buildBoxFace("front", &vs);
pMB->buildBoxFace("back", &vs);
pMB->buildBoxFace("top", &vs);
pMB->buildBoxFace("bottom", &vs);
pMB->buildBoxFace("left", &vs);
mt.uColor.setRGBA(0, 0, 255,255); pMB->useMaterial(&mt); //blue
pMB->buildBoxFace("right", &vs);
pMB->buildDrawJobs(gameSubjs);
delete pMB;
//===== set up camera
v3set(mainCamera.ownCoords.pos, 0, 200, 1000); //set position
float cameraDir[3];
v3set(cameraDir, 0, -200, -1000); //set direction vector
float cameraYawDg = v3yawDg(cameraDir);
float cameraPitchDg = v3pitchDg(cameraDir);
//mylog("cameraYaw=%f, cameraPitch=%f\n", cameraYawDg, cameraPitchDg);
mainCamera.ownCoords.setDegrees(cameraPitchDg, cameraYawDg, 0);
float cameraUp[4] = { 0,1,0,0 }; //y - up
mat4x4_mul_vec4plus(cameraUp, *mainCamera.ownCoords.getRotationMatrix(), cameraUp, 0);
mat4x4_look_at(mainCamera.lookAtMatrix, mainCamera.ownCoords.pos, pGS->ownCoords.pos, cameraUp);
//===== set up light
v3set(dirToMainLight, -1, 1, 1);
vec3_norm(dirToMainLight, dirToMainLight);
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
//glClearColor(0.0, 0.0, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
//calculate halfVector
float dirToCamera[4] = { 0,0,-1,0 }; //-z
mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);
float uHalfVector[4] = { 0,0,0,0 };
for (int i = 0; i < 3; i++)
uHalfVector[i] = (dirToCamera[i] + dirToMainLight[i]) / 2;
vec3_norm(uHalfVector, uHalfVector);
mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;
//mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 100.f, 500.f);
mat4x4_perspective(mProjection, 3.14f / 6.0f, (float)screenSize[0] / screenSize[1], 700.f, 1300.f);
mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);
//mViewProjection[1][3] = 0; //keystone effect
//scan subjects
int subjsN = gameSubjs.size();
for (int subjN = 0; subjN < subjsN; subjN++) {
GameSubj* pGS = gameSubjs.at(subjN);
//behavior - apply rotation speed
pGS->moveSubj();
//prepare subject for rendering
pGS->buildModelMatrix(pGS);
//build MVP matrix for given subject
mat4x4_mul(mMVP, mViewProjection, pGS->ownModelMatrix);
//build Model-View (rotation) matrix for normals
mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS->ownCoords.getRotationMatrix());
//convert to 3x3 matrix
float mMV3x3[3][3];
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
mMV3x3[y][x] = mMV4x4[y][x];
//render subject
for (int i = 0; i < pGS->djTotalN; i++) {
DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
pDJ->execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);
}
}
mySwapBuffers();
return 1;
}
int TheGame::cleanUp() {
int itemsN = gameSubjs.size();
//delete all UISubjs
for (int i = 0; i < itemsN; i++) {
GameSubj* pGS = gameSubjs.at(i);
delete pGS;
}
gameSubjs.clear();
//clear all other classes
Texture::cleanUp();
Shader::cleanUp();
DrawJob::cleanUp();
return 1;
}
int TheGame::onScreenResize(int width, int height) {
if (screenSize[0] == width && screenSize[1] == height)
return 0;
screenSize[0] = width;
screenSize[1] = height;
screenRatio = (float)width / (float)height;
glViewport(0, 0, width, height);
mylog(" screen size %d x %d\n", width, height);
return 1;
}
int TheGame::run() {
getReady();
while (!bExitGame) {
drawFrame();
}
cleanUp();
return 1;
}
12. Компиляция и запуск.
До:
После:
Так-то гораздо лучше !
Android
На этот раз ВСЁ было сделано на кросс-платформенной стороне. Просто на всякий случай:
13. Пере-запускаем VS. Открываем C:\CPP\a997modeler\p_android\p_android.sln.
Включаем, разблокируем, подключаем, разрешаем.
Rebuild solution.
Run.
Отлично!