diff --git a/src/cache.h b/src/cache.h
index 8224ca1..f409917 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -17,12 +17,16 @@ const char SHADER[] =
#include "shader.glsl"
;
+const char WATER[] =
+ #include "water.glsl"
+;
+
const char FILTER[] =
#include "filter.glsl"
;
-const char WATER[] =
- #include "water.glsl"
+const char VOLUME[] =
+ #include "volume.glsl"
;
const char GUI[] =
@@ -113,7 +117,7 @@ struct ShaderCache {
}
}
- const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "GUI" };
+ const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "VOLUME", "GUI" };
const char *src = NULL;
const char *typ = NULL;
switch (pass) {
@@ -146,12 +150,19 @@ struct ShaderCache {
break;
}
case Core::passFilter : {
- static const char *typeNames[] = { "DOWNSAMPLE" };
+ static const char *typeNames[] = { "DEFAULT", "DOWNSAMPLE" };
src = FILTER;
typ = typeNames[type];
sprintf(def, "%s#define PASS_%s\n#define FILTER_%s\n", ext, passNames[pass], typ);
break;
}
+ case Core::passVolume : {
+ static const char *typeNames[] = { "DEFAULT" };
+ src = VOLUME;
+ typ = typeNames[type];
+ sprintf(def, "%s#define PASS_%s\n", ext, passNames[pass]);
+ break;
+ }
case Core::passGUI : {
static const char *typeNames[] = { "DEFAULT" };
src = GUI;
diff --git a/src/controller.h b/src/controller.h
index df61906..1446c6d 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -26,7 +26,7 @@ struct IGame {
virtual void waterDrop(const vec3 &pos, float radius, float strength) {}
virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
- virtual void renderCompose(int roomIndex) {}
+ virtual void renderCompose(int roomIndex, bool genShadowMask = false) {}
};
struct Controller {
@@ -489,23 +489,6 @@ struct Controller {
}
void renderShadow(MeshBuilder *mesh, const vec3 &pos, const vec3 &offset, const vec3 &size, float angle) {
- /*
- mat4 m;
- m.identity();
- m.translate(pos);
- m.rotateY(angle);
- m.translate(vec3(offset.x, 0.0f, offset.z));
- m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
-
- game->setShader(Core::pass, Shader::FLASH, false, false);
- Core::active.shader->setParam(uBasis, Basis(m));
- Core::active.shader->setParam(uColor, vec4(0.5f, 0.5f, 0.5f, 1.0f));
- Core::active.shader->setParam(uAmbient, vec3(0.0f));
-
- Core::setBlending(bmMultiply);
- mesh->renderShadowSpot();
- Core::setBlending(bmNone);
- */
mat4 m = Core::mViewProj;
m.translate(pos);
m.rotateY(angle);
@@ -522,7 +505,7 @@ struct Controller {
Core::active.shader->setParam(uAmbient, vec3(0.0f));
Core::setBlending(bmAlpha);
- mesh->renderShadowSpot();
+ mesh->renderShadowBlob();
Core::setBlending(bmNone);
Core::active.shader->setParam(uViewProj, Core::mViewProj);
diff --git a/src/core.h b/src/core.h
index 7c68e42..4a79a29 100644
--- a/src/core.h
+++ b/src/core.h
@@ -79,8 +79,10 @@
#define GL_RGBA16F GL_RGBA
#define GL_HALF_FLOAT GL_HALF_FLOAT_OES
- #define glGetProgramBinary(...) 0
- #define glProgramBinary(...) 0
+ #define GL_STENCIL_TEST_TWO_SIDE_EXT 0
+ #define glGetProgramBinary(...)
+ #define glProgramBinary(...)
+ #define glActiveStencilFaceEXT(...)
#endif
#include "utils.h"
@@ -158,6 +160,10 @@
PFNGLDELETEBUFFERSARBPROC glDeleteBuffers;
PFNGLBINDBUFFERARBPROC glBindBuffer;
PFNGLBUFFERDATAARBPROC glBufferData;
+ // Stencil
+ PFNGLACTIVESTENCILFACEEXTPROC glActiveStencilFaceEXT;
+ PFNGLSTENCILFUNCSEPARATEPROC glStencilFuncSeparate;
+ PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate;
#endif
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
@@ -180,6 +186,7 @@ struct Texture;
namespace Core {
struct {
+ bool shaderBinary;
bool VAO;
bool depthTexture;
bool shadowSampler;
@@ -187,7 +194,7 @@ namespace Core {
bool texNPOT;
bool texFloat, texFloatLinear;
bool texHalf, texHalfLinear;
- bool shaderBinary;
+ char stencil;
#ifdef PROFILE
bool profMarker;
bool profTiming;
@@ -254,7 +261,7 @@ namespace Core {
Texture *blackTex, *whiteTex;
- enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passGUI, passMAX } pass;
+ enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passVolume, passGUI, passMAX } pass;
GLuint FBO;
struct RenderTargetCache {
@@ -274,6 +281,7 @@ namespace Core {
GLuint VAO;
BlendMode blendMode;
CullMode cullMode;
+ bool stencilTwoSide;
} active;
struct Stats {
@@ -312,7 +320,6 @@ namespace Core {
#include "texture.h"
#include "shader.h"
-#include "mesh.h"
namespace Core {
@@ -379,6 +386,10 @@ namespace Core {
GetProcOGL(glDeleteBuffers);
GetProcOGL(glBindBuffer);
GetProcOGL(glBufferData);
+
+ GetProcOGL(glActiveStencilFaceEXT);
+ GetProcOGL(glStencilFuncSeparate);
+ GetProcOGL(glStencilOpSeparate);
#endif
#ifdef MOBILE
@@ -395,7 +406,7 @@ namespace Core {
char *ext = (char*)glGetString(GL_EXTENSIONS);
//LOG("%s\n", ext);
- support.shaderBinary = extSupport(ext, "_program_binary");
+ support.shaderBinary = false;//extSupport(ext, "_program_binary");
support.VAO = extSupport(ext, "_vertex_array_object");
support.depthTexture = extSupport(ext, "_depth_texture");
support.shadowSampler = extSupport(ext, "_shadow_samplers") || extSupport(ext, "GL_ARB_shadow");
@@ -405,6 +416,15 @@ namespace Core {
support.texFloat = support.texFloatLinear || extSupport(ext, "_texture_float");
support.texHalfLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear");
support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float");
+
+ if (extSupport(ext, "_ATI_separate_stencil"))
+ support.stencil = 2;
+ else
+ if (extSupport(ext, "_stencil_two_side"))
+ support.stencil = 1;
+ else
+ support.stencil = 0;
+
#ifdef PROFILE
support.profMarker = extSupport(ext, "_KHR_debug");
support.profTiming = extSupport(ext, "_timer_query");
@@ -425,6 +445,7 @@ namespace Core {
LOG(" float textures : float = %s, half = %s\n",
support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false",
support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false");
+ LOG(" stencil : %s\n", support.stencil == 2 ? "separate" : (support.stencil == 1 ? "two_side" : "false"));
LOG("\n");
glGenFramebuffers(1, &FBO);
@@ -472,14 +493,14 @@ namespace Core {
item.height = height;
glBindRenderbuffer(GL_RENDERBUFFER, item.ID);
- glRenderbufferStorage(GL_RENDERBUFFER, depth ? GL_RGB : GL_DEPTH_COMPONENT16, width, height);
+ glRenderbufferStorage(GL_RENDERBUFFER, depth ? GL_RGB565 : GL_DEPTH_COMPONENT16, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
return cache.count++;
}
- void clear(bool clearColor, bool clearDepth) {
- if (GLbitfield mask = (clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0))
+ void clear(bool clearColor, bool clearDepth, bool clearStencil = false) {
+ if (GLbitfield mask = (clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0) | (clearStencil ? GL_STENCIL_BUFFER_BIT : 0))
glClear(mask);
}
@@ -487,6 +508,10 @@ namespace Core {
glClearColor(color.x, color.y, color.z, color.w);
}
+ void setClearStencil(int value) {
+ glClearStencil(value);
+ }
+
void setViewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
@@ -555,6 +580,47 @@ namespace Core {
glDisable(GL_DEPTH_TEST);
}
+ void setStencilTest(bool test) {
+ if (test)
+ glEnable(GL_STENCIL_TEST);
+ else
+ glDisable(GL_STENCIL_TEST);
+ }
+
+ void setStencilTwoSide(int ref, bool test) { // preset for z-fail shadow volumes
+ active.stencilTwoSide = test;
+ if (test) {
+ switch (Core::support.stencil) {
+ case 0 :
+ glStencilFunc(GL_ALWAYS, ref, ~0);
+ break;
+ case 1 :
+ setCulling(cfNone);
+ glEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);
+ glActiveStencilFaceEXT(GL_BACK);
+ glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+ glStencilFunc(GL_ALWAYS, ref, ~0);
+ glActiveStencilFaceEXT(GL_FRONT);
+ glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
+ glStencilFunc(GL_ALWAYS, ref, ~0);
+ break;
+ case 2 :
+ setCulling(cfNone);
+ glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, ref, ~0);
+ glStencilFuncSeparate(GL_BACK, GL_ALWAYS, ref, ~0);
+ glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR, GL_KEEP);
+ glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR, GL_KEEP);
+ break;
+ }
+ } else {
+ if (Core::support.stencil == 1)
+ glDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ glStencilFunc(GL_NOTEQUAL, ref, ~0);
+ setCulling(cfFront);
+ }
+ }
+
void invalidateTarget(bool color, bool depth) {
#ifdef MOBILE
if (support.discardFrame && (color || depth)) {
@@ -619,4 +685,6 @@ namespace Core {
}
}
+#include "mesh.h"
+
#endif
diff --git a/src/level.h b/src/level.h
index 3d52f6c..8febaca 100644
--- a/src/level.h
+++ b/src/level.h
@@ -86,7 +86,7 @@ struct Level : IGame {
}
}
- virtual void renderCompose(int roomIndex) {
+ virtual void renderCompose(int roomIndex, bool genShadowMask = false) {
PROFILE_MARKER("PASS_COMPOSE");
Core::pass = Core::passCompose;
@@ -95,6 +95,51 @@ struct Level : IGame {
if (shadow)
shadow->bind(sShadow);
renderScene(roomIndex);
+
+ if (genShadowMask) {
+ // renderShadowVolumes();
+ }
+ }
+
+ void renderShadowVolumes() {
+ getLight(lara->pos, lara->getRoomIndex());
+
+ Core::setCulling(cfNone);
+ Core::setDepthWrite(false);
+ Core::setColorWrite(false, false, false, false);
+
+ Core::setStencilTest(true);
+ Core::setStencilTwoSide(128, true);
+
+ setShader(Core::passVolume, Shader::DEFAULT, false, false);
+ vec4 lp(Core::lightPos[0], Core::lightColor[0].w);
+ Core::active.shader->setParam(uLightPos, lp);
+
+ for (int i = 0; i < level.entitiesCount; i++) {
+ TR::Entity &e = level.entities[i];
+ if (e.flags.rendered && TR::castShadow(e.type)) {
+ Controller *controller = (Controller*)e.controller;
+
+ mat4 matrix = controller->getMatrix();
+ Box box = controller->animation.getBoundingBox(vec3(0.0f), 0);
+ matrix.translate(box.center());
+
+ Core::active.shader->setParam(uBasis, Basis(matrix) );
+ Core::active.shader->setParam(uPosScale, box.size() * 0.5);
+
+ mesh->renderShadowBox();
+ }
+ }
+
+ Core::setDepthWrite(true);
+ Core::setColorWrite(true, true, true, true);
+
+ Core::setStencilTwoSide(128, false);
+
+ setShader(Core::passFilter, Shader::DEFAULT, false, false);
+ mesh->renderQuad();
+
+ Core::setStencilTest(false);
}
//==============================
@@ -739,14 +784,15 @@ struct Level : IGame {
if (shadow)
renderShadows(lara->getRoomIndex());
+ Core::setClearStencil(128);
Core::setTarget(NULL);
- Core::clear(true, true);
+ Core::clear(true, true, true);
Core::setViewport(0, 0, Core::width, Core::height);
if (waterCache)
waterCache->checkVisibility = true;
- renderCompose(camera->getRoomIndex());
+ renderCompose(camera->getRoomIndex(), true);
if (waterCache) {
waterCache->checkVisibility = false;
@@ -825,7 +871,7 @@ struct Level : IGame {
Core::setDepthTest(true);
-
+ /*
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
diff --git a/src/mesh.h b/src/mesh.h
index 5547b1e..2df516a 100644
--- a/src/mesh.h
+++ b/src/mesh.h
@@ -90,13 +90,25 @@ struct Mesh {
glEnableVertexAttribArray(aColor);
}
- void render(const MeshRange &range) {
- range.bind(VAO);
+ void DIP(const MeshRange &range) {
glDrawElements(GL_TRIANGLES, range.iCount, GL_UNSIGNED_SHORT, (GLvoid*)(range.iStart * sizeof(Index)));
-
Core::stats.dips++;
Core::stats.tris += range.iCount / 3;
}
+
+ void render(const MeshRange &range) {
+ range.bind(VAO);
+
+ if (Core::active.stencilTwoSide && Core::support.stencil == 0) {
+ Core::setCulling(cfBack);
+ glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+ DIP(range);
+ Core::setCulling(cfFront);
+ glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
+ }
+
+ DIP(range);
+ }
};
@@ -145,7 +157,7 @@ struct MeshBuilder {
} *models;
MeshRange *sequences;
// procedured
- MeshRange shadowBlob;
+ MeshRange shadowBlob, shadowBox;
MeshRange bar;
MeshRange quad, circle;
MeshRange plane;
@@ -231,13 +243,20 @@ struct MeshBuilder {
vCount += level.spriteSequences[i].sCount * 4;
}
- // get size of simple shadow spot mesh (8 triangles, 8 vertices)
+ // shadow blob mesh (8 triangles, 8 vertices)
shadowBlob.vStart = vCount;
shadowBlob.iStart = iCount;
shadowBlob.iCount = 8 * 3;
iCount += shadowBlob.iCount;
vCount += 8;
+ // shadow box (for stencil shadow volumes with degenerate triangles)
+ shadowBox.vStart = vCount;
+ shadowBox.iStart = iCount;
+ shadowBox.iCount = (3 * (2 + 4)) * 6;
+ iCount += shadowBox.iCount;
+ vCount += 4 * 6;
+
// bar (health, oxygen)
bar.vStart = vCount;
bar.iStart = iCount;
@@ -350,7 +369,7 @@ struct MeshBuilder {
}
aCount += level.spriteSequencesCount;
- // build shadow spot
+ // build shadow blob
for (int i = 0; i < 8; i++) {
Vertex &v = vertices[vCount + i];
v.normal = { 0, -1, 0, 1 };
@@ -371,6 +390,59 @@ struct MeshBuilder {
iCount += shadowBlob.iCount;
aCount++;
+ // build shadow box volume
+ {
+ static const Index tmpIndices[] = {
+ 0,1,2, 0,2,3, 0,7,1, 0,4,7,
+ 1,11,2, 1,8,11, 2,15,3, 2,12,15,
+ 3,19,0, 3,16,19, 21,6,5, 21,20,6,
+ 20,10,9, 20,23,10, 23,14,13, 23,22,14,
+ 22,18,17, 22,21,18, 20,21,22, 20,22,23,
+ 7,6,9, 7,9,8, 11,10,13, 11,13,12,
+ 15,14,17, 15,17,16, 19,5,4, 19,18,5,
+ 4,6,7, 4,5,6, 8,9,10, 8,10,11,
+ 12,14,15, 12,13,14, 16,18,19, 16,17,18
+ };
+ static const short4 tmpCoords[] = {
+ { -1, -1, -1, 0 }, { 1, -1, -1, 0 }, { 1, 1, -1, 0 }, { -1, 1, -1, 0 },
+ { -1, -1, -1, 0 }, { -1, -1, 1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 },
+ { 1, -1, -1, 0 }, { 1, -1, 1, 0 }, { 1, 1, 1, 0 }, { 1, 1, -1, 0 },
+ { 1, 1, -1, 0 }, { 1, 1, 1, 0 }, { -1, 1, 1, 0 }, { -1, 1, -1, 0 },
+ { -1, 1, -1, 0 }, { -1, 1, 1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 },
+ { 1, -1, 1, 0 }, { -1, -1, 1, 0 }, { -1, 1, 1, 0 }, { 1, 1, 1, 0 },
+ };
+
+ const short n = 32767;
+ static const short4 tmpNormals[] = {
+ { 0, 0, -n, 0 },
+ { 0, -n, 0, 0 },
+ { n, 0, 0, 0 },
+ { 0, n, 0, 0 },
+ { -n, 0, 0, 0 },
+ { 0, 0, n, 0 },
+ };
+
+ static const ubyte4 tmpColors[] = {
+ { 255, 0, 0, 0 },
+ { 0, 255, 0, 0 },
+ { 0, 0, 255, 0 },
+ { 255, 0, 255, 0 },
+ { 255, 255, 0, 0 },
+ { 0, 255, 255, 0 },
+ };
+
+ memcpy(&indices[iCount], &tmpIndices[0], shadowBox.iCount * sizeof(Index));
+ memset(&vertices[vCount], 0, 4 * 6 * sizeof(Vertex));
+ for (int i = 0; i < 4 * 6; i++) {
+ vertices[vCount + i].coord = tmpCoords[i];
+ vertices[vCount + i].normal = tmpNormals[i / 4];
+ vertices[vCount + i].color = tmpColors[i / 4];
+ }
+ iCount += shadowBox.iCount;
+ vCount += 4 * 6;
+ aCount++;
+ }
+
// white bar
addQuad(indices, iCount, vCount, bar.vStart, vertices, &whiteTile);
vertices[vCount + 0].coord = { 0, 0, 0, 0 };
@@ -467,6 +539,7 @@ struct MeshBuilder {
for (int i = 0; i < level.modelsCount; i++)
mesh->initRange(models[i].geometry);
mesh->initRange(shadowBlob);
+ mesh->initRange(shadowBox);
mesh->initRange(bar);
mesh->initRange(quad);
mesh->initRange(circle);
@@ -856,12 +929,12 @@ struct MeshBuilder {
mesh->render(range);
}
- void renderShadowSpot() {
+ void renderShadowBlob() {
mesh->render(shadowBlob);
}
- void renderLine(const vec2 &pos, const vec2 &size, uint32 color) {
-
+ void renderShadowBox() {
+ mesh->render(shadowBox);
}
void renderQuad() {
diff --git a/src/platform/win/OpenLara.vcxproj b/src/platform/win/OpenLara.vcxproj
index aeac447..966c500 100644
--- a/src/platform/win/OpenLara.vcxproj
+++ b/src/platform/win/OpenLara.vcxproj
@@ -218,6 +218,7 @@
+
diff --git a/src/shader.h b/src/shader.h
index 57ee8e7..cb678f2 100644
--- a/src/shader.h
+++ b/src/shader.h
@@ -57,9 +57,9 @@ struct Shader {
vec4 params[uMAX][4];
enum Type : GLint {
- NONE = 0,
+ DEFAULT = 0,
/* shader */ SPRITE = 0, FLASH = 1, ROOM = 2, ENTITY = 3, MIRROR = 4,
- /* filter */ FILTER_DOWNSAMPLE = 0,
+ /* filter */ FILTER_DOWNSAMPLE = 1,
/* water */ WATER_DROP = 0, WATER_STEP = 1, WATER_CAUSTICS = 2, WATER_MASK = 3, WATER_COMPOSE = 4,
MAX = 5
};
diff --git a/src/ui.h b/src/ui.h
index e11d82d..8dcd4b2 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -66,13 +66,12 @@ struct UI {
}
void update() {
- if (touch[zMove] != ikNone || touch[zLook] != ikNone || touch[zButton] != ikNone) {
+ if (touchTimerTap > 0.0f)
+ touchTimerTap = max(0.0f, touchTimerTap - Core::deltaTime);
+
+ if (touch[zMove] != ikNone || touch[zLook] != ikNone || touch[zButton] != ikNone)
touchTimerVis = 30.0f;
-
- if (touchTimerTap > 0.0f)
- touchTimerTap = max(0.0f, touchTimerTap - Core::deltaTime);
-
- } else
+ else
if (touchTimerVis > 0.0f)
touchTimerVis = max(0.0f, touchTimerVis - Core::deltaTime);
@@ -116,11 +115,11 @@ struct UI {
if (t == ikNone) {
t = key;
if (zone == zMove) {
- if (touchTimerTap > 0.0f && touchTimerTap < 0.4f) { // 100 ms gap to reduce false positives (bad touch sensors)
+ if (touchTimerTap > 0.0f && touchTimerTap < 0.3f) { // 100 ms gap to reduce false positives (bad touch sensors)
doubleTap = true;
touchTimerTap = 0.0f;
} else
- touchTimerTap = 0.5f;
+ touchTimerTap = 0.3f;
}
}
}
@@ -151,7 +150,7 @@ struct UI {
Core::mViewProj = mat4(0.0f, float(Core::width), float(Core::height), 0.0f, 0.0f, 1.0f);
- game->setShader(Core::passGUI, Shader::NONE);
+ game->setShader(Core::passGUI, Shader::DEFAULT);
float offset = Core::height * 0.25f;