Index: ps/trunk/source/graphics/Camera.cpp =================================================================== --- ps/trunk/source/graphics/Camera.cpp (revision 25354) +++ ps/trunk/source/graphics/Camera.cpp (revision 25355) @@ -1,394 +1,433 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #include "precompiled.h" #include "Camera.h" #include "graphics/HFTracer.h" #include "graphics/Terrain.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" CCamera::CCamera() { // Set viewport to something anything should handle, but should be initialised // to window size before use. m_ViewPort.m_X = 0; m_ViewPort.m_Y = 0; m_ViewPort.m_Width = 800; m_ViewPort.m_Height = 600; } CCamera::~CCamera() = default; void CCamera::SetProjection(const CMatrix3D& matrix) { m_ProjType = ProjectionType::CUSTOM; m_ProjMat = matrix; } void CCamera::SetProjectionFromCamera(const CCamera& camera) { m_ProjType = camera.m_ProjType; m_NearPlane = camera.m_NearPlane; m_FarPlane = camera.m_FarPlane; if (m_ProjType == ProjectionType::PERSPECTIVE) { m_FOV = camera.m_FOV; } else if (m_ProjType == ProjectionType::ORTHO) { m_OrthoScale = camera.m_OrthoScale; } m_ProjMat = camera.m_ProjMat; } void CCamera::SetOrthoProjection(float nearp, float farp, float scale) { m_ProjType = ProjectionType::ORTHO; m_NearPlane = nearp; m_FarPlane = farp; m_OrthoScale = scale; const float halfHeight = 0.5f * m_OrthoScale; const float halfWidth = halfHeight * GetAspectRatio(); m_ProjMat.SetOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, m_NearPlane, m_FarPlane); } void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov) { m_ProjType = ProjectionType::PERSPECTIVE; m_NearPlane = nearp; m_FarPlane = farp; m_FOV = fov; m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane); } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor) { CMatrix3D MatFinal; CMatrix3D MatView; m_Orientation.GetInverse(MatView); MatFinal = m_ProjMat * MatView; m_ViewFrustum.SetNumPlanes(6); // get the RIGHT plane m_ViewFrustum[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; m_ViewFrustum[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; m_ViewFrustum[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; m_ViewFrustum[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14; // get the LEFT plane m_ViewFrustum[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11; m_ViewFrustum[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12; m_ViewFrustum[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13; m_ViewFrustum[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14; // get the BOTTOM plane m_ViewFrustum[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21; m_ViewFrustum[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22; m_ViewFrustum[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23; m_ViewFrustum[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24; // get the TOP plane m_ViewFrustum[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21; m_ViewFrustum[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22; m_ViewFrustum[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23; m_ViewFrustum[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24; // get the FAR plane m_ViewFrustum[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31; m_ViewFrustum[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32; m_ViewFrustum[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33; m_ViewFrustum[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34; // get the NEAR plane m_ViewFrustum[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31; m_ViewFrustum[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32; m_ViewFrustum[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33; m_ViewFrustum[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34; for (size_t i = 0; i < 6; ++i) m_ViewFrustum[i].Normalize(); } void CCamera::ClipFrustum(const CPlane& clipPlane) { CPlane normClipPlane = clipPlane; normClipPlane.Normalize(); m_ViewFrustum.AddPlane(normClipPlane); } void CCamera::SetViewPort(const SViewPort& viewport) { m_ViewPort.m_X = viewport.m_X; m_ViewPort.m_Y = viewport.m_Y; m_ViewPort.m_Width = viewport.m_Width; m_ViewPort.m_Height = viewport.m_Height; } float CCamera::GetAspectRatio() const { return static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height); } void CCamera::GetViewQuad(float dist, Quad& quad) const { ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO); const float y = m_ProjType == ProjectionType::PERSPECTIVE ? dist * tanf(m_FOV * 0.5f) : m_OrthoScale * 0.5f; const float x = y * GetAspectRatio(); quad[0].X = -x; quad[0].Y = -y; quad[0].Z = dist; quad[1].X = x; quad[1].Y = -y; quad[1].Z = dist; quad[2].X = x; quad[2].Y = y; quad[2].Z = dist; quad[3].X = -x; quad[3].Y = y; quad[3].Z = dist; } void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const { ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO); // Coordinates relative to the camera plane. const float dx = static_cast(px) / m_ViewPort.m_Width; const float dy = 1.0f - static_cast(py) / m_ViewPort.m_Height; Quad points; GetViewQuad(m_FarPlane, points); // Transform from camera space to world space. for (CVector3D& point : points) point = m_Orientation.Transform(point); // Get world space position of mouse point at the far clipping plane. const CVector3D basisX = points[1] - points[0]; const CVector3D basisY = points[3] - points[0]; if (m_ProjType == ProjectionType::PERSPECTIVE) { // Build direction for the camera origin to the target point. origin = m_Orientation.GetTranslation(); CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy); dir = targetPoint - origin; } else if (m_ProjType == ProjectionType::ORTHO) { origin = m_Orientation.GetTranslation() + (basisX * (dx - 0.5f)) + (basisY * (dy - 0.5f)); dir = m_Orientation.GetIn(); } dir.Normalize(); } void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const { CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse(); CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f)); x = screenspace.X / screenspace.W; y = screenspace.Y / screenspace.W; x = (x + 1) * 0.5f * m_ViewPort.m_Width; y = (1 - y) * 0.5f * m_ViewPort.m_Height; } CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const { CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; BuildCameraRay(px, py, origin, dir); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); if (!aboveWater) { if (gotTerrain) return terrainPoint; // Off the edge of the world? // Work out where it /would/ hit, if the map were extended out to infinity with average height. return GetWorldCoordinates(px, py, 50.0f); } CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = Clamp(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE); waterPoint.Z = Clamp(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const { CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h CVector3D origin, dir, delta, currentTarget; BuildCameraRay(px, py, origin, dir); if (plane.FindRayIntersection(origin, dir, ¤tTarget)) return currentTarget; // No intersection with the infinite plane - nothing sensible can be returned, // so just choose an arbitrary point on the plane return CVector3D(0.f, h, 0.f); } CVector3D CCamera::GetFocus() const { // Basically the same as GetWorldCoordinates CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; origin = m_Orientation.GetTranslation(); dir = m_Orientation.GetIn(); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = Clamp(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE); waterPoint.Z = Clamp(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } +CBoundingBoxAligned CCamera::GetBoundsInViewPort(const CBoundingBoxAligned& boundigBox) const +{ + CVector4D v1 = GetViewProjection().Transform(CVector4D(boundigBox[0].X, boundigBox[1].Y, boundigBox[0].Z, 1.0f)); + CVector4D v2 = GetViewProjection().Transform(CVector4D(boundigBox[1].X, boundigBox[1].Y, boundigBox[0].Z, 1.0f)); + CVector4D v3 = GetViewProjection().Transform(CVector4D(boundigBox[0].X, boundigBox[1].Y, boundigBox[1].Z, 1.0f)); + CVector4D v4 = GetViewProjection().Transform(CVector4D(boundigBox[1].X, boundigBox[1].Y, boundigBox[1].Z, 1.0f)); + CBoundingBoxAligned viewPortBounds; + #define ADDBOUND(v1, v2, v3, v4) \ + if (v1.Z >= -v1.W) \ + viewPortBounds += CVector3D(v1.X, v1.Y, v1.Z) * (1.0f / v1.W); \ + else \ + { \ + float t = v1.Z + v1.W; \ + if (v2.Z > -v2.W) \ + { \ + CVector4D c2 = v1 + (v2 - v1) * (t / (t - (v2.Z + v2.W))); \ + viewPortBounds += CVector3D(c2.X, c2.Y, c2.Z) * (1.0f / c2.W); \ + } \ + if (v3.Z > -v3.W) \ + { \ + CVector4D c3 = v1 + (v3 - v1) * (t / (t - (v3.Z + v3.W))); \ + viewPortBounds += CVector3D(c3.X, c3.Y, c3.Z) * (1.0f / c3.W); \ + } \ + if (v4.Z > -v4.W) \ + { \ + CVector4D c4 = v1 + (v4 - v1) * (t / (t - (v4.Z + v4.W))); \ + viewPortBounds += CVector3D(c4.X, c4.Y, c4.Z) * (1.0f / c4.W); \ + } \ + } + ADDBOUND(v1, v2, v3, v4); + ADDBOUND(v2, v1, v3, v4); + ADDBOUND(v3, v1, v2, v4); + ADDBOUND(v4, v1, v2, v3); + #undef ADDBOUND + if (viewPortBounds[0].X >= 1.0f || viewPortBounds[1].X <= -1.0f || viewPortBounds[0].Y >= 1.0f || viewPortBounds[1].Y <= -1.0f) + return CBoundingBoxAligned{}; + return viewPortBounds; +} + void CCamera::LookAt(const CVector3D& camera, const CVector3D& focus, const CVector3D& up) { CVector3D delta = focus - camera; LookAlong(camera, delta, up); } void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up) { orientation.Normalize(); up.Normalize(); CVector3D s = orientation.Cross(up); m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X; m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y; m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z; m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f; } Index: ps/trunk/source/graphics/Camera.h =================================================================== --- ps/trunk/source/graphics/Camera.h (revision 25354) +++ ps/trunk/source/graphics/Camera.h (revision 25355) @@ -1,132 +1,134 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #ifndef INCLUDED_CAMERA #define INCLUDED_CAMERA #include "maths/BoundingBoxAligned.h" #include "maths/Frustum.h" #include "maths/Matrix3D.h" #include // view port struct SViewPort { int m_X; int m_Y; int m_Width; int m_Height; }; class CCamera { public: // Represents camera viewport or frustum side in 3D space. using Quad = std::array; enum class ProjectionType { CUSTOM, ORTHO, PERSPECTIVE, }; CCamera(); ~CCamera(); CMatrix3D& GetProjection() { return m_ProjMat; } const CMatrix3D& GetProjection() const { return m_ProjMat; } CMatrix3D GetViewProjection() const { return m_ProjMat * m_Orientation.GetInverse(); } void SetProjection(const CMatrix3D& matrix); void SetProjectionFromCamera(const CCamera& camera); void SetOrthoProjection(float nearp, float farp, float scale); void SetPerspectiveProjection(float nearp, float farp, float fov); ProjectionType GetProjectionType() const { return m_ProjType; } CMatrix3D& GetOrientation() { return m_Orientation; } const CMatrix3D& GetOrientation() const { return m_Orientation; } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void UpdateFrustum(const CBoundingBoxAligned& scissor = CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, -1.0f), CVector3D(1.0f, 1.0f, 1.0f))); void ClipFrustum(const CPlane& clipPlane); const CFrustum& GetFrustum() const { return m_ViewFrustum; } void SetViewPort(const SViewPort& viewport); const SViewPort& GetViewPort() const { return m_ViewPort; } float GetAspectRatio() const; float GetNearPlane() const { return m_NearPlane; } float GetFarPlane() const { return m_FarPlane; } float GetFOV() const { return m_FOV; } float GetOrthoScale() const { return m_OrthoScale; } // Returns a quad of view in camera space at given distance from camera. void GetViewQuad(float dist, Quad& quad) const; // Builds a ray passing through the screen coordinate (px, py), calculates // origin and direction of the ray. void BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const; // General helpers that seem to fit here // Get the screen-space coordinates corresponding to a given world-space position void GetScreenCoordinates(const CVector3D& world, float& x, float& y) const; // Get the point on the terrain corresponding to pixel (px,py) (or the mouse coordinates) // The aboveWater parameter determines whether we want to stop at the water plane or also get underwater points CVector3D GetWorldCoordinates(int px, int py, bool aboveWater=false) const; // Get the point on the plane at height h corresponding to pixel (px,py) CVector3D GetWorldCoordinates(int px, int py, float h) const; // Get the point on the terrain (or water plane) the camera is pointing towards CVector3D GetFocus() const; + CBoundingBoxAligned GetBoundsInViewPort(const CBoundingBoxAligned& boundigBox) const; + // Build an orientation matrix from camera position, camera focus point, and up-vector void LookAt(const CVector3D& camera, const CVector3D& focus, const CVector3D& up); // Build an orientation matrix from camera position, camera orientation, and up-vector void LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up); public: // This is the orientation matrix. The inverse of this // is the view matrix CMatrix3D m_Orientation; private: CMatrix3D m_ProjMat; ProjectionType m_ProjType = ProjectionType::CUSTOM; float m_NearPlane = 0.0f; float m_FarPlane = 0.0f; union { float m_FOV; float m_OrthoScale; }; SViewPort m_ViewPort; CFrustum m_ViewFrustum; }; #endif // INCLUDED_CAMERA Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 25354) +++ ps/trunk/source/renderer/Renderer.cpp (revision 25355) @@ -1,1937 +1,1937 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/Decal.h" #include "graphics/FontManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/MaterialManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/DebugRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderingOptions.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SilhouetteRenderer.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include #include #include #include struct SScreenRect { GLint x1, y1, x2, y2; }; /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Material manager CMaterialManager materialManager; /// Time manager CTimeManager timeManager; /// Shadow map ShadowMap shadow; /// Postprocessing effect manager CPostprocManager postprocManager; CDebugRenderer debugRenderer; CFontManager fontManager; SilhouetteRenderer silhouetteRenderer; /// Various model renderers struct Models { // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer, // RenderModifier, etc) is mostly a relic of an older design that implemented // the different materials and rendering modes through extensive subclassing // and hooking objects together in various combinations. // The new design uses the CShaderManager API to abstract away the details // of rendering, and uses a data-driven approach to materials, so there are // now a small number of generic subclasses instead of many specialised subclasses, // but most of the old infrastructure hasn't been refactored out yet and leads to // some unwanted complexity. // Submitted models are split on two axes: // - Normal vs Transp[arent] - alpha-blended models are stored in a separate // list so we can draw them above/below the alpha-blended water plane correctly // - Skinned vs Unskinned - with hardware lighting we don't need to // duplicate mesh data per model instance (except for skinned models), // so non-skinned models get different ModelVertexRenderers ModelRendererPtr NormalSkinned; ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported ModelRendererPtr TranspSkinned; ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; } Model; CShaderDefines globalContext; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } /** * Renders all alpha-blended models with the given context. */ void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width = 0; m_Height = 0; m_TerrainRenderMode = SOLID; m_WaterRenderMode = SOLID; m_ModelRenderMode = SOLID; m_OverlayRenderMode = SOLID; m_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0; m_DisplayTerrainPriorities = false; m_SkipSubmit = false; CStr skystring = "0 0 0"; CColor skycolor; CFG_GET_VAL("skycolor", skystring); if (skycolor.ParseString(skystring, 255.f)) SetClearColor(skycolor.AsSColor4ub()); m_LightEnv = nullptr; m_CurrentScene = nullptr; m_hCompositeAlphaMap = 0; m_Stats.Reset(); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; m_Caps.m_PrettyWater = false; // now start querying extensions if (!g_RenderingOptions.GetNoVBO() && ogl_HaveExtension("GL_ARB_vertex_buffer_object")) m_Caps.m_VBO = true; if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } #if CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif #if CONFIG2_GLES m_Caps.m_PrettyWater = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", "GL_EXT_framebuffer_object", NULL)) m_Caps.m_PrettyWater = true; #endif } void CRenderer::RecomputeSystemShaderDefines() { CShaderDefines defines; if (m_Caps.m_ARBProgram) defines.Add(str_SYS_HAS_ARB, str_1); if (m_Caps.m_VertexShader && m_Caps.m_FragmentShader) defines.Add(str_SYS_HAS_GLSL, str_1); if (g_RenderingOptions.GetPreferGLSL()) defines.Add(str_SYS_PREFER_GLSL, str_1); m_SystemShaderDefines = defines; } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->globalContext = m_SystemShaderDefines; if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && g_RenderingOptions.GetARBProgramShadow()) m->globalContext.Add(str_USE_FP_SHADOW, str_1); if (g_RenderingOptions.GetShadowPCF()) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif } if (g_RenderingOptions.GetPreferGLSL() && g_RenderingOptions.GetFog()) m->globalContext.Add(str_USE_FOG, str_1); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer()); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, g_RenderingOptions.GetPreferGLSL())); if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, g_RenderingOptions.GetPreferGLSL())); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } else { m->Model.VertexGPUSkinningShader.reset(); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); } m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE("CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE("CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE("CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(g_RenderingOptions.GetRenderPath()); RecomputeSystemShaderDefines(); // Let component renderers perform one-time initialization after graphics capabilities and // the shader path have been determined. m->overlayRenderer.Initialize(); if (g_RenderingOptions.GetPostProc()) m->postprocManager.Initialize(); return true; } // resize renderer view void CRenderer::Resize(int width, int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow.RecreateTexture(); m_Width = width; m_Height = height; m->postprocManager.Resize(); m_WaterManager->Resize(); } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. return; } // Renderer has been opened, so validate the selected renderpath if (rp == RenderPath::DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_RenderingOptions.GetPreferGLSL())) rp = RenderPath::SHADER; else rp = RenderPath::FIXED; } if (rp == RenderPath::SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_RenderingOptions.GetPreferGLSL()))) { LOGWARNING("Falling back to fixed function\n"); rp = RenderPath::FIXED; } } // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere. g_RenderingOptions.m_RenderPath = rp; MakeShadersDirty(); RecomputeSystemShaderDefines(); // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m->ShadersDirty) ReloadShaders(); m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader->SetLightEnv(m_LightEnv); } ////////////////////////////////////////////////////////////////////////////////////////// void CRenderer::SetSimulation(CSimulation2* simulation) { // set current simulation context for terrain renderer m->terrainRenderer.SetSimulation(simulation); } // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap(const CShaderDefines& context) { PROFILE3_GPU("shadow map"); m->shadow.BeginRender(); { PROFILE("render patches"); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); m->terrainRenderer.RenderPatches(CULL_SHADOWS); glCullFace(GL_BACK); } CShaderDefines contextCast = context; contextCast.Add(str_MODE_SHADOWCAST, str_1); { PROFILE("render models"); m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } m->shadow.EndRender(); SetViewport(m_ViewCamera.GetViewPort()); } void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("patches"); #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // render all the patches, including blend pass ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) ? &m->shadow : 0); #if !CONFIG2_GLES if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // setup some renderstate .. pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(cullGroup, CColor(0.5f, 0.5f, 1.0f, 1.0f)); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer.RenderOutlines(cullGroup); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("models"); int flags = 0; #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif m->CallModelRenderers(context, cullGroup, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = context; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling) { PROFILE3_GPU("transparent models"); int flags = 0; #if !CONFIG2_GLES // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // disable face culling for two-sided models in sub-renders if (disableFaceCulling) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1); CShaderDefines contextBlend = context; contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) m->CallTranspModelRenderers(contextOpaque, cullGroup, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) m->CallTranspModelRenderers(contextBlend, cullGroup, flags); if (disableFaceCulling) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = contextOpaque; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallTranspModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const { // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D normalMatrix = camera.GetOrientation().GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); CMatrix3D matrix = camera.GetProjection(); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix CVector4D q; q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0]; q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5]; q.Z = 1.0f/matrix[11]; q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.X; matrix[6] = c.Y; matrix[10] = c.Z - matrix[11]; matrix[14] = c.W; // Load it back into the camera camera.SetProjection(matrix); } void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; CMatrix3D projection; if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { const float aspectRatio = 1.0f; // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane()); } else projection = m_ViewCamera.GetProjection(); camera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. camera.m_Orientation.Scale(1, -1, 1); camera.m_Orientation.Translate(0, 2 * wm.m_WaterHeight, 0); camera.UpdateFrustum(scissor); // Clip slightly above the water to improve reflections of objects on the water // when the reflections are distorted. camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f)); SViewPort vp; vp.m_Height = wm.m_RefTextureSize; vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(projection); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height / static_cast(std::max(1, m_Width)), 1.0f, 1.0f); camera.SetProjection(scaleMat * camera.GetProjection()); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f); SetObliqueFrustumClipping(camera, camPlane); } void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; CMatrix3D projection; if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { const float aspectRatio = 1.0f; // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane()); } else projection = m_ViewCamera.GetProjection(); camera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores. SViewPort vp; vp.m_Height = wm.m_RefTextureSize; vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(projection); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height / static_cast(std::max(1, m_Width)), 1.0f, 1.0f); camera.SetProjection(scaleMat * camera.GetProjection()); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); SetViewport(m_ViewCamera.GetViewPort()); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo); glClearColor(0.5f, 0.5f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFrontFace(GL_CW); if (!g_RenderingOptions.GetWaterReflection()) { m->skyManager.RenderSky(); ogl_WarnIfError(); } else { // Render terrain and models RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true); ogl_WarnIfError(); } glFrontFace(GL_CCW); // Particles are always oriented to face the camera in the vertex shader, // so they don't need the inverted glFrontFace if (g_RenderingOptions.GetParticles()) { RenderParticles(CULL_REFLECTIONS); ogl_WarnIfError(); } glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; SetViewport(m_ViewCamera.GetViewPort()); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeRefractionCamera(m_ViewCamera, scissor); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f); SetObliqueFrustumClipping(m_ViewCamera, camPlane); SetViewport(m_ViewCamera.GetViewPort()); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse(); wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation(); float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo); glClearColor(1.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render terrain and models RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; SetViewport(m_ViewCamera.GetViewPort()); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE3_GPU("silhouettes"); CShaderDefines contextOccluder = context; contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1); CShaderDefines contextDisplay = context; contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // color. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer.RenderPatches(CULL_SILHOUETTE_OCCLUDER); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } { PROFILE("render transparent occluders"); m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the color of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } { PROFILE("render model casters"); m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); } { PROFILE("render transparent casters"); m->CallTranspModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles(int cullGroup) { PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); m->particleRenderer.RenderParticles(true); m->particleRenderer.RenderBounds(cullGroup); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor) { PROFILE3("render submissions"); GetScene().GetLOSTexture().InterpolateLOS(); CShaderDefines context = m->globalContext; int cullGroup = CULL_DEFAULT; ogl_WarnIfError(); // Set the camera SetViewport(m_ViewCamera.GetViewPort()); // Prepare model renderers { PROFILE3("prepare models"); m->Model.NormalSkinned->PrepareModels(); m->Model.TranspSkinned->PrepareModels(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->PrepareModels(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->PrepareModels(); } m->terrainRenderer.PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(context); if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { RenderShadowMap(context); } ogl_WarnIfError(); if (m_WaterManager->m_RenderWater) { if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); RenderReflections(context, waterScissor); if (g_RenderingOptions.GetWaterRefraction()) RenderRefractions(context, waterScissor); } } if (g_RenderingOptions.GetPostProc()) { // We have to update the post process manager with real near/far planes // that we use for the scene rendering. m->postprocManager.SetDepthBufferClipPlanes( m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane() ); m->postprocManager.Initialize(); m->postprocManager.CaptureRenderOutput(); } { PROFILE3_GPU("clear buffers"); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } m->skyManager.RenderSky(); // render submitted patches and models RenderPatches(context, cullGroup); ogl_WarnIfError(); // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysBeforeWater(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(context, cullGroup); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { if (m_WaterManager->WillRenderFancyWater()) { // Render transparent stuff, but only the solid parts that can occlude block water. RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // Render transparent stuff again, but only the blended parts that overlap water. RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); ogl_WarnIfError(); } else { m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // Render transparent stuff, so it can overlap models/terrain. RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError(); // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water if (g_RenderingOptions.GetParticles()) { RenderParticles(cullGroup); ogl_WarnIfError(); } if (g_RenderingOptions.GetPostProc()) { if (g_Renderer.GetPostprocManager().IsMultisampleEnabled()) g_Renderer.GetPostprocManager().ResolveMultisampleFramebuffer(); m->postprocManager.ApplyPostproc(); m->postprocManager.ReleaseRenderOutput(); } if (g_RenderingOptions.GetSilhouettes()) { RenderSilhouettes(context); } // render debug lines if (g_RenderingOptions.GetDisplayFrustum()) DisplayFrustum(); if (g_RenderingOptions.GetDisplayShadowsFrustum()) { m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); } m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera); // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer.EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); m->silhouetteRenderer.EndFrame(); // Finish model renderers m->Model.NormalSkinned->EndFrame(); m->Model.TranspSkinned->EndFrame(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->EndFrame(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->EndFrame(); ogl_tex_bind(0, 0); } void CRenderer::OnSwapBuffers() { bool checkGLErrorAfterSwap = false; CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap); if (!checkGLErrorAfterSwap) return; PROFILE3("error check"); // We have to check GL errors after SwapBuffer to avoid possible // synchronizations during rendering. if (GLenum err = glGetError()) ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err)); } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { #if CONFIG2_GLES #warning TODO: implement CRenderer::DisplayFrustum for GLES #else glDepthMask(0); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 0.25f), 2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 1.0f), 2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); #endif ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer.RenderPriorities(CULL_DEFAULT); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { m_Viewport = vp; glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::Submit(CPatch* patch) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(patch->GetWorldBounds()); m->silhouetteRenderer.AddOccluder(patch); } if (m_CurrentCullGroup == CULL_SHADOWS) { m->shadow.AddShadowCasterBound(patch->GetWorldBounds()); } m->terrainRenderer.Submit(m_CurrentCullGroup, patch); } void CRenderer::Submit(SOverlayLine* overlay) { // Overlays are only needed in the default cull group for now, // so just ignore submissions to any other group if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayQuad* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySphere* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { // Decals can't cast shadows since they're flat on the terrain. // They can receive shadows, but the terrain under them will have // already been passed to AddShadowCasterBound, so don't bother // doing it again here. m->terrainRenderer.Submit(m_CurrentCullGroup, decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(m_CurrentCullGroup, emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(model->GetWorldBounds()); if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER) m->silhouetteRenderer.AddOccluder(model); if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY) m->silhouetteRenderer.AddCaster(model); } if (m_CurrentCullGroup == CULL_SHADOWS) { if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS)) return; m->shadow.AddShadowCasterBound(model->GetWorldBounds()); } bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); else m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); } else { if (requiresSkinning) m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); else m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); m_CurrentCullGroup = CULL_DEFAULT; scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); if (g_RenderingOptions.GetSilhouettes()) { m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera); m_CurrentCullGroup = CULL_DEFAULT; m->silhouetteRenderer.RenderSubmitOverlays(*this); m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER; m->silhouetteRenderer.RenderSubmitOccluders(*this); m_CurrentCullGroup = CULL_SILHOUETTE_CASTER; m->silhouetteRenderer.RenderSubmitCasters(*this); } if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { m_CurrentCullGroup = CULL_SHADOWS; CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(); scene.EnumerateObjects(shadowFrustum, this); } CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { - waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection()); + waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { if (g_RenderingOptions.GetWaterReflection()) { m_CurrentCullGroup = CULL_REFLECTIONS; CCamera reflectionCamera; ComputeReflectionCamera(reflectionCamera, waterScissor); scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); } if (g_RenderingOptions.GetWaterRefraction()) { m_CurrentCullGroup = CULL_REFRACTIONS; CCamera refractionCamera; ComputeRefractionCamera(refractionCamera, waterScissor); scene.EnumerateObjects(refractionCamera.GetFrustum(), this); } // Render the waves to the Fancy effects texture m_WaterManager->RenderWaves(frustum); } } m_CurrentCullGroup = -1; ogl_WarnIfError(); RenderSubmissions(waterScissor); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit, GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D, tex); #if !CONFIG2_GLES if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture // NB: A variant of this function is duplicated in TerrainTextureEntry.cpp, for use with the Shader // renderpath. This copy is kept to load the 'standard' maps for the fixed pipeline and should // be removed if/when the fixed pipeline goes. int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; ignore_result(ogl_tex_get_data(textures[i], &src)); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) ignore_result(ogl_tex_free(textures[i])); // upload the composite texture Tex t; ignore_result(t.wrap(total_w, total_h, 8, TEX_GREY, data, 0)); /*VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } ignore_result(da_free(&da));*/ m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); ignore_result(ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR)); ignore_result(ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)); int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; m_WaterManager->m_NeedsReloading = true; } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } TerrainRenderer& CRenderer::GetTerrainRenderer() { return m->terrainRenderer; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CDebugRenderer& CRenderer::GetDebugRenderer() { return m->debugRenderer; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; } ShadowMap& CRenderer::GetShadowMap() { return m->shadow; } void CRenderer::ResetState() { // Clear all emitters, that were created in previous games GetParticleManager().ClearUnattachedEmitters(); } Index: ps/trunk/source/renderer/TerrainRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 25354) +++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 25355) @@ -1,672 +1,629 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * Terrain rendering (everything related to patches and water) is * encapsulated in TerrainRenderer */ #include "precompiled.h" +#include "renderer/TerrainRenderer.h" + #include "graphics/Camera.h" #include "graphics/Decal.h" +#include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" -#include "graphics/GameView.h" #include "graphics/Model.h" #include "graphics/ShaderManager.h" -#include "renderer/ShadowMap.h" -#include "renderer/SkyManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/TextRenderer.h" - #include "maths/MathUtil.h" - -#include "ps/Filesystem.h" #include "ps/CLogger.h" +#include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" - #include "renderer/DecalRData.h" #include "renderer/PatchRData.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/ShadowMap.h" -#include "renderer/TerrainRenderer.h" +#include "renderer/SkyManager.h" #include "renderer/VertexArray.h" #include "renderer/WaterManager.h" -#include "tools/atlas/GameInterface/GameLoop.h" - -extern GameLoopState* g_AtlasGameLoop; - -/////////////////////////////////////////////////////////////////////////////////////////////// -// TerrainRenderer implementation - namespace { CShaderProgramPtr GetDummyShader() { const char* shaderName; if (g_RenderingOptions.GetPreferGLSL()) shaderName = "glsl/dummy"; else shaderName = "arb/dummy"; return g_Renderer.GetShaderManager().LoadProgram(shaderName, CShaderDefines()); } } // anonymous namespace /** * TerrainRenderer keeps track of which phase it is in, to detect * when Submit, PrepareForRendering etc. are called in the wrong order. */ enum Phase { Phase_Submit, Phase_Render }; /** * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class. */ struct TerrainRendererInternals { /// Which phase (submitting or rendering patches) are we in right now? Phase phase; /// Patches that were submitted for this frame std::vector visiblePatches[CRenderer::CULL_MAX]; /// Decals that were submitted for this frame std::vector visibleDecals[CRenderer::CULL_MAX]; /// Fancy water shader CShaderProgramPtr fancyWaterShader; CSimulation2* simulation; }; /////////////////////////////////////////////////////////////////// // Construction/Destruction TerrainRenderer::TerrainRenderer() { m = new TerrainRendererInternals(); m->phase = Phase_Submit; } TerrainRenderer::~TerrainRenderer() { delete m; } void TerrainRenderer::SetSimulation(CSimulation2* simulation) { m->simulation = simulation; } /////////////////////////////////////////////////////////////////// // Submit a patch for rendering void TerrainRenderer::Submit(int cullGroup, CPatch* patch) { ENSURE(m->phase == Phase_Submit); CPatchRData* data = (CPatchRData*)patch->GetRenderData(); if (data == 0) { // no renderdata for patch, create it now data = new CPatchRData(patch, m->simulation); patch->SetRenderData(data); } data->Update(m->simulation); m->visiblePatches[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Submit a decal for rendering void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal) { ENSURE(m->phase == Phase_Submit); CDecalRData* data = (CDecalRData*)decal->GetRenderData(); if (data == 0) { // no renderdata for decal, create it now data = new CDecalRData(decal, m->simulation); decal->SetRenderData(data); } data->Update(m->simulation); m->visibleDecals[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Prepare for rendering void TerrainRenderer::PrepareForRendering() { ENSURE(m->phase == Phase_Submit); m->phase = Phase_Render; } /////////////////////////////////////////////////////////////////// // Clear submissions lists void TerrainRenderer::EndFrame() { ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit); for (int i = 0; i < CRenderer::CULL_MAX; ++i) { m->visiblePatches[i].clear(); m->visibleDecals[i].clear(); } m->phase = Phase_Submit; } void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, GLuint texture) { #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES UNUSED2(cullGroup); UNUSED2(textureMatrix); UNUSED2(texture); #else ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(0); glDisable(GL_DEPTH_TEST); CShaderTechniquePtr debugOverlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay); debugOverlayTech->BeginPass(); CShaderProgramPtr debugOverlayShader = debugOverlayTech->GetShader(); debugOverlayShader->Bind(); debugOverlayShader->BindTexture(str_baseTex, texture); debugOverlayShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); debugOverlayShader->Uniform(str_textureTransform, textureMatrix); CPatchRData::RenderStreams(visiblePatches, debugOverlayShader, STREAM_POS | STREAM_POSTOUV0); glEnable(GL_DEPTH_TEST); // To make the overlay visible over water, render an additional map-sized // water-height patch. CBoundingBoxAligned waterBounds; for (CPatchRData* data : visiblePatches) waterBounds += data->GetWaterBounds(); if (!waterBounds.IsEmpty()) { // Add a delta to avoid z-fighting. const float height = g_Renderer.GetWaterManager()->m_WaterHeight + 0.05f; const float waterPos[] = { waterBounds[0].X, height, waterBounds[0].Z, waterBounds[1].X, height, waterBounds[0].Z, waterBounds[0].X, height, waterBounds[1].Z, waterBounds[1].X, height, waterBounds[1].Z }; const GLsizei stride = sizeof(float) * 3; debugOverlayShader->VertexPointer(3, GL_FLOAT, stride, waterPos); debugOverlayShader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, waterPos); debugOverlayShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } debugOverlayShader->Unbind(); debugOverlayTech->EndPass(); glDepthMask(1); glDisable(GL_BLEND); #endif } /////////////////////////////////////////////////////////////////// /** * Set up all the uniforms for a shader pass. */ void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow) { shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); shader->Uniform(str_cameraPos, g_Renderer.GetViewCamera().GetOrientation().GetTranslation()); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); if (shadow) shadow->BindTo(shader); CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); shader->BindTexture(str_losTex, los.GetTextureSmooth()); shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); shader->Uniform(str_ambient, lightEnv.m_AmbientColor); shader->Uniform(str_sunColor, lightEnv.m_SunColor); shader->Uniform(str_sunDir, lightEnv.GetSunDir()); shader->Uniform(str_fogColor, lightEnv.m_FogColor); shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f); } void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; std::vector& visibleDecals = m->visibleDecals[cullGroup]; if (visiblePatches.empty() && visibleDecals.empty()) return; // render the solid black sides of the map first CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); techSolid->BeginPass(); CShaderProgramPtr shaderSolid = techSolid->GetShader(); shaderSolid->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); shaderSolid->Uniform(str_color, 0.0f, 0.0f, 0.0f, 1.0f); CPatchRData::RenderSides(visiblePatches, shaderSolid); techSolid->EndPass(); CPatchRData::RenderBases(visiblePatches, context, shadow); // no need to write to the depth buffer a second time glDepthMask(0); // render blend passes for each patch CPatchRData::RenderBlends(visiblePatches, context, shadow); CDecalRData::RenderDecals(visibleDecals, context, shadow); // restore OpenGL state g_Renderer.BindTexture(1, 0); g_Renderer.BindTexture(2, 0); g_Renderer.BindTexture(3, 0); glDepthMask(1); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); } /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons void TerrainRenderer::RenderPatches(int cullGroup, const CColor& color) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderPatches for GLES #else CShaderProgramPtr dummyShader = GetDummyShader(); dummyShader->Bind(); dummyShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); dummyShader->Uniform(str_color, color); CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS); dummyShader->Unbind(); #endif } /////////////////////////////////////////////////////////////////// // Render outlines of submitted patches as lines void TerrainRenderer::RenderOutlines(int cullGroup) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderOutline(); } /////////////////////////////////////////////////////////////////// // Scissor rectangle of water patches -CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CMatrix3D &viewproj) +CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CCamera& camera) { - std::vector& visiblePatches = m->visiblePatches[cullGroup]; - CBoundingBoxAligned scissor; - for (size_t i = 0; i < visiblePatches.size(); ++i) + for (const CPatchRData* data : m->visiblePatches[cullGroup]) { - CPatchRData* data = visiblePatches[i]; const CBoundingBoxAligned& waterBounds = data->GetWaterBounds(); if (waterBounds.IsEmpty()) continue; - CVector4D v1 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f)); - CVector4D v2 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f)); - CVector4D v3 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f)); - CVector4D v4 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f)); - CBoundingBoxAligned screenBounds; - #define ADDBOUND(v1, v2, v3, v4) \ - if (v1.Z >= -v1.W) \ - screenBounds += CVector3D(v1.X, v1.Y, v1.Z) * (1.0f / v1.W); \ - else \ - { \ - float t = v1.Z + v1.W; \ - if (v2.Z > -v2.W) \ - { \ - CVector4D c2 = v1 + (v2 - v1) * (t / (t - (v2.Z + v2.W))); \ - screenBounds += CVector3D(c2.X, c2.Y, c2.Z) * (1.0f / c2.W); \ - } \ - if (v3.Z > -v3.W) \ - { \ - CVector4D c3 = v1 + (v3 - v1) * (t / (t - (v3.Z + v3.W))); \ - screenBounds += CVector3D(c3.X, c3.Y, c3.Z) * (1.0f / c3.W); \ - } \ - if (v4.Z > -v4.W) \ - { \ - CVector4D c4 = v1 + (v4 - v1) * (t / (t - (v4.Z + v4.W))); \ - screenBounds += CVector3D(c4.X, c4.Y, c4.Z) * (1.0f / c4.W); \ - } \ - } - ADDBOUND(v1, v2, v3, v4); - ADDBOUND(v2, v1, v3, v4); - ADDBOUND(v3, v1, v2, v4); - ADDBOUND(v4, v1, v2, v3); - #undef ADDBOUND - if (screenBounds[0].X >= 1.0f || screenBounds[1].X <= -1.0f || screenBounds[0].Y >= 1.0f || screenBounds[1].Y <= -1.0f) - continue; - scissor += screenBounds; - } - return CBoundingBoxAligned(CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f), - CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f)); + const CBoundingBoxAligned waterBoundsInViewPort = + camera.GetBoundsInViewPort(waterBounds); + if (!waterBoundsInViewPort.IsEmpty()) + scissor += waterBoundsInViewPort; + } + return CBoundingBoxAligned( + CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f), + CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f)); } // Render fancy water bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { PROFILE3_GPU("fancy water"); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); CShaderDefines defines = context; // If we're using fancy water, make sure its shader is loaded if (!m->fancyWaterShader || WaterMgr->m_NeedsReloading) { if (WaterMgr->m_WaterRealDepth) defines.Add(str_USE_REAL_DEPTH, str_1); if (WaterMgr->m_WaterFancyEffects) defines.Add(str_USE_FANCY_EFFECTS, str_1); if (WaterMgr->m_WaterRefraction) defines.Add(str_USE_REFRACTION, str_1); if (WaterMgr->m_WaterReflection) defines.Add(str_USE_REFLECTION, str_1); // haven't updated the ARB shader yet so I'll always load the GLSL /*if (!g_RenderingOptions.GetPreferGLSL() && !superFancy) m->fancyWaterShader = g_Renderer.GetShaderManager().LoadProgram("arb/water_high", defines); else*/ m->fancyWaterShader = g_Renderer.GetShaderManager().LoadProgram("glsl/water_high", defines); if (!m->fancyWaterShader) { LOGERROR("Failed to load water shader. Falling back to fixed pipeline water.\n"); WaterMgr->m_RenderWater = false; return false; } WaterMgr->m_NeedsReloading = false; } CLOSTexture& losTexture = g_Renderer.GetScene().GetLOSTexture(); // Calculating the advanced informations about Foam and all if the quality calls for it. /*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves)) { WaterMgr->m_NeedInfoUpdate = false; WaterMgr->CreateSuperfancyInfo(); }*/ double time = WaterMgr->m_WaterTexTimer; double period = 8; int curTex = (int)(time*60/period) % 60; int nexTex = (curTex + 1) % 60; float repeatPeriod = WaterMgr->m_RepeatPeriod; // Render normals and foam to a framebuffer if we're in fancy effects if (WaterMgr->m_WaterFancyEffects) { // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, WaterMgr->m_FancyEffectsFBO); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); // Overwrite waves that would be behind the ground. CShaderProgramPtr dummyShader = g_Renderer.GetShaderManager().LoadProgram("glsl/gui_solid", CShaderDefines()); dummyShader->Bind(); dummyShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(dummyShader, true, true); } dummyShader->Unbind(); glEnable(GL_CULL_FACE); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); m->fancyWaterShader->Bind(); const CCamera& camera = g_Renderer.GetViewCamera(); m->fancyWaterShader->BindTexture(str_normalMap, WaterMgr->m_NormalMap[curTex]); m->fancyWaterShader->BindTexture(str_normalMap2, WaterMgr->m_NormalMap[nexTex]); if (WaterMgr->m_WaterFancyEffects) { m->fancyWaterShader->BindTexture(str_waterEffectsTex, WaterMgr->m_FancyTexture); } if (WaterMgr->m_WaterRefraction && WaterMgr->m_WaterRealDepth) { m->fancyWaterShader->BindTexture(str_depthTex, WaterMgr->m_RefrFboDepthTexture); m->fancyWaterShader->Uniform(str_projInvTransform, WaterMgr->m_RefractionProjInvMatrix); m->fancyWaterShader->Uniform(str_viewInvTransform, WaterMgr->m_RefractionViewInvMatrix); } if (WaterMgr->m_WaterRefraction) m->fancyWaterShader->BindTexture(str_refractionMap, WaterMgr->m_RefractionTexture); if (WaterMgr->m_WaterReflection) m->fancyWaterShader->BindTexture(str_reflectionMap, WaterMgr->m_ReflectionTexture); m->fancyWaterShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); m->fancyWaterShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); m->fancyWaterShader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube()); // TODO: check that this rotates in the right direction. CMatrix3D skyBoxRotation; skyBoxRotation.SetIdentity(); skyBoxRotation.RotateY(M_PI + lightEnv.GetRotation()); m->fancyWaterShader->Uniform(str_skyBoxRot, skyBoxRotation); if (WaterMgr->m_WaterRefraction) m->fancyWaterShader->Uniform(str_refractionMatrix, WaterMgr->m_RefractionMatrix); if (WaterMgr->m_WaterReflection) m->fancyWaterShader->Uniform(str_reflectionMatrix, WaterMgr->m_ReflectionMatrix); m->fancyWaterShader->Uniform(str_ambient, lightEnv.m_AmbientColor); m->fancyWaterShader->Uniform(str_sunDir, lightEnv.GetSunDir()); m->fancyWaterShader->Uniform(str_sunColor, lightEnv.m_SunColor); m->fancyWaterShader->Uniform(str_color, WaterMgr->m_WaterColor); m->fancyWaterShader->Uniform(str_tint, WaterMgr->m_WaterTint); m->fancyWaterShader->Uniform(str_waviness, WaterMgr->m_Waviness); m->fancyWaterShader->Uniform(str_murkiness, WaterMgr->m_Murkiness); m->fancyWaterShader->Uniform(str_windAngle, WaterMgr->m_WindAngle); m->fancyWaterShader->Uniform(str_repeatScale, 1.0f / repeatPeriod); m->fancyWaterShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f); m->fancyWaterShader->Uniform(str_cameraPos, camera.GetOrientation().GetTranslation()); m->fancyWaterShader->Uniform(str_fogColor, lightEnv.m_FogColor); m->fancyWaterShader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f); m->fancyWaterShader->Uniform(str_time, (float)time); m->fancyWaterShader->Uniform(str_screenSize, (float)g_Renderer.GetWidth(), (float)g_Renderer.GetHeight(), 0.0f, 0.0f); if (WaterMgr->m_WaterType == L"clap") { m->fancyWaterShader->Uniform(str_waveParams1, 30.0f,1.5f,20.0f,0.03f); m->fancyWaterShader->Uniform(str_waveParams2, 0.5f,0.0f,0.0f,0.0f); } else if (WaterMgr->m_WaterType == L"lake") { m->fancyWaterShader->Uniform(str_waveParams1, 8.5f,1.5f,15.0f,0.03f); m->fancyWaterShader->Uniform(str_waveParams2, 0.2f,0.0f,0.0f,0.07f); } else { m->fancyWaterShader->Uniform(str_waveParams1, 15.0f,0.8f,10.0f,0.1f); m->fancyWaterShader->Uniform(str_waveParams2, 0.3f,0.0f,0.1f,0.3f); } if (shadow) shadow->BindTo(m->fancyWaterShader); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(m->fancyWaterShader); } m->fancyWaterShader->Unbind(); glDepthFunc(GL_LEQUAL); glDisable(GL_BLEND); return true; } void TerrainRenderer::RenderSimpleWater(int cullGroup) { #if CONFIG2_GLES UNUSED2(cullGroup); #else PROFILE3_GPU("simple water"); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); double time = WaterMgr->m_WaterTexTimer; double period = 1.6f; int curTex = (int)(time*60/period) % 60; CShaderTechniquePtr waterSimpleTech = g_Renderer.GetShaderManager().LoadEffect(str_water_simple); waterSimpleTech->BeginPass(); CShaderProgramPtr waterSimpleShader = waterSimpleTech->GetShader(); waterSimpleShader->Bind(); waterSimpleShader->BindTexture(str_baseTex, WaterMgr->m_WaterTexture[curTex]); waterSimpleShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); waterSimpleShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); waterSimpleShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f); waterSimpleShader->Uniform(str_time, static_cast(time)); waterSimpleShader->Uniform(str_color, WaterMgr->m_WaterColor); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(waterSimpleShader, false, true); } waterSimpleShader->Unbind(); g_Renderer.BindTexture(1, 0); pglActiveTextureARB(GL_TEXTURE0_ARB); glDisable(GL_TEXTURE_2D); waterSimpleTech->EndPass(); #endif } /////////////////////////////////////////////////////////////////// // Render water that is part of the terrain void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { WaterManager* WaterMgr = g_Renderer.GetWaterManager(); WaterMgr->UpdateQuality(); if (!WaterMgr->WillRenderFancyWater()) RenderSimpleWater(cullGroup); else RenderFancyWater(context, cullGroup, shadow); } void TerrainRenderer::RenderPriorities(int cullGroup) { PROFILE("priorities"); ENSURE(m->phase == Phase_Render); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); tech->BeginPass(); CTextRenderer textRenderer(tech->GetShader()); textRenderer.Font(CStrIntern("mono-stroke-10")); textRenderer.Color(1.0f, 1.0f, 0.0f); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderPriorities(textRenderer); textRenderer.Render(); tech->EndPass(); } Index: ps/trunk/source/renderer/TerrainRenderer.h =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.h (revision 25354) +++ ps/trunk/source/renderer/TerrainRenderer.h (revision 25355) @@ -1,161 +1,167 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * Terrain rendering (everything related to patches and water) is * encapsulated in TerrainRenderer */ #ifndef INCLUDED_TERRAINRENDERER #define INCLUDED_TERRAINRENDERER +#include "graphics/Color.h" #include "maths/BoundingBoxAligned.h" +class CCamera; +class CMatrix3D; +class CModelDecal; class CPatch; +class CShaderDefines; class CSimulation2; + class ShadowMap; class WaterManager; struct TerrainRendererInternals; /** * Class TerrainRenderer: Render everything related to the terrain, * especially patches and water. */ class TerrainRenderer { friend class CPatchRData; friend class CDecalRData; public: TerrainRenderer(); ~TerrainRenderer(); /** * Set the simulation context for this frame. * Call at start of frame, before any other Submits. */ void SetSimulation(CSimulation2* simulation); /** * Submit: Add a patch for rendering in this frame. * * preconditions : PrepareForRendering must not have been called * for this frame yet. * The patch must not have been submitted in this frame yet (i.e. you * can only submit a frame once). * * @param patch the patch */ void Submit(int cullGroup, CPatch* patch); /** * Submit: Add a terrain decal for rendering in this frame. */ void Submit(int cullGroup, CModelDecal* decal); /** * PrepareForRendering: Prepare internal data structures like vertex * buffers for rendering. * * All patches must have been submitted before the call to * PrepareForRendering. * PrepareForRendering must be called before any rendering calls. */ void PrepareForRendering(); /** * EndFrame: Remove all patches from the list of submitted patches. */ void EndFrame(); /** * Render textured terrain (including blends between * different terrain types). * * preconditions : PrepareForRendering must have been called this * frame before calling RenderTerrain. * * @param shadow A prepared shadow map, in case rendering with shadows is enabled. */ void RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderPatches: Render all patches un-textured as polygons. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderPatches. * * @param filtered If true then only render objects that passed CullPatches. * @param color Fill color of the patches. */ void RenderPatches(int cullGroup, const CColor& color = CColor(0.0f, 0.0f, 0.0f, 1.0f)); /** * RenderOutlines: Render the outline of patches as lines. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderOutlines. * * @param filtered If true then only render objects that passed CullPatches. */ void RenderOutlines(int cullGroup); /** * RenderWater: Render water for all patches that have been submitted * this frame. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderWater. */ void RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * Calculate a scissor rectangle for the visible water patches. */ - CBoundingBoxAligned ScissorWater(int cullGroup, const CMatrix3D& viewproj); + CBoundingBoxAligned ScissorWater(int cullGroup, const CCamera& camera); /** * Render priority text for all submitted patches, for debugging. */ void RenderPriorities(int cullGroup); /** * Render texture unit 0 over the terrain mesh, with UV coords calculated * by the given texture matrix. * Intended for use by TerrainTextureOverlay. */ void RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, GLuint texture); private: TerrainRendererInternals* m; /** * RenderFancyWater: internal rendering method for fancy water. * Returns false if unable to render with fancy water. */ bool RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderSimpleWater: internal rendering method for water */ void RenderSimpleWater(int cullGroup); static void PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow); }; #endif // INCLUDED_TERRAINRENDERER