Глава 19. Освещение и Phong Shading

Для более реалистичного изображения нужны свет и тени, а значит – новые шейдеры. Мы применим 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.

Отлично!


Leave a Reply

Your email address will not be published. Required fields are marked *