From cd2b5a0e3995652ce95d2ffb852876b9eeb6396f Mon Sep 17 00:00:00 2001 From: Armin Burgmeier Date: Sun, 27 Nov 2016 22:06:10 -0800 Subject: [PATCH] Add a directional shader for light rendering This allows to render light as a cone in a certain direction with an inner circle that is illuminating all directions. It is basically implemented as a mask when rendering into the light texture. A separate fragment shader for the first of the three render passes is used to set the light intensity based on where the fragment is relative to the light source. --- src/landscape/fow/C4FoW.cpp | 103 +++++++++++++++++++++++- src/landscape/fow/C4FoW.h | 8 +- src/landscape/fow/C4FoWDrawStrategy.cpp | 63 +++++++++------ src/landscape/fow/C4FoWDrawStrategy.h | 6 +- src/landscape/fow/C4FoWLight.cpp | 4 +- src/landscape/fow/C4FoWLight.h | 2 +- 6 files changed, 150 insertions(+), 36 deletions(-) diff --git a/src/landscape/fow/C4FoW.cpp b/src/landscape/fow/C4FoW.cpp index 587b13785..f20feda6f 100644 --- a/src/landscape/fow/C4FoW.cpp +++ b/src/landscape/fow/C4FoW.cpp @@ -146,6 +146,7 @@ C4Shader *C4FoW::GetRenderShader() const char* szUniforms[C4FoWRSU_Count + 1]; szUniforms[C4FoWRSU_ProjectionMatrix] = "projectionMatrix"; szUniforms[C4FoWRSU_VertexOffset] = "vertexOffset"; + szUniforms[C4FoWRSU_LightSourcePosition] = "lightSourcePosition"; szUniforms[C4FoWRSU_Count] = nullptr; const char* szAttributes[C4FoWRSA_Count + 1]; @@ -165,6 +166,101 @@ C4Shader *C4FoW::GetRenderShader() #endif } +C4Shader *C4FoW::GetDirectionalRenderShader() +{ +#ifndef USE_CONSOLE + // Not created yet? + if (!DirectionalRenderShader.Initialised()) + { + // Create the directional render shader. Similar to the + // normal render shader except discards pixels not in the + // direction of the light (w/ fade-out). + const char* DirectionalRenderVertexShader = + "in vec2 oc_Position;\n" + "in vec4 oc_Color;\n" + "out vec4 vtxColor;\n" + "out vec2 vtxLightDir;\n" + "uniform mat4 projectionMatrix;\n" + "uniform vec2 lightSourcePosition;\n" + "\n" + "slice(position)\n" + "{\n" + " gl_Position = projectionMatrix * vec4(oc_Position, 0.0, 1.0);\n" + "}\n" + "\n" + "slice(color)\n" + "{\n" + " vtxColor = oc_Color;\n" + " vtxLightDir = (oc_Position - lightSourcePosition);\n" + "}"; + + const char* DirectionalRenderFragmentShader = + "in vec4 vtxColor;\n" + "in vec2 vtxLightDir;\n" + "out vec4 fragColor;\n" + "\n" + // TODO: these are going to be uniforms: + "const vec2 lightDirection = vec2(1.0, 0.0);\n" + "const float PI = 3.141592653589793238462643383;\n" + "const float lightAngularRangeCos = cos(20.0 * PI / 180.0);\n" // 50 deg + "const float lightAngularFadeCos = cos((20.0 + 30.0) * PI / 180.0);\n" // 80 deg + "\n" + "const float lightAngularDistance = 5.0;\n" + "const float lightAngularDistanceFade = 5.0 + 15.0;\n" + "\n" + "slice(color)\n" + "{\n" + " float lightLen = sqrt(vtxLightDir.x * vtxLightDir.x + vtxLightDir.y * vtxLightDir.y);\n" + " float angDiffCos = dot(lightDirection, vtxLightDir) / lightLen;\n" + "\n" + " float angOneMinusIntensity;\n" + " if (angDiffCos <= lightAngularFadeCos)\n" + " angOneMinusIntensity = 1.0;\n" + //" float dist = min(1.0, 0.02 * sqrt(vtxLightDir.x * vtxLightDir.x + vtxLightDir.y * vtxLightDir.y));\n" + //" fragColor = vec4(0.0, 0.5/1.5, 0.5/1.5, 1.0);\n" + //" fragColor = vtxColor;\n" + " else if (angDiffCos <= lightAngularRangeCos)\n" + // TODO: this uses linear interpolation on the cosine values, which is not linear in the angle. Might need to use acos() here if things look funny. + " angOneMinusIntensity = (lightAngularRangeCos - angDiffCos) / (lightAngularRangeCos - lightAngularFadeCos);\n" + " else\n" + " angOneMinusIntensity = 0.0;\n" + "\n" + " float distOneMinusIntensity;\n" + " if (lightLen >= lightAngularDistanceFade)\n" + " distOneMinusIntensity = 1.0;\n" + " else if (lightLen >= lightAngularDistance)\n" + " distOneMinusIntensity = (lightLen - lightAngularDistance) / (lightAngularDistanceFade - lightAngularDistance);\n" + " else\n" + " distOneMinusIntensity = 0.0;\n" + "\n" + " fragColor = vec4(vtxColor.rgb, (1.0 - distOneMinusIntensity*angOneMinusIntensity) * vtxColor.a);\n" + "}"; + + DirectionalRenderShader.AddVertexSlices("built-in FoW directional render shader", DirectionalRenderVertexShader); + DirectionalRenderShader.AddFragmentSlices("built-in FoW directional render shader", DirectionalRenderFragmentShader); + + const char* szUniforms[C4FoWRSU_Count + 1]; + szUniforms[C4FoWRSU_ProjectionMatrix] = "projectionMatrix"; + szUniforms[C4FoWRSU_VertexOffset] = "vertexOffset"; + szUniforms[C4FoWRSU_LightSourcePosition] = "lightSourcePosition"; + szUniforms[C4FoWRSU_Count] = nullptr; + + const char* szAttributes[C4FoWRSA_Count + 1]; + szAttributes[C4FoWRSA_Position] = "oc_Position"; + szAttributes[C4FoWRSA_Color] = "oc_Color"; + szAttributes[C4FoWRSA_Count] = nullptr; + + if (!DirectionalRenderShader.Init("fowDirectionalRender", szUniforms, szAttributes)) { + DirectionalRenderShader.ClearSlices(); + return nullptr; + } + + } + return &DirectionalRenderShader; +#else + return nullptr; +#endif +} void C4FoW::Add(C4Object *pObj) { @@ -249,9 +345,12 @@ void C4FoW::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen, C4Playe assert(pShader); if (!pShader) return; + C4Shader *pDirectionalShader = GetDirectionalRenderShader(); + assert(pDirectionalShader); + if (!pDirectionalShader) return; + for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext()) if (pLight->IsVisibleForPlayer(pPlr)) - pLight->Render(pRegion, pOnScreen, projectionMatrix, *pShader); - + pLight->Render(pRegion, pOnScreen, projectionMatrix, *pShader, *pDirectionalShader); #endif } diff --git a/src/landscape/fow/C4FoW.h b/src/landscape/fow/C4FoW.h index 9e874d189..339cf7c15 100644 --- a/src/landscape/fow/C4FoW.h +++ b/src/landscape/fow/C4FoW.h @@ -81,8 +81,9 @@ enum C4FoWFramebufShaderAttributes { }; enum C4FoWRenderShaderUniforms { - C4FoWRSU_ProjectionMatrix, // projection matrix - C4FoWRSU_VertexOffset, // offset applied to vertex (TODO: could be encoded in projection matrix) + C4FoWRSU_ProjectionMatrix, // projection matrix + C4FoWRSU_VertexOffset, // offset applied to vertex (TODO: could be encoded in projection matrix) + C4FoWRSU_LightSourcePosition, // position of the light source (in same coordinates as C4FoWRSA_Position passed into the shader) C4FoWRSU_Count }; @@ -118,6 +119,8 @@ public: C4Shader *GetFramebufShader(); // Shader to use for rendering the lights C4Shader *GetRenderShader(); + // Shader to use for rendering the lights + C4Shader *GetDirectionalRenderShader(); void ClearDeletedLights(); @@ -138,6 +141,7 @@ private: // Shader for updating the frame buffer C4Shader FramebufShader; C4Shader RenderShader; + C4Shader DirectionalRenderShader; #endif }; diff --git a/src/landscape/fow/C4FoWDrawStrategy.cpp b/src/landscape/fow/C4FoWDrawStrategy.cpp index 6a1d0a407..13fa43efd 100644 --- a/src/landscape/fow/C4FoWDrawStrategy.cpp +++ b/src/landscape/fow/C4FoWDrawStrategy.cpp @@ -139,15 +139,21 @@ void C4FoWDrawLightTextureStrategy::Begin(const C4FoWRegion* regionPar) } } -void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader) +void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader, const C4Shader& directionalRenderShader) { - C4ShaderCall call(&renderShader); - call.Start(); - call.SetUniformMatrix4x4(C4FoWRSU_ProjectionMatrix, projectionMatrix); - // If we have nothing to draw (e.g. directly after initialization), abort early. if (vertices.empty()) return; + C4ShaderCall directionalRenderCall(&directionalRenderShader); + directionalRenderCall.Start(); + directionalRenderCall.SetUniformMatrix4x4(C4FoWRSU_ProjectionMatrix, projectionMatrix); + + float y_offset[] = { 0.0f, 0.0f }; + directionalRenderCall.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset); + + const float light_source_position[2] = { light->getX() - region->getRegion().x, light->getY() - region->getRegion().y }; + directionalRenderCall.SetUniform2fv(C4FoWRSU_LightSourcePosition, 1, light_source_position); + const GLuint vbo = bo[0]; const GLuint ibo = bo[1]; @@ -181,10 +187,6 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat const float width = region->getSurfaceWidth(); const float height = region->getSurfaceHeight() / 2.0; - // Set Y offset for vertex - float y_offset[] = { 0.0f, 0.0f }; - call.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset); - // Enable scissor test to only draw in upper or lower half of texture glEnable(GL_SCISSOR_TEST); glScissor(0, height, width, height); @@ -197,10 +199,10 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat { glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position)); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color)); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r1))); + glEnableVertexAttribArray(directionalRenderCall.GetAttribute(C4FoWRSA_Position)); + glEnableVertexAttribArray(directionalRenderCall.GetAttribute(C4FoWRSA_Color)); + glVertexAttribPointer(directionalRenderCall.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); + glVertexAttribPointer(directionalRenderCall.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r1))); } // Set up blend equation, see C4FoWDrawLightTextureStrategy::DrawVertex @@ -211,7 +213,14 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat // Render 1st pass glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, 0); + directionalRenderCall.Finish(); + // Prepare state for 2nd pass + C4ShaderCall renderCall(&renderShader); + renderCall.Start(); + renderCall.SetUniformMatrix4x4(C4FoWRSU_ProjectionMatrix, projectionMatrix); + renderCall.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset); + //glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); @@ -221,10 +230,10 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat { glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position)); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color)); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r2))); + glEnableVertexAttribArray(renderCall.GetAttribute(C4FoWRSA_Position)); + glEnableVertexAttribArray(renderCall.GetAttribute(C4FoWRSA_Color)); + glVertexAttribPointer(renderCall.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); + glVertexAttribPointer(renderCall.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r2))); } // Render 2nd pass @@ -235,7 +244,7 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat glBlendEquation(GL_FUNC_ADD); glScissor(0, 0, width, height); y_offset[1] = height; - call.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset); + renderCall.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset); const bool has_vao3 = pGL->GetVAO(vaoids[2], vao3); glBindVertexArray(vao3); @@ -243,12 +252,12 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat { glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position)); - glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color)); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); - glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r3))); + glEnableVertexAttribArray(renderCall.GetAttribute(C4FoWRSA_Position)); + glEnableVertexAttribArray(renderCall.GetAttribute(C4FoWRSA_Color)); + glVertexAttribPointer(renderCall.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, x))); + glVertexAttribPointer(renderCall.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, r3))); } - + // Render 3rd pass glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, 0); @@ -258,7 +267,9 @@ void C4FoWDrawLightTextureStrategy::End(const StdProjectionMatrix& projectionMat // Assume the capacity stays the same: vertices.resize(0); - C4FoWDrawStrategy::End(projectionMatrix, renderShader); + C4FoWDrawStrategy::End(projectionMatrix, renderShader, directionalRenderShader); + + renderCall.Finish(); } void C4FoWDrawLightTextureStrategy::DrawVertex(float x, float y, bool shadow) @@ -363,7 +374,7 @@ void C4FoWDrawWireframeStrategy::Begin(const C4FoWRegion* region) { } -void C4FoWDrawWireframeStrategy::End(const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader) +void C4FoWDrawWireframeStrategy::End(const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader, const C4Shader& directionalRenderShader) { C4ShaderCall call(&renderShader); call.Start(); @@ -429,7 +440,7 @@ void C4FoWDrawWireframeStrategy::End(const StdProjectionMatrix& projectionMatrix // Assume the capacity stays the same: vertices.resize(0); - C4FoWDrawStrategy::End(projectionMatrix, renderShader); + C4FoWDrawStrategy::End(projectionMatrix, renderShader, directionalRenderShader); } void C4FoWDrawWireframeStrategy::DrawVertex(Vertex& vtx) diff --git a/src/landscape/fow/C4FoWDrawStrategy.h b/src/landscape/fow/C4FoWDrawStrategy.h index 099d43e0b..cc66515b9 100644 --- a/src/landscape/fow/C4FoWDrawStrategy.h +++ b/src/landscape/fow/C4FoWDrawStrategy.h @@ -87,7 +87,7 @@ public: /* TODO: shaders should be created in drawstrategy, but: make sure * there is only one shader per class * (static? but then when to clean up?). */ - virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader) { triangulator.Reset(); } + virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader, const C4Shader& directionalRenderShader) { triangulator.Reset(); } virtual void DrawLightVertex(float x, float y) { triangulator.AddVertex(); } virtual void DrawDarkVertex(float x, float y) { triangulator.AddVertex(); } @@ -127,7 +127,7 @@ public: virtual void DrawLightVertex(float x, float y); virtual void DrawDarkVertex(float x, float y); virtual void Begin(const C4FoWRegion* region); - virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader); + virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader, const C4Shader& directionalRenderShader); private: void DrawVertex(float x, float y, bool shadeLight); @@ -164,7 +164,7 @@ public: virtual void DrawLightVertex(float x, float y); virtual void DrawDarkVertex(float x, float y); virtual void Begin(const C4FoWRegion* region); - virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader); + virtual void End(const StdProjectionMatrix&, const C4Shader& renderShader, const C4Shader& directionalRenderShader); private: struct Vertex { diff --git a/src/landscape/fow/C4FoWLight.cpp b/src/landscape/fow/C4FoWLight.cpp index fceafd496..3d7756c3a 100644 --- a/src/landscape/fow/C4FoWLight.cpp +++ b/src/landscape/fow/C4FoWLight.cpp @@ -119,7 +119,7 @@ void C4FoWLight::Update(C4Rect Rec) sections[i]->Update(Rec); } -void C4FoWLight::Render(C4FoWRegion *region, const C4TargetFacet *onScreen, const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader) +void C4FoWLight::Render(C4FoWRegion *region, const C4TargetFacet *onScreen, const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader, const C4Shader& directionalRenderShader) { TriangleList triangles; @@ -157,7 +157,7 @@ void C4FoWLight::Render(C4FoWRegion *region, const C4TargetFacet *onScreen, cons DrawFade(strategy.get(), triangles); DrawIntermediateFadeTriangles(strategy.get(), triangles); - strategy->End(projectionMatrix, renderShader); + strategy->End(projectionMatrix, renderShader, directionalRenderShader); } void C4FoWLight::CalculateFanMaxed(TriangleList &triangles) const diff --git a/src/landscape/fow/C4FoWLight.h b/src/landscape/fow/C4FoWLight.h index 25cf9eaa0..6cfb92d0f 100644 --- a/src/landscape/fow/C4FoWLight.h +++ b/src/landscape/fow/C4FoWLight.h @@ -85,7 +85,7 @@ public: /** Update all light beams within the given rectangle for this light */ void Update(C4Rect r); /** Render this light*/ - void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen, const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader); + void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen, const StdProjectionMatrix& projectionMatrix, const C4Shader& renderShader, const C4Shader& directionalRenderShader); bool IsVisibleForPlayer(C4Player *player) const; // check if attached to an object that is not hostile to the given player