Here I want to add "reflection" and "over mask" shaders, each of which will double number of shader-programs. Correspondingly, we'll need a function which will load a group of shaders in 1 shot (for example - Phong or flat for different data feeds). Also, we'll need a function that will pick particular shader number depending on shader type and provided Material (for example - flat, ucolor, no textures).
The call for loading a shaders group will look like this:
loadShadersGroup("phong", "PHONG; COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
This function supposed to build 4 shader programs with following defines combinations:
- PHONG + COLOR
- PHONG + COLOR + OVERMASK
- PHONG + TEXTURE
- PHONG + TEXTURE + OVERMASK
Implementation:
1. Start VS, open C:\CPP\a997modeler\p_windows\p_windows.sln.
It will require new variables and functions in Shader class.
2. Open Shader.h and replace code by
#pragma once
#include "platform.h"
#include <string>
#include <vector>
class Shader
{
public:
//Shader program's individual descriptor:
unsigned int GLid = -1; // GL shader id
char shaderType[20] = "";
//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;
public:
static int loadShaders();
static int cleanUp();
static unsigned int getGLid(int shN) { return shaders.at(shN)->GLid; };
static int shaderErrorCheck(int shaderId, std::string ref);
static int programErrorCheck(int programId, std::string ref);
static int fillLocations(Shader* pSh);
static int buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS);
static int linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS);
static int compileShaderFromFile(const char* filePath, GLenum shaderType);
static int buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment);
static int linkShaderProgramWithDefines(std::string definesString, char* sourceVertex, char* sourceFragment);
static int compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType);
static int loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment);
};
3. Open Shader.cpp and replace code by
#include "Shader.h"
#include "platform.h"
#include "utils.h"
#include "FileLoader.h"
extern std::string filesRoot;
//static array (vector) of all loaded shaders
std::vector<Shader*> Shader::shaders;
int Shader::loadShaders() {
FileLoader* pFLvertex = new FileLoader("/dt/shaders/phong_v.txt");
FileLoader* pFLfragment = new FileLoader("/dt/shaders/phong_f.txt");
loadShadersGroup("flat", "FLAT; COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
loadShadersGroup("phong", "PHONG; COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
loadShadersGroup("mirror", "PHONG;MIRROR; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
delete pFLvertex;
delete pFLfragment;
return 1;
}
int Shader::buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS) {
//create shader object
Shader* pSh = new Shader();
shaders.push_back(pSh);
pSh->GLid = linkShaderProgramFromFiles((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::compileShaderFromFile(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::linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS) {
int vertexShaderId = compileShaderFromFile(filePathVertexS, GL_VERTEX_SHADER);
int fragmentShaderId = compileShaderFromFile(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;
}
int Shader::buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment) {
//create shader object
Shader* pSh = new Shader();
shaders.push_back(pSh);
myStrcpy_s(pSh->shaderType, 20, shaderType.c_str());
pSh->GLid = linkShaderProgramWithDefines(definesString, sourceVertex, sourceFragment);
//common variables. If not presented, = -1;
fillLocations(pSh);
return (shaders.size() - 1);
}
int Shader::linkShaderProgramWithDefines(std::string definesString00, char* sourceVertex, char* sourceFragment) {
//build extended definesString
bool bUSE_NORMALS = false;
bool bUSE_TEX0 = false;
bool bUSE_TUV0 = false;
if (definesString00.find(" PHONG\n") != std::string::npos)
bUSE_NORMALS = true;
if (definesString00.find(" TEXTURE\n") != std::string::npos) {
bUSE_TEX0 = true;
bUSE_TUV0 = true;
}
if (definesString00.find(" MIRROR\n") != std::string::npos) {
bUSE_NORMALS = true;
bUSE_TEX0 = true;
}
if (definesString00.find(" OVERMASK\n") != std::string::npos) {
bUSE_TUV0 = true;
}
std::string definesString;
definesString.assign("#version 320 es\n");
definesString.append(definesString00);
if (bUSE_NORMALS)
definesString.append("#define USE_NORMALS\n");
if (bUSE_TEX0)
definesString.append("#define USE_TEX0\n");
if (bUSE_TUV0)
definesString.append("#define USE_TUV0\n");
int vertexShaderId = compileShaderWithDefines(definesString, sourceVertex, GL_VERTEX_SHADER);
int fragmentShaderId = compileShaderWithDefines(definesString, sourceFragment, 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);
//mylog("linking program\n%s\n", definesString.c_str());
return programId;
}
int Shader::compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType) {
int shaderId = glCreateShader(shaderType);
if (definesString.empty())
glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
else { //2 strings
const char* sourceStrings[2];
sourceStrings[0] = definesString.c_str();
sourceStrings[1] = shaderSource;
// source code loaded, compile
glShaderSource(shaderId, 2, (const GLchar**)sourceStrings, NULL);
}
//myglErrorCheck("glShaderSource");
glCompileShader(shaderId);
if (shaderErrorCheck(shaderId, "glCompileShader") < 0) {
mylog("ERROR in compileShader,\n%s\n%s\n", definesString.c_str(), shaderSource);
return -1;
}
return shaderId;
}
int Shader::loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment) {
struct Terms {
std::vector<std::string> terms;
int totalN = 0;
int currentN = 0;
};
std::vector<Terms*> terms;
std::vector<std::string> termGroups = splitString(optionsString, ";");
int groupsN = termGroups.size();
for (int groupN = 0; groupN < groupsN; groupN++) {
Terms* pTerms = new Terms();
terms.push_back(pTerms);
pTerms->terms = splitString(termGroups.at(groupN), "|");
pTerms->totalN = pTerms->terms.size();
}
while (1) {
std::string definesString = "";
for (int groupN = 0; groupN < groupsN; groupN++) {
Terms* pTerms = terms.at(groupN);
std::string term = pTerms->terms.at(pTerms->currentN);
if (term.compare("NONE") != 0) {
definesString.append("#define ");
definesString.append(term);
definesString.append("\n");
}
}
int shaderObjN = buildShaderObjectWithDefines(shaderType, definesString, sourceVertex, sourceFragment);
//go to next terms combo
bool noMoreOptions = false;
for (int groupN = groupsN - 1; groupN >= 0; groupN--) {
Terms* pTerms = terms.at(groupN);
if (pTerms->currentN < pTerms->totalN - 1) {
pTerms->currentN++;
break;
}
else { // the level exhausted
pTerms->currentN = 0;
//proceed to upper level
if (groupN == 0) {
noMoreOptions = true;
break;
}
}
}
if (noMoreOptions)
break;
}
return 1;
}
- Please note: in 3 calls we are generating TEN shader programs out of just ONE single shaders pair.
Now - shaders themselves.
4. Vertex shader:
//#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)
#if defined(USE_NORMALS)
in vec3 aNormal; // normal attribute (x,y,z)
out vec3 vNormal; // varying normal (to pass to fragment shader)
#endif
#if defined(USE_TUV0)
in vec2 aTuv; //attribute TUV (texture coordinates)
out vec2 vTuv; //varying TUV (pass to fragment shader)
#endif
#if defined(MIRROR)
out vec2 vTuvMirror; //varying TUV (pass to fragment shader)
#endif
void main(void) {
gl_Position = uMVP * vec4(aPos, 1.0);
#if defined(USE_NORMALS)
// Transform the normal's orientation into eye space.
vNormal = uMV3x3 * aNormal;
#endif
#if defined(USE_TUV0)
vTuv = aTuv;
#endif
#if defined(MIRROR)
vTuvMirror[0] = (gl_Position[0]/gl_Position[3]*0.1+vNormal[0]*0.4)+0.5;
vTuvMirror[1] = -(gl_Position[1]/gl_Position[3]*0.1+vNormal[1]*0.4)+0.5;
#endif
}
Copy this code to a Text Editor and save it as (overwrite)
C:\CPP\engine\dt\shaders\phong_v.txt
- Just in case: reflection (lines 28 to 31): we'll take a white noise image (from previous chapter) as a texture, then will calculate texture tUV coordinates from vertex screen position and normal's orientation. Construction "gl_Position[0]/gl_Position[3]" will give us vertex coordinates in GL -1 to 1 format. Construction in brackets will give a position + orientation combo in -0.5 to 0.5 range. So, vTuvMirror - is a conversion of vertex' position/orientation into texture tUV coordinates.
5. Fragment shader:
//#version 320 es
precision lowp float;
out vec4 FragColor; //output pixel color
uniform float uAlphaFactor; //for semi-transparency
uniform int uAlphaBlending; //for semi-transparency
#if defined(USE_NORMALS)
in vec3 vNormal; //normal passed from rasterizer
#endif
#if defined(USE_TEX0)
uniform sampler2D uTex0; //texture id
uniform sampler2D uTex3; //translate texture id
uniform int uTex0translateChannelN;
#else
uniform vec4 uColor;
#endif
#if defined(USE_TUV0)
in vec2 vTuv; //varying TUV (passed from vertex shader)
#endif
#if defined(MIRROR)
in vec2 vTuvMirror; //varying TUV (passed from vertex shader)
#endif
#if defined(OVERMASK)
uniform sampler2D uTex1mask; //texture id
uniform int uTex1alphaChannelN;
uniform int uTex1alphaNegative;
#endif
#if defined(PHONG)
uniform float uAmbient;
uniform float uSpecularIntencity;
uniform float uSpecularMinDot;
uniform float uSpecularPowerOf;
uniform vec3 uVectorToLight;
uniform vec3 uHalfVector;
#endif
void main(void) {
vec4 outColor;
float alpha = 1.0;
#if defined(OVERMASK)
outColor = texture(uTex1mask, vTuv);
alpha = outColor[uTex1alphaChannelN];
if(uTex1alphaNegative > 0)
alpha = 1.0 - alpha;
if(alpha < 0.5){
if(uAlphaBlending > 0){
if(alpha == 0.0){
discard;
return;
}
}
else{ //no AlphaBlending
discard;
return;
}
}
#endif
#if defined(USE_TEX0)
#if defined(MIRROR)
outColor = texture(uTex0, vTuvMirror);
#else
outColor = texture(uTex0, vTuv);
#endif
if(uTex0translateChannelN >= 0){ //translate channel
vec2 tuv3;
tuv3[0] = outColor[uTex0translateChannelN];
tuv3[1] = 0.0;
outColor = texture(uTex3, tuv3);
}
FragColor = outColor;
#else
FragColor = uColor;
#endif
if(FragColor.a != 1.0){
alpha *= FragColor.a;
if(alpha < 0.5){
if(uAlphaBlending > 0){
if(alpha == 0.0){
discard;
return;
}
}
else{ //no AlphaBlending
discard;
return;
}
}
}
#if defined(USE_NORMALS)
vec3 vNormalNormal = normalize(vNormal);
#endif
#if defined(PHONG)
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.
FragColor *= 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;
FragColor += specularIntencity;
}
}
#endif
if(uAlphaFactor != 1.0)
alpha *= uAlphaFactor;
FragColor.a = alpha;
}
Copy this code to a Text Editor and save it as (overwrite)
C:\CPP\engine\dt\shaders\phong_f.txt
Comments:
- Line 45. For transparency map default uTex1alphaChannelN is 3 (Alpha-channel), but can be set to any of 4 RGBA channels.
- Lines 67-72. If uTex0translateChannelN is set (>=0), then we'll "translate" black-and-white channel's value to colors defined in 1-dimensional uTex3 texture.
As for "translation" to 1-dimensional texture:
For example, gold. In our case it will be 4x1 texture:
6. In Windows File Explorer create new folder C:\CPP\engine\dt\common\img\materials
Then download gold01.bmp hereand save it to C:\CPP\engine\dt\common\img\materials\gold01.bmp
In Material class new variable shaderType and functions
7. Open Material.h and replace code by:
#pragma once
#include "MyColor.h"
#include <string>
class Material
{
public:
char shaderType[20] = "";
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;
public:
int pickShaderNumber() { return pickShaderNumber(this); };
static int pickShaderNumber(Material* pMT);
void setShaderType(std::string needType) { setShaderType(this, needType); };
static void setShaderType(Material* pMT, std::string needType) { myStrcpy_s(pMT->shaderType, 20, (char*)needType.c_str()); };
void clear() { clear(this); };
static void clear(Material* pMT);
int assignShader(std::string needType) { return assignShader(this, needType); };
static int assignShader(Material* pMT, std::string needType);
};
8. Open our empty Material.cpp and replace by:
#include "Material.h"
#include "Shader.h"
#include "platform.h"
int Material::pickShaderNumber(Material* pMT) {
int shadersN = Shader::shaders.size();
for (int i = 0; i < shadersN; i++) {
Shader* pSH = Shader::shaders.at(i);
if (strcmp(pMT->shaderType, pSH->shaderType) != 0)
continue;
if (pMT->uColor.isZero() != (pSH->l_uColor < 0))
continue;
if ((pMT->uTex0 < 0) != (pSH->l_uTex0 < 0))
continue;
if ((pMT->uTex1mask < 0) != (pSH->l_uTex1mask < 0))
continue;
if ((pMT->uTex2nm < 0) != (pSH->l_uTex2nm < 0))
continue;
pMT->shaderN = i;
return i;
}
mylog("ERROR in Material::pickShaderNumber:\n");
mylog("Can't find '%s' shader for uColor=%08x uTex0=%d uTex1mask=%d uTex2nm=%d\n",
pMT->shaderType, pMT->uColor.getUint32(), pMT->uTex0, pMT->uTex1mask, pMT->uTex2nm);
return -1;
}
void Material::clear(Material* pMT) {
Material mtZero;
memcpy(pMT, &mtZero, sizeof(Material));
}
int Material::assignShader(Material* pMT, std::string shaderType) {
setShaderType(pMT, shaderType);
return pickShaderNumber(pMT);
}
ModelBuilder1base.cpp now, when building DrawJobs, picks shaders programmatically (line 228).
9. Open ModelBuilder1base.cpp and replace code by
#include "ModelBuilder1base.h"
#include "platform.h"
#include "utils.h"
#include "DrawJob.h"
#include "Shader.h"
extern float degrees2radians;
ModelBuilder1base::~ModelBuilder1base() {
//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 ModelBuilder1base::useSubjN(ModelBuilder1base* 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 ModelBuilder1base::useMaterial(ModelBuilder1base* 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 ModelBuilder1base::add2triangles(ModelBuilder1base* 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 ModelBuilder1base::addTriangle(ModelBuilder1base* 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 ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {
if (pMB->pCurrentGroup != NULL)
pMB->groupsStack.push_back(pMB->pCurrentGroup);
pMB->pCurrentGroup = new Group01();
pMB->pCurrentGroup->fromVertexN = pMB->vertices.size();
pMB->pCurrentGroup->fromTriangleN = pMB->triangles.size();
}
void ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {
delete pMB->pCurrentGroup;
if (pMB->groupsStack.size() > 0) {
pMB->pCurrentGroup = pMB->groupsStack.back();
pMB->groupsStack.pop_back();
}
else
pMB->pCurrentGroup = NULL;
}
int ModelBuilder1base::addVertex(ModelBuilder1base* 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 ModelBuilder1base::buildDrawJobs(ModelBuilder1base* 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 ModelBuilder1base::buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
int totalVertsN = useVertices.size();
if (totalVertsN < 1)
return 0;
//if (pMT->uTex2nm >= 0)
// calculateTangentSpace(useVertices, useTriangles);
pMT->pickShaderNumber();
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] = (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 ModelBuilder1base::rearrangeArraysForDrawJob(ModelBuilder1base* 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;
}
int ModelBuilder1base::moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ) {
//moves and rotates vertex group
//rotation angles are set in degrees
mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
mat4x4_translate(transformMatrix, kX, kY, kZ);
//rotation order: Z-X-Y
if (aY != 0) mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * aY);
if (aX != 0) mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * aX);
if (aZ != 0) mat4x4_rotate_Z(transformMatrix, transformMatrix, degrees2radians * aZ);
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);
}
return 1;
}
And, finally, TheGame.cpp
10. Open TheGame.cpp and replace code by:
#include "TheGame.h"
#include "platform.h"
#include "utils.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
#include "ModelBuilder.h"
#include "TexCoords.h"
extern std::string filesRoot;
extern float degrees2radians;
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,3,0);
ModelBuilder* pMB = new ModelBuilder();
pMB->useSubjN(gameSubjs.size() - 1);
//define VirtualShape
VirtualShape vs;
vs.setShapeType("box-tank");
vs.whl[0] = 60;
vs.whl[1] = 160;
vs.whl[2] = 390;
vs.setExt(20);
vs.extD = 0;
vs.extF = 0; //to make front face "flat"
vs.sectionsR = 2;
Material mt;
//define material - gold
mt.setShaderType("mirror");
mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/common/img/whitenoise/wn64_blur3.bmp"); //white noise
mt.uTex0translateChannelN = 0;
mt.uTex3 = Texture::loadTexture(filesRoot + "/dt/common/img/materials/gold01.bmp");
pMB->useMaterial(&mt);
pMB->buildBoxFace(pMB,"front v", &vs);
pMB->buildBoxFace(pMB, "back v", &vs);
pMB->buildBoxFace(pMB, "top", &vs);
pMB->buildBoxFace(pMB, "bottom", &vs);
pMB->buildBoxFace(pMB, "left all", &vs);
//textured surface
mt.clear(); // set to zero;
mt.setShaderType("phong");
mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/sample_img.png"); //sample img
//transparency map
mt.uTex1mask = Texture::loadTexture(filesRoot + "/dt/common/img/whitenoise/wn64_blur1.bmp"); //white noise
mt.uTex1alphaChannelN = 0;
pMB->useMaterial(&mt);
TexCoords tc;
tc.set(mt.uTex0, 11, 12, 256, 128, "h"); //flip horizontally
pMB->buildBoxFace(pMB, "right all", &vs, &tc);
pMB->buildDrawJobs(gameSubjs);
delete pMB;
//===== set up camera
mainCamera.ownCoords.setDegrees(15, 180, 0); //set camera angles/orientation
mainCamera.viewRangeDg = 30;
mainCamera.stageSize[0] = 500;
mainCamera.stageSize[1] = 375;
memcpy(mainCamera.lookAtPoint, pGS->ownCoords.pos, sizeof(float) * 3);
mainCamera.onScreenResize();
//===== 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);
float nearClip = mainCamera.focusDistance - 250;
float farClip = mainCamera.focusDistance + 250;
mat4x4_perspective(mProjection, mainCamera.viewRangeDg * degrees2radians, screenAspectRatio, nearClip, farClip);
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);
}
}
//synchronization
while (1) {
long long int currentMillis = getSystemMillis();
long long int millisSinceLastFrame = currentMillis - lastFrameMillis;
if (millisSinceLastFrame >= millisPerFrame) {
lastFrameMillis = currentMillis;
break;
}
}
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;
screenAspectRatio = (float)width / height;
glViewport(0, 0, width, height);
mainCamera.onScreenResize();
mylog(" screen size %d x %d\n", width, height);
return 1;
}
int TheGame::run() {
getReady();
while (!bExitGame) {
drawFrame();
}
cleanUp();
return 1;
}
Please note: now we set Materials a bit differently:
- Instead of dealing with shaders numbers, now we are assigning desirable shader type (lines 46 and 60). Specific shader number will be picked later automatically, while building DrawJob.
- Defining "gold" material, lines 46-50: The main texture uTex0 - is a white noise. We'll take black-and-white channel #0 and will translate retrieved 0-to-1 values to colors from 4x1 uTex3, which is set to gold01.bmp.
- Transparency mask, lines 62-64: We are setting uTex1mask to a white noise image. Setting uTex1alphaChannelN is optional. Default value is 3 - Alpha-channel.
- And, of course, we are back to normal run().
11. Build and run:
- Black holes on textured side are from Transparency Mask.
Verified on Android too.
Direct fit!