diff --git a/engines/wintermute/ad/ad_object_3d.cpp b/engines/wintermute/ad/ad_object_3d.cpp index 5f51e810f28..e9ec03eb81e 100644 --- a/engines/wintermute/ad/ad_object_3d.cpp +++ b/engines/wintermute/ad/ad_object_3d.cpp @@ -576,7 +576,7 @@ bool AdObject3D::skipTo(int x, int y, bool tolerant) { ////////////////////////////////////////////////////////////////////////// ShadowVolume *AdObject3D::getShadowVolume() { if (_shadowVolume == nullptr) { - _shadowVolume = new ShadowVolume(_gameRef); + _shadowVolume = _gameRef->_renderer3D->createShadowVolume(); } return _shadowVolume; diff --git a/engines/wintermute/base/gfx/opengl/base_render_opengl3d.cpp b/engines/wintermute/base/gfx/opengl/base_render_opengl3d.cpp index 613d5c8c138..5cbf2a87e9d 100644 --- a/engines/wintermute/base/gfx/opengl/base_render_opengl3d.cpp +++ b/engines/wintermute/base/gfx/opengl/base_render_opengl3d.cpp @@ -26,6 +26,7 @@ #include "engines/wintermute/base/gfx/opengl/camera3d.h" #include "engines/wintermute/base/gfx/opengl/mesh3ds_opengl.h" #include "engines/wintermute/base/gfx/opengl/meshx_opengl.h" +#include "engines/wintermute/base/gfx/opengl/shadow_volume_opengl.h" #include "graphics/opengl/system_headers.h" #include "math/glmath.h" @@ -614,4 +615,8 @@ MeshX *BaseRenderOpenGL3D::createMeshX() { return new MeshXOpenGL(_gameRef); } +ShadowVolume *BaseRenderOpenGL3D::createShadowVolume() { + return new ShadowVolumeOpenGL(_gameRef); +} + } // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/base_render_opengl3d.h b/engines/wintermute/base/gfx/opengl/base_render_opengl3d.h index 85fc27387ed..5213a5fd9d1 100644 --- a/engines/wintermute/base/gfx/opengl/base_render_opengl3d.h +++ b/engines/wintermute/base/gfx/opengl/base_render_opengl3d.h @@ -179,6 +179,7 @@ public: Mesh3DS *createMesh3DS() override; MeshX *createMeshX() override; + ShadowVolume *createShadowVolume() override; private: Math::Matrix4 _lastProjectionMatrix; diff --git a/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.cpp b/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.cpp index 1665a1a9ae1..819406cdbc6 100644 --- a/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.cpp +++ b/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.cpp @@ -26,6 +26,7 @@ #include "engines/wintermute/base/gfx/opengl/camera3d.h" #include "engines/wintermute/base/gfx/opengl/mesh3ds_opengl_shader.h" #include "engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h" +#include "engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h" #include "graphics/opengl/system_headers.h" #include "math/glmath.h" @@ -279,6 +280,9 @@ bool BaseRenderOpenGL3DShader::setProjection2D() { _projectionMatrix2d(3, 0) = -1.0f; _projectionMatrix2d(3, 1) = -1.0f; _projectionMatrix2d(3, 2) = -(farPlane + nearPlane) / (farPlane - nearPlane); + + _shadowMaskShader->use(); + _shadowMaskShader->setUniform("projMatrix", _projectionMatrix2d); return true; } @@ -302,6 +306,9 @@ void BaseRenderOpenGL3DShader::pushWorldTransform(const Math::Matrix4 &transform _modelXShader->use(); _modelXShader->setUniform("modelMatrix", newTop); _modelXShader->setUniform("normalMatrix", newInvertedTranspose); + + _shadowVolumeShader->use(); + _shadowVolumeShader->setUniform("modelMatrix", newTop); } void BaseRenderOpenGL3DShader::popWorldTransform() { @@ -317,6 +324,9 @@ void BaseRenderOpenGL3DShader::popWorldTransform() { _modelXShader->use(); _modelXShader->setUniform("modelMatrix", currentTransform); _modelXShader->setUniform("normalMatrix", currentInvertedTranspose); + + _shadowVolumeShader->use(); + _shadowVolumeShader->setUniform("modelMatrix", currentTransform); } bool BaseRenderOpenGL3DShader::windowedBlt() { @@ -350,6 +360,12 @@ bool BaseRenderOpenGL3DShader::initRenderer(int width, int height, bool windowed static const char *geometryAttributes[] = { "position", nullptr }; _geometryShader = OpenGL::Shader::fromFiles("geometry", geometryAttributes); + static const char *shadowVolumeAttributes[] = { "position", nullptr }; + _shadowVolumeShader = OpenGL::Shader::fromFiles("shadow_volume", shadowVolumeAttributes); + + static const char *shadowMaskAttributes[] = { "position", nullptr }; + _shadowMaskShader = OpenGL::Shader::fromFiles("shadow_mask", shadowMaskAttributes); + _transformStack.push_back(Math::Matrix4()); _transformStack.back().setToIdentity(); @@ -496,6 +512,10 @@ bool BaseRenderOpenGL3DShader::setup3D(Camera3D *camera, bool force) { _geometryShader->setUniform("viewMatrix", _lastViewMatrix); _geometryShader->setUniform("projMatrix", _projectionMatrix3d); + _shadowVolumeShader->use(); + _shadowVolumeShader->setUniform("viewMatrix", _lastViewMatrix); + _shadowVolumeShader->setUniform("projMatrix", _projectionMatrix3d); + return true; } @@ -657,4 +677,8 @@ MeshX *BaseRenderOpenGL3DShader::createMeshX() { return new MeshXOpenGLShader(_gameRef, _modelXShader); } +ShadowVolume *BaseRenderOpenGL3DShader::createShadowVolume() { + return new ShadowVolumeOpenGLShader(_gameRef, _shadowVolumeShader, _shadowMaskShader); +} + } // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.h b/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.h index 27e540e64f2..9f22b96c99a 100644 --- a/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.h +++ b/engines/wintermute/base/gfx/opengl/base_render_opengl3d_shader.h @@ -180,6 +180,7 @@ public: Mesh3DS *createMesh3DS() override; MeshX *createMeshX() override; + ShadowVolume *createShadowVolume() override; private: Math::Matrix4 _lastViewMatrix; @@ -199,6 +200,8 @@ private: OpenGL::Shader *_fadeShader; OpenGL::Shader *_modelXShader; OpenGL::Shader *_geometryShader; + OpenGL::Shader *_shadowVolumeShader; + OpenGL::Shader *_shadowMaskShader; }; } // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/base_renderer3d.h b/engines/wintermute/base/gfx/opengl/base_renderer3d.h index 0e267fda64e..92a953df6c4 100644 --- a/engines/wintermute/base/gfx/opengl/base_renderer3d.h +++ b/engines/wintermute/base/gfx/opengl/base_renderer3d.h @@ -38,6 +38,7 @@ namespace Wintermute { class BaseSurfaceOpenGL3D; class Mesh3DS; class MeshX; +class ShadowVolume; class BaseRenderer3D : public BaseRenderer { public: @@ -74,6 +75,7 @@ public: virtual Mesh3DS *createMesh3DS() = 0; virtual MeshX *createMeshX() = 0; + virtual ShadowVolume *createShadowVolume() = 0; virtual bool drawSprite(BaseSurfaceOpenGL3D &tex, const Rect32 &rect, float zoomX, float zoomY, const Vector2 &pos, uint32 color, bool alphaDisable, Graphics::TSpriteBlendMode blendMode, bool mirrorX, bool mirrorY) = 0; diff --git a/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.fragment b/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.fragment new file mode 100644 index 00000000000..ea57b6835be --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.fragment @@ -0,0 +1,8 @@ +in vec4 Color; + +OUTPUT + +void main() +{ + outColor = Color; +} diff --git a/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.vertex b/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.vertex new file mode 100644 index 00000000000..8d0e7ddb695 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shaders/shadow_mask.vertex @@ -0,0 +1,12 @@ +in vec2 position; + +uniform highp mat4 projMatrix; +uniform vec4 color; + +out vec4 Color; + +void main() +{ + gl_Position = projMatrix * vec4(position, 0.0f, 1.0f); + Color = color; +} diff --git a/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.fragment b/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.fragment new file mode 100644 index 00000000000..919810306a4 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.fragment @@ -0,0 +1,3 @@ +void main() +{ +} diff --git a/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.vertex b/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.vertex new file mode 100644 index 00000000000..6d9ee1e31a0 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shaders/shadow_volume.vertex @@ -0,0 +1,10 @@ +in vec3 position; + +uniform highp mat4 modelMatrix; +uniform highp mat4 viewMatrix; +uniform highp mat4 projMatrix; + +void main() +{ + gl_Position = projMatrix * viewMatrix * modelMatrix * vec4(position, 1.0f); +} diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume.cpp b/engines/wintermute/base/gfx/opengl/shadow_volume.cpp index a8417639eeb..bdbe8f0bf9a 100644 --- a/engines/wintermute/base/gfx/opengl/shadow_volume.cpp +++ b/engines/wintermute/base/gfx/opengl/shadow_volume.cpp @@ -53,143 +53,6 @@ void ShadowVolume::addVertex(const Math::Vector3d &vertex) { _vertices.add(vertex); } -////////////////////////////////////////////////////////////////////////// -bool ShadowVolume::render() { - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glEnableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glVertexPointer(3, GL_FLOAT, 0, _vertices.data()); - glDrawArrays(GL_TRIANGLES, 0, _vertices.size()); - - return true; -} - -////////////////////////////////////////////////////////////////////////// -bool ShadowVolume::renderToStencilBuffer() { - // Disable z-buffer writes (note: z-testing still occurs), and enable the - // stencil-buffer - glDepthMask(GL_FALSE); - glEnable(GL_STENCIL_TEST); - glEnable(GL_CULL_FACE); - - // Set up stencil compare fuction, reference value, and masks. - // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true. - // Note: since we set up the stencil-test to always pass, the STENCILFAIL - // renderstate is really not needed. - glStencilFunc(GL_ALWAYS, 0x1, 0xFFFFFFFF); - - glShadeModel(GL_FLAT); - glDisable(GL_LIGHTING); - - // Make sure that no pixels get drawn to the frame buffer - glEnable(GL_BLEND); - glBlendFunc(GL_ZERO, GL_ONE); - - glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); - - // Draw back-side of shadow volume in stencil/z only - glCullFace(GL_FRONT); - render(); - - // // Decrement stencil buffer value - glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); - - // Draw front-side of shadow volume in stencil/z only - glCullFace(GL_BACK); - render(); - - // // Restore render states - glEnable(GL_LIGHTING); - glFrontFace(GL_CCW); - glShadeModel(GL_SMOOTH); - glDepthMask(GL_TRUE); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - - return true; -} - -////////////////////////////////////////////////////////////////////////// -bool ShadowVolume::renderToScene() { - initMask(); - - glDisable(GL_DEPTH_TEST); - glEnable(GL_STENCIL_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Only write where stencil val >= 1 (count indicates # of shadows that overlap that pixel) - glStencilFunc(GL_LEQUAL, 0x1, 0xFFFFFFFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - - glDisable(GL_FOG); - glDisable(GL_LIGHTING); - glDisable(GL_ALPHA_TEST); - - _gameRef->_renderer3D->setProjection2D(); - - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - // Draw a big, gray square - glInterleavedArrays(GL_C4UB_V3F, 0, _shadowMask); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - // Restore render states - glEnable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - - _gameRef->_renderer3D->setup3D(nullptr, true); - - // clear stencil buffer - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - - return true; -} - -////////////////////////////////////////////////////////////////////////// -bool ShadowVolume::initMask() { - Rect32 viewport = _gameRef->_renderer->getViewPort(); - - _shadowMask[0].x = viewport.left; - _shadowMask[0].y = viewport.bottom; - _shadowMask[0].z = 0.0f; - - _shadowMask[1].x = viewport.left; - _shadowMask[1].y = viewport.top; - _shadowMask[1].z = 0.0f; - - _shadowMask[2].x = viewport.right; - _shadowMask[2].y = viewport.bottom; - _shadowMask[2].z = 0.0f; - - _shadowMask[3].x = viewport.right; - _shadowMask[3].y = viewport.top; - _shadowMask[3].z = 0.0f; - - byte a = RGBCOLGetA(_color); - byte r = RGBCOLGetR(_color); - byte g = RGBCOLGetG(_color); - byte b = RGBCOLGetB(_color); - - for (int i = 0; i < 4; ++i) { - _shadowMask[i].r = r; - _shadowMask[i].g = g; - _shadowMask[i].b = b; - _shadowMask[i].a = a; - } - - return true; -} - ////////////////////////////////////////////////////////////////////////// bool ShadowVolume::setColor(uint32 color) { if (color != _color) { diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume.h b/engines/wintermute/base/gfx/opengl/shadow_volume.h index 455f56401ba..8bef75f1168 100644 --- a/engines/wintermute/base/gfx/opengl/shadow_volume.h +++ b/engines/wintermute/base/gfx/opengl/shadow_volume.h @@ -58,17 +58,17 @@ public: void addVertex(const Math::Vector3d &vertex); bool reset(); - bool renderToStencilBuffer(); - bool renderToScene(); + virtual bool renderToStencilBuffer() = 0; + virtual bool renderToScene() = 0; bool setColor(uint32 color); -private: - bool render(); - ShadowVertex _shadowMask[4]; - uint32 _color; - bool initMask(); +protected: BaseArray _vertices; // Vertex data for rendering shadow volume + uint32 _color; + +private: + virtual bool initMask() = 0; }; } // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.cpp b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.cpp new file mode 100644 index 00000000000..d06eb24dc0b --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.cpp @@ -0,0 +1,182 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME. + * http://dead-code.org/redir.php?target=wme + * Copyright (c) 2003-2013 Jan Nedoma and contributors + */ + +#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/gfx/opengl/base_render_opengl3d.h" +#include "engines/wintermute/base/gfx/opengl/shadow_volume_opengl.h" +#include "engines/wintermute/dcgf.h" +#include "graphics/opengl/system_headers.h" + +namespace Wintermute { + +////////////////////////////////////////////////////////////////////////// +ShadowVolumeOpenGL::ShadowVolumeOpenGL(BaseGame *inGame) : ShadowVolume(inGame) { +} + +////////////////////////////////////////////////////////////////////////// +ShadowVolumeOpenGL::~ShadowVolumeOpenGL() { +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGL::render() { + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glEnableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, _vertices.data()); + glDrawArrays(GL_TRIANGLES, 0, _vertices.size()); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGL::renderToStencilBuffer() { + // Disable z-buffer writes (note: z-testing still occurs), and enable the + // stencil-buffer + glDepthMask(GL_FALSE); + glEnable(GL_STENCIL_TEST); + glEnable(GL_CULL_FACE); + + // Set up stencil compare fuction, reference value, and masks. + // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true. + // Note: since we set up the stencil-test to always pass, the STENCILFAIL + // renderstate is really not needed. + glStencilFunc(GL_ALWAYS, 0x1, 0xFFFFFFFF); + + glShadeModel(GL_FLAT); + glDisable(GL_LIGHTING); + + // Make sure that no pixels get drawn to the frame buffer + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_ONE); + + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + + // Draw back-side of shadow volume in stencil/z only + glCullFace(GL_FRONT); + render(); + + // // Decrement stencil buffer value + glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); + + // Draw front-side of shadow volume in stencil/z only + glCullFace(GL_BACK); + render(); + + // // Restore render states + glEnable(GL_LIGHTING); + glFrontFace(GL_CCW); + glShadeModel(GL_SMOOTH); + glDepthMask(GL_TRUE); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGL::renderToScene() { + initMask(); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_STENCIL_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Only write where stencil val >= 1 (count indicates # of shadows that overlap that pixel) + glStencilFunc(GL_LEQUAL, 0x1, 0xFFFFFFFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + glDisable(GL_FOG); + glDisable(GL_LIGHTING); + glDisable(GL_ALPHA_TEST); + + _gameRef->_renderer3D->setProjection2D(); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + // Draw a big, gray square + glInterleavedArrays(GL_C4UB_V3F, 0, _shadowMask); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Restore render states + glEnable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + + _gameRef->_renderer3D->setup3D(nullptr, true); + + // clear stencil buffer + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGL::initMask() { + Rect32 viewport = _gameRef->_renderer->getViewPort(); + + _shadowMask[0].x = viewport.left; + _shadowMask[0].y = viewport.bottom; + _shadowMask[0].z = 0.0f; + + _shadowMask[1].x = viewport.left; + _shadowMask[1].y = viewport.top; + _shadowMask[1].z = 0.0f; + + _shadowMask[2].x = viewport.right; + _shadowMask[2].y = viewport.bottom; + _shadowMask[2].z = 0.0f; + + _shadowMask[3].x = viewport.right; + _shadowMask[3].y = viewport.top; + _shadowMask[3].z = 0.0f; + + byte a = RGBCOLGetA(_color); + byte r = RGBCOLGetR(_color); + byte g = RGBCOLGetG(_color); + byte b = RGBCOLGetB(_color); + + for (int i = 0; i < 4; ++i) { + _shadowMask[i].r = r; + _shadowMask[i].g = g; + _shadowMask[i].b = b; + _shadowMask[i].a = a; + } + + return true; +} + +} // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.h b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.h new file mode 100644 index 00000000000..0fcae9c8513 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl.h @@ -0,0 +1,52 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME. + * http://dead-code.org/redir.php?target=wme + * Copyright (c) 2003-2013 Jan Nedoma and contributors + */ + +#ifndef WINTERMUTE_SHADOW_VOLUME_OPENGL_H +#define WINTERMUTE_SHADOW_VOLUME_OPENGL_H + +#include "engines/wintermute/base/gfx/opengl/shadow_volume.h" + +namespace Wintermute { + +class ShadowVolumeOpenGL : public ShadowVolume { +public: + ShadowVolumeOpenGL(BaseGame *inGame); + virtual ~ShadowVolumeOpenGL(); + + bool renderToStencilBuffer() override; + bool renderToScene() override; + +private: + bool render(); + ShadowVertex _shadowMask[4]; + bool initMask(); +}; + +} // namespace Wintermute + +#endif diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.cpp b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.cpp new file mode 100644 index 00000000000..3894b504ed1 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.cpp @@ -0,0 +1,205 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME. + * http://dead-code.org/redir.php?target=wme + * Copyright (c) 2003-2013 Jan Nedoma and contributors + */ + +#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/gfx/opengl/base_render_opengl3d.h" +#include "engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h" +#include "engines/wintermute/dcgf.h" +#include "graphics/opengl/system_headers.h" + +namespace Wintermute { + +#include "common/pack-start.h" + +struct ShadowVertexShader { + float x; + float y; +} PACKED_STRUCT; + +#include "common/pack-end.h" + +////////////////////////////////////////////////////////////////////////// +ShadowVolumeOpenGLShader::ShadowVolumeOpenGLShader(BaseGame *inGame, OpenGL::Shader *volumeShader, OpenGL::Shader *maskShader) + : ShadowVolume(inGame), _color(0x7f000000), _volumeShader(volumeShader), _maskShader(maskShader) { + ShadowVertexShader shadowMask[4]; + Rect32 viewport = _gameRef->_renderer->getViewPort(); + + shadowMask[0].x = viewport.left; + shadowMask[0].y = viewport.bottom; + + shadowMask[1].x = viewport.left; + shadowMask[1].y = viewport.top; + + shadowMask[2].x = viewport.right; + shadowMask[2].y = viewport.bottom; + + shadowMask[3].x = viewport.right; + shadowMask[3].y = viewport.top; + + glGenBuffers(1, &_shadowMaskVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _shadowMaskVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(ShadowVertexShader), shadowMask, GL_DYNAMIC_DRAW); +} + +////////////////////////////////////////////////////////////////////////// +ShadowVolumeOpenGLShader::~ShadowVolumeOpenGLShader() { +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGLShader::render() { + glBindTexture(GL_TEXTURE_2D, 0); + glDrawArrays(GL_TRIANGLES, 0, _vertices.size()); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGLShader::renderToStencilBuffer() { + // since the vertex count of the volume might change, + // we just create a new buffer per frame + // we might as well use the number of vertices of the mesh as an upper bound + // or get rid of this completely by moving everything onto the gpu + glDeleteBuffers(1, &_shadowVolumeVertexBuffer); + glGenBuffers(1, &_shadowVolumeVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _shadowVolumeVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, 12 * _vertices.size(), _vertices.data(), GL_STATIC_DRAW); + + _volumeShader->enableVertexAttribute("position", _shadowVolumeVertexBuffer, 3, GL_FLOAT, false, 12, 0); + _volumeShader->use(true); + + // Disable z-buffer writes (note: z-testing still occurs), and enable the + // stencil-buffer + glDepthMask(GL_FALSE); + glEnable(GL_STENCIL_TEST); + glEnable(GL_CULL_FACE); + + // Set up stencil compare fuction, reference value, and masks. + // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true. + // Note: since we set up the stencil-test to always pass, the STENCILFAIL + // renderstate is really not needed. + glStencilFunc(GL_ALWAYS, 0x1, 0xFFFFFFFF); + + glShadeModel(GL_FLAT); + + // Make sure that no pixels get drawn to the frame buffer + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_ONE); + + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + + // Draw back-side of shadow volume in stencil/z only + glCullFace(GL_FRONT); + render(); + + // // Decrement stencil buffer value + glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); + + // Draw front-side of shadow volume in stencil/z only + glCullFace(GL_BACK); + render(); + + // // Restore render states + glFrontFace(GL_CCW); + glShadeModel(GL_SMOOTH); + glDepthMask(GL_TRUE); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGLShader::renderToScene() { + initMask(); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_STENCIL_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Only write where stencil val >= 1 (count indicates # of shadows that overlap that pixel) + glStencilFunc(GL_LEQUAL, 0x1, 0xFFFFFFFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + _gameRef->_renderer3D->setProjection2D(); + + glBindTexture(GL_TEXTURE_2D, 0); + + _maskShader->enableVertexAttribute("position", _shadowMaskVertexBuffer, 2, GL_FLOAT, false, 8, 0); + _maskShader->use(true); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Restore render states + glEnable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + + _gameRef->_renderer3D->setup3D(nullptr, true); + + // clear stencil buffer + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +bool ShadowVolumeOpenGLShader::initMask() { + Rect32 viewport = _gameRef->_renderer->getViewPort(); + + ShadowVertexShader shadowMask[4]; + + shadowMask[0].x = viewport.left; + shadowMask[0].y = viewport.bottom; + + shadowMask[1].x = viewport.left; + shadowMask[1].y = viewport.top; + + shadowMask[2].x = viewport.right; + shadowMask[2].y = viewport.bottom; + + shadowMask[3].x = viewport.right; + shadowMask[3].y = viewport.top; + + glBindBuffer(GL_ARRAY_BUFFER, _shadowMaskVertexBuffer); + glBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(ShadowVertexShader), shadowMask); + + Math::Vector4d color; + + color.x() = RGBCOLGetR(_color) / 255.0f; + color.y() = RGBCOLGetG(_color) / 255.0f; + color.z() = RGBCOLGetB(_color) / 255.0f; + color.w() = RGBCOLGetA(_color) / 255.0f; + + _maskShader->use(); + _maskShader->setUniform("color", color); + + return true; +} + +} // namespace Wintermute diff --git a/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h new file mode 100644 index 00000000000..a850ca32de2 --- /dev/null +++ b/engines/wintermute/base/gfx/opengl/shadow_volume_opengl_shader.h @@ -0,0 +1,58 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME. + * http://dead-code.org/redir.php?target=wme + * Copyright (c) 2003-2013 Jan Nedoma and contributors + */ + +#ifndef WINTERMUTE_SHADOW_VOLUME_OPENGL_SHADER_H +#define WINTERMUTE_SHADOW_VOLUME_OPENGL_SHADER_H + +#include "engines/wintermute/base/gfx/opengl/shadow_volume.h" +#include "graphics/opengl/shader.h" +#include "graphics/opengl/system_headers.h" + +namespace Wintermute { + +class ShadowVolumeOpenGLShader : public ShadowVolume { +public: + ShadowVolumeOpenGLShader(BaseGame *inGame, OpenGL::Shader *volumeShader, OpenGL::Shader *maskShader); + virtual ~ShadowVolumeOpenGLShader(); + + bool renderToStencilBuffer() override; + bool renderToScene() override; + +private: + bool render(); + uint32 _color; + bool initMask() override; + GLuint _shadowVolumeVertexBuffer; + GLuint _shadowMaskVertexBuffer; + OpenGL::Shader *_volumeShader; + OpenGL::Shader *_maskShader; +}; + +} // namespace Wintermute + +#endif diff --git a/engines/wintermute/module.mk b/engines/wintermute/module.mk index 2efa93f67df..2e0cdefd5f9 100644 --- a/engines/wintermute/module.mk +++ b/engines/wintermute/module.mk @@ -87,6 +87,8 @@ MODULE_OBJS := \ base/gfx/opengl/mesh3ds_opengl_shader.o \ base/gfx/opengl/loader3ds.o \ base/gfx/opengl/shadow_volume.o \ + base/gfx/opengl/shadow_volume_opengl.o \ + base/gfx/opengl/shadow_volume_opengl_shader.o \ base/gfx/x/active_animation.o \ base/gfx/x/animation.o \ base/gfx/x/animation_channel.o \