/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "openglrenderer.h" #include "color.h" #include "Globals.h" #include "Camera.h" #include "Timer.h" #include "simulation.h" #include "simulationtime.h" #include "Train.h" #include "DynObj.h" #include "AnimModel.h" #include "Traction.h" #include "application.h" #include "Logs.h" #include "utilities.h" int const EU07_PICKBUFFERSIZE { 1024 }; // size of (square) textures bound with the pick framebuffer int const EU07_ENVIRONMENTBUFFERSIZE { 256 }; // size of (square) environmental cube map texture float const EU07_OPACITYDEFAULT { 0.5f }; bool opengl_renderer::Init( GLFWwindow *Window ) { if( false == Init_caps() ) { return false; } m_window = Window; ::glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); ::glPixelStorei( GL_PACK_ALIGNMENT, 1 ); glClearDepth( 1.0f ); glClearColor( 51.0f / 255.0f, 102.0f / 255.0f, 85.0f / 255.0f, 1.0f ); // initial background Color glPolygonMode( GL_FRONT, GL_FILL ); glFrontFace( GL_CCW ); // Counter clock-wise polygons face out glEnable( GL_CULL_FACE ); // Cull back-facing triangles glShadeModel( GL_SMOOTH ); // Enable Smooth Shading m_geometry.units().texture = ( Global.BasicRenderer ? std::vector{ m_diffusetextureunit } : std::vector{ m_normaltextureunit, m_diffusetextureunit } ); m_textures.assign_units( m_helpertextureunit, m_shadowtextureunit, m_normaltextureunit, m_diffusetextureunit ); // TODO: add reflections unit ui_layer::set_unit( m_diffusetextureunit ); m_precipitationrenderer.set_unit( m_diffusetextureunit ); select_unit( m_diffusetextureunit ); ::glDepthFunc( GL_LEQUAL ); glEnable( GL_DEPTH_TEST ); glAlphaFunc( GL_GREATER, 0.f ); glEnable( GL_ALPHA_TEST ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); glEnable( GL_TEXTURE_2D ); // Enable Texture Mapping glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); // Really Nice Perspective Calculations glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); glHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); glLineWidth( 1.0f ); glPointSize( 3.0f ); glEnable( GL_POINT_SMOOTH ); ::glLightModeli( GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR ); ::glMaterialf( GL_FRONT, GL_SHININESS, 15.0f ); if( true == Global.ScaleSpecularValues ) { m_specularopaquescalefactor = 0.25f; m_speculartranslucentscalefactor = 1.5f; } ::glEnable( GL_COLOR_MATERIAL ); ::glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); // setup lighting ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr(m_baseambient) ); ::glEnable( GL_LIGHTING ); ::glEnable( opengl_renderer::sunlight ); // rgb value for 5780 kelvin Global.DayLight.diffuse[ 0 ] = 255.0f / 255.0f; Global.DayLight.diffuse[ 1 ] = 242.0f / 255.0f; Global.DayLight.diffuse[ 2 ] = 231.0f / 255.0f; Global.DayLight.is_directional = true; m_sunlight.id = opengl_renderer::sunlight; // ::glLightf( opengl_renderer::sunlight, GL_SPOT_CUTOFF, 90.0f ); // create dynamic light pool for( int idx = 0; idx < Global.DynamicLightCount; ++idx ) { opengl_light light; light.id = GL_LIGHT1 + idx; light.is_directional = false; ::glEnable( light.id ); // experimental intel chipset fix ::glLightf( light.id, GL_SPOT_CUTOFF, 7.5f ); ::glLightf( light.id, GL_SPOT_EXPONENT, 7.5f ); ::glLightf( light.id, GL_CONSTANT_ATTENUATION, 0.0f ); ::glLightf( light.id, GL_LINEAR_ATTENUATION, 0.035f ); ::glDisable( light.id ); // experimental intel chipset fix m_lights.emplace_back( light ); } // preload some common textures WriteLog( "Loading common gfx data..." ); m_glaretexture = Fetch_Texture( "fx/lightglare" ); m_suntexture = Fetch_Texture( "fx/sun" ); m_moontexture = Fetch_Texture( "fx/moon" ); if( m_helpertextureunit >= 0 ) { m_reflectiontexture = Fetch_Texture( "fx/reflections" ); } m_smoketexture = Fetch_Texture( "fx/smoke" ); WriteLog( "...gfx data pre-loading done" ); #ifdef EU07_USE_PICKING_FRAMEBUFFER // pick buffer resources if( true == m_framebuffersupport ) { // try to create the pick buffer. RGBA8 2D texture, 24 bit depth texture, 1024x1024 (height of 512 would suffice but, eh) // texture ::glGenTextures( 1, &m_picktexture ); ::glBindTexture( GL_TEXTURE_2D, m_picktexture ); ::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); // depth buffer ::glGenRenderbuffersEXT( 1, &m_pickdepthbuffer ); ::glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_pickdepthbuffer ); ::glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE ); // create and assemble the framebuffer ::glGenFramebuffersEXT( 1, &m_pickframebuffer ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer ); ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_picktexture, 0 ); ::glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_pickdepthbuffer ); // check if we got it working GLenum status = ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) { WriteLog( "Picking framebuffer setup complete" ); } else{ ErrorLog( "Picking framebuffer setup failed" ); m_framebuffersupport = false; } ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target for now } #endif // shadowmap resources if( ( true == Global.RenderShadows ) && ( true == m_framebuffersupport ) ) { // primary shadow map { // texture: ::glGenTextures( 1, &m_shadowtexture ); ::glBindTexture( GL_TEXTURE_2D, m_shadowtexture ); // allocate memory ::glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_shadowbuffersize, m_shadowbuffersize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); // setup parameters ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // enable shadow comparison: true (ie not in shadow) if r<=texture... ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL ); ::glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE ); ::glBindTexture( GL_TEXTURE_2D, 0 ); #ifdef EU07_USE_DEBUG_SHADOWMAP ::glGenTextures( 1, &m_shadowdebugtexture ); ::glBindTexture( GL_TEXTURE_2D, m_shadowdebugtexture ); // allocate memory ::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_shadowbuffersize, m_shadowbuffersize, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL ); // setup parameters ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glBindTexture( GL_TEXTURE_2D, 0 ); #endif // create and assemble the framebuffer ::glGenFramebuffersEXT( 1, &m_shadowframebuffer ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_shadowframebuffer ); #ifdef EU07_USE_DEBUG_SHADOWMAP ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_shadowdebugtexture, 0 ); #else ::glDrawBuffer( GL_NONE ); // we won't be rendering colour data, so can skip the colour attachment ::glReadBuffer( GL_NONE ); #endif ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_shadowtexture, 0 ); // check if we got it working GLenum const status{ ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ) }; if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) { WriteLog( "Shadows framebuffer setup complete" ); } else { ErrorLog( "Shadows framebuffer setup failed" ); Global.RenderShadows = false; } // switch back to primary render target for now ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } // cab shadow map { // NOTE: using separate framebuffer as more efficient arrangement than swapping attachments on a single one // TODO: for shader-based implementation use instead a quarter of the CSM // texture: ::glGenTextures( 1, &m_cabshadowtexture ); ::glBindTexture( GL_TEXTURE_2D, m_cabshadowtexture ); // allocate memory ::glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_shadowbuffersize / 2, m_shadowbuffersize / 2, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); // setup parameters ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // enable shadow comparison: true (ie not in shadow) if r<=texture... ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL ); ::glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE ); ::glBindTexture( GL_TEXTURE_2D, 0 ); #ifdef EU07_USE_DEBUG_CABSHADOWMAP ::glGenTextures( 1, &m_cabshadowdebugtexture ); ::glBindTexture( GL_TEXTURE_2D, m_cabshadowdebugtexture ); // allocate memory ::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_shadowbuffersize / 2, m_shadowbuffersize / 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL ); // setup parameters ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glBindTexture( GL_TEXTURE_2D, 0 ); #endif // create and assemble the framebuffer ::glGenFramebuffersEXT( 1, &m_cabshadowframebuffer ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_cabshadowframebuffer ); #ifdef EU07_USE_DEBUG_CABSHADOWMAP ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_cabshadowdebugtexture, 0 ); #else ::glDrawBuffer( GL_NONE ); // we won't be rendering colour data, so can skip the colour attachment ::glReadBuffer( GL_NONE ); #endif ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_cabshadowtexture, 0 ); // check if we got it working GLenum const status{ ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ) }; if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) { WriteLog( "Cab shadows framebuffer setup complete" ); } else { ErrorLog( "Cab shadows framebuffer setup failed" ); Global.RenderShadows = false; } } // switch back to primary render target for now ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } // environment cube map resources if( ( false == Global.BasicRenderer ) && ( true == m_framebuffersupport ) ) { // texture: ::glGenTextures( 1, &m_environmentcubetexture ); ::glBindTexture( GL_TEXTURE_CUBE_MAP, m_environmentcubetexture ); // allocate space for( GLuint faceindex = GL_TEXTURE_CUBE_MAP_POSITIVE_X; faceindex < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; ++faceindex ) { ::glTexImage2D( faceindex, 0, GL_RGBA8, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL ); } // setup parameters ::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE ); // depth buffer ::glGenRenderbuffersEXT( 1, &m_environmentdepthbuffer ); ::glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_environmentdepthbuffer ); ::glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE ); // create and assemble the framebuffer ::glGenFramebuffersEXT( 1, &m_environmentframebuffer ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_environmentframebuffer ); ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, m_environmentcubetexture, 0 ); ::glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_environmentdepthbuffer ); // check if we got it working GLenum status = ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) { WriteLog( "Reflections framebuffer setup complete" ); m_environmentcubetexturesupport = true; } else { ErrorLog( "Reflections framebuffer setup failed" ); m_environmentcubetexturesupport = false; } ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target for now } // prepare basic geometry chunks auto const geometrybank = m_geometry.create_bank(); float const size = 2.5f; m_billboardgeometry = m_geometry.create_chunk( gfx::vertex_array{ { { -size, size, 0.f }, glm::vec3(), { 1.f, 1.f } }, { { size, size, 0.f }, glm::vec3(), { 0.f, 1.f } }, { { -size, -size, 0.f }, glm::vec3(), { 1.f, 0.f } }, { { size, -size, 0.f }, glm::vec3(), { 0.f, 0.f } } }, geometrybank, GL_TRIANGLE_STRIP ); // prepare debug mode objects m_quadric = ::gluNewQuadric(); ::gluQuadricNormals( m_quadric, GLU_FLAT ); return true; } bool opengl_renderer::Render() { Timer::subsystem.gfx_total.stop(); Timer::subsystem.gfx_total.start(); // note: gfx_total is actually frame total, clean this up Timer::subsystem.gfx_color.start(); // fetch simulation data if( simulation::is_ready ) { m_sunlight = Global.DayLight; // quantize sun angle to reduce shadow crawl auto const quantizationstep { 0.004f }; m_sunlight.direction = glm::normalize( quantizationstep * glm::roundEven( m_sunlight.direction * ( 1.f / quantizationstep ) ) ); } // generate new frame m_renderpass.draw_mode = rendermode::none; // force setup anew opengl_texture::reset_unit_cache(); m_debugtimestext.clear(); m_debugstats = debug_stats(); Render_pass( rendermode::color ); Timer::subsystem.gfx_color.stop(); // add user interface setup_units( true, false, false ); ::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); ::glClientActiveTexture( m_diffusetextureunit ); ::glBindBuffer( GL_ARRAY_BUFFER, 0 ); Application.render_ui(); ::glPopClientAttrib(); if( Global.bUseVBO ) { // swapbuffers() will unbind current buffers so we prepare for it on our end gfx::opengl_vbogeometrybank::reset(); } Timer::subsystem.gfx_swap.start(); glfwSwapBuffers( m_window ); Timer::subsystem.gfx_swap.stop(); m_drawcount = m_cellqueue.size(); m_debugtimestext += "color: " + to_string( Timer::subsystem.gfx_color.average(), 2 ) + " msec (" + std::to_string( m_cellqueue.size() ) + " sectors)\n" + "gpu side: " + to_string( Timer::subsystem.gfx_swap.average(), 2 ) + " msec\n" + "frame total: " + to_string( Timer::subsystem.gfx_color.average() + Timer::subsystem.gfx_swap.average(), 2 ) + " msec"; m_debugstatstext = "drawcalls: " + to_string( m_debugstats.drawcalls ) + "\n" + " vehicles: " + to_string( m_debugstats.dynamics ) + "\n" + " models: " + to_string( m_debugstats.models ) + "\n" + " submodels: " + to_string( m_debugstats.submodels ) + "\n" + " paths: " + to_string( m_debugstats.paths ) + "\n" + " shapes: " + to_string( m_debugstats.shapes ) + "\n" + " traction: " + to_string( m_debugstats.traction ) + "\n" + " lines: " + to_string( m_debugstats.lines ) + "\n" + "particles: " + to_string( m_debugstats.particles ); ++m_framestamp; return true; // for now always succeed } // runs jobs needed to generate graphics for specified render pass void opengl_renderer::Render_pass( rendermode const Mode ) { #ifdef EU07_USE_DEBUG_CAMERA // setup world camera for potential visualization setup_pass( m_worldcamera, rendermode::color, 0.f, 1.0, true ); #endif setup_pass( m_renderpass, Mode ); switch( m_renderpass.draw_mode ) { case rendermode::color: { m_colorpass = m_renderpass; if( ( true == Global.RenderShadows ) && ( false == Global.bWireFrame ) && ( true == simulation::is_ready ) && ( m_shadowcolor != colors::white ) ) { // run shadowmaps pass before color Timer::subsystem.gfx_shadows.start(); if( false == FreeFlyModeFlag ) { Render_pass( rendermode::cabshadows ); } Render_pass( rendermode::shadows ); Timer::subsystem.gfx_shadows.stop(); m_debugtimestext += "shadows: " + to_string( Timer::subsystem.gfx_shadows.average(), 2 ) + " msec (" + std::to_string( m_cellqueue.size() ) + " sectors)\n"; #ifdef EU07_USE_DEBUG_SHADOWMAP UILayer.set_texture( m_shadowdebugtexture ); #endif #ifdef EU07_USE_DEBUG_CABSHADOWMAP UILayer.set_texture( m_cabshadowdebugtexture ); #endif setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack // setup shadowmap matrices m_shadowtexturematrix = glm::mat4 { 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f } // bias from [-1, 1] to [0, 1] * m_shadowpass.camera.projection() // during colour pass coordinates are moved from camera-centric to light-centric, essentially the difference between these two origins * glm::translate( glm::mat4{ glm::mat3{ m_shadowpass.camera.modelview() } }, glm::vec3{ m_renderpass.camera.position() - m_shadowpass.camera.position() } ); if( false == FreeFlyModeFlag ) { m_cabshadowtexturematrix = glm::mat4{ 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f } // bias from [-1, 1] to [0, 1] * m_cabshadowpass.camera.projection() // during colour pass coordinates are moved from camera-centric to light-centric, essentially the difference between these two origins * glm::translate( glm::mat4{ glm::mat3{ m_cabshadowpass.camera.modelview() } }, glm::vec3{ m_renderpass.camera.position() - m_cabshadowpass.camera.position() } ); } } if( ( true == m_environmentcubetexturesupport ) && ( true == simulation::is_ready ) ) { // potentially update environmental cube map if( true == Render_reflections() ) { setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack } } ::glViewport( 0, 0, Global.iWindowWidth, Global.iWindowHeight ); if( simulation::is_ready ) { auto const skydomecolour = simulation::Environment.m_skydome.GetAverageColor(); ::glClearColor( skydomecolour.x, skydomecolour.y, skydomecolour.z, 0.f ); // kolor nieba } else { ::glClearColor( 51.0f / 255.f, 102.0f / 255.f, 85.0f / 255.f, 1.f ); // initial background Color } ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); if( simulation::is_ready ) { // setup setup_matrices(); // render setup_drawing( true ); setup_units( true, false, false ); Render( &simulation::Environment ); // opaque parts... setup_drawing( false ); setup_units( true, true, true ); #ifdef EU07_USE_DEBUG_CAMERA if( DebugModeFlag ) { // draw light frustum ::glLineWidth( 2.f ); ::glColor4f( 1.f, 0.9f, 0.8f, 1.f ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_TEXTURE_2D ); if( ( true == Global.RenderShadows ) && ( false == Global.bWireFrame ) ) { m_shadowpass.camera.draw( m_renderpass.camera.position() - m_shadowpass.camera.position() ); } if( DebugCameraFlag ) { ::glColor4f( 0.8f, 1.f, 0.9f, 1.f ); m_worldcamera.camera.draw( m_renderpass.camera.position() - m_worldcamera.camera.position() ); } ::glLineWidth( 1.f ); ::glEnable( GL_LIGHTING ); ::glEnable( GL_TEXTURE_2D ); } #endif // without rain/snow we can render the cab early to limit the overdraw if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.f ) ) { // precipitation happens when overcast is in 1-2 range #ifdef EU07_DISABLECABREFLECTIONS switch_units( true, true, false ); #endif setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix ); // cache shadow colour in case we need to account for cab light auto const shadowcolor { m_shadowcolor }; auto const *vehicle{ simulation::Train->Dynamic() }; if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) ); } Render_cab( vehicle, vehicle->InteriorLightLevel, false ); if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( shadowcolor ); } } switch_units( true, true, true ); setup_shadow_map( m_shadowtexture, m_shadowtexturematrix ); Render( simulation::Region ); // ...translucent parts setup_drawing( true ); Render_Alpha( simulation::Region ); // particles Render_particles(); // precipitation; done at the end, only before cab render Render_precipitation(); // cab render if( false == FreeFlyModeFlag ) { #ifdef EU07_DISABLECABREFLECTIONS switch_units( true, true, false ); #endif setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix ); // cache shadow colour in case we need to account for cab light auto const shadowcolor{ m_shadowcolor }; auto *vehicle { simulation::Train->Dynamic() }; if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) ); } if( Global.Overcast > 1.f ) { // with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab setup_drawing( false ); Render_cab( vehicle, vehicle->InteriorLightLevel, false ); setup_drawing( true ); } Render_cab( vehicle, vehicle->InteriorLightLevel, true ); if( vehicle->InteriorLightLevel > 0.f ) { setup_shadow_color( shadowcolor ); } // with the cab in place we can (finally) safely draw translucent part of the occupied vehicle Render_Alpha( vehicle ); } if( m_environmentcubetexturesupport ) { // restore default texture matrix for reflections cube map select_unit( m_helpertextureunit ); ::glMatrixMode( GL_TEXTURE ); // ::glPopMatrix(); ::glLoadIdentity(); select_unit( m_diffusetextureunit ); ::glMatrixMode( GL_MODELVIEW ); } } break; } case rendermode::shadows: { if( simulation::is_ready ) { // setup ::glEnable( GL_POLYGON_OFFSET_FILL ); // alleviate depth-fighting ::glPolygonOffset( 1.f, 1.f ); // main shadowmap ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_shadowframebuffer ); ::glViewport( 0, 0, m_shadowbuffersize, m_shadowbuffersize ); #ifdef EU07_USE_DEBUG_SHADOWMAP ::glClearColor( 0.f / 255.f, 0.f / 255.f, 0.f / 255.f, 1.f ); // initial background Color ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); #else ::glClear( GL_DEPTH_BUFFER_BIT ); #endif ::glScissor( 1, 1, m_shadowbuffersize - 2, m_shadowbuffersize - 2 ); ::glEnable( GL_SCISSOR_TEST ); setup_matrices(); // render // opaque parts... setup_drawing( false ); #ifdef EU07_USE_DEBUG_SHADOWMAP setup_units( true, false, false ); #else setup_units( false, false, false ); #endif Render( simulation::Region ); m_shadowpass = m_renderpass; // post-render restore ::glDisable( GL_SCISSOR_TEST ); ::glDisable( GL_POLYGON_OFFSET_FILL ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target } break; } case rendermode::cabshadows: { if( ( simulation::is_ready ) && ( false == FreeFlyModeFlag ) ) { // setup ::glEnable( GL_POLYGON_OFFSET_FILL ); // alleviate depth-fighting ::glPolygonOffset( 1.f, 1.f ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_cabshadowframebuffer ); ::glViewport( 0, 0, m_shadowbuffersize / 2, m_shadowbuffersize / 2 ); #ifdef EU07_USE_DEBUG_CABSHADOWMAP ::glClearColor( 0.f / 255.f, 0.f / 255.f, 0.f / 255.f, 1.f ); // initial background Color ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); #else ::glClear( GL_DEPTH_BUFFER_BIT ); #endif ::glScissor( 1, 1, m_shadowbuffersize / 2 - 2, m_shadowbuffersize / 2 - 2 ); ::glEnable( GL_SCISSOR_TEST ); setup_matrices(); // render // opaque parts... setup_drawing( false ); #ifdef EU07_USE_DEBUG_CABSHADOWMAP setup_units( true, false, false ); #else setup_units( false, false, false ); #endif ::glDisable( GL_CULL_FACE ); if( Global.RenderCabShadowsRange > 0 ) { Render( simulation::Region ); } Render_cab( simulation::Train->Dynamic(), 0.f, false ); Render_cab( simulation::Train->Dynamic(), 0.f, true ); m_cabshadowpass = m_renderpass; // post-render restore ::glDisable( GL_SCISSOR_TEST ); ::glEnable( GL_CULL_FACE ); ::glDisable( GL_POLYGON_OFFSET_FILL ); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target } break; } case rendermode::reflections: { // NOTE: buffer and viewport setup in this mode is handled by the wrapper method ::glClearColor( 0.f, 0.f, 0.f, 1.f ); ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); if( simulation::is_ready ) { ::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE ); // setup setup_matrices(); // render setup_drawing( true ); setup_units( true, false, false ); Render( &simulation::Environment ); // opaque parts... setup_drawing( false ); setup_units( true, true, true ); setup_shadow_map( m_shadowtexture, m_shadowtexturematrix ); Render( simulation::Region ); /* // reflections are limited to sky and ground only, the update rate is too low for anything else // ...translucent parts setup_drawing( true ); Render_Alpha( &World.Ground ); // cab render is performed without shadows, due to low resolution and number of models without windows :| switch_units( true, false, false ); // cab render is done in translucent phase to deal with badly configured vehicles if( World.Train != nullptr ) { Render_cab( World.Train->Dynamic() ); } */ ::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); } break; } case rendermode::pickcontrols: { if( simulation::is_ready ) { // setup #ifdef EU07_USE_PICKING_FRAMEBUFFER ::glViewport( 0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE ); #endif ::glClearColor( 0.f, 0.f, 0.f, 1.f ); ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); m_pickcontrolsitems.clear(); setup_matrices(); // render // opaque parts... setup_drawing( false ); setup_units( false, false, false ); // cab render skips translucent parts, so we can do it here if( simulation::Train != nullptr ) { Render_cab( simulation::Train->Dynamic(), 0.f ); Render( simulation::Train->Dynamic() ); } // post-render cleanup } break; } case rendermode::pickscenery: { if( simulation::is_ready ) { // setup m_picksceneryitems.clear(); #ifdef EU07_USE_PICKING_FRAMEBUFFER ::glViewport( 0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE ); #endif ::glClearColor( 0.f, 0.f, 0.f, 1.f ); ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); setup_matrices(); // render // opaque parts... setup_drawing( false ); setup_units( false, false, false ); Render( simulation::Region ); // post-render cleanup } break; } default: { break; } } } // creates dynamic environment cubemap bool opengl_renderer::Render_reflections() { if( Global.ReflectionUpdateInterval == 0 ) { return false; } auto const timestamp { Timer::GetTime() }; if( ( timestamp - m_environmentupdatetime < Global.ReflectionUpdateInterval ) && ( glm::length( m_renderpass.camera.position() - m_environmentupdatelocation ) < 1000.0 ) ) { // run update every 5+ mins of simulation time, or at least 1km from the last location return false; } m_environmentupdatetime = timestamp; m_environmentupdatelocation = m_renderpass.camera.position(); ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_environmentframebuffer ); ::glViewport( 0, 0, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE ); for( m_environmentcubetextureface = GL_TEXTURE_CUBE_MAP_POSITIVE_X; m_environmentcubetextureface < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; ++m_environmentcubetextureface ) { ::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, m_environmentcubetextureface, m_environmentcubetexture, 0 ); Render_pass( rendermode::reflections ); } ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); return true; } void opengl_renderer::setup_pass( renderpass_config &Config, rendermode const Mode, float const Znear, float const Zfar, bool const Ignoredebug ) { Config.draw_mode = Mode; if( false == simulation::is_ready ) { return; } // setup draw range switch( Mode ) { case rendermode::color: { Config.draw_range = Global.BaseDrawRange; break; } case rendermode::shadows: { Config.draw_range = Global.BaseDrawRange * 0.5f; break; } case rendermode::cabshadows: { Config.draw_range = ( Global.RenderCabShadowsRange > 0 ? clamp( Global.RenderCabShadowsRange, 5, 100 ) : simulation::Train->Occupied()->Dim.L ); break; } case rendermode::reflections: { Config.draw_range = Global.BaseDrawRange; break; } case rendermode::pickcontrols: { Config.draw_range = 50.f; break; } case rendermode::pickscenery: { Config.draw_range = Global.BaseDrawRange * 0.5f; break; } default: { Config.draw_range = 0.f; break; } } // setup camera auto &camera = Config.camera; camera.projection() = glm::mat4( 1.f ); glm::dmat4 viewmatrix( 1.0 ); switch( Mode ) { case rendermode::color: { // modelview if( ( false == DebugCameraFlag ) || ( true == Ignoredebug ) ) { camera.position() = Global.pCamera.Pos; Global.pCamera.SetMatrix( viewmatrix ); } else { camera.position() = Global.pDebugCamera.Pos; Global.pDebugCamera.SetMatrix( viewmatrix ); } // projection auto const zfar = Config.draw_range * Global.fDistanceFactor * Zfar; auto const znear = ( Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor ); camera.projection() *= glm::perspective( glm::radians( Global.FieldOfView / Global.ZoomFactor ), std::max( 1.f, (float)Global.iWindowWidth ) / std::max( 1.f, (float)Global.iWindowHeight ), znear, zfar ); /* m_sunandviewangle = glm::dot( m_sunlight.direction, glm::vec3( 0.f, 0.f, -1.f ) * glm::mat3( viewmatrix ) ); */ break; } case rendermode::shadows: { // calculate lightview boundaries based on relevant area of the world camera frustum: // ...setup chunk of frustum we're interested in... auto const zfar = std::min( 1.f, Global.shadowtune.depth / ( Global.BaseDrawRange * Global.fDistanceFactor ) * std::max( 1.f, Global.ZoomFactor * 0.5f ) ); renderpass_config worldview; setup_pass( worldview, rendermode::color, 0.f, zfar, true ); auto &frustumchunkshapepoints = worldview.camera.frustum_points(); // ...modelview matrix: determine the centre of frustum chunk in world space... glm::vec3 frustumchunkmin, frustumchunkmax; bounding_box( frustumchunkmin, frustumchunkmax, std::begin( frustumchunkshapepoints ), std::end( frustumchunkshapepoints ) ); auto const frustumchunkcentre = ( frustumchunkmin + frustumchunkmax ) * 0.5f; // ...cap the vertical angle to keep shadows from getting too long... auto const lightvector = glm::normalize( glm::vec3{ m_sunlight.direction.x, std::min( m_sunlight.direction.y, -0.2f ), m_sunlight.direction.z } ); // ...place the light source at the calculated centre and setup world space light view matrix... camera.position() = worldview.camera.position() + glm::dvec3{ frustumchunkcentre }; viewmatrix *= glm::lookAt( camera.position(), camera.position() + glm::dvec3{ lightvector }, glm::dvec3{ 0.f, 1.f, 0.f } ); // ...projection matrix: calculate boundaries of the frustum chunk in light space... auto const lightviewmatrix = glm::translate( glm::mat4{ glm::mat3{ viewmatrix } }, -frustumchunkcentre ); for( auto &point : frustumchunkshapepoints ) { point = lightviewmatrix * point; } bounding_box( frustumchunkmin, frustumchunkmax, std::begin( frustumchunkshapepoints ), std::end( frustumchunkshapepoints ) ); // quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes auto const quantizationstep{ std::min( Global.shadowtune.depth, 50.f ) }; frustumchunkmin = quantizationstep * glm::floor( frustumchunkmin * ( 1.f / quantizationstep ) ); frustumchunkmax = quantizationstep * glm::ceil( frustumchunkmax * ( 1.f / quantizationstep ) ); // ...use the dimensions to set up light projection boundaries... // NOTE: since we only have one cascade map stage, we extend the chunk forward/back to catch areas normally covered by other stages camera.projection() *= glm::ortho( frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f ); /* // fixed ortho projection from old build, for quick quality comparisons camera.projection() *= glm::ortho( -Global.shadowtune.width, Global.shadowtune.width, -Global.shadowtune.width, Global.shadowtune.width, -Global.shadowtune.depth, Global.shadowtune.depth ); camera.position() = Global.pCameraPosition - glm::dvec3{ m_sunlight.direction }; if( camera.position().y - Global.pCameraPosition.y < 0.1 ) { camera.position().y = Global.pCameraPosition.y + 0.1; } viewmatrix *= glm::lookAt( camera.position(), glm::dvec3{ Global.pCameraPosition }, glm::dvec3{ 0.f, 1.f, 0.f } ); */ // ... and adjust the projection to sample complete shadow map texels: // get coordinates for a sample texel... auto shadowmaptexel = glm::vec2 { camera.projection() * glm::mat4{ viewmatrix } * glm::vec4{ 0.f, 0.f, 0.f, 1.f } }; // ...convert result from clip space to texture coordinates, and calculate adjustment... shadowmaptexel *= m_shadowbuffersize * 0.5f; auto shadowmapadjustment = glm::round( shadowmaptexel ) - shadowmaptexel; // ...transform coordinate change back to homogenous light space... shadowmapadjustment /= m_shadowbuffersize * 0.5f; // ... and bake the adjustment into the projection matrix camera.projection() = glm::translate( glm::mat4{ 1.f }, glm::vec3{ shadowmapadjustment, 0.f } ) * camera.projection(); break; } case rendermode::cabshadows: { // fixed size cube large enough to enclose a vehicle compartment // modelview auto const lightvector = glm::normalize( glm::vec3{ m_sunlight.direction.x, std::min( m_sunlight.direction.y, -0.2f ), m_sunlight.direction.z } ); camera.position() = Global.pCamera.Pos - glm::dvec3 { lightvector }; viewmatrix *= glm::lookAt( camera.position(), glm::dvec3 { Global.pCamera.Pos }, glm::dvec3 { 0.f, 1.f, 0.f } ); // projection auto const maphalfsize { Config.draw_range * 0.5f }; camera.projection() *= glm::ortho( -maphalfsize, maphalfsize, -maphalfsize, maphalfsize, -Config.draw_range, Config.draw_range ); /* // adjust the projection to sample complete shadow map texels auto shadowmaptexel = glm::vec2 { camera.projection() * glm::mat4{ viewmatrix } * glm::vec4{ 0.f, 0.f, 0.f, 1.f } }; shadowmaptexel *= ( m_shadowbuffersize / 2 ) * 0.5f; auto shadowmapadjustment = glm::round( shadowmaptexel ) - shadowmaptexel; shadowmapadjustment /= ( m_shadowbuffersize / 2 ) * 0.5f; camera.projection() = glm::translate( glm::mat4{ 1.f }, glm::vec3{ shadowmapadjustment, 0.f } ) * camera.projection(); */ break; } case rendermode::reflections: { // modelview camera.position() = ( ( ( true == DebugCameraFlag ) && ( false == Ignoredebug ) ) ? Global.pDebugCamera.Pos : Global.pCamera.Pos ); glm::dvec3 const cubefacetargetvectors[ 6 ] = { { 1.0, 0.0, 0.0 }, { -1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 } }; glm::dvec3 const cubefaceupvectors[ 6 ] = { { 0.0, -1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 }, { 0.0, -1.0, 0.0 }, { 0.0, -1.0, 0.0 } }; auto const cubefaceindex = m_environmentcubetextureface - GL_TEXTURE_CUBE_MAP_POSITIVE_X; viewmatrix *= glm::lookAt( camera.position(), camera.position() + cubefacetargetvectors[ cubefaceindex ], cubefaceupvectors[ cubefaceindex ] ); // projection camera.projection() *= glm::perspective( glm::radians( 90.f ), 1.f, 0.1f * Global.ZoomFactor, Config.draw_range * Global.fDistanceFactor ); break; } case rendermode::pickcontrols: case rendermode::pickscenery: { // TODO: scissor test for pick modes // modelview camera.position() = Global.pCamera.Pos; Global.pCamera.SetMatrix( viewmatrix ); // projection camera.projection() *= glm::perspective( glm::radians( Global.FieldOfView / Global.ZoomFactor ), std::max( 1.f, (float)Global.iWindowWidth ) / std::max( 1.f, (float)Global.iWindowHeight ), 0.1f * Global.ZoomFactor, Config.draw_range * Global.fDistanceFactor ); break; } default: { break; } } camera.modelview() = viewmatrix; camera.update_frustum(); } void opengl_renderer::setup_matrices() { ::glMatrixMode( GL_PROJECTION ); OpenGLMatrices.load_matrix( m_renderpass.camera.projection() ); if( ( m_renderpass.draw_mode == rendermode::color ) && ( m_environmentcubetexturesupport ) ) { // special case, for colour render pass setup texture matrix for reflections cube map select_unit( m_helpertextureunit ); ::glMatrixMode( GL_TEXTURE ); // ::glPushMatrix(); ::glLoadIdentity(); ::glMultMatrixf( glm::value_ptr( glm::inverse( glm::mat4{ glm::mat3{ m_renderpass.camera.modelview() } } ) ) ); select_unit( m_diffusetextureunit ); } // trim modelview matrix just to rotation, since rendering is done in camera-centric world space ::glMatrixMode( GL_MODELVIEW ); OpenGLMatrices.load_matrix( glm::mat4( glm::mat3( m_renderpass.camera.modelview() ) ) ); } void opengl_renderer::setup_drawing( bool const Alpha ) { if( true == Alpha ) { ::glEnable( GL_BLEND ); ::glAlphaFunc( GL_GREATER, 0.f ); } else { ::glDisable( GL_BLEND ); ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); } switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { ::glEnable( GL_LIGHTING ); ::glShadeModel( GL_SMOOTH ); if( Global.iMultisampling ) { ::glEnable( GL_MULTISAMPLE ); } // setup fog if( Global.fFogEnd > 0 ) { // fog setup m_fogrange = Global.fFogEnd / std::max( 1.f, Global.Overcast * 2.f ); ::glFogfv( GL_FOG_COLOR, glm::value_ptr( Global.FogColor ) ); ::glFogf( GL_FOG_DENSITY, static_cast( 1.0 / m_fogrange ) ); ::glEnable( GL_FOG ); } else { ::glDisable( GL_FOG ); } break; } case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickcontrols: case rendermode::pickscenery: { ::glColor4fv( glm::value_ptr( colors::white ) ); ::glDisable( GL_LIGHTING ); ::glShadeModel( GL_FLAT ); if( Global.iMultisampling ) { ::glDisable( GL_MULTISAMPLE ); } ::glDisable( GL_FOG ); break; } default: { break; } } } // configures, enables and disables specified texture units void opengl_renderer::setup_units( bool const Diffuse, bool const Shadows, bool const Reflections ) { // helper texture unit. // darkens previous stage, preparing data for the shadow texture unit to select from if( m_helpertextureunit >= 0 ) { select_unit( m_helpertextureunit ); if( ( true == Reflections ) || ( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) ) { // we need to have texture on the helper for either the reflection and shadow generation (or both) if( true == m_environmentcubetexturesupport ) { // bind dynamic environment cube if it's enabled... // NOTE: environment cube map isn't part of regular texture system, so we use direct bind call here ::glBindTexture( GL_TEXTURE_CUBE_MAP, m_environmentcubetexture ); ::glEnable( GL_TEXTURE_CUBE_MAP ); } else { // ...otherwise fallback on static spherical image m_textures.bind( textureunit::helper, m_reflectiontexture ); ::glEnable( GL_TEXTURE_2D ); } } else { if( true == m_environmentcubetexturesupport ) { ::glDisable( GL_TEXTURE_CUBE_MAP ); } else { ::glDisable( GL_TEXTURE_2D ); } } if( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) { ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, glm::value_ptr( m_shadowcolor ) ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE ); // darken the previous stage ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS ); } else { ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE ); // pass the previous stage colour down the chain ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS ); } if( true == Reflections ) { if( true == m_environmentcubetexturesupport ) { ::glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP ); ::glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP ); ::glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP ); ::glEnable( GL_TEXTURE_GEN_S ); ::glEnable( GL_TEXTURE_GEN_T ); ::glEnable( GL_TEXTURE_GEN_R ); } else { // fixed texture fall back ::glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP ); ::glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP ); ::glEnable( GL_TEXTURE_GEN_S ); ::glEnable( GL_TEXTURE_GEN_T ); } } else { if( true == m_environmentcubetexturesupport ) { ::glDisable( GL_TEXTURE_GEN_S ); ::glDisable( GL_TEXTURE_GEN_T ); ::glDisable( GL_TEXTURE_GEN_R ); } else { ::glDisable( GL_TEXTURE_GEN_S ); ::glDisable( GL_TEXTURE_GEN_T ); } } } // shadow texture unit. // interpolates between primary colour and the previous unit, which should hold darkened variant of the primary colour if( m_shadowtextureunit >= 0 ) { if( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) && ( m_shadowcolor != colors::white ) ) { select_unit( m_shadowtextureunit ); // NOTE: texture and transformation matrix setup are done through separate call ::glEnable( GL_TEXTURE_2D ); // s ::glEnable( GL_TEXTURE_GEN_S ); // t ::glEnable( GL_TEXTURE_GEN_T ); // r ::glEnable( GL_TEXTURE_GEN_R ); // q ::glEnable( GL_TEXTURE_GEN_Q ); ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); // choose between lit and lit * shadow colour, based on depth test ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS ); } else { // turn off shadow map tests select_unit( m_shadowtextureunit ); ::glDisable( GL_TEXTURE_2D ); ::glDisable( GL_TEXTURE_GEN_S ); ::glDisable( GL_TEXTURE_GEN_T ); ::glDisable( GL_TEXTURE_GEN_R ); ::glDisable( GL_TEXTURE_GEN_Q ); } } // reflections/normals texture unit // NOTE: comes after diffuse stage in the operation chain if( m_normaltextureunit >= 0 ) { select_unit( m_normaltextureunit ); if( true == Reflections ) { ::glEnable( GL_TEXTURE_2D ); ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); // blend between object colour and env.reflection ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0 ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE ); // alpha channel of the normal map controls reflection strength ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS ); } else { ::glDisable( GL_TEXTURE_2D ); } } // diffuse texture unit. // NOTE: diffuse texture mapping is never fully disabled, alpha channel information is always included select_unit( m_diffusetextureunit ); ::glEnable( GL_TEXTURE_2D ); if( true == Diffuse ) { // default behaviour, modulate with previous stage ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); /* ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA ); */ } else { // solid colour with texture alpha ::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); /* ::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE ); ::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA ); */ } // update unit state m_unitstate.diffuse = Diffuse; m_unitstate.shadows = Shadows; m_unitstate.reflections = Reflections; } // configures shadow texture unit for specified shadow map and conersion matrix void opengl_renderer::setup_shadow_map( GLuint const Texture, glm::mat4 const &Transformation ) { if( ( m_shadowtextureunit == -1 ) || ( false == Global.RenderShadows ) || ( true == Global.bWireFrame ) || ( m_shadowcolor == colors::white ) ) { // shadows are off return; } select_unit( m_shadowtextureunit ); // NOTE: shadowmap isn't part of regular texture system, so we use direct bind call here ::glBindTexture( GL_TEXTURE_2D, Texture ); // s ::glTexGenfv( GL_S, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 0 ) ) ); // t ::glTexGenfv( GL_T, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 1 ) ) ); // r ::glTexGenfv( GL_R, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 2 ) ) ); // q ::glTexGenfv( GL_Q, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 3 ) ) ); select_unit( m_diffusetextureunit ); } // enables and disables specified texture units void opengl_renderer::switch_units( bool const Diffuse, bool const Shadows, bool const Reflections ) { // helper texture unit. if( m_helpertextureunit >= 0 ) { select_unit( m_helpertextureunit ); if( ( true == Reflections ) || ( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) && ( m_shadowcolor != colors::white ) ) ) { if( true == m_environmentcubetexturesupport ) { ::glEnable( GL_TEXTURE_CUBE_MAP ); } else { ::glEnable( GL_TEXTURE_2D ); } } else { if( true == m_environmentcubetexturesupport ) { ::glDisable( GL_TEXTURE_CUBE_MAP ); } else { ::glDisable( GL_TEXTURE_2D ); } } } // shadow texture unit. if( m_shadowtextureunit >= 0 ) { if( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) { select_unit( m_shadowtextureunit ); ::glEnable( GL_TEXTURE_2D ); } else { select_unit( m_shadowtextureunit ); ::glDisable( GL_TEXTURE_2D ); } } // normal/reflection texture unit if( m_normaltextureunit >= 0 ) { if( true == Reflections ) { select_unit( m_normaltextureunit ); ::glEnable( GL_TEXTURE_2D ); } else { select_unit( m_normaltextureunit ); ::glDisable( GL_TEXTURE_2D ); } } // diffuse texture unit. // NOTE: toggle actually disables diffuse texture mapping, unlike setup counterpart if( true == Diffuse ) { select_unit( m_diffusetextureunit ); ::glEnable( GL_TEXTURE_2D ); } else { select_unit( m_diffusetextureunit ); ::glDisable( GL_TEXTURE_2D ); } // update unit state m_unitstate.diffuse = Diffuse; m_unitstate.shadows = Shadows; m_unitstate.reflections = Reflections; } void opengl_renderer::setup_shadow_color( glm::vec4 const &Shadowcolor ) { select_unit( m_helpertextureunit ); ::glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, glm::value_ptr( Shadowcolor ) ); // in-shadow colour multiplier select_unit( m_diffusetextureunit ); } void opengl_renderer::setup_environment_light( TEnvironmentType const Environment ) { switch( Environment ) { case e_flat: { m_sunlight.apply_intensity(); // m_environment = Environment; break; } case e_canyon: { m_sunlight.apply_intensity( 0.4f ); // m_environment = Environment; break; } case e_tunnel: { m_sunlight.apply_intensity( 0.2f ); // m_environment = Environment; break; } default: { break; } } } bool opengl_renderer::Render( world_environment *Environment ) { // calculate shadow tone, based on positions of celestial bodies m_shadowcolor = interpolate( glm::vec4{ colors::shadow }, glm::vec4{ colors::white }, clamp( -Environment->m_sun.getAngle(), 0.f, 6.f ) / 6.f ); if( ( Environment->m_sun.getAngle() < -18.f ) && ( Environment->m_moon.getAngle() > 0.f ) ) { // turn on moon shadows after nautical twilight, if the moon is actually up m_shadowcolor = colors::shadow; } // soften shadows depending on sky overcast factor m_shadowcolor = glm::min( colors::white, m_shadowcolor + ( ( colors::white - colors::shadow ) * Global.Overcast ) ); if( Global.bWireFrame ) { // bez nieba w trybie rysowania linii return false; } Bind_Material( null_handle ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_DEPTH_TEST ); ::glDepthMask( GL_FALSE ); // skydome // drawn with 500m radius to blend in if the fog range is low ::glPushMatrix(); ::glScalef( 500.f, 500.f, 500.f ); m_skydomerenderer.render(); if( Global.bUseVBO ) { gfx::opengl_vbogeometrybank::reset(); } ::glPopMatrix(); // stars if( Environment->m_stars.m_stars != nullptr ) { // setup ::glPushMatrix(); ::glRotatef( Environment->m_stars.m_latitude, 1.f, 0.f, 0.f ); // ustawienie osi OY na północ ::glRotatef( -std::fmod( (float)Global.fTimeAngleDeg, 360.f ), 0.f, 1.f, 0.f ); // obrót dobowy osi OX ::glPointSize( 2.f ); // render Render( Environment->m_stars.m_stars, nullptr, 1.0 ); // post-render cleanup ::glPointSize( 3.f ); ::glPopMatrix(); } // celestial bodies if( DebugModeFlag == true ) { // mark sun position for easier debugging Environment->m_sun.render(); Environment->m_moon.render(); } // render actual sun and moon ::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_ALPHA_TEST ); ::glEnable( GL_BLEND ); ::glBlendFunc( GL_SRC_ALPHA, GL_ONE ); auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW ); auto const fogfactor { clamp( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // closer/denser fog reduces opacity of the celestial bodies float const duskfactor = 1.0f - clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f; glm::vec3 suncolor = interpolate( glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), duskfactor ); // sun { Bind_Texture( m_suntexture ); ::glColor4f( suncolor.x, suncolor.y, suncolor.z, clamp( 1.5f - Global.Overcast, 0.f, 1.f ) * fogfactor ); auto const sunvector = Environment->m_sun.getDirection(); auto const sunposition = modelview * glm::vec4( sunvector.x, sunvector.y, sunvector.z, 1.0f ); ::glPushMatrix(); ::glLoadIdentity(); // macierz jedynkowa ::glTranslatef( sunposition.x, sunposition.y, sunposition.z ); // początek układu zostaje bez zmian float const size = interpolate( // TODO: expose distance/scale factor from the moon object 0.0325f, 0.0275f, clamp( Environment->m_sun.getAngle(), 0.f, 90.f ) / 90.f ); ::glBegin( GL_TRIANGLE_STRIP ); ::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 1.f ); ::glVertex3f( -size, size, 0.f ); ::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 0.f ); ::glVertex3f( -size, -size, 0.f ); ::glMultiTexCoord2f( m_diffusetextureunit, 0.f, 1.f ); ::glVertex3f( size, size, 0.f ); ::glMultiTexCoord2f( m_diffusetextureunit, 0.f, 0.f ); ::glVertex3f( size, -size, 0.f ); ::glEnd(); ::glPopMatrix(); } // moon { Bind_Texture( m_moontexture ); glm::vec3 mooncolor( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ); ::glColor4f( mooncolor.r, mooncolor.g, mooncolor.b, // fade the moon if it's near the sun in the sky, especially during the day std::max( 0.f, 1.0 - 0.5 * Global.fLuminance - 0.65 * std::max( 0.f, glm::dot( Environment->m_sun.getDirection(), Environment->m_moon.getDirection() ) ) ) * fogfactor ); auto const moonposition = modelview * glm::vec4( Environment->m_moon.getDirection(), 1.0f ); ::glPushMatrix(); ::glLoadIdentity(); // macierz jedynkowa ::glTranslatef( moonposition.x, moonposition.y, moonposition.z ); float const size = interpolate( // TODO: expose distance/scale factor from the moon object 0.0160f, 0.0135f, clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f ); // choose the moon appearance variant, based on current moon phase // NOTE: implementation specific, 8 variants are laid out in 3x3 arrangement // from new moon onwards, top left to right bottom (last spot is left for future use, if any) auto const moonphase = Environment->m_moon.getPhase(); float moonu, moonv; if( moonphase < 1.84566f ) { moonv = 1.0f - 0.0f; moonu = 0.0f; } else if( moonphase < 5.53699f ) { moonv = 1.0f - 0.0f; moonu = 0.333f; } else if( moonphase < 9.22831f ) { moonv = 1.0f - 0.0f; moonu = 0.667f; } else if( moonphase < 12.91963f ) { moonv = 1.0f - 0.333f; moonu = 0.0f; } else if( moonphase < 16.61096f ) { moonv = 1.0f - 0.333f; moonu = 0.333f; } else if( moonphase < 20.30228f ) { moonv = 1.0f - 0.333f; moonu = 0.667f; } else if( moonphase < 23.99361f ) { moonv = 1.0f - 0.667f; moonu = 0.0f; } else if( moonphase < 27.68493f ) { moonv = 1.0f - 0.667f; moonu = 0.333f; } else { moonv = 1.0f - 0.0f; moonu = 0.0f; } ::glBegin( GL_TRIANGLE_STRIP ); ::glMultiTexCoord2f( m_diffusetextureunit, moonu, moonv ); ::glVertex3f( -size, size, 0.0f ); ::glMultiTexCoord2f( m_diffusetextureunit, moonu, moonv - 0.333f ); ::glVertex3f( -size, -size, 0.0f ); ::glMultiTexCoord2f( m_diffusetextureunit, moonu + 0.333f, moonv ); ::glVertex3f( size, size, 0.0f ); ::glMultiTexCoord2f( m_diffusetextureunit, moonu + 0.333f, moonv - 0.333f ); ::glVertex3f( size, -size, 0.0f ); ::glEnd(); ::glPopMatrix(); } ::glPopAttrib(); m_sunlight.apply_angle(); m_sunlight.apply_intensity(); // clouds if( Environment->m_clouds.mdCloud ) { // setup Disable_Lights(); ::glEnable( GL_LIGHTING ); ::glEnable( GL_LIGHT0 ); // other lights will be enabled during lights update ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( interpolate( Environment->m_skydome.GetAverageColor(), suncolor, duskfactor * 0.25f ) * interpolate( 1.f, 0.35f, Global.Overcast / 2.f ) // overcast darkens the clouds * 0.5f // arbitrary adjustment factor ) ); // render Render( Environment->m_clouds.mdCloud, nullptr, 100.0 ); Render_Alpha( Environment->m_clouds.mdCloud, nullptr, 100.0 ); // post-render cleanup ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( colors::none ) ); ::glDisable( GL_LIGHTING ); } ::glDepthMask( GL_TRUE ); ::glEnable( GL_DEPTH_TEST ); ::glEnable( GL_LIGHTING ); return true; } // geometry methods // creates a new geometry bank. returns: handle to the bank or NULL gfx::geometrybank_handle opengl_renderer::Create_Bank() { return m_geometry.create_bank(); } // creates a new geometry chunk of specified type from supplied vertex data, in specified bank. returns: handle to the chunk or NULL gfx::geometry_handle opengl_renderer::Insert( gfx::vertex_array &Vertices, gfx::geometrybank_handle const &Geometry, int const Type ) { return m_geometry.create_chunk( Vertices, Geometry, Type ); } // replaces data of specified chunk with the supplied vertex data, starting from specified offset bool opengl_renderer::Replace( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset ) { return m_geometry.replace( Vertices, Geometry, Offset ); } // adds supplied vertex data at the end of specified chunk bool opengl_renderer::Append( gfx::vertex_array &Vertices, gfx::geometry_handle const &Geometry, int const Type ) { return m_geometry.append( Vertices, Geometry ); } // provides direct access to vertex data of specfied chunk gfx::vertex_array const & opengl_renderer::Vertices( gfx::geometry_handle const &Geometry ) const { return m_geometry.vertices( Geometry ); } // material methods material_handle opengl_renderer::Fetch_Material( std::string const &Filename, bool const Loadnow ) { return m_materials.create( Filename, Loadnow ); } void opengl_renderer::Bind_Material( material_handle const Material, TSubModel *sm ) { auto const &material = m_materials.material( Material ); if( false == Global.BasicRenderer ) { m_textures.bind( m_normaltextureunit - GL_TEXTURE0, material.textures[1] ); } m_textures.bind( m_diffusetextureunit - GL_TEXTURE0, material.textures[0] ); } opengl_material const & opengl_renderer::Material( material_handle const Material ) const { return m_materials.material( Material ); } // shader methods std::shared_ptr opengl_renderer::Fetch_Shader( std::string const &name ) { return std::shared_ptr(); } // texture methods void opengl_renderer::select_unit( GLint const Textureunit ) { return m_textures.unit( Textureunit ); } texture_handle opengl_renderer::Fetch_Texture( std::string const &Filename, bool const Loadnow, GLint format_hint ) { return m_textures.create( Filename, Loadnow, GL_RGBA ); } void opengl_renderer::Bind_Texture( texture_handle const Texture ) { m_textures.bind( m_diffusetextureunit - GL_TEXTURE0, Texture ); } void opengl_renderer::Bind_Texture( std::size_t const Unit, texture_handle const Texture ) { m_textures.bind( Unit, Texture ); } opengl_texture & opengl_renderer::Texture( texture_handle const Texture ) { return m_textures.texture( Texture ); } opengl_texture const & opengl_renderer::Texture( texture_handle const Texture ) const { return m_textures.texture( Texture ); } void opengl_renderer::Pick_Control_Callback( std::function Callback ) { m_control_pick_requests.emplace_back( Callback ); } void opengl_renderer::Pick_Node_Callback( std::function Callback ) { m_node_pick_requests.emplace_back( Callback ); } void opengl_renderer::Render( scene::basic_region *Region ) { m_sectionqueue.clear(); m_cellqueue.clear(); // build a list of region sections to render glm::vec3 const cameraposition { m_renderpass.camera.position() }; auto const camerax = static_cast( std::floor( cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); auto const cameraz = static_cast( std::floor( cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); int const segmentcount = 2 * static_cast( std::ceil( m_renderpass.draw_range * Global.fDistanceFactor / scene::EU07_SECTIONSIZE ) ); int const originx = camerax - segmentcount / 2; int const originz = cameraz - segmentcount / 2; for( int row = originz; row <= originz + segmentcount; ++row ) { if( row < 0 ) { continue; } if( row >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; } for( int column = originx; column <= originx + segmentcount; ++column ) { if( column < 0 ) { continue; } if( column >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; } auto *section { Region->m_sections[ row * scene::EU07_REGIONSIDESECTIONCOUNT + column ] }; if( ( section != nullptr ) && ( m_renderpass.camera.visible( section->m_area ) ) ) { m_sectionqueue.emplace_back( section ); } } } switch( m_renderpass.draw_mode ) { case rendermode::color: { Update_Lights( simulation::Lights ); Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); if( EditorModeFlag ) { // when editor mode is active calculate world position of the cursor // at this stage the z-buffer is filled with only ground geometry Update_Mouse_Position(); } // draw queue is filled while rendering sections Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) ); break; } case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickscenery: { // these render modes don't bother with lights Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); // they can also skip queue sorting, as they only deal with opaque geometry // NOTE: there's benefit from rendering front-to-back, but is it significant enough? TODO: investigate Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) ); break; } case rendermode::reflections: { // for the time being reflections render only terrain geometry Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) ); break; } case rendermode::pickcontrols: default: { // no need to render anything ourside of the cab in control picking mode break; } } } void opengl_renderer::Render( section_sequence::iterator First, section_sequence::iterator Last ) { switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { break; } case rendermode::shadows: case rendermode::cabshadows: { // experimental, for shadows render both back and front faces, to supply back faces of the 'forest strips' ::glDisable( GL_CULL_FACE ); break; } case rendermode::pickscenery: { // non-interactive scenery elements get neutral colour ::glColor3fv( glm::value_ptr( colors::none ) ); break; } default: { break; } } while( First != Last ) { auto *section = *First; section->create_geometry(); // render shapes held by the section switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickscenery: { if( false == section->m_shapes.empty() ) { // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { section->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render for( auto const &shape : section->m_shapes ) { Render( shape, true ); } // post-render cleanup ::glPopMatrix(); } break; } case rendermode::pickcontrols: default: { break; } } // add the section's cells to the cell queue switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickscenery: { for( auto &cell : section->m_cells ) { if( ( true == cell.m_active ) && ( m_renderpass.camera.visible( cell.m_area ) ) ) { // store visible cells with content as well as their current distance, for sorting later m_cellqueue.emplace_back( glm::length2( m_renderpass.camera.position() - cell.m_area.center ), &cell ); } } break; } case rendermode::reflections: case rendermode::pickcontrols: default: { break; } } // proceed to next section ++First; } switch( m_renderpass.draw_mode ) { case rendermode::shadows: case rendermode::cabshadows: { // restore standard face cull mode ::glEnable( GL_CULL_FACE ); break; } default: { break; } } } void opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator Last ) { // cache initial iterator for the second sweep auto first { First }; // first pass draws elements which we know are located in section banks, to reduce vbo switching while( First != Last ) { auto *cell = First->second; // przeliczenia animacji torów w sektorze cell->RaAnimate( m_framestamp ); switch( m_renderpass.draw_mode ) { case rendermode::color: { // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render // opaque non-instanced shapes for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } // tracks // TODO: update after path node refactoring Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) ); #ifdef EU07_USE_DEBUG_CULLING // debug ::glLineWidth( 2.f ); float const width = cell->m_area.radius; float const height = cell->m_area.radius * 0.2f; glDisable( GL_LIGHTING ); glDisable( GL_TEXTURE_2D ); glColor3ub( 255, 128, 128 ); glBegin( GL_LINE_LOOP ); glVertex3f( -width, height, width ); glVertex3f( -width, height, -width ); glVertex3f( width, height, -width ); glVertex3f( width, height, width ); glEnd(); glBegin( GL_LINE_LOOP ); glVertex3f( -width, 0, width ); glVertex3f( -width, 0, -width ); glVertex3f( width, 0, -width ); glVertex3f( width, 0, width ); glEnd(); glBegin( GL_LINES ); glVertex3f( -width, height, width ); glVertex3f( -width, 0, width ); glVertex3f( -width, height, -width ); glVertex3f( -width, 0, -width ); glVertex3f( width, height, -width ); glVertex3f( width, 0, -width ); glVertex3f( width, height, width ); glVertex3f( width, 0, width ); glEnd(); glColor3ub( 255, 255, 255 ); glEnable( GL_TEXTURE_2D ); glEnable( GL_LIGHTING ); glLineWidth( 1.f ); #endif // post-render cleanup ::glPopMatrix(); break; } case rendermode::shadows: { // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render // opaque non-instanced shapes for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } // tracks Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) ); // post-render cleanup ::glPopMatrix(); break; } case rendermode::cabshadows: { // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render // opaque non-instanced shapes for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } // NOTE: tracks aren't likely to cast shadows into the cab, so we skip them in this pass // post-render cleanup ::glPopMatrix(); break; } case rendermode::pickscenery: { // same procedure like with regular render, but editor-enabled nodes receive custom colour used for picking // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render // opaque non-instanced shapes // non-interactive scenery elements get neutral colour ::glColor3fv( glm::value_ptr( colors::none ) ); for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } // tracks for( auto *path : cell->m_paths ) { ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); Render( path ); } // post-render cleanup ::glPopMatrix(); break; } case rendermode::reflections: case rendermode::pickcontrols: default: { break; } } ++First; } // second pass draws elements with their own vbos while( first != Last ) { auto const *cell = first->second; switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::shadows: case rendermode::cabshadows: { // opaque parts of instanced models for( auto *instance : cell->m_instancesopaque ) { Render( instance ); } // opaque parts of vehicles for( auto *path : cell->m_paths ) { for( auto *dynamic : path->Dynamics ) { Render( dynamic ); } } // memcells if( ( EditorModeFlag ) && ( DebugModeFlag ) ) { ::glPushAttrib( GL_ENABLE_BIT ); ::glDisable( GL_TEXTURE_2D ); ::glColor3f( 0.36f, 0.75f, 0.35f ); for( auto *memorycell : cell->m_memorycells ) { Render( memorycell ); } ::glPopAttrib(); } break; } case rendermode::pickscenery: { // opaque parts of instanced models // same procedure like with regular render, but each node receives custom colour used for picking for( auto *instance : cell->m_instancesopaque ) { ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); Render( instance ); } // memcells if( ( EditorModeFlag ) && ( DebugModeFlag ) ) { for( auto *memorycell : cell->m_memorycells ) { ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); Render( memorycell ); } } // vehicles aren't included in scenery picking for the time being break; } case rendermode::reflections: case rendermode::pickcontrols: default: { break; } } ++first; } #ifdef EU07_USE_DEBUG_SOUNDEMITTERS // sound emitters if( DebugModeFlag ) { switch( m_renderpass.draw_mode ) { case rendermode::color: { ::glPushAttrib( GL_ENABLE_BIT ); ::glDisable( GL_TEXTURE_2D ); ::glColor3f( 0.36f, 0.75f, 0.35f ); for( auto const &audiosource : audio::renderer.m_sources ) { ::glPushMatrix(); auto const position = audiosource.properties.location - m_renderpass.camera.position(); ::glTranslated( position.x, position.y, position.z ); ::gluSphere( m_quadric, 0.1, 4, 2 ); ::glPopMatrix(); } ::glPopAttrib(); break; } default: { break; } } } #endif } void opengl_renderer::Render( scene::shape_node const &Shape, bool const Ignorerange ) { auto const &data { Shape.data() }; if( false == Ignorerange ) { double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: case rendermode::cabshadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees distancesquared = Math3D::SquareMagnitude( ( data.area.center - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { distancesquared = glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor; break; } } if( ( distancesquared < data.rangesquared_min ) || ( distancesquared >= data.rangesquared_max ) ) { return; } } // setup Bind_Material( data.material ); switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { ::glColor3fv( glm::value_ptr( data.lighting.diffuse ) ); /* // NOTE: ambient component is set by diffuse component // NOTE: for the time being non-instanced shapes are rendered without specular component due to wrong/arbitrary values set in legacy scenarios // TBD, TODO: find a way to resolve this with the least amount of tears? ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( data.lighting.specular * m_sunlight.specular.a * m_specularopaquescalefactor ) ); */ break; } // pick modes are painted with custom colours, and shadow pass doesn't use any case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickscenery: case rendermode::pickcontrols: default: { break; } } // render m_geometry.draw( data.geometry ); // debug data ++m_debugstats.shapes; ++m_debugstats.drawcalls; } void opengl_renderer::Render( TAnimModel *Instance ) { if( false == Instance->m_visible ) { return; } double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: case rendermode::cabshadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees distancesquared = Math3D::SquareMagnitude( ( Instance->location() - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { distancesquared = Math3D::SquareMagnitude( ( Instance->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor; break; } } if( ( distancesquared < Instance->m_rangesquaredmin ) || ( distancesquared >= Instance->m_rangesquaredmax ) ) { return; } // crude way to reject early items too far to affect the output (mostly relevant for shadow passes) auto const drawdistancethreshold{ m_renderpass.draw_range + 250 }; if( distancesquared > drawdistancethreshold * drawdistancethreshold ) { return; } switch( m_renderpass.draw_mode ) { case rendermode::pickscenery: { // add the node to the pick list m_picksceneryitems.emplace_back( Instance ); break; } default: { break; } } Instance->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji Instance->RaPrepare(); if( Instance->pModel ) { // renderowanie rekurencyjne submodeli Render( Instance->pModel, Instance->Material(), distancesquared, Instance->location() - m_renderpass.camera.position(), Instance->vAngle ); } } bool opengl_renderer::Render( TDynamicObject *Dynamic ) { Dynamic->renderme = m_renderpass.camera.visible( Dynamic ); if( false == Dynamic->renderme ) { return false; } // lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom float squaredistance; glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position(); switch( m_renderpass.draw_mode ) { case rendermode::shadows: { squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor ) / Global.fDistanceFactor; if( false == FreeFlyModeFlag ) { // filter out small details if we're in vehicle cab squaredistance = std::max( 100.f * 100.f, squaredistance ); } break; } case rendermode::cabshadows: { squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor ) / Global.fDistanceFactor; // filter out small details squaredistance = std::max( 100.f * 100.f, squaredistance ); break; } default: { squaredistance = glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor ) / Global.fDistanceFactor; break; } } // crude way to reject early items too far to affect the output (mostly relevant for shadow passes) auto const drawdistancethreshold { m_renderpass.draw_range + 250 }; if( squaredistance > drawdistancethreshold * drawdistancethreshold ) { return false; } // debug data ++m_debugstats.dynamics; // setup TSubModel::iInstance = reinterpret_cast( Dynamic ); //żeby nie robić cudzych animacji Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu ::glPushMatrix(); ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); ::glMultMatrixd( Dynamic->mMatrix.getArray() ); switch( m_renderpass.draw_mode ) { case rendermode::color: { if( Dynamic->fShade > 0.0f ) { // change light level based on light level of the occupied track m_sunlight.apply_intensity( Dynamic->fShade ); } m_renderspecular = true; // vehicles are rendered with specular component. static models without, at least for the time being // render if( Dynamic->mdLowPolyInt ) { // HACK: reduce light level for vehicle interior if there's strong global lighting source auto const luminance { static_cast( 0.5 * ( std::max( 0.3, Global.fLuminance - Global.Overcast ) ) ) }; m_sunlight.apply_intensity( clamp( ( Dynamic->fShade > 0.f ? Dynamic->fShade : 1.f ) - luminance, 0.f, 1.f ) ); // low poly interior Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); // HACK: if the model has low poly interior, we presume the load is placed inside and also affected by reduced light level if( Dynamic->mdLoad ) { // renderowanie nieprzezroczystego ładunku Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); } m_sunlight.apply_intensity( Dynamic->fShade > 0.f ? Dynamic->fShade : 1.f ); } else { // HACK: if the model lacks low poly interior, we presume the load is placed outside if( Dynamic->mdLoad ) { // renderowanie nieprzezroczystego ładunku Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); } } if( Dynamic->mdModel ) { // main model Render( Dynamic->mdModel, Dynamic->Material(), squaredistance ); } // optional attached models for( auto *attachment : Dynamic->mdAttachments ) { Render( attachment, Dynamic->Material(), squaredistance ); } // post-render cleanup m_renderspecular = false; if( Dynamic->fShade > 0.0f ) { // restore regular light level m_sunlight.apply_intensity(); } break; } case rendermode::shadows: case rendermode::cabshadows: { if( Dynamic->mdLowPolyInt ) { // low poly interior // if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) { Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); // } } if( Dynamic->mdModel ) { // main model Render( Dynamic->mdModel, Dynamic->Material(), squaredistance ); } // optional attached models for( auto *attachment : Dynamic->mdAttachments ) { Render( attachment, Dynamic->Material(), squaredistance ); } if( Dynamic->mdLoad ) { // renderowanie nieprzezroczystego ładunku Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); } // post-render cleanup break; } case rendermode::pickcontrols: { if( Dynamic->mdLowPolyInt ) { // low poly interior Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); } break; } case rendermode::pickscenery: default: { break; } } ::glPopMatrix(); // TODO: check if this reset is needed. In theory each object should render all parts based on its own instance data anyway? if( Dynamic->btnOn ) Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli return true; } // rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana bool opengl_renderer::Render_cab( TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha ) { if( Dynamic == nullptr ) { TSubModel::iInstance = 0; return false; } TSubModel::iInstance = reinterpret_cast( Dynamic ); if( ( true == FreeFlyModeFlag ) || ( false == Dynamic->bDisplayCab ) || ( Dynamic->mdKabina == Dynamic->mdModel ) ) { // ABu: Rendering kabiny jako ostatniej, zeby bylo widac przez szyby, tylko w widoku ze srodka return false; } if( Dynamic->mdKabina ) { // bo mogła zniknąć przy przechodzeniu do innego pojazdu // setup shared by all render paths ::glPushMatrix(); auto const originoffset = Dynamic->GetPosition() - m_renderpass.camera.position(); ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); ::glMultMatrixd( Dynamic->mMatrix.readArray() ); switch( m_renderpass.draw_mode ) { case rendermode::color: { // render path specific setup: if( Dynamic->fShade > 0.0f ) { // change light level based on light level of the occupied track m_sunlight.apply_intensity( Dynamic->fShade ); } if( Lightlevel > 0.f ) { // crude way to light the cabin, until we have something more complete in place ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Lightlevel ) ); } // render if( true == Alpha ) { // translucent parts Render_Alpha( Dynamic->mdKabina, Dynamic->Material(), 0.0 ); } else { // opaque parts Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 ); } // post-render restore if( Dynamic->fShade > 0.0f ) { // change light level based on light level of the occupied track m_sunlight.apply_intensity(); } if( Lightlevel > 0.f ) { // reset the overall ambient ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr(m_baseambient) ); } break; } case rendermode::cabshadows: { // cab shadowmap mode skips lighting setup and translucent parts // render if( true == Alpha ) { // translucent parts Render_Alpha( Dynamic->mdKabina, Dynamic->Material(), 0.0 ); } else { // opaque parts Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 ); } // since the setup is simpler, there's nothing to reset afterwards break; } case rendermode::pickcontrols: { // control picking mode skips lighting setup and translucent parts // render Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 ); // since the setup is simpler, there's nothing to reset afterwards break; } default: { break; } } // post-render restore ::glPopMatrix(); } return true; } bool opengl_renderer::Render( TModel3d *Model, material_data const *Material, float const Squaredistance ) { auto alpha = ( Material != nullptr ? Material->textures_alpha : 0x30300030 ); alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste if( 0 == ( alpha & Model->iFlags & 0x1F1F001F ) ) { // czy w ogóle jest co robić w tym cyklu? return false; } Model->Root->fSquareDist = Squaredistance; // zmienna globalna! // setup Model->Root->ReplacableSet( ( Material != nullptr ? Material->replacable_skins : nullptr ), alpha ); Model->Root->pRoot = Model; // render Render( Model->Root ); // debug data ++m_debugstats.models; // post-render cleanup return true; } bool opengl_renderer::Render( TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &Angle ) { ::glPushMatrix(); ::glTranslated( Position.x, Position.y, Position.z ); if( Angle.y != 0.0 ) ::glRotatef( Angle.y, 0.f, 1.f, 0.f ); if( Angle.x != 0.0 ) ::glRotatef( Angle.x, 1.f, 0.f, 0.f ); if( Angle.z != 0.0 ) ::glRotatef( Angle.z, 0.f, 0.f, 1.f ); auto const result = Render( Model, Material, Squaredistance ); ::glPopMatrix(); return result; } void opengl_renderer::Render( TSubModel *Submodel ) { if( ( Submodel->iVisible ) && ( TSubModel::fSquareDist >= Submodel->fSquareMinDist ) && ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) { // debug data ++m_debugstats.submodels; ++m_debugstats.drawcalls; if( Submodel->iFlags & 0xC000 ) { ::glPushMatrix(); if( Submodel->fMatrix ) ::glMultMatrixf( Submodel->fMatrix->readArray() ); if( Submodel->b_Anim != TAnimType::at_None ) Submodel->RaAnimation( Submodel->b_Anim ); } if( Submodel->eType < TP_ROTATOR ) { // renderowanie obiektów OpenGL if( Submodel->iAlpha & Submodel->iFlags & 0x1F ) { // rysuj gdy element nieprzezroczysty switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { // NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains // for the time being we'll do with enforced worst-case scaling method, when speculars are enabled #ifdef EU07_USE_OPTIMIZED_NORMALIZATION switch( Submodel->m_normalizenormals ) { case TSubModel::normalize: { ::glEnable( GL_NORMALIZE ); break; } case TSubModel::rescale: { ::glEnable( GL_RESCALE_NORMAL ); break; } default: { break; } } #else if( true == m_renderspecular ) { ::glEnable( GL_NORMALIZE ); } #endif // material configuration: auto const material{ ( Submodel->m_material < 0 ? Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry Submodel->m_material ) }; // również 0 // textures... Bind_Material( material ); // ...colors and opacity... auto const opacity { clamp( Material( material ).get_or_guess_opacity(), 0.f, 1.f ) }; if( Submodel->fVisible < 1.f ) { // setup ::glAlphaFunc( GL_GREATER, 0.f ); ::glEnable( GL_BLEND ); ::glColor4f( Submodel->f4Diffuse.r, Submodel->f4Diffuse.g, Submodel->f4Diffuse.b, Submodel->fVisible ); } else { ::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub if( ( opacity != 0.f ) && ( opacity != EU07_OPACITYDEFAULT ) ) { ::glAlphaFunc( GL_GREATER, opacity ); } } // ...specular... if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { // specular strength in legacy models is set uniformly to 150, 150, 150 so we scale it down for opaque elements ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( Submodel->f4Specular * m_sunlight.specular.a * m_specularopaquescalefactor ) ); ::glEnable( GL_RESCALE_NORMAL ); } // ...luminance auto const unitstate = m_unitstate; auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; if( isemissive ) { // zeby swiecilo na kolorowo ::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( /* Submodel->f4Emision */ Submodel->f4Diffuse * Submodel->f4Emision.a ) ); // disable shadows so they don't obstruct self-lit items /* setup_shadow_color( colors::white ); */ switch_units( unitstate.diffuse, false, false ); } // main draw call m_geometry.draw( Submodel->m_geometry ); /* if( DebugModeFlag ) { auto const & vertices { m_geometry.vertices( Submodel->m_geometry ) }; ::glBegin( GL_LINES ); for( auto const &vertex : vertices ) { ::glVertex3fv( glm::value_ptr( vertex.position ) ); ::glVertex3fv( glm::value_ptr( vertex.position + vertex.normal * 0.2f ) ); } ::glEnd(); } */ // post-draw reset if( Submodel->fVisible < 1.f ) { ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); ::glDisable( GL_BLEND ); } else if( ( opacity != 0.f ) && ( opacity != EU07_OPACITYDEFAULT ) ) { ::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT ); } if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) ); } if( isemissive ) { // restore default (lack of) brightness ::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( colors::none ) ); /* setup_shadow_color( m_shadowcolor ); */ switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections ); } #ifdef EU07_USE_OPTIMIZED_NORMALIZATION switch( Submodel->m_normalizenormals ) { case TSubModel::normalize: { ::glDisable( GL_NORMALIZE ); break; } case TSubModel::rescale: { ::glDisable( GL_RESCALE_NORMAL ); break; } default: { break; } } #else if( true == m_renderspecular ) { ::glDisable( GL_NORMALIZE ); } #endif break; } case rendermode::shadows: case rendermode::cabshadows: case rendermode::pickscenery: { // scenery picking and shadow both use enforced colour and no frills // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); } else { // również 0 Bind_Material( Submodel->m_material ); } // main draw call m_geometry.draw( Submodel->m_geometry ); // post-draw reset break; } case rendermode::pickcontrols: { // material configuration: // control picking applies individual colour for each submodel m_pickcontrolsitems.emplace_back( Submodel ); ::glColor3fv( glm::value_ptr( pick_color( m_pickcontrolsitems.size() ) ) ); // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); } else { // również 0 Bind_Material( Submodel->m_material ); } // main draw call m_geometry.draw( Submodel->m_geometry ); // post-draw reset break; } default: { break; } } } } else if( Submodel->eType == TP_FREESPOTLIGHT ) { switch( m_renderpass.draw_mode ) { // spotlights are only rendered in colour mode(s) case rendermode::color: case rendermode::reflections: { auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW ); auto const lightcenter = modelview * interpolate( glm::vec4( 0.f, 0.f, -0.05f, 1.f ), glm::vec4( 0.f, 0.f, -0.25f, 1.f ), static_cast( TSubModel::fSquareDist / Submodel->fSquareMaxDist ) ); // pozycja punktu świecącego względem kamery Submodel->fCosViewAngle = glm::dot( glm::normalize( modelview * glm::vec4( 0.f, 0.f, -1.f, 1.f ) - lightcenter ), glm::normalize( -lightcenter ) ); if( Submodel->fCosViewAngle > Submodel->fCosFalloffAngle ) { // kąt większy niż maksymalny stożek swiatła float lightlevel = 1.f; // TODO, TBD: parameter to control light strength // view angle attenuation float const anglefactor = clamp( ( Submodel->fCosViewAngle - Submodel->fCosFalloffAngle ) / ( Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle ), 0.f, 1.f ); lightlevel *= anglefactor; // distance attenuation. NOTE: since it's fixed pipeline with built-in gamma correction we're using linear attenuation // we're capping how much effect the distance attenuation can have, otherwise the lights get too tiny at regular distances float const distancefactor { std::max( 0.5f, ( Submodel->fSquareMaxDist - TSubModel::fSquareDist ) / Submodel->fSquareMaxDist ) }; auto const pointsize { std::max( 3.f, 5.f * distancefactor * anglefactor ) }; auto const resolutionratio { Global.iWindowHeight / 1080.f }; // additionally reduce light strength for farther sources in rain or snow if( Global.Overcast > 0.75f ) { float const precipitationfactor{ interpolate( interpolate( 1.f, 0.25f, clamp( Global.Overcast * 0.75f - 0.5f, 0.f, 1.f ) ), 1.f, distancefactor ) }; lightlevel *= precipitationfactor; } if( lightlevel > 0.f ) { // material configuration: Bind_Material( null_handle ); // limit impact of dense fog on the lights auto const lightrange { std::max( 500, m_fogrange * 2 ) }; // arbitrary, visibility at least 750m ::glFogf( GL_FOG_DENSITY, static_cast( 1.0 / lightrange ) ); ::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_POINT_BIT ); ::glDisable( GL_LIGHTING ); ::glEnable( GL_BLEND ); ::glAlphaFunc( GL_GREATER, 0.f ); ::glPushMatrix(); ::glLoadIdentity(); ::glTranslatef( lightcenter.x, lightcenter.y, lightcenter.z ); // początek układu zostaje bez zmian auto const unitstate = m_unitstate; switch_units( m_unitstate.diffuse, false, false ); auto const *lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override glm::value_ptr( Submodel->f4Diffuse ) : glm::value_ptr( Submodel->DiffuseOverride ) }; // main draw call if( Global.Overcast > 1.f ) { // fake fog halo float const fogfactor { interpolate( 2.f, 1.f, clamp( Global.fFogEnd / 2000, 0.f, 1.f ) ) * std::max( 1.f, Global.Overcast ) }; ::glPointSize( pointsize * resolutionratio * fogfactor ); ::glColor4f( lightcolor[ 0 ], lightcolor[ 1 ], lightcolor[ 2 ], Submodel->fVisible * std::min( 1.f, lightlevel ) * 0.5f ); ::glDepthMask( GL_FALSE ); m_geometry.draw( Submodel->m_geometry ); ::glDepthMask( GL_TRUE ); } ::glPointSize( pointsize * resolutionratio ); ::glColor4f( lightcolor[ 0 ], lightcolor[ 1 ], lightcolor[ 2 ], Submodel->fVisible * std::min( 1.f, lightlevel ) ); m_geometry.draw( Submodel->m_geometry ); // post-draw reset switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections ); ::glPopMatrix(); ::glPopAttrib(); ::glFogf( GL_FOG_DENSITY, static_cast( 1.0 / m_fogrange ) ); } } break; } default: { break; } } } else if( Submodel->eType == TP_STARS ) { switch( m_renderpass.draw_mode ) { // colour points are only rendered in colour mode(s) case rendermode::color: case rendermode::reflections: { if( Global.fLuminance < Submodel->fLight ) { // material configuration: ::glPushAttrib( GL_ENABLE_BIT ); Bind_Material( null_handle ); ::glDisable( GL_LIGHTING ); // main draw call m_geometry.draw( Submodel->m_geometry, gfx::color_streams ); // post-draw reset ::glPopAttrib(); } break; } default: { break; } } } if( Submodel->Child != nullptr ) if( Submodel->iAlpha & Submodel->iFlags & 0x001F0000 ) Render( Submodel->Child ); if( Submodel->iFlags & 0xC000 ) ::glPopMatrix(); } /* if( Submodel->b_Anim < at_SecondsJump ) Submodel->b_Anim = at_None; // wyłączenie animacji dla kolejnego użycia subm */ if( Submodel->Next ) if( Submodel->iAlpha & Submodel->iFlags & 0x1F000000 ) Render( Submodel->Next ); // dalsze rekurencyjnie } void opengl_renderer::Render( TTrack *Track ) { if( ( Track->m_material1 == 0 ) && ( Track->m_material2 == 0 ) && ( ( Track->eType != tt_Switch ) || ( Track->SwitchExtension->m_material3 == 0 ) ) ) { return; } if( false == Track->m_visible ) { return; } ++m_debugstats.paths; ++m_debugstats.drawcalls; switch( m_renderpass.draw_mode ) { // single path pieces are rendererd in pick scenery mode only case rendermode::pickscenery: { // add the node to the pick list m_picksceneryitems.emplace_back( Track ); if( Track->m_material1 != 0 ) { Bind_Material( Track->m_material1 ); m_geometry.draw( std::begin( Track->Geometry1 ), std::end( Track->Geometry1 ) ); } if( Track->m_material2 != 0 ) { Bind_Material( Track->m_material2 ); m_geometry.draw( std::begin( Track->Geometry2 ), std::end( Track->Geometry2 ) ); } if( ( Track->eType == tt_Switch ) && ( Track->SwitchExtension->m_material3 != 0 ) ) { Bind_Material( Track->SwitchExtension->m_material3 ); m_geometry.draw( Track->SwitchExtension->Geometry3 ); } break; } default: { break; } } } // experimental, does track rendering in two passes, to take advantage of reduced texture switching void opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last ) { ::glColor3fv( glm::value_ptr( colors::white ) ); // setup switch( m_renderpass.draw_mode ) { case rendermode::shadows: case rendermode::cabshadows: { // NOTE: roads-based platforms tend to miss parts of shadows if rendered with either back or front culling ::glDisable( GL_CULL_FACE ); break; } default: { break; } } // TODO: render auto generated trackbeds together with regular trackbeds in pass 1, and all rails in pass 2 // first pass, material 1 for( auto first { First }; first != Last; ++first ) { auto const track { *first }; if( track->m_material1 == 0 ) { continue; } if( false == track->m_visible ) { continue; } ++m_debugstats.paths; ++m_debugstats.drawcalls; switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { if( track->eEnvironment != e_flat ) { setup_environment_light( track->eEnvironment ); } Bind_Material( track->m_material1 ); m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) ); if( track->eEnvironment != e_flat ) { // restore default lighting setup_environment_light(); } break; } case rendermode::shadows: case rendermode::cabshadows: { if( ( std::abs( track->fTexHeight1 ) < 0.35f ) || ( track->iCategoryFlag != 2 ) ) { // shadows are only calculated for high enough roads, typically meaning track platforms continue; } Bind_Material( track->m_material1 ); m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) ); break; } case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach case rendermode::pickcontrols: default: { break; } } } // second pass, material 2 for( auto first { First }; first != Last; ++first ) { auto const track { *first }; if( track->m_material2 == 0 ) { continue; } if( false == track->m_visible ) { continue; } switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { if( track->eEnvironment != e_flat ) { setup_environment_light( track->eEnvironment ); } Bind_Material( track->m_material2 ); m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) ); if( track->eEnvironment != e_flat ) { // restore default lighting setup_environment_light(); } break; } case rendermode::shadows: case rendermode::cabshadows: { if( ( std::abs( track->fTexHeight1 ) < 0.35f ) || ( ( track->iCategoryFlag == 1 ) && ( track->eType != tt_Normal ) ) ) { // shadows are only calculated for high enough trackbeds continue; } Bind_Material( track->m_material2 ); m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) ); break; } case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach case rendermode::pickcontrols: default: { break; } } } // third pass, material 3 for( auto first { First }; first != Last; ++first ) { auto const track { *first }; if( track->eType != tt_Switch ) { continue; } if( track->SwitchExtension->m_material3 == 0 ) { continue; } if( false == track->m_visible ) { continue; } switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::reflections: { if( track->eEnvironment != e_flat ) { setup_environment_light( track->eEnvironment ); } Bind_Material( track->SwitchExtension->m_material3 ); m_geometry.draw( track->SwitchExtension->Geometry3 ); if( track->eEnvironment != e_flat ) { // restore default lighting setup_environment_light(); } break; } case rendermode::shadows: case rendermode::cabshadows: { if( ( std::abs( track->fTexHeight1 ) < 0.35f ) || ( ( track->iCategoryFlag == 1 ) && ( track->eType != tt_Normal ) ) ) { // shadows are only calculated for high enough trackbeds continue; } Bind_Material( track->SwitchExtension->m_material3 ); m_geometry.draw( track->SwitchExtension->Geometry3 ); break; } case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach case rendermode::pickcontrols: default: { break; } } } // post-render reset switch( m_renderpass.draw_mode ) { case rendermode::shadows: case rendermode::cabshadows: { ::glEnable( GL_CULL_FACE ); break; } default: { break; } } } void opengl_renderer::Render( TMemCell *Memcell ) { ::glPushMatrix(); auto const position = Memcell->location() - m_renderpass.camera.position(); ::glTranslated( position.x, position.y + 0.5, position.z ); switch( m_renderpass.draw_mode ) { case rendermode::color: case rendermode::shadows: case rendermode::cabshadows: { ::gluSphere( m_quadric, 0.35, 4, 2 ); break; } case rendermode::pickscenery: { // add the node to the pick list m_picksceneryitems.emplace_back( Memcell ); ::gluSphere( m_quadric, 0.35, 4, 2 ); break; } case rendermode::reflections: case rendermode::pickcontrols: { break; } default: { break; } } ::glPopMatrix(); } void opengl_renderer::Render_particles() { switch_units( true, false, false ); Bind_Material( null_handle ); // TODO: bind smoke texture // TBD: leave lighting on to allow vehicle lights to affect it? ::glDisable( GL_LIGHTING ); // momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside' ::glDepthMask( GL_FALSE ); Bind_Texture( m_smoketexture ); m_debugstats.particles = m_particlerenderer.render( m_diffusetextureunit ); if( Global.bUseVBO ) { gfx::opengl_vbogeometrybank::reset(); } ::glDepthMask( GL_TRUE ); ::glEnable( GL_LIGHTING ); } void opengl_renderer::Render_precipitation() { if( Global.Overcast <= 1.f ) { return; } switch_units( true, false, false ); // ::glColor4fv( glm::value_ptr( glm::vec4( glm::min( glm::vec3( Global.fLuminance ), glm::vec3( 1 ) ), 1 ) ) ); ::glColor4fv( glm::value_ptr( interpolate( 0.5f * ( Global.DayLight.diffuse + Global.DayLight.ambient ), colors::white, 0.5f * clamp( Global.fLuminance, 0.f, 1.f ) ) ) ); ::glPushMatrix(); // tilt the precipitation cone against the camera movement vector for crude motion blur // include current wind vector while at it auto const velocity { simulation::Environment.m_precipitation.m_cameramove * -1.0 + glm::dvec3{ simulation::Environment.wind() } * 0.5 }; if( glm::length2( velocity ) > 0.0 ) { auto const forward{ glm::normalize( velocity ) }; auto left { glm::cross( forward, {0.0,1.0,0.0} ) }; auto const rotationangle { std::min( 45.0, ( FreeFlyModeFlag ? 5 * glm::length( velocity ) : simulation::Train->Dynamic()->GetVelocity() * 0.2 ) ) }; ::glRotated( rotationangle, left.x, 0.0, left.z ); } if( false == FreeFlyModeFlag ) { // counter potential vehicle roll auto const roll { 0.5 * glm::degrees( simulation::Train->Dynamic()->Roll() ) }; if( roll != 0.0 ) { auto const forward { simulation::Train->Dynamic()->VectorFront() }; auto const vehicledirection = simulation::Train->Dynamic()->DirectionGet(); ::glRotated( roll, forward.x, 0.0, forward.z ); } } if( Global.Weather == "rain:" ) { // oddly enough random streaks produce more natural looking rain than ones the eye can follow ::glRotated( Random() * 360, 0.0, 1.0, 0.0 ); } // TBD: leave lighting on to allow vehicle lights to affect it? ::glDisable( GL_LIGHTING ); // momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside' ::glDepthMask( GL_FALSE ); m_precipitationrenderer.render(); if( Global.bUseVBO ) { gfx::opengl_vbogeometrybank::reset(); } ::glDepthMask( GL_TRUE ); ::glEnable( GL_LIGHTING ); ::glPopMatrix(); } void opengl_renderer::Render_Alpha( scene::basic_region *Region ) { // sort the nodes based on their distance to viewer std::sort( std::begin( m_cellqueue ), std::end( m_cellqueue ), []( distancecell_pair const &Left, distancecell_pair const &Right ) { return ( Left.first ) < ( Right.first ); } ); Render_Alpha( std::rbegin( m_cellqueue ), std::rend( m_cellqueue ) ); } void opengl_renderer::Render_Alpha( cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last ) { // NOTE: this method is launched only during color pass therefore we don't bother with mode test here // first pass draws elements which we know are located in section banks, to reduce vbo switching { auto first { First }; while( first != Last ) { auto const *cell = first->second; if( false == cell->m_shapestranslucent.empty() ) { // since all shapes of the cell share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset{ cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); // render // NOTE: we can reuse the method used to draw opaque geometry for( auto const &shape : cell->m_shapestranslucent ) { Render( shape, false ); } // post-render cleanup ::glPopMatrix(); } ++first; } } // second pass draws elements with their own vbos { auto first { First }; while( first != Last ) { auto const *cell = first->second; // translucent parts of instanced models for( auto *instance : cell->m_instancetranslucent ) { Render_Alpha( instance ); } // translucent parts of vehicles for( auto *path : cell->m_paths ) { for( auto *dynamic : path->Dynamics ) { if( ( false == FreeFlyModeFlag ) && ( simulation::Train->Dynamic() == dynamic ) ) { // delay drawing translucent parts of occupied vehicle until after the cab was drawn continue; } Render_Alpha( dynamic ); } } ++first; } } // third pass draws the wires; // wires use section vbos, but for the time being we want to draw them at the very end { ::glDisable( GL_LIGHTING ); // linie nie powinny świecić auto first{ First }; while( first != Last ) { auto const *cell = first->second; if( ( false == cell->m_traction.empty() || ( false == cell->m_lines.empty() ) ) ) { // since all shapes of the cell share center point we can optimize out a few calls here ::glPushMatrix(); auto const originoffset { cell->m_area.center - m_renderpass.camera.position() }; ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); if( !Global.bSmoothTraction ) { // na liniach kiepsko wygląda - robi gradient ::glDisable( GL_LINE_SMOOTH ); } Bind_Material( null_handle ); // render for( auto *traction : cell->m_traction ) { Render_Alpha( traction ); } for( auto &lines : cell->m_lines ) { Render_Alpha( lines ); } // post-render cleanup ::glLineWidth( 1.0 ); if( !Global.bSmoothTraction ) { ::glEnable( GL_LINE_SMOOTH ); } ::glPopMatrix(); } ++first; } ::glEnable( GL_LIGHTING ); } } void opengl_renderer::Render_Alpha( TAnimModel *Instance ) { if( false == Instance->m_visible ) { return; } double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees distancesquared = Math3D::SquareMagnitude( ( Instance->location() - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { distancesquared = glm::length2( ( Instance->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor; break; } } if( ( distancesquared < Instance->m_rangesquaredmin ) || ( distancesquared >= Instance->m_rangesquaredmax ) ) { return; } Instance->RaPrepare(); if( Instance->pModel ) { // renderowanie rekurencyjne submodeli Render_Alpha( Instance->pModel, Instance->Material(), distancesquared, Instance->location() - m_renderpass.camera.position(), Instance->vAngle ); } } void opengl_renderer::Render_Alpha( TTraction *Traction ) { /* // NOTE: test disabled as this call is only executed as part of the colour pass double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees distancesquared = Math3D::SquareMagnitude( ( Traction->location() - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { distancesquared = glm::length2( ( Traction->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor; break; } } */ auto const distancesquared { glm::length2( ( Traction->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; if( ( distancesquared < Traction->m_rangesquaredmin ) || ( distancesquared >= Traction->m_rangesquaredmax ) ) { return; } if( false == Traction->m_visible ) { return; } // rysuj jesli sa druty i nie zerwana if( ( Traction->Wires == 0 ) || ( true == TestFlag( Traction->DamageFlag, 128 ) ) ) { return; } // setup /* float const linealpha = static_cast( std::min( 1.25, 5000 * Traction->WireThickness / ( distancesquared + 1.0 ) ) ); // zbyt grube nie są dobre ::glLineWidth( linealpha ); */ auto const distance { static_cast( std::sqrt( distancesquared ) ) }; auto const linealpha { 20.f * Traction->WireThickness / std::max( 0.5f * Traction->radius() + 1.f, distance - ( 0.5f * Traction->radius() ) ) }; ::glLineWidth( clamp( 0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f, 1.f, 1.75f ) ); // McZapkie-261102: kolor zalezy od materialu i zasniedzenia ::glColor4fv( glm::value_ptr( glm::vec4{ Traction->wire_color() /* * ( DebugModeFlag ? 1.f : clamp( m_sunandviewangle, 0.25f, 1.f ) ) */, linealpha } ) ); // render m_geometry.draw( Traction->m_geometry ); // debug data ++m_debugstats.traction; ++m_debugstats.drawcalls; } void opengl_renderer::Render_Alpha( scene::lines_node const &Lines ) { auto const &data { Lines.data() }; /* // NOTE: test disabled as this call is only executed as part of the colour pass double distancesquared; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees distancesquared = Math3D::SquareMagnitude( ( data.area.center - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { distancesquared = glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor; break; } } */ auto const distancesquared { glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor }; if( ( distancesquared < data.rangesquared_min ) || ( distancesquared >= data.rangesquared_max ) ) { return; } // setup auto const distance { static_cast( std::sqrt( distancesquared ) ) }; auto const linealpha = ( data.line_width > 0.f ? 10.f * data.line_width / std::max( 0.5f * data.area.radius + 1.f, distance - ( 0.5f * data.area.radius ) ) : 1.f ); // negative width means the lines are always opague ::glLineWidth( clamp( 0.5f * linealpha + data.line_width * data.area.radius / 1000.f, 1.f, 8.f ) ); ::glColor4fv( glm::value_ptr( glm::vec4{ glm::vec3{ data.lighting.diffuse * m_sunlight.ambient }, // w zaleznosci od koloru swiatla std::min( 1.f, linealpha ) } ) ); // render m_geometry.draw( data.geometry ); ++m_debugstats.lines; ++m_debugstats.drawcalls; } bool opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) { if( false == Dynamic->renderme ) { return false; } // setup TSubModel::iInstance = ( size_t )Dynamic; //żeby nie robić cudzych animacji glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position(); // lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom float squaredistance; switch( m_renderpass.draw_mode ) { case rendermode::shadows: { squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor ) / Global.fDistanceFactor; break; } default: { squaredistance = glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor ) / Global.fDistanceFactor; break; } } Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu ::glPushMatrix(); ::glTranslated( originoffset.x, originoffset.y, originoffset.z ); ::glMultMatrixd( Dynamic->mMatrix.getArray() ); if( Dynamic->fShade > 0.0f ) { // change light level based on light level of the occupied track m_sunlight.apply_intensity( Dynamic->fShade ); } m_renderspecular = true; // render if( Dynamic->mdLowPolyInt ) { // low poly interior // if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) { /* // enable cab light if needed if( Dynamic->InteriorLightLevel > 0.0f ) { // crude way to light the cabin, until we have something more complete in place ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) ); } */ Render_Alpha( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); /* if( Dynamic->InteriorLightLevel > 0.0f ) { // reset the overall ambient ::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) ); } */ // } } if( Dynamic->mdModel ) { // main model Render_Alpha( Dynamic->mdModel, Dynamic->Material(), squaredistance ); } // optional attached models for( auto *attachment : Dynamic->mdAttachments ) { Render_Alpha( attachment, Dynamic->Material(), squaredistance ); } if( Dynamic->mdLoad ) { // renderowanie nieprzezroczystego ładunku Render_Alpha( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); } // post-render cleanup m_renderspecular = false; if( Dynamic->fShade > 0.0f ) { // restore regular light level m_sunlight.apply_intensity(); } ::glPopMatrix(); if( Dynamic->btnOn ) Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli return true; } bool opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance ) { auto alpha = ( Material != nullptr ? Material->textures_alpha : 0x30300030 ); if( 0 == ( alpha & Model->iFlags & 0x2F2F002F ) ) { // nothing to render return false; } Model->Root->fSquareDist = Squaredistance; // zmienna globalna! // setup Model->Root->ReplacableSet( ( Material != nullptr ? Material->replacable_skins : nullptr ), alpha ); Model->Root->pRoot = Model; // render Render_Alpha( Model->Root ); // post-render cleanup return true; } bool opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance, Math3D::vector3 const &Position, glm::vec3 const &Angle ) { ::glPushMatrix(); ::glTranslated( Position.x, Position.y, Position.z ); if( Angle.y != 0.0 ) ::glRotatef( Angle.y, 0.f, 1.f, 0.f ); if( Angle.x != 0.0 ) ::glRotatef( Angle.x, 1.f, 0.f, 0.f ); if( Angle.z != 0.0 ) ::glRotatef( Angle.z, 0.f, 0.f, 1.f ); auto const result = Render_Alpha( Model, Material, Squaredistance ); // position is effectively camera offset ::glPopMatrix(); return result; } void opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // renderowanie przezroczystych przez DL if( ( Submodel->iVisible ) && ( TSubModel::fSquareDist >= Submodel->fSquareMinDist ) && ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) { // debug data ++m_debugstats.submodels; ++m_debugstats.drawcalls; if( Submodel->iFlags & 0xC000 ) { ::glPushMatrix(); if( Submodel->fMatrix ) ::glMultMatrixf( Submodel->fMatrix->readArray() ); if( Submodel->b_aAnim != TAnimType::at_None ) Submodel->RaAnimation( Submodel->b_aAnim ); } if( Submodel->eType < TP_ROTATOR ) { // renderowanie obiektów OpenGL if( Submodel->iAlpha & Submodel->iFlags & 0x2F ) { // rysuj gdy element przezroczysty switch( m_renderpass.draw_mode ) { case rendermode::color: { // NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains // for the time being we'll do with enforced worst-case scaling method, when speculars are enabled #ifdef EU07_USE_OPTIMIZED_NORMALIZATION switch( Submodel->m_normalizenormals ) { case TSubModel::normalize: { ::glEnable( GL_NORMALIZE ); break; } case TSubModel::rescale: { ::glEnable( GL_RESCALE_NORMAL ); break; } default: { break; } } #else if( true == m_renderspecular ) { ::glEnable( GL_NORMALIZE ); } #endif // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); } else { // również 0 Bind_Material( Submodel->m_material ); } // ...colors... if( Submodel->fVisible < 1.f ) { ::glColor4f( Submodel->f4Diffuse.r, Submodel->f4Diffuse.g, Submodel->f4Diffuse.b, Submodel->fVisible ); } else { ::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub } if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( Submodel->f4Specular * m_sunlight.specular.a * m_speculartranslucentscalefactor ) ); } // ...luminance auto const unitstate = m_unitstate; auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; if( isemissive ) { // zeby swiecilo na kolorowo ::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( /* Submodel->f4Emision */ Submodel->f4Diffuse * Submodel->f4Emision.a ) ); // disable shadows so they don't obstruct self-lit items /* setup_shadow_color( colors::white ); */ switch_units( unitstate.diffuse, false, false ); } // main draw call m_geometry.draw( Submodel->m_geometry ); // post-draw reset if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) ); } if( isemissive ) { // restore default (lack of) brightness ::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( colors::none ) ); /* setup_shadow_color( m_shadowcolor ); */ switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections ); } #ifdef EU07_USE_OPTIMIZED_NORMALIZATION switch( Submodel->m_normalizenormals ) { case TSubModel::normalize: { ::glDisable( GL_NORMALIZE ); break; } case TSubModel::rescale: { ::glDisable( GL_RESCALE_NORMAL ); break; } default: { break; } } #else if( true == m_renderspecular ) { ::glDisable( GL_NORMALIZE ); } #endif break; } case rendermode::cabshadows: { // scenery picking and shadow both use enforced colour and no frills // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); } else { // również 0 Bind_Material( Submodel->m_material ); } // main draw call m_geometry.draw( Submodel->m_geometry ); // post-draw reset break; } default: { break; } } } } else if( Submodel->eType == TP_FREESPOTLIGHT ) { if( ( Global.fLuminance < Submodel->fLight ) || ( Global.Overcast > 1.f ) ) { // NOTE: we're forced here to redo view angle calculations etc, because this data isn't instanced but stored along with the single mesh // TODO: separate instance data from reusable geometry auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW ); auto const lightcenter = modelview * interpolate( glm::vec4( 0.f, 0.f, -0.05f, 1.f ), glm::vec4( 0.f, 0.f, -0.10f, 1.f ), static_cast( TSubModel::fSquareDist / Submodel->fSquareMaxDist ) ); // pozycja punktu świecącego względem kamery Submodel->fCosViewAngle = glm::dot( glm::normalize( modelview * glm::vec4( 0.f, 0.f, -1.f, 1.f ) - lightcenter ), glm::normalize( -lightcenter ) ); if( Submodel->fCosViewAngle > Submodel->fCosFalloffAngle ) { // only bother if the viewer is inside the visibility cone // luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness' auto glarelevel { clamp( std::max( 0.6f - Global.fLuminance, // reduce the glare in bright daylight Global.Overcast - 1.f ), // ensure some glare in rainy/foggy conditions 0.f, 1.f ) }; // view angle attenuation float const anglefactor { clamp( ( Submodel->fCosViewAngle - Submodel->fCosFalloffAngle ) / ( Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle ), 0.f, 1.f ) }; glarelevel *= anglefactor; if( glarelevel > 0.0f ) { // setup ::glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT ); Bind_Texture( m_glaretexture ); ::glDisable( GL_LIGHTING ); ::glDisable( GL_FOG ); ::glDepthMask( GL_FALSE ); ::glBlendFunc( GL_SRC_ALPHA, GL_ONE ); ::glPushMatrix(); ::glLoadIdentity(); // macierz jedynkowa ::glTranslatef( lightcenter.x, lightcenter.y, lightcenter.z ); // początek układu zostaje bez zmian ::glRotated( std::atan2( lightcenter.x, lightcenter.z ) * 180.0 / M_PI, 0.0, 1.0, 0.0 ); // jedynie obracamy w pionie o kąt // disable shadows so they don't obstruct self-lit items auto const unitstate = m_unitstate; switch_units( unitstate.diffuse, false, false ); auto const *lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override glm::value_ptr( Submodel->f4Diffuse ) : glm::value_ptr( Submodel->DiffuseOverride ) }; ::glColor4f( lightcolor[ 0 ], lightcolor[ 1 ], lightcolor[ 2 ], Submodel->fVisible * glarelevel ); // main draw call m_geometry.draw( m_billboardgeometry ); /* // NOTE: we could do simply... vec3 vertexPosition_worldspace = particleCenter_wordspace + CameraRight_worldspace * squareVertices.x * BillboardSize.x + CameraUp_worldspace * squareVertices.y * BillboardSize.y; // ...etc instead IF we had easy access to camera's forward and right vectors. TODO: check if Camera matrix is accessible */ // post-render cleanup switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections ); ::glPopMatrix(); ::glPopAttrib(); } } } } if( Submodel->Child != nullptr ) { if( Submodel->eType == TP_TEXT ) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child int i, j = (int)Submodel->pasText->size(); TSubModel *p; if( !Submodel->smLetter ) { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być Submodel->smLetter = new TSubModel *[ 256 ]; // tablica wskaźników submodeli dla wyświetlania tekstu ::ZeroMemory( Submodel->smLetter, 256 * sizeof( TSubModel * ) ); // wypełnianie zerami p = Submodel->Child; while( p ) { Submodel->smLetter[ p->pName[ 0 ] ] = p; p = p->Next; // kolejny znak } } for( i = 1; i <= j; ++i ) { p = Submodel->smLetter[ ( *( Submodel->pasText) )[ i ] ]; // znak do wyświetlenia if( p ) { // na razie tylko jako przezroczyste Render_Alpha( p ); if( p->fMatrix ) ::glMultMatrixf( p->fMatrix->readArray() ); // przesuwanie widoku } } } else if( Submodel->iAlpha & Submodel->iFlags & 0x002F0000 ) Render_Alpha( Submodel->Child ); } if( Submodel->iFlags & 0xC000 ) ::glPopMatrix(); } /* if( Submodel->b_aAnim < at_SecondsJump ) Submodel->b_aAnim = at_None; // wyłączenie animacji dla kolejnego użycia submodelu */ if( Submodel->Next != nullptr ) if( Submodel->iAlpha & Submodel->iFlags & 0x2F000000 ) Render_Alpha( Submodel->Next ); }; // utility methods void opengl_renderer::Update_Pick_Control() { #ifdef EU07_USE_PICKING_FRAMEBUFFER if( true == m_framebuffersupport ) { ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer ); } #endif Render_pass( rendermode::pickcontrols ); // determine point to examine glm::dvec2 mousepos; glfwGetCursorPos( m_window, &mousepos.x, &mousepos.y ); mousepos.y = Global.iWindowHeight - mousepos.y; // cursor coordinates are flipped compared to opengl #ifdef EU07_USE_PICKING_FRAMEBUFFER glm::ivec2 pickbufferpos; if( true == m_framebuffersupport ) { // ::glReadBuffer( GL_COLOR_ATTACHMENT0_EXT ); pickbufferpos = glm::ivec2{ mousepos.x * EU07_PICKBUFFERSIZE / std::max( 1, Global.iWindowWidth ), mousepos.y * EU07_PICKBUFFERSIZE / std::max( 1, Global.iWindowHeight ) }; } else { // ::glReadBuffer( GL_BACK ); pickbufferpos = glm::ivec2{ mousepos }; } #else // ::glReadBuffer( GL_BACK ); glm::ivec2 pickbufferpos{ mousepos }; #endif unsigned char pickreadout[4]; ::glReadPixels( pickbufferpos.x, pickbufferpos.y, 1, 1, GL_BGRA, GL_UNSIGNED_BYTE, pickreadout ); auto const controlindex = pick_index( glm::ivec3{ pickreadout[ 2 ], pickreadout[ 1 ], pickreadout[ 0 ] } ); TSubModel *control { nullptr }; if( ( controlindex > 0 ) && ( controlindex <= m_pickcontrolsitems.size() ) ) { control = m_pickcontrolsitems[ controlindex - 1 ]; } #ifdef EU07_USE_PICKING_FRAMEBUFFER if( true == m_framebuffersupport ) { ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } #endif m_pickcontrolitem = control; // return control; for( auto callback : m_control_pick_requests ) { callback( m_pickcontrolitem ); } m_control_pick_requests.clear(); } void opengl_renderer::Update_Pick_Node() { #ifdef EU07_USE_PICKING_FRAMEBUFFER if( true == m_framebuffersupport ) { ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer ); } #endif Render_pass( rendermode::pickscenery ); // determine point to examine glm::dvec2 mousepos; glfwGetCursorPos( m_window, &mousepos.x, &mousepos.y ); mousepos.y = Global.iWindowHeight - mousepos.y; // cursor coordinates are flipped compared to opengl #ifdef EU07_USE_PICKING_FRAMEBUFFER glm::ivec2 pickbufferpos; if( true == m_framebuffersupport ) { // ::glReadBuffer( GL_COLOR_ATTACHMENT0_EXT ); pickbufferpos = glm::ivec2{ mousepos.x * EU07_PICKBUFFERSIZE / Global.iWindowWidth, mousepos.y * EU07_PICKBUFFERSIZE / Global.iWindowHeight }; } else { // ::glReadBuffer( GL_BACK ); pickbufferpos = glm::ivec2{ mousepos }; } #else // ::glReadBuffer( GL_BACK ); glm::ivec2 pickbufferpos{ mousepos }; #endif unsigned char pickreadout[4]; ::glReadPixels( pickbufferpos.x, pickbufferpos.y, 1, 1, GL_BGRA, GL_UNSIGNED_BYTE, pickreadout ); auto const nodeindex = pick_index( glm::ivec3{ pickreadout[ 2 ], pickreadout[ 1 ], pickreadout[ 0 ] } ); scene::basic_node *node { nullptr }; if( ( nodeindex > 0 ) && ( nodeindex <= m_picksceneryitems.size() ) ) { node = m_picksceneryitems[ nodeindex - 1 ]; } #ifdef EU07_USE_PICKING_FRAMEBUFFER if( true == m_framebuffersupport ) { ::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } #endif m_picksceneryitem = node; // return node; for( auto callback : m_node_pick_requests ) { callback( m_picksceneryitem ); } m_node_pick_requests.clear(); } // converts provided screen coordinates to world coordinates of most recent color pass glm::dvec3 opengl_renderer::Update_Mouse_Position() { glm::dvec2 mousepos; Application.get_cursor_pos( mousepos.x, mousepos.y ); // glfwGetCursorPos( m_window, &mousepos.x, &mousepos.y ); mousepos.x = clamp( mousepos.x, 0, Global.iWindowWidth - 1 ); mousepos.y = clamp( Global.iWindowHeight - clamp( mousepos.y, 0, Global.iWindowHeight ), 0, Global.iWindowHeight - 1 ) ; GLfloat pointdepth; ::glReadPixels( mousepos.x, mousepos.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pointdepth ); if( pointdepth < 1.0 ) { m_worldmousecoordinates = glm::unProject( glm::vec3{ mousepos, pointdepth }, glm::mat4{ glm::mat3{ m_colorpass.camera.modelview() } }, m_colorpass.camera.projection(), glm::vec4{ 0, 0, Global.iWindowWidth, Global.iWindowHeight } ); } return m_colorpass.camera.position() + glm::dvec3{ m_worldmousecoordinates }; } void opengl_renderer::Update( double const Deltatime ) { // per frame updates if( simulation::is_ready ) { // update particle subsystem renderpass_config renderpass; setup_pass( renderpass, rendermode::color ); m_skydomerenderer.update(); m_precipitationrenderer.update(); m_particlerenderer.update( renderpass.camera ); } // fixed step updates /* m_pickupdateaccumulator += Deltatime; if( m_updateaccumulator > 0.5 ) { m_pickupdateaccumulator = 0.0; if( ( true == Global.ControlPicking ) && ( false == FreeFlyModeFlag ) ) { Update_Pick_Control(); } else { m_pickcontrolitem = nullptr; } // temporary conditions for testing. eventually will be coupled with editor mode if( ( true == Global.ControlPicking ) && ( true == DebugModeFlag ) && ( true == FreeFlyModeFlag ) ) { Update_Pick_Node(); } else { m_picksceneryitem = nullptr; } } */ m_updateaccumulator += Deltatime; if( ( true == Global.ControlPicking ) && ( false == FreeFlyModeFlag ) ) { if( ( false == m_control_pick_requests.empty() ) || ( m_updateaccumulator >= 1.0 ) ) { Update_Pick_Control(); } } else { m_pickcontrolitem = nullptr; } // temporary conditions for testing. eventually will be coupled with editor mode if( ( true == Global.ControlPicking ) && ( true == FreeFlyModeFlag ) && ( ( true == DebugModeFlag ) || ( true == EditorModeFlag ) ) ) { if( ( false == m_node_pick_requests.empty() ) || ( m_updateaccumulator >= 1.0 ) ) { Update_Pick_Node(); } } else { m_picksceneryitem = nullptr; } if( m_updateaccumulator < 1.0 ) { // too early for any work return; } m_updateaccumulator = 0.0; m_framerate = 1000.f / ( Timer::subsystem.gfx_total.average() ); // adjust draw ranges etc, based on recent performance auto const framerate = 1000.f / Timer::subsystem.gfx_color.average(); float targetfactor; if( framerate > 90.0 ) { targetfactor = 3.0f; } else if( framerate > 60.0 ) { targetfactor = 1.5f; } else if( framerate > 30.0 ) { targetfactor = 1.25; } else { targetfactor = std::max( Global.iWindowHeight / 768.f, 1.f ); } if( targetfactor > Global.fDistanceFactor ) { Global.fDistanceFactor = std::min( targetfactor, Global.fDistanceFactor + 0.05f ); } else if( targetfactor < Global.fDistanceFactor ) { Global.fDistanceFactor = std::max( targetfactor, Global.fDistanceFactor - 0.05f ); } if( ( framerate < 15.0 ) && ( Global.iSlowMotion < 7 ) ) { Global.iSlowMotion = ( Global.iSlowMotion << 1 ) + 1; // zapalenie kolejnego bitu if( Global.iSlowMotionMask & 1 ) if( Global.iMultisampling ) // a multisampling jest włączony ::glDisable( GL_MULTISAMPLE ); // wyłączenie multisamplingu powinno poprawić FPS } else if( ( framerate > 20.0 ) && Global.iSlowMotion ) { // FPS się zwiększył, można włączyć bajery Global.iSlowMotion = ( Global.iSlowMotion >> 1 ); // zgaszenie bitu if( Global.iSlowMotion == 0 ) // jeśli jest pełna prędkość if( Global.iMultisampling ) // a multisampling jest włączony ::glEnable( GL_MULTISAMPLE ); } if( ( true == Global.ResourceSweep ) && ( true == simulation::is_ready ) ) { // garbage collection m_geometry.update(); m_textures.update(); } if( true == DebugModeFlag ) { m_debugtimestext += m_textures.info(); } // dump last opengl error, if any auto const glerror = ::glGetError(); if( glerror != GL_NO_ERROR ) { std::string glerrorstring( ( char * )::gluErrorString( glerror ) ); win1250_to_ascii( glerrorstring ); Global.LastGLError = std::to_string( glerror ) + " (" + glerrorstring + ")"; } } // debug performance string std::string const & opengl_renderer::info_times() const { return m_debugtimestext; } std::string const & opengl_renderer::info_stats() const { return m_debugstatstext; } void opengl_renderer::Update_Lights( light_array &Lights ) { // arrange the light array from closest to farthest from current position of the camera auto const camera = m_renderpass.camera.position(); std::sort( std::begin( Lights.data ), std::end( Lights.data ), [&camera]( light_array::light_record const &Left, light_array::light_record const &Right ) { // move lights which are off at the end... if( Left.intensity == 0.f ) { return false; } if( Right.intensity == 0.f ) { return true; } // ...otherwise prefer closer and/or brigher light sources return ( glm::length2( camera - Left.position ) * ( 1.f - Left.intensity ) ) < ( glm::length2( camera - Right.position ) * ( 1.f - Right.intensity ) ); } ); size_t const count = std::min( m_lights.size(), Lights.data.size() ); if( count == 0 ) { return; } auto renderlight = m_lights.begin(); for( auto const &scenelight : Lights.data ) { if( renderlight == m_lights.end() ) { // we ran out of lights to assign break; } if( scenelight.intensity == 0.f ) { // all lights past this one are bound to be off break; } auto const lightoffset = glm::vec3{ scenelight.position - camera }; if( glm::length( lightoffset ) > 1000.f ) { // we don't care about lights past arbitrary limit of 1 km. // but there could still be weaker lights which are closer, so keep looking continue; } // if the light passed tests so far, it's good enough renderlight->position = lightoffset; renderlight->direction = scenelight.direction; auto luminance = static_cast( Global.fLuminance ); // adjust luminance level based on vehicle's location, e.g. tunnels auto const environment = scenelight.owner->fShade; if( environment > 0.f ) { luminance *= environment; } renderlight->diffuse = glm::vec4{ glm::max( glm::vec3{ colors::none }, scenelight.color - glm::vec3{ luminance } ), renderlight->diffuse[ 3 ] }; renderlight->ambient = glm::vec4{ glm::max( glm::vec3{ colors::none }, scenelight.color * glm::vec3{ scenelight.intensity } - glm::vec3{ luminance } ), renderlight->ambient[ 3 ] }; ::glLightf( renderlight->id, GL_LINEAR_ATTENUATION, static_cast( (0.25 * scenelight.count) / std::pow( scenelight.count, 2 ) * (scenelight.owner->DimHeadlights ? 1.25 : 1.0) ) ); ::glEnable( renderlight->id ); renderlight->apply_intensity(); renderlight->apply_angle(); ++renderlight; } while( renderlight != m_lights.end() ) { // if we went through all scene lights and there's still opengl lights remaining, kill these ::glDisable( renderlight->id ); ++renderlight; } } void opengl_renderer::Disable_Lights() { for( size_t idx = 0; idx < m_lights.size() + 1; ++idx ) { ::glDisable( GL_LIGHT0 + (int)idx ); } } bool opengl_renderer::Init_caps() { std::string oglversion = ( (char *)glGetString( GL_VERSION ) ); WriteLog( "Gfx Renderer: " + std::string( (char *)glGetString( GL_RENDERER ) ) + " Vendor: " + std::string( (char *)glGetString( GL_VENDOR ) ) + " OpenGL Version: " + oglversion ); #ifdef EU07_USEIMGUIIMPLOPENGL2 if( !GLAD_GL_VERSION_1_5 ) { ErrorLog( "Requires openGL >= 1.5" ); return false; } #else if( !GLAD_GL_VERSION_3_0 ) { ErrorLog( "Requires openGL >= 3.0" ); return false; } #endif WriteLog( "Supported extensions: " + std::string((char *)glGetString( GL_EXTENSIONS )) ); WriteLog( std::string("Render path: ") + ( Global.bUseVBO ? "VBO" : "Display lists" ) ); if( GL_EXT_framebuffer_object ) { m_framebuffersupport = true; WriteLog( "Framebuffer objects enabled" ); } else { WriteLog( "Framebuffer objects not supported, resorting to back buffer rendering where possible" ); } // ograniczenie maksymalnego rozmiaru tekstur - parametr dla skalowania tekstur { GLint texturesize; ::glGetIntegerv( GL_MAX_TEXTURE_SIZE, &texturesize ); Global.iMaxTextureSize = std::min( Global.iMaxTextureSize, texturesize ); WriteLog( "Texture sizes capped at " + std::to_string( Global.iMaxTextureSize ) + " pixels" ); m_shadowbuffersize = Global.shadowtune.map_size; m_shadowbuffersize = std::min( m_shadowbuffersize, texturesize ); WriteLog( "Shadows map size capped at " + std::to_string( m_shadowbuffersize ) + " pixels" ); } // cap the number of supported lights based on hardware { GLint maxlights; ::glGetIntegerv( GL_MAX_LIGHTS, &maxlights ); Global.DynamicLightCount = std::min( Global.DynamicLightCount, maxlights - 1 ); WriteLog( "Dynamic light amount capped at " + std::to_string( Global.DynamicLightCount ) + " (" + std::to_string(maxlights) + " lights total supported by the gfx card)" ); } // select renderer mode if( true == Global.BasicRenderer ) { WriteLog( "Basic renderer selected, shadow and reflection mapping will be disabled" ); Global.RenderShadows = false; m_diffusetextureunit = GL_TEXTURE0; m_helpertextureunit = -1; m_shadowtextureunit = -1; m_normaltextureunit = -1; } else { GLint maxtextureunits; ::glGetIntegerv( GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits ); if( maxtextureunits < 4 ) { WriteLog( "Less than 4 texture units, shadow and reflection mapping will be disabled" ); Global.BasicRenderer = true; Global.RenderShadows = false; m_diffusetextureunit = GL_TEXTURE0; m_helpertextureunit = -1; m_shadowtextureunit = -1; m_normaltextureunit = -1; } } if( Global.iMultisampling ) { WriteLog( "Using multisampling x" + std::to_string( 1 << Global.iMultisampling ) ); } return true; } glm::vec3 opengl_renderer::pick_color( std::size_t const Index ) { /* // pick colours are set with step of 4 for some slightly easier visual debugging. not strictly needed but, eh int const colourstep = 4; int const componentcapacity = 256 / colourstep; auto const redgreen = std::div( Index, componentcapacity * componentcapacity ); auto const greenblue = std::div( redgreen.rem, componentcapacity ); auto const blue = Index % componentcapacity; return glm::vec3 { redgreen.quot * colourstep / 255.0f, greenblue.quot * colourstep / 255.0f, greenblue.rem * colourstep / 255.0f }; */ // alternatively return glm::vec3{ ( ( Index & 0xff0000 ) >> 16 ) / 255.0f, ( ( Index & 0x00ff00 ) >> 8 ) / 255.0f, ( Index & 0x0000ff ) / 255.0f }; } std::size_t opengl_renderer::pick_index( glm::ivec3 const &Color ) { /* return ( std::floor( Color.b / 4 ) + std::floor( Color.g / 4 ) * 64 + std::floor( Color.r / 4 ) * 64 * 64 ); */ // alternatively return Color.b + ( Color.g * 256 ) + ( Color.r * 256 * 256 ); } //---------------------------------------------------------------------------