From 09f54b09409ddb65debc0a186eed06d1cca40252 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 27 Apr 2016 00:41:00 +0200 Subject: [PATCH] - cleaned up the clip plane management for portals. Unfortunately the math behind the old clip planes is utterly impenetrable and so poorly documented that I have no idea how to set that up, so it is deactivated for now. It wasn't working anyway. --- src/gl/data/gl_matrix.cpp | 12 ++++------ src/gl/data/gl_matrix.h | 5 ++++- src/gl/renderer/gl_renderstate.cpp | 33 +++++++++++++++++++++++----- src/gl/renderer/gl_renderstate.h | 20 +++++------------ src/gl/scene/gl_flats.cpp | 2 +- src/gl/scene/gl_portal.cpp | 34 +++++++---------------------- src/gl/shaders/gl_shader.cpp | 4 ++-- src/gl/shaders/gl_shader.h | 4 ++-- wadsrc/static/glstuff/gllight.png | Bin 0 -> 13073 bytes 9 files changed, 55 insertions(+), 59 deletions(-) create mode 100644 wadsrc/static/glstuff/gllight.png diff --git a/src/gl/data/gl_matrix.cpp b/src/gl/data/gl_matrix.cpp index 35683dbb8..a16dc770f 100644 --- a/src/gl/data/gl_matrix.cpp +++ b/src/gl/data/gl_matrix.cpp @@ -10,6 +10,7 @@ This is a simplified version of VSMatrix that has been adjusted for GZDoom's nee ----------------------------------------------------*/ +#include #include "gl/system/gl_system.h" #include #include @@ -139,14 +140,9 @@ VSMatrix::translate(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z) void VSMatrix::scale(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z) { - FLOATTYPE mat[16]; - - setIdentityMatrix(mat,4); - mat[0] = x; - mat[5] = y; - mat[10] = z; - - multMatrix(mat); + mMatrix[0] *= x; mMatrix[1] *= x; mMatrix[2] *= x; mMatrix[3] *= x; + mMatrix[4] *= y; mMatrix[5] *= y; mMatrix[6] *= y; mMatrix[7] *= y; + mMatrix[8] *= z; mMatrix[9] *= z; mMatrix[10] *= z; mMatrix[11] *= z; } diff --git a/src/gl/data/gl_matrix.h b/src/gl/data/gl_matrix.h index de4b400da..3ec1f5ff4 100644 --- a/src/gl/data/gl_matrix.h +++ b/src/gl/data/gl_matrix.h @@ -48,6 +48,7 @@ class VSMatrix { #ifdef USE_DOUBLE void multMatrix(const float *aMatrix); #endif + void multVector(FLOATTYPE *aVector); void multMatrix(const FLOATTYPE *aMatrix); void multMatrix(const VSMatrix &aMatrix) { @@ -92,7 +93,9 @@ class VSMatrix { { computeNormalMatrix(aMatrix.mMatrix); } - + bool inverseMatrix(VSMatrix &result); + void transpose(); + protected: static void crossProduct(const FLOATTYPE *a, const FLOATTYPE *b, FLOATTYPE *res); static FLOATTYPE dotProduct(const FLOATTYPE *a, const FLOATTYPE * b); diff --git a/src/gl/renderer/gl_renderstate.cpp b/src/gl/renderer/gl_renderstate.cpp index d1e30c560..fdb0e3cc5 100644 --- a/src/gl/renderer/gl_renderstate.cpp +++ b/src/gl/renderer/gl_renderstate.cpp @@ -86,8 +86,8 @@ void FRenderState::Reset() mColormapState = CM_DEFAULT; mLightParms[3] = -1.f; mSpecialEffect = EFF_NONE; - mClipHeightTop = 65536.f; - mClipHeightBottom = -65536.f; + mClipHeight = 0.f; + mClipHeightDirection = 0.f; ClearClipSplit(); stSrcBlend = stDstBlend = -1; @@ -140,8 +140,8 @@ bool FRenderState::ApplyShader() activeShader->muObjectColor.Set(mObjectColor); activeShader->muDynLightColor.Set(mDynColor.vec); activeShader->muInterpolationFactor.Set(mInterpolationFactor); - activeShader->muClipHeightTop.Set(mClipHeightTop); - activeShader->muClipHeightBottom.Set(mClipHeightBottom); + activeShader->muClipHeight.Set(mClipHeight); + activeShader->muClipHeightDirection.Set(mClipHeightDirection); activeShader->muTimer.Set(gl_frameMS * mShaderTimer / 1000.f); activeShader->muAlphaThreshold.Set(mAlphaThreshold); activeShader->muLightIndex.Set(mLightIndex); // will always be -1 for now @@ -324,4 +324,27 @@ void FRenderState::ApplyLightIndex(int index) } activeShader->muLightIndex.Set(index); } -} \ No newline at end of file +} + +void FRenderState::SetClipHeight(float height, float direction) +{ + mClipHeight = height; + mClipHeightDirection = direction; + if (direction != 0.f) + { + if (gl.glslversion >= 1.3f) glEnable(GL_CLIP_DISTANCE0); + else + { + // This does not work. Need someone who understands how glClipPlane works... + //glEnable(GL_CLIP_PLANE0); + // Plane mirrors never are slopes. + //double d[4] = { 0, direction, 0, -direction * height }; + //glClipPlane(GL_CLIP_PLANE0, d); + } + } + else + { + if (gl.glslversion >= 1.3f) glDisable(GL_CLIP_DISTANCE0); + //else glDisable(GL_CLIP_PLANE0); + } +} diff --git a/src/gl/renderer/gl_renderstate.h b/src/gl/renderer/gl_renderstate.h index e61ab004d..ca240c398 100644 --- a/src/gl/renderer/gl_renderstate.h +++ b/src/gl/renderer/gl_renderstate.h @@ -63,7 +63,7 @@ class FRenderState bool mModelMatrixEnabled; bool mTextureMatrixEnabled; float mInterpolationFactor; - float mClipHeightTop, mClipHeightBottom; + float mClipHeight, mClipHeightDirection; float mShaderTimer; bool mLastDepthClamp; @@ -135,25 +135,17 @@ public: mCurrentVertexBuffer = NULL; } - void SetClipHeightTop(float clip) + float GetClipHeight() { - mClipHeightTop = clip; + return mClipHeight; } - float GetClipHeightTop() + float GetClipHeightDirection() { - return mClipHeightTop; + return mClipHeightDirection; } - void SetClipHeightBottom(float clip) - { - mClipHeightBottom = clip; - } - - float GetClipHeightBottom() - { - return mClipHeightBottom; - } + void SetClipHeight(float height, float direction); void SetColor(float r, float g, float b, float a = 1.f, int desat = 0) { diff --git a/src/gl/scene/gl_flats.cpp b/src/gl/scene/gl_flats.cpp index c90e9541b..7406bb20e 100644 --- a/src/gl/scene/gl_flats.cpp +++ b/src/gl/scene/gl_flats.cpp @@ -438,7 +438,7 @@ void GLFlat::Draw(int pass, bool trans) // trans only has meaning for GLPASS_LIG { gl_RenderState.SetMaterial(gltexture, CLAMP_NONE, 0, -1, false); gl_SetPlaneTextureRotation(&plane, gltexture); - DrawSubsectors(pass, true, true); + DrawSubsectors(pass, gl.lightmethod != LM_SOFTWARE, true); gl_RenderState.EnableTextureMatrix(false); } if (renderstyle==STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index 6c2a05592..93f25f28e 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -300,11 +300,9 @@ bool GLPortal::Start(bool usestencil, bool doquery) glDisable(GL_DEPTH_TEST); } } - planestack.Push(gl_RenderState.GetClipHeightTop()); - planestack.Push(gl_RenderState.GetClipHeightBottom()); - glDisable(GL_CLIP_DISTANCE0); - gl_RenderState.SetClipHeightBottom(-65536.f); - gl_RenderState.SetClipHeightTop(65536.f); + planestack.Push(gl_RenderState.GetClipHeight()); + planestack.Push(gl_RenderState.GetClipHeightDirection()); + gl_RenderState.SetClipHeight(0., 0.); // save viewpoint savedViewPos = ViewPos; @@ -363,13 +361,10 @@ void GLPortal::End(bool usestencil) PortalAll.Clock(); GLRenderer->mCurrentPortal = NextPortal; - float f; + float f, d; + planestack.Pop(d); planestack.Pop(f); - gl_RenderState.SetClipHeightBottom(f); - if (f > -65535.f) glEnable(GL_CLIP_DISTANCE0); - planestack.Pop(f); - gl_RenderState.SetClipHeightTop(f); - if (f < 65535.f) glEnable(GL_CLIP_DISTANCE0); + gl_RenderState.SetClipHeight(f, d); if (usestencil) { @@ -804,22 +799,9 @@ void GLPlaneMirrorPortal::DrawContents() GLRenderer->SetupView(ViewPos.X, ViewPos.Y, ViewPos.Z, ViewAngle, !!(MirrorFlag&1), !!(PlaneMirrorFlag&1)); ClearClipper(); - if (PlaneMirrorMode < 0) - { - gl_RenderState.SetClipHeightTop(planez); // ceiling mirror: clip everything with a z lower than the portal's ceiling - gl_RenderState.SetClipHeightBottom(-65536.f); - } - else - { - gl_RenderState.SetClipHeightBottom(planez); // floor mirror: clip everything with a z higher than the portal's floor - gl_RenderState.SetClipHeightTop(65536.f); - } - - glEnable(GL_CLIP_DISTANCE0); + gl_RenderState.SetClipHeight(planez, PlaneMirrorMode < 0? -1.f : 1.f); GLRenderer->DrawScene(); - glDisable(GL_CLIP_DISTANCE0); - gl_RenderState.SetClipHeightBottom(-65536.f); - gl_RenderState.SetClipHeightTop(65536.f); + gl_RenderState.SetClipHeight(0.f, 0.f); PlaneMirrorFlag--; PlaneMirrorMode=old_pm; } diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index af6accdb5..a86c57101 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -280,8 +280,8 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * muSplitTopPlane.Init(hShader, "uSplitTopPlane"); muFixedColormap.Init(hShader, "uFixedColormap"); muInterpolationFactor.Init(hShader, "uInterpolationFactor"); - muClipHeightTop.Init(hShader, "uClipHeightTop"); - muClipHeightBottom.Init(hShader, "uClipHeightBottom"); + muClipHeight.Init(hShader, "uClipHeight"); + muClipHeightDirection.Init(hShader, "uClipHeightDirection"); muAlphaThreshold.Init(hShader, "uAlphaThreshold"); muTimer.Init(hShader, "timer"); diff --git a/src/gl/shaders/gl_shader.h b/src/gl/shaders/gl_shader.h index cf6480d9b..a7f7e4277 100644 --- a/src/gl/shaders/gl_shader.h +++ b/src/gl/shaders/gl_shader.h @@ -221,8 +221,8 @@ class FShader FUniform4f muSplitBottomPlane; FUniform4f muSplitTopPlane; FBufferedUniform1f muInterpolationFactor; - FBufferedUniform1f muClipHeightTop; - FBufferedUniform1f muClipHeightBottom; + FBufferedUniform1f muClipHeight; + FBufferedUniform1f muClipHeightDirection; FBufferedUniform1f muAlphaThreshold; FBufferedUniform1f muTimer; diff --git a/wadsrc/static/glstuff/gllight.png b/wadsrc/static/glstuff/gllight.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a296b9f856f4654aae244e3400a4f33fd95902 GIT binary patch literal 13073 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 b^8eXyONlr0HB~$X?RD{V^>bP0l+XkK3f!mG literal 0 HcmV?d00001