Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 22402) +++ ps/trunk/source/graphics/GameView.cpp (revision 22403) @@ -1,1135 +1,1132 @@ /* Copyright (C) 2019 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 . */ #include "precompiled.h" #include "GameView.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LOSTexture.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" extern int g_xres, g_yres; // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f*TERRAIN_TILE_SIZE; /** * A value with exponential decay towards the target value. */ class CSmoothedValue { public: CSmoothedValue(float value, float smoothness, float minDelta) : m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta) { } float GetSmoothedValue() const { return m_Current; } void SetValueSmoothly(float value) { m_Target = value; } void AddSmoothly(float value) { m_Target += value; } void Add(float value) { m_Target += value; m_Current += value; } float GetValue() const { return m_Target; } void SetValue(float value) { m_Target = value; m_Current = value; } float Update(float time) { if (fabs(m_Target - m_Current) < m_MinDelta) return 0.0f; double p = pow(static_cast(m_Smoothness), 10.0 * static_cast(time)); // (add the factor of 10 so that smoothnesses don't have to be tiny numbers) double delta = (m_Target - m_Current) * (1.0 - p); m_Current += delta; return (float)delta; } void ClampSmoothly(float min, float max) { m_Target = Clamp(m_Target, static_cast(min), static_cast(max)); } // Wrap so 'target' is in the range [min, max] void Wrap(float min, float max) { double t = fmod(m_Target - min, static_cast(max - min)); if (t < 0) t += max - min; t += min; m_Current += t - m_Target; m_Target = t; } float m_Smoothness; private: double m_Target; // the value which m_Current is tending towards double m_Current; // (We use double because the extra precision is worthwhile here) float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects) }; class CGameViewImpl { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), ConstrainCamera(true), Culling(true), FollowEntity(INVALID_ENTITY), FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) ViewScrollSpeed(0), ViewScrollSpeedModifier(1), ViewRotateXSpeed(0), ViewRotateXMin(0), ViewRotateXMax(0), ViewRotateXDefault(0), ViewRotateYSpeed(0), ViewRotateYSpeedWheel(0), ViewRotateYDefault(0), ViewRotateSpeedModifier(1), ViewDragSpeed(0), ViewZoomSpeed(0), ViewZoomSpeedWheel(0), ViewZoomMin(0), ViewZoomMax(0), ViewZoomDefault(0), ViewZoomSpeedModifier(1), ViewFOV(DEGTORAD(45.f)), ViewNear(2.f), ViewFar(4096.f), JoystickPanX(-1), JoystickPanY(-1), JoystickRotateX(-1), JoystickRotateY(-1), JoystickZoomIn(-1), JoystickZoomOut(-1), HeightSmoothness(0.5f), HeightMin(16.f), PosX(0, 0, 0.01f), PosY(0, 0, 0.01f), PosZ(0, 0, 0.01f), Zoom(0, 0, 0.1f), RotateX(0, 0, 0.001f), RotateY(0, 0, 0.001f) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Whether the camera movement should be constrained by min/max limits * and terrain avoidance. */ bool ConstrainCamera; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager CinemaManager; /** * Entity for the camera to follow, or INVALID_ENTITY if none. */ entity_id_t FollowEntity; /** * Whether to follow FollowEntity in first-person mode. */ bool FollowFirstPerson; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewScrollSpeedModifier; float ViewRotateXSpeed; float ViewRotateXMin; float ViewRotateXMax; float ViewRotateXDefault; float ViewRotateYSpeed; float ViewRotateYSpeedWheel; float ViewRotateYDefault; float ViewRotateSpeedModifier; float ViewDragSpeed; float ViewZoomSpeed; float ViewZoomSpeedWheel; float ViewZoomMin; float ViewZoomMax; float ViewZoomDefault; float ViewZoomSpeedModifier; float ViewFOV; float ViewNear; float ViewFar; int JoystickPanX; int JoystickPanY; int JoystickRotateX; int JoystickRotateY; int JoystickZoomIn; int JoystickZoomOut; float HeightSmoothness; float HeightMin; //////////////////////////////////////// // Camera Controls State CSmoothedValue PosX; CSmoothedValue PosY; CSmoothedValue PosZ; CSmoothedValue Zoom; CSmoothedValue RotateX; // inclination around x axis (relative to camera) CSmoothedValue RotateY; // rotation around y (vertical) axis }; #define IMPLEMENT_BOOLEAN_SETTING(NAME) \ bool CGameView::Get##NAME##Enabled() const \ { \ return m->NAME; \ } \ \ void CGameView::Set##NAME##Enabled(bool Enabled) \ { \ m->NAME = Enabled; \ } IMPLEMENT_BOOLEAN_SETTING(Culling); IMPLEMENT_BOOLEAN_SETTING(LockCullCamera); IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera); #undef IMPLEMENT_BOOLEAN_SETTING static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue()); } static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetValue()); orientation->RotateY(m->RotateY.GetValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { SViewPort vp; vp.m_X = 0; vp.m_Y = 0; vp.m_Width = g_xres; vp.m_Height = g_yres; m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } CObjectManager& CGameView::GetObjectManager() { return m->ObjectManager; } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->CinemaManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } int CGameView::Initialize() { CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed); CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier); CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed); CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin); CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax); CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault); CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed); CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel); CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault); CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier); CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed); CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed); CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel); CFG_GET_VAL("view.zoom.min", m->ViewZoomMin); CFG_GET_VAL("view.zoom.max", m->ViewZoomMax); CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault); CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier); CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX); CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY); CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX); CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY); CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn); CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut); CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness); CFG_GET_VAL("view.height.min", m->HeightMin); CFG_GET_VAL("view.pos.smoothness", m->PosX.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosY.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosZ.m_Smoothness); CFG_GET_VAL("view.zoom.smoothness", m->Zoom.m_Smoothness); CFG_GET_VAL("view.rotate.x.smoothness", m->RotateX.m_Smoothness); CFG_GET_VAL("view.rotate.y.smoothness", m->RotateY.m_Smoothness); CFG_GET_VAL("view.near", m->ViewNear); CFG_GET_VAL("view.far", m->ViewFar); CFG_GET_VAL("view.fov", m->ViewFOV); // Convert to radians m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->ViewFOV = DEGTORAD(m->ViewFOV); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColors(); } void CGameView::Render() { g_Renderer.RenderScene(*this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBoundingBoxAligned bounds = patch->GetWorldBounds(); if(bounds[1].Y < waterHeight) bounds[1].Y = waterHeight; if (!m->Culling || frustum.IsBoxVisible(bounds)) c->Submit(patch); } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; - if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) - g_Renderer.MakeShadersDirty(); - m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } static void FocusHeight(CGameViewImpl* m, bool smooth) { /* The camera pivot height is moved towards ground level. To prevent excessive zoom when looking over a cliff, the target ground level is the maximum of the ground level at the camera's near and pivot points. The ground levels are filtered to achieve smooth camera movement. The filter radius is proportional to the zoom level. The camera height is clamped to prevent map penetration. */ if (!m->ConstrainCamera) return; CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); const CVector3D position = targetCam.m_Orientation.GetTranslation(); const CVector3D forwards = targetCam.m_Orientation.GetIn(); // horizontal view radius const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue(); const float near_radius = radius * m->HeightSmoothness; const float pivot_radius = radius * m->HeightSmoothness; const CVector3D nearPoint = position + forwards * m->ViewNear; const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue(); const float ground = std::max( m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z), g_Renderer.GetWaterManager()->m_WaterHeight); // filter ground levels for smooth camera movement const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius); const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius); // filtered maximum visible ground level in view const float filtered_ground = std::max( std::max(filtered_near_ground, filtered_pivot_ground), g_Renderer.GetWaterManager()->m_WaterHeight); // target camera height above pivot point const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear); // minimum camera height above filtered ground level const float min_height = (m->HeightMin + ground - filtered_ground); const float target_height = std::max(pivot_height, min_height); const float height = (nearPoint.Y - filtered_ground); const float diff = target_height - height; if (fabsf(diff) < 0.0001f) return; if (smooth) m->PosY.AddSmoothly(diff); else m->PosY.Add(diff); } CVector3D CGameView::GetSmoothPivot(CCamera& camera) const { return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue(); } void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; m->CinemaManager.Update(deltaRealTime); if (m->CinemaManager.IsEnabled()) return; // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpPosition && cmpPosition->IsInWorld() && cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime); if (m->ConstrainCamera) m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax); float zoomDelta = -m->Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); FocusHeight(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(deltaRealTime); m->PosY.Update(deltaRealTime); m->PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); } CVector3D CGameView::GetCameraPivot() const { return GetSmoothPivot(m->ViewCamera); } CVector3D CGameView::GetCameraPosition() const { return CVector3D(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } CVector3D CGameView::GetCameraRotation() const { // The angle of rotation around the Z axis is not used. return CVector3D(m->RotateX.GetValue(), m->RotateY.GetValue(), 0.0f); } float CGameView::GetCameraZoom() const { return m->Zoom.GetValue(); } void CGameView::SetCamera(CVector3D Pos, float RotX, float RotY, float zoom) { m->PosX.SetValue(Pos.X); m->PosY.SetValue(Pos.Y); m->PosZ.SetValue(Pos.Z); m->RotateX.SetValue(RotX); m->RotateY.SetValue(RotY); m->Zoom.SetValue(zoom); FocusHeight(m, false); SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::MoveCameraTarget(const CVector3D& target) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = target - pivot; m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); FocusHeight(m, false); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->Zoom.SetValue(m->ViewZoomDefault); FocusHeight(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraAngleZoom() { CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); m->Zoom.AddSmoothly(m->ViewZoomDefault - dist); // Reset orientations to default m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault)); } void CGameView::CameraFollow(entity_id_t entity, bool firstPerson) { m->FollowEntity = entity; m->FollowFirstPerson = firstPerson; } entity_id_t CGameView::GetFollowedEntity() { return m->FollowEntity; } float CGameView::GetNear() const { return m->ViewNear; } float CGameView::GetFar() const { return m->ViewFar; } float CGameView::GetFOV() const { return m->ViewFOV; } void CGameView::SetCameraProjection() { m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsEnabled()) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) break; else if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetWaterRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetWaterRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetWaterRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return IN_HANDLED; } // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). else if (hotkey == "camera.zoom.wheel.in") { m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.increase") { m->ViewScrollSpeed *= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.decrease") { m->ViewScrollSpeed /= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.increase") { m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.decrease") { m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.increase") { m->ViewZoomSpeed *= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.decrease") { m->ViewZoomSpeed /= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } } return IN_PASS; } Index: ps/trunk/source/graphics/LightEnv.cpp =================================================================== --- ps/trunk/source/graphics/LightEnv.cpp (revision 22402) +++ ps/trunk/source/graphics/LightEnv.cpp (revision 22403) @@ -1,63 +1,58 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2019 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 . */ -/* - * CLightEnv implementation - */ - #include "precompiled.h" -#include "maths/MathUtil.h" - #include "graphics/LightEnv.h" +#include "maths/MathUtil.h" + CLightEnv::CLightEnv() : m_Elevation(DEGTORAD(45)), m_Rotation(DEGTORAD(315)), - m_LightingModel("standard"), m_SunColor(1.5, 1.5, 1.5), m_TerrainAmbientColor(0x50/255.f, 0x60/255.f, 0x85/255.f), m_UnitsAmbientColor(0x80/255.f, 0x80/255.f, 0x80/255.f), m_FogColor(0xCC/255.f, 0xCC/255.f, 0xE5/255.f), m_FogFactor(0.000f), m_FogMax(0.5f), m_Brightness(0.0f), m_Contrast(1.0f), m_Saturation(0.99f), m_Bloom(0.1999f) { CalculateSunDirection(); } void CLightEnv::SetElevation(float f) { m_Elevation = f; CalculateSunDirection(); } void CLightEnv::SetRotation(float f) { m_Rotation = f; CalculateSunDirection(); } void CLightEnv::CalculateSunDirection() { m_SunDir.Y = -sinf(m_Elevation); float scale = 1 + m_SunDir.Y; m_SunDir.X = scale * sinf(m_Rotation); m_SunDir.Z = scale * cosf(m_Rotation); m_SunDir.Normalize(); } Index: ps/trunk/source/graphics/LightEnv.h =================================================================== --- ps/trunk/source/graphics/LightEnv.h (revision 22402) +++ ps/trunk/source/graphics/LightEnv.h (revision 22403) @@ -1,162 +1,151 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 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 . */ /* * CLightEnv, a class describing the current lights */ #ifndef INCLUDED_LIGHTENV #define INCLUDED_LIGHTENV #include "graphics/Color.h" #include "maths/MathUtil.h" #include "maths/Vector3D.h" class CMapWriter; class CMapReader; /** * Class CLightEnv: description of a lighting environment - contains all the * necessary parameters for representation of the lighting within a scenario */ class CLightEnv { -friend class CMapWriter; -friend class CMapReader; -friend class CXMLReader; -private: - /** - * Height of sun above the horizon, in radians. - * For example, an elevation of M_PI/2 means the sun is straight up. - */ - float m_Elevation; - - /** - * Direction of sun on the compass, in radians. - * For example, a rotation of zero means the sun is in the direction (0,0,-1) - * and a rotation of M_PI/2 means the sun is in the direction (1,0,0) (not taking - * elevation into account). - */ - float m_Rotation; - - /** - * Vector corresponding to m_Elevation and m_Rotation. - * Updated by CalculateSunDirection. - */ - CVector3D m_SunDir; - - /** - * A string that shaders use to determine what lighting model to implement. - * Current recognised values are "old" and "standard". - */ - std::string m_LightingModel; - public: RGBColor m_SunColor; RGBColor m_TerrainAmbientColor; RGBColor m_UnitsAmbientColor; RGBColor m_FogColor; float m_FogFactor; float m_FogMax; float m_Brightness, m_Contrast, m_Saturation, m_Bloom; -public: CLightEnv(); float GetElevation() const { return m_Elevation; } float GetRotation() const { return m_Rotation; } const CVector3D& GetSunDir() const { return m_SunDir; } - const std::string& GetLightingModel() const { return m_LightingModel; } void SetElevation(float f); void SetRotation(float f); - void SetLightingModel(const std::string& model) { m_LightingModel = model; } - /** * Calculate brightness of a point of a unit with the given normal vector, * for rendering with CPU lighting. * The resulting color contains both ambient and diffuse light. * To cope with sun overbrightness, the color is scaled by 0.5. * * @param normal normal vector (must have length 1) */ RGBColor EvaluateUnitScaled(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); RGBColor color = m_UnitsAmbientColor; if (dot > 0) color += m_SunColor * dot; return color * 0.5f; } /** * Compute the diffuse sun lighting color on terrain, for rendering with CPU lighting. * To cope with sun overbrightness, the color is scaled by 0.5. * * @param normal normal vector (must have length 1) */ SColor4ub EvaluateTerrainDiffuseScaled(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); return ConvertRGBColorTo4ub(m_SunColor * dot * 0.5f); } /** * Compute the diffuse sun lighting factor on terrain, for rendering with shader lighting. * * @param normal normal vector (must have length 1) */ SColor4ub EvaluateTerrainDiffuseFactor(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); u8 c = static_cast(clamp(dot * 255.f, 0.f, 255.f)); return SColor4ub(c, c, c, 255); } // Comparison operators bool operator==(const CLightEnv& o) const { return m_Elevation == o.m_Elevation && m_Rotation == o.m_Rotation && - m_LightingModel == o.m_LightingModel && m_SunColor == o.m_SunColor && m_TerrainAmbientColor == o.m_TerrainAmbientColor && m_UnitsAmbientColor == o.m_UnitsAmbientColor && m_FogColor == o.m_FogColor && m_FogFactor == o.m_FogFactor && m_FogMax == o.m_FogMax && m_Brightness == o.m_Brightness && m_Contrast == o.m_Contrast && m_Saturation == o.m_Saturation && m_Bloom == o.m_Bloom; } bool operator!=(const CLightEnv& o) const { return !(*this == o); } private: + friend class CMapWriter; + friend class CMapReader; + friend class CXMLReader; + + /** + * Height of sun above the horizon, in radians. + * For example, an elevation of M_PI/2 means the sun is straight up. + */ + float m_Elevation; + + /** + * Direction of sun on the compass, in radians. + * For example, a rotation of zero means the sun is in the direction (0,0,-1) + * and a rotation of M_PI/2 means the sun is in the direction (1,0,0) (not taking + * elevation into account). + */ + float m_Rotation; + + /** + * Vector corresponding to m_Elevation and m_Rotation. + * Updated by CalculateSunDirection. + */ + CVector3D m_SunDir; + void CalculateSunDirection(); }; #endif // INCLUDED_LIGHTENV Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 22402) +++ ps/trunk/source/renderer/Renderer.cpp (revision 22403) @@ -1,2153 +1,2150 @@ /* Copyright (C) 2019 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 #include #include #include #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/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PostprocManager.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" 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; 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) { } /** * Load the OpenGL projection and modelview matrices and the viewport according * to the given camera. */ void SetOpenGLCamera(const CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj = camera.GetProjection(); #if CONFIG2_GLES #warning TODO: fix CRenderer camera handling for GLES (do not use global matrixes) #else glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); #endif g_Renderer.SetViewport(camera.GetViewPort()); } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) { 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_Renderer.m_Options.m_GPUSkinning) { 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_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0; m_DisplayTerrainPriorities = false; m_SkipSubmit = false; m_Options.m_NoVBO = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_Shadows = false; m_Options.m_WaterEffects = false; m_Options.m_WaterFancyEffects = false; m_Options.m_WaterRealDepth = false; m_Options.m_WaterRefraction = false; m_Options.m_WaterReflection = false; m_Options.m_WaterShadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; m_Options.m_Particles = false; m_Options.m_Silhouettes = false; m_Options.m_PreferGLSL = false; m_Options.m_Fog = false; m_Options.m_ForceAlphaTest = false; m_Options.m_GPUSkinning = false; m_Options.m_SmoothLOS = false; m_Options.m_Postproc = false; m_Options.m_ShowSky = false; m_Options.m_DisplayFrustum = false; // TODO: be more consistent in use of the config system CFG_GET_VAL("preferglsl", m_Options.m_PreferGLSL); CFG_GET_VAL("forcealphatest", m_Options.m_ForceAlphaTest); CFG_GET_VAL("gpuskinning", m_Options.m_GPUSkinning); CFG_GET_VAL("smoothlos", m_Options.m_SmoothLOS); CFG_GET_VAL("postproc", m_Options.m_Postproc); CStr skystring = "0 0 0"; CColor skycolor; CFG_GET_VAL("skycolor", skystring); if (skycolor.ParseString(skystring, 255.f)) SetClearColor(skycolor.AsSColor4ub()); #if CONFIG2_GLES // Override config option since GLES only supports GLSL m_Options.m_PreferGLSL = true; #endif m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; 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 (!m_Options.m_NoVBO && 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 (GetRenderPath() == RP_SHADER && m_Caps.m_ARBProgram) defines.Add(str_SYS_HAS_ARB, str_1); if (GetRenderPath() == RP_SHADER && m_Caps.m_VertexShader && m_Caps.m_FragmentShader) defines.Add(str_SYS_HAS_GLSL, str_1); if (m_Options.m_PreferGLSL) 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 && m_Options.m_Shadows) { m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) m->globalContext.Add(str_USE_FP_SHADOW, str_1); if (m_Options.m_ShadowPCF) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif } - if (m_LightEnv) - m->globalContext.Add(CStrIntern("LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()), str_1); - if (m_Options.m_PreferGLSL && m_Options.m_Fog) m->globalContext.Add(str_USE_FOG, str_1); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); bool cpuLighting = (GetRenderPath() == RP_FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting)); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, m_Options.m_PreferGLSL)); if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, m_Options.m_PreferGLSL)); 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)); } // Use instancing renderers in shader mode if (GetRenderPath() == RP_SHADER) { m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); } else { m->Model.NormalUnskinned = m->Model.NormalSkinned; m->Model.TranspUnskinned = m->Model.TranspSkinned; } 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(m_Options.m_RenderPath); RecomputeSystemShaderDefines(); // Let component renderers perform one-time initialization after graphics capabilities and // the shader path have been determined. m->overlayRenderer.Initialize(); if (m_Options.m_Postproc) 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(); } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { // Don't do anything if the option didn't change from its previous value. if (value == GetOptionBool(opt)) return; switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO = value; break; case OPT_SHADOWS: m_Options.m_Shadows = value; MakeShadersDirty(); break; case OPT_WATEREFFECTS: m_Options.m_WaterEffects = value; break; case OPT_WATERFANCYEFFECTS: m_Options.m_WaterFancyEffects = value; break; case OPT_WATERREALDEPTH: m_Options.m_WaterRealDepth = value; break; case OPT_WATERREFLECTION: m_Options.m_WaterReflection = value; break; case OPT_WATERREFRACTION: m_Options.m_WaterRefraction = value; break; case OPT_SHADOWSONWATER: m_Options.m_WaterShadows = value; break; case OPT_SHADOWPCF: m_Options.m_ShadowPCF = value; MakeShadersDirty(); break; case OPT_PARTICLES: m_Options.m_Particles = value; break; case OPT_PREFERGLSL: m_Options.m_PreferGLSL = value; MakeShadersDirty(); RecomputeSystemShaderDefines(); break; case OPT_FOG: m_Options.m_Fog = value; MakeShadersDirty(); break; case OPT_SILHOUETTES: m_Options.m_Silhouettes = value; break; case OPT_SHOWSKY: m_Options.m_ShowSky = value; break; case OPT_SMOOTHLOS: m_Options.m_SmoothLOS = value; break; case OPT_POSTPROC: m_Options.m_Postproc = value; break; case OPT_DISPLAYFRUSTUM: m_Options.m_DisplayFrustum = value; break; default: debug_warn(L"CRenderer::SetOptionBool: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_WATEREFFECTS: return m_Options.m_WaterEffects; case OPT_WATERFANCYEFFECTS: return m_Options.m_WaterFancyEffects; case OPT_WATERREALDEPTH: return m_Options.m_WaterRealDepth; case OPT_WATERREFLECTION: return m_Options.m_WaterReflection; case OPT_WATERREFRACTION: return m_Options.m_WaterRefraction; case OPT_SHADOWSONWATER: return m_Options.m_WaterShadows; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; case OPT_PARTICLES: return m_Options.m_Particles; case OPT_PREFERGLSL: return m_Options.m_PreferGLSL; case OPT_FOG: return m_Options.m_Fog; case OPT_SILHOUETTES: return m_Options.m_Silhouettes; case OPT_SHOWSKY: return m_Options.m_ShowSky; case OPT_SMOOTHLOS: return m_Options.m_SmoothLOS; case OPT_POSTPROC: return m_Options.m_Postproc; case OPT_DISPLAYFRUSTUM: return m_Options.m_DisplayFrustum; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // 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. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL)) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL))) { LOGWARNING("Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.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); } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_SHADER: return "shader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) { if (name == "fixed") return RP_FIXED; if (name == "shader") return RP_SHADER; if (name == "default") return RP_DEFAULT; LOGWARNING("Unknown render path name '%s', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // 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(); m->SetOpenGLCamera(m_ViewCamera); } 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 if (GetRenderPath() == RP_SHADER) m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0); else m->terrainRenderer.RenderTerrain(cullGroup); #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); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(cullGroup); // set color for outline glColor3f(0, 0, 1); 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.m_Orientation.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; float fov = m_ViewCamera.GetFOV(); // 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 fov *= 1.05f; 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(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; 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; float fov = m_ViewCamera.GetFOV(); // 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 fov *= 1.05f; 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(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); m->SetOpenGLCamera(m_ViewCamera); // 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 (!m_Options.m_WaterReflection) { 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 (m_Options.m_Particles) { RenderParticles(CULL_REFLECTIONS); ogl_WarnIfError(); } glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); 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); m->SetOpenGLCamera(m_ViewCamera); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = 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_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; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } 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) { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 0.5f, 0.0f); m->particleRenderer.RenderParticles(true); CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); shaderTech->BeginPass(); CShaderProgramPtr shader = shaderTech->GetShader(); shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f); shader->Uniform(str_transform, m_ViewCamera.GetViewProjection()); m->particleRenderer.RenderBounds(cullGroup, shader); shaderTech->EndPass(); 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(); if (m_Options.m_Postproc) { m->postprocManager.Initialize(); m->postprocManager.CaptureRenderOutput(); } CShaderDefines context = m->globalContext; int cullGroup = CULL_DEFAULT; ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // 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 && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(context); } { 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); } ogl_WarnIfError(); if (m_WaterManager->m_RenderWater) { if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); RenderReflections(context, waterScissor); if (m_Options.m_WaterRefraction) RenderRefractions(context, waterScissor); } } if (m_Options.m_ShowSky) { 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) { // 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 { // 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 (m_Options.m_Particles) { RenderParticles(cullGroup); ogl_WarnIfError(); } if (m_Options.m_Postproc) { m->postprocManager.ApplyPostproc(); m->postprocManager.ReleaseRenderOutput(); } if (m_Options.m_Silhouettes) { RenderSilhouettes(context); } #if !CONFIG2_GLES // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif // render debug lines if (m_Options.m_DisplayFrustum) { DisplayFrustum(); m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); ogl_WarnIfError(); } 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); { PROFILE3("error check"); int err = glGetError(); if (err) { ONCE(LOGERROR("CRenderer::EndFrame: GL errors %s (%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); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) 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 (m_Options.m_Silhouettes) { 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 && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { 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()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { if (m_Options.m_WaterReflection) { m_CurrentCullGroup = CULL_REFLECTIONS; CCamera reflectionCamera; ComputeReflectionCamera(reflectionCamera, waterScissor); scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); } if (m_Options.m_WaterRefraction) { 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; (void)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++) (void)ogl_tex_free(textures[i]); // upload the composite texture Tex t; (void)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; } (void)da_free(&da);*/ m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); (void)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; } 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(); }