mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
A few files snuck in with CRLF for the line endings. These make it hard to look at diffs as every line appears to have changed. This commit makes only line ending changes, so can be ignored
2159 lines
89 KiB
C++
2159 lines
89 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
|
|
* Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
/**
|
|
* @file c3d_render_raytracing.cpp
|
|
* @brief
|
|
*/
|
|
|
|
#include <GL/glew.h>
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <climits>
|
|
#include <thread>
|
|
|
|
#include "c3d_render_raytracing.h"
|
|
#include "mortoncodes.h"
|
|
#include "../ccolorrgb.h"
|
|
#include "3d_fastmath.h"
|
|
#include "3d_math.h"
|
|
#include "../common_ogl/ogl_utils.h"
|
|
#include <profile.h> // To use GetRunningMicroSecs or another profiling utility
|
|
|
|
// This should be used in future for the function
|
|
// convertLinearToSRGB
|
|
//#include <glm/gtc/color_space.hpp>
|
|
|
|
C3D_RENDER_RAYTRACING::C3D_RENDER_RAYTRACING( CINFO3D_VISU &aSettings ) :
|
|
C3D_RENDER_BASE( aSettings ),
|
|
m_postshader_ssao( aSettings.CameraGet() )
|
|
{
|
|
wxLogTrace( m_logTrace, wxT( "C3D_RENDER_RAYTRACING::C3D_RENDER_RAYTRACING" ) );
|
|
|
|
m_opengl_support_vertex_buffer_object = false;
|
|
m_pboId = GL_NONE;
|
|
m_pboDataSize = 0;
|
|
m_accelerator = NULL;
|
|
m_stats_converted_dummy_to_plane = 0;
|
|
m_stats_converted_roundsegment2d_to_roundsegment = 0;
|
|
m_oldWindowsSize.x = 0;
|
|
m_oldWindowsSize.y = 0;
|
|
m_outlineBoard2dObjects = NULL;
|
|
m_firstHitinfo = NULL;
|
|
m_shaderBuffer = NULL;
|
|
m_camera_light = NULL;
|
|
|
|
m_xoffset = 0;
|
|
m_yoffset = 0;
|
|
|
|
m_isPreview = false;
|
|
m_rt_render_state = RT_RENDER_STATE_MAX; // Set to an initial invalid state
|
|
m_stats_start_rendering_time = 0;
|
|
m_nrBlocksRenderProgress = 0;
|
|
}
|
|
|
|
|
|
C3D_RENDER_RAYTRACING::~C3D_RENDER_RAYTRACING()
|
|
{
|
|
wxLogTrace( m_logTrace, wxT( "C3D_RENDER_RAYTRACING::~C3D_RENDER_RAYTRACING" ) );
|
|
|
|
delete m_accelerator;
|
|
m_accelerator = NULL;
|
|
|
|
delete m_outlineBoard2dObjects;
|
|
m_outlineBoard2dObjects = NULL;
|
|
|
|
delete[] m_shaderBuffer;
|
|
m_shaderBuffer = NULL;
|
|
|
|
opengl_delete_pbo();
|
|
}
|
|
|
|
|
|
int C3D_RENDER_RAYTRACING::GetWaitForEditingTimeOut()
|
|
{
|
|
return 1000; // ms
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::opengl_delete_pbo()
|
|
{
|
|
// Delete PBO if it was created
|
|
if( m_opengl_support_vertex_buffer_object )
|
|
{
|
|
if( glIsBufferARB( m_pboId ) )
|
|
glDeleteBuffers( 1, &m_pboId );
|
|
|
|
m_pboId = GL_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::SetCurWindowSize( const wxSize &aSize )
|
|
{
|
|
if( m_windowSize != aSize )
|
|
{
|
|
m_windowSize = aSize;
|
|
glViewport( 0, 0, m_windowSize.x, m_windowSize.y );
|
|
|
|
initializeNewWindowSize();
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::restart_render_state()
|
|
{
|
|
m_stats_start_rendering_time = GetRunningMicroSecs();
|
|
|
|
m_rt_render_state = RT_RENDER_STATE_TRACING;
|
|
m_nrBlocksRenderProgress = 0;
|
|
|
|
m_postshader_ssao.InitFrame();
|
|
|
|
m_blockPositionsWasProcessed.resize( m_blockPositions.size() );
|
|
|
|
// Mark the blocks not processed yet
|
|
std::fill( m_blockPositionsWasProcessed.begin(),
|
|
m_blockPositionsWasProcessed.end(),
|
|
0 );
|
|
}
|
|
|
|
|
|
static inline void SetPixel( GLubyte *p, const CCOLORRGB &v )
|
|
{
|
|
p[0] = v.c[0]; p[1] = v.c[1]; p[2] = v.c[2]; p[3] = 255;
|
|
}
|
|
|
|
|
|
bool C3D_RENDER_RAYTRACING::Redraw( bool aIsMoving, REPORTER *aStatusTextReporter )
|
|
{
|
|
bool requestRedraw = false;
|
|
|
|
// Initialize openGL if need
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
if( !m_is_opengl_initialized )
|
|
{
|
|
if( !initializeOpenGL() )
|
|
return false;
|
|
|
|
//aIsMoving = true;
|
|
requestRedraw = true;
|
|
|
|
// It will assign the first time the windows size, so it will now
|
|
// revert to preview mode the first time the Redraw is called
|
|
m_oldWindowsSize = m_windowSize;
|
|
initialize_block_positions();
|
|
}
|
|
|
|
std::unique_ptr<BUSY_INDICATOR> busy = CreateBusyIndicator();
|
|
|
|
// Reload board if it was requested
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
if( m_reloadRequested )
|
|
{
|
|
if( aStatusTextReporter )
|
|
aStatusTextReporter->Report( _( "Loading..." ) );
|
|
|
|
//aIsMoving = true;
|
|
requestRedraw = true;
|
|
reload( aStatusTextReporter );
|
|
}
|
|
|
|
|
|
// Recalculate constants if windows size was changed
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
if( m_windowSize != m_oldWindowsSize )
|
|
{
|
|
m_oldWindowsSize = m_windowSize;
|
|
aIsMoving = true;
|
|
requestRedraw = true;
|
|
|
|
initialize_block_positions();
|
|
}
|
|
|
|
|
|
// Clear buffers
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
|
|
glClearDepth( 1.0f );
|
|
glClearStencil( 0x00 );
|
|
glClear( GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
|
|
|
|
// 4-byte pixel alignment
|
|
glPixelStorei( GL_UNPACK_ALIGNMENT, 4 );
|
|
|
|
glDisable( GL_STENCIL_TEST );
|
|
glDisable( GL_LIGHTING );
|
|
glDisable( GL_COLOR_MATERIAL );
|
|
glDisable( GL_DEPTH_TEST );
|
|
glDisable( GL_TEXTURE_2D );
|
|
glDisable( GL_BLEND );
|
|
|
|
|
|
const bool was_camera_changed = m_settings.CameraGet().ParametersChanged();
|
|
|
|
if( requestRedraw || aIsMoving || was_camera_changed )
|
|
m_rt_render_state = RT_RENDER_STATE_MAX; // Set to an invalid state,
|
|
// so it will restart again latter
|
|
|
|
|
|
// This will only render if need, otherwise it will redraw the PBO on the screen again
|
|
if( aIsMoving || was_camera_changed )
|
|
{
|
|
// Set head light (camera view light) with the oposite direction of the camera
|
|
if( m_camera_light )
|
|
m_camera_light->SetDirection( -m_settings.CameraGet().GetDir() );
|
|
|
|
OGL_DrawBackground( SFVEC3F(m_settings.m_BgColorTop),
|
|
SFVEC3F(m_settings.m_BgColorBot) );
|
|
|
|
// Bind PBO
|
|
glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );
|
|
|
|
// Get the PBO pixel pointer to write the data
|
|
GLubyte *ptrPBO = (GLubyte *)glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB,
|
|
GL_WRITE_ONLY_ARB );
|
|
|
|
if( ptrPBO )
|
|
{
|
|
render_preview( ptrPBO );
|
|
|
|
// release pointer to mapping buffer, this initialize the coping to PBO
|
|
glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB );
|
|
}
|
|
|
|
glWindowPos2i( m_xoffset, m_yoffset );
|
|
}
|
|
else
|
|
{
|
|
// Bind PBO
|
|
glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );
|
|
|
|
if( m_rt_render_state != RT_RENDER_STATE_FINISH )
|
|
{
|
|
// Get the PBO pixel pointer to write the data
|
|
GLubyte *ptrPBO = (GLubyte *)glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB,
|
|
GL_WRITE_ONLY_ARB );
|
|
|
|
if( ptrPBO )
|
|
{
|
|
render( ptrPBO, aStatusTextReporter );
|
|
|
|
if( m_rt_render_state != RT_RENDER_STATE_FINISH )
|
|
requestRedraw = true;
|
|
|
|
// release pointer to mapping buffer, this initialize the coping to PBO
|
|
glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB);
|
|
}
|
|
}
|
|
|
|
if( m_rt_render_state == RT_RENDER_STATE_FINISH )
|
|
{
|
|
glClear( GL_COLOR_BUFFER_BIT );
|
|
// Options if we want draw background instead
|
|
//OGL_DrawBackground( SFVEC3F(m_settings.m_BgColorTop),
|
|
// SFVEC3F(m_settings.m_BgColorBot) );
|
|
}
|
|
|
|
glWindowPos2i( m_xoffset, m_yoffset );
|
|
}
|
|
|
|
// This way it will blend the progress rendering with the last buffer. eg:
|
|
// if it was called after a openGL.
|
|
glEnable( GL_BLEND );
|
|
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
glEnable( GL_ALPHA_TEST );
|
|
|
|
glDrawPixels( m_realBufferSize.x,
|
|
m_realBufferSize.y,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
0 );
|
|
|
|
glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
|
|
|
|
return requestRedraw;
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::render( GLubyte *ptrPBO , REPORTER *aStatusTextReporter )
|
|
{
|
|
if( (m_rt_render_state == RT_RENDER_STATE_FINISH) ||
|
|
(m_rt_render_state >= RT_RENDER_STATE_MAX) )
|
|
{
|
|
restart_render_state();
|
|
|
|
if( m_camera_light )
|
|
m_camera_light->SetDirection( -m_settings.CameraGet().GetDir() );
|
|
|
|
if( m_settings.RenderEngineGet() == RENDER_ENGINE_OPENGL_LEGACY )
|
|
{
|
|
// Set all pixels of PBO transparent (Alpha to 0)
|
|
// This way it will draw the full buffer but only shows the updated (
|
|
// already calculated) squares
|
|
// /////////////////////////////////////////////////////////////////////
|
|
unsigned int nPixels = m_realBufferSize.x * m_realBufferSize.y;
|
|
GLubyte *tmp_ptrPBO = ptrPBO + 3; // PBO is RGBA
|
|
|
|
for( unsigned int i = 0; i < nPixels; ++i )
|
|
{
|
|
*tmp_ptrPBO = 0;
|
|
tmp_ptrPBO += 4; // PBO is RGBA
|
|
}
|
|
}
|
|
|
|
m_BgColorTop_LinearRGB = ConvertSRGBToLinear( (SFVEC3F)m_settings.m_BgColorTop );
|
|
m_BgColorBot_LinearRGB = ConvertSRGBToLinear( (SFVEC3F)m_settings.m_BgColorBot );
|
|
}
|
|
|
|
switch( m_rt_render_state )
|
|
{
|
|
case RT_RENDER_STATE_TRACING:
|
|
rt_render_tracing( ptrPBO, aStatusTextReporter );
|
|
break;
|
|
|
|
case RT_RENDER_STATE_POST_PROCESS_SHADE:
|
|
rt_render_post_process_shade( ptrPBO, aStatusTextReporter );
|
|
break;
|
|
|
|
case RT_RENDER_STATE_POST_PROCESS_BLUR_AND_FINISH:
|
|
rt_render_post_process_blur_finish( ptrPBO, aStatusTextReporter );
|
|
break;
|
|
|
|
default:
|
|
wxASSERT_MSG( false, "Invalid state on m_rt_render_state");
|
|
restart_render_state();
|
|
break;
|
|
}
|
|
|
|
if( aStatusTextReporter && (m_rt_render_state == RT_RENDER_STATE_FINISH) )
|
|
{
|
|
// Calculation time in seconds
|
|
const double calculation_time = (double)( GetRunningMicroSecs() -
|
|
m_stats_start_rendering_time ) / 1e6;
|
|
|
|
aStatusTextReporter->Report( wxString::Format( _( "Rendering time %.3f s" ),
|
|
calculation_time ) );
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_render_tracing( GLubyte *ptrPBO ,
|
|
REPORTER *aStatusTextReporter )
|
|
{
|
|
m_isPreview = false;
|
|
|
|
auto startTime = std::chrono::steady_clock::now();
|
|
bool breakLoop = false;
|
|
|
|
std::atomic<size_t> numBlocksRendered( 0 );
|
|
std::atomic<size_t> currentBlock( 0 );
|
|
std::atomic<size_t> threadsFinished( 0 );
|
|
|
|
size_t parallelThreadCount = std::min<size_t>(
|
|
std::max<size_t>( std::thread::hardware_concurrency(), 2 ),
|
|
m_blockPositions.size() );
|
|
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
|
|
{
|
|
std::thread t = std::thread( [&]()
|
|
{
|
|
for( size_t iBlock = currentBlock.fetch_add( 1 );
|
|
iBlock < m_blockPositions.size() && !breakLoop;
|
|
iBlock = currentBlock.fetch_add( 1 ) )
|
|
{
|
|
if( !m_blockPositionsWasProcessed[iBlock] )
|
|
{
|
|
rt_render_trace_block( ptrPBO, iBlock );
|
|
numBlocksRendered++;
|
|
m_blockPositionsWasProcessed[iBlock] = 1;
|
|
|
|
// Check if it spend already some time render and request to exit
|
|
// to display the progress
|
|
if( std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - startTime ).count() > 150 )
|
|
breakLoop = true;
|
|
}
|
|
}
|
|
|
|
threadsFinished++;
|
|
} );
|
|
|
|
t.detach();
|
|
}
|
|
|
|
while( threadsFinished < parallelThreadCount )
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
|
|
|
m_nrBlocksRenderProgress += numBlocksRendered;
|
|
|
|
if( aStatusTextReporter )
|
|
aStatusTextReporter->Report( wxString::Format( _( "Rendering: %.0f %%" ),
|
|
(float)(m_nrBlocksRenderProgress * 100) /
|
|
(float)m_blockPositions.size() ) );
|
|
|
|
// Check if it finish the rendering and if should continue to a post processing
|
|
// or mark it as finished
|
|
if( m_nrBlocksRenderProgress >= m_blockPositions.size() )
|
|
{
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
m_rt_render_state = RT_RENDER_STATE_POST_PROCESS_SHADE;
|
|
else
|
|
{
|
|
m_rt_render_state = RT_RENDER_STATE_FINISH;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SRGB_SPACE
|
|
|
|
// This should be removed in future when the KiCad support a greater version of
|
|
// glm lib.
|
|
|
|
#define SRGB_GAMA 2.4f
|
|
|
|
// This function implements the conversion from linear RGB to sRGB
|
|
// https://github.com/g-truc/glm/blob/master/glm/gtc/color_space.inl#L12
|
|
static SFVEC3F convertLinearToSRGB( const SFVEC3F &aRGBcolor )
|
|
{
|
|
const float gammaCorrection = 1.0f / SRGB_GAMA;
|
|
const SFVEC3F clampedColor = glm::clamp( aRGBcolor, SFVEC3F(0.0f), SFVEC3F(1.0f) );
|
|
|
|
return glm::mix(
|
|
glm::pow( clampedColor, SFVEC3F(gammaCorrection) ) * 1.055f - 0.055f,
|
|
clampedColor * 12.92f,
|
|
glm::lessThan( clampedColor, SFVEC3F(0.0031308f) ) );
|
|
}
|
|
|
|
// This function implements the conversion from sRGB to linear RGB
|
|
// https://github.com/g-truc/glm/blob/master/glm/gtc/color_space.inl#L35
|
|
SFVEC3F ConvertSRGBToLinear( const SFVEC3F &aSRGBcolor )
|
|
{
|
|
const float gammaCorrection = SRGB_GAMA;
|
|
|
|
return glm::mix(
|
|
glm::pow( (aSRGBcolor + SFVEC3F(0.055f)) * SFVEC3F(0.94786729857819905213270142180095f),
|
|
SFVEC3F(gammaCorrection) ),
|
|
aSRGBcolor * SFVEC3F(0.07739938080495356037151702786378f),
|
|
glm::lessThanEqual( aSRGBcolor, SFVEC3F(0.04045f) ) );
|
|
}
|
|
|
|
#endif
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_final_color( GLubyte *ptrPBO,
|
|
const SFVEC3F &rgbColor,
|
|
bool applyColorSpaceConversion )
|
|
{
|
|
|
|
SFVEC3F color = rgbColor;
|
|
|
|
#ifdef USE_SRGB_SPACE
|
|
|
|
// This should be used in future when the KiCad support a greater version of
|
|
// glm lib.
|
|
// if( applyColorSpaceConversion )
|
|
// rgbColor = glm::convertLinearToSRGB( rgbColor );
|
|
|
|
if( applyColorSpaceConversion )
|
|
color = convertLinearToSRGB( rgbColor );
|
|
#endif
|
|
|
|
ptrPBO[0] = (unsigned int)glm::clamp( (int)(color.r * 255), 0, 255 );
|
|
ptrPBO[1] = (unsigned int)glm::clamp( (int)(color.g * 255), 0, 255 );
|
|
ptrPBO[2] = (unsigned int)glm::clamp( (int)(color.b * 255), 0, 255 );
|
|
ptrPBO[3] = 255;
|
|
}
|
|
|
|
|
|
static void HITINFO_PACKET_init( HITINFO_PACKET *aHitPacket )
|
|
{
|
|
// Initialize hitPacket with a "not hit" information
|
|
for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
|
|
{
|
|
aHitPacket[i].m_HitInfo.m_tHit = std::numeric_limits<float>::infinity();
|
|
aHitPacket[i].m_HitInfo.m_acc_node_info = 0;
|
|
aHitPacket[i].m_hitresult = false;
|
|
aHitPacket[i].m_HitInfo.m_HitNormal = SFVEC3F( 0.0f );
|
|
aHitPacket[i].m_HitInfo.m_ShadowFactor = 1.0f;
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_shades_packet(const SFVEC3F *bgColorY,
|
|
const RAY *aRayPkt,
|
|
HITINFO_PACKET *aHitPacket,
|
|
bool is_testShadow,
|
|
SFVEC3F *aOutHitColor )
|
|
{
|
|
for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
|
|
{
|
|
if( aHitPacket[i].m_hitresult == true )
|
|
{
|
|
aOutHitColor[i] = shadeHit( bgColorY[y],
|
|
aRayPkt[i],
|
|
aHitPacket[i].m_HitInfo,
|
|
false,
|
|
0,
|
|
is_testShadow );
|
|
}
|
|
else
|
|
{
|
|
aOutHitColor[i] = bgColorY[y];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_trace_AA_packet( const SFVEC3F *aBgColorY,
|
|
const HITINFO_PACKET *aHitPck_X0Y0,
|
|
const HITINFO_PACKET *aHitPck_AA_X1Y1,
|
|
const RAY *aRayPck,
|
|
SFVEC3F *aOutHitColor )
|
|
{
|
|
const bool is_testShadow = m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS );
|
|
|
|
for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
|
|
{
|
|
const RAY &rayAA = aRayPck[i];
|
|
|
|
HITINFO hitAA;
|
|
hitAA.m_tHit = std::numeric_limits<float>::infinity();
|
|
hitAA.m_acc_node_info = 0;
|
|
|
|
bool hitted = false;
|
|
|
|
const unsigned int idx0y1 = ( x + 0 ) + RAYPACKET_DIM * ( y + 1 );
|
|
const unsigned int idx1y1 = ( x + 1 ) + RAYPACKET_DIM * ( y + 1 );
|
|
|
|
// Gets the node info from the hit.
|
|
const unsigned int nodex0y0 = aHitPck_X0Y0[ i ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int node_AA_x0y0 = aHitPck_AA_X1Y1[ i ].m_HitInfo.m_acc_node_info;
|
|
|
|
unsigned int nodex1y0 = 0;
|
|
|
|
if( x < (RAYPACKET_DIM - 1) )
|
|
nodex1y0 = aHitPck_X0Y0[ i + 1 ].m_HitInfo.m_acc_node_info;
|
|
|
|
unsigned int nodex0y1 = 0;
|
|
|
|
if( y < (RAYPACKET_DIM - 1) )
|
|
nodex0y1 = aHitPck_X0Y0[ idx0y1 ].m_HitInfo.m_acc_node_info;
|
|
|
|
unsigned int nodex1y1 = 0;
|
|
|
|
if( ((x < (RAYPACKET_DIM - 1)) &&
|
|
(y < (RAYPACKET_DIM - 1))) )
|
|
nodex1y1 = aHitPck_X0Y0[ idx1y1 ].m_HitInfo.m_acc_node_info;
|
|
|
|
|
|
if( ((nodex0y0 == nodex1y0) || (nodex1y0 == 0)) && // If all notes are equal we assume there was no change on the object hits
|
|
((nodex0y0 == nodex0y1) || (nodex0y1 == 0)) &&
|
|
((nodex0y0 == nodex1y1) || (nodex1y1 == 0)) &&
|
|
(nodex0y0 == node_AA_x0y0) )
|
|
{
|
|
// Option 1
|
|
// This option will give a very good quality on reflections (slow)
|
|
/*
|
|
if( m_accelerator->Intersect( rayAA, hitAA, nodex0y0 ) )
|
|
{
|
|
aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );
|
|
}
|
|
else
|
|
{
|
|
if( m_accelerator->Intersect( rayAA, hitAA ) )
|
|
aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );
|
|
else
|
|
aOutHitColor[i] += hitColor[i];
|
|
}
|
|
*/
|
|
|
|
// Option 2
|
|
// Trace again with the same node,
|
|
// then if miss just give the same color as before
|
|
//if( m_accelerator->Intersect( rayAA, hitAA, nodex0y0 ) )
|
|
// aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );
|
|
|
|
// Option 3
|
|
// Use same color
|
|
|
|
}
|
|
else
|
|
{
|
|
// Try to intersect the different nodes
|
|
// It tests the possible combination of hitted or not hitted points
|
|
// This will try to get the best hit for this ray
|
|
|
|
if( nodex0y0 != 0 )
|
|
hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex0y0 );
|
|
|
|
if( ( nodex1y0 != 0 ) &&
|
|
( nodex0y0 != nodex1y0 ) )
|
|
hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex1y0 );
|
|
|
|
if( ( nodex0y1 != 0 ) &&
|
|
( nodex0y0 != nodex0y1 ) &&
|
|
( nodex1y0 != nodex0y1 ) )
|
|
hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex0y1 );
|
|
|
|
if( (nodex1y1 != 0 ) &&
|
|
( nodex0y0 != nodex1y1 ) &&
|
|
( nodex0y1 != nodex1y1 ) &&
|
|
( nodex1y0 != nodex1y1 ) )
|
|
hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex1y1 );
|
|
|
|
if( (node_AA_x0y0 != 0 ) &&
|
|
( nodex0y0 != node_AA_x0y0 ) &&
|
|
( nodex0y1 != node_AA_x0y0 ) &&
|
|
( nodex1y0 != node_AA_x0y0 ) &&
|
|
( nodex1y1 != node_AA_x0y0 ) )
|
|
hitted |= m_accelerator->Intersect( rayAA, hitAA, node_AA_x0y0 );
|
|
|
|
if( hitted )
|
|
{
|
|
// If we got any result, shade it
|
|
aOutHitColor[i] = shadeHit( aBgColorY[y], rayAA, hitAA, false, 0, is_testShadow );
|
|
}
|
|
else
|
|
{
|
|
// Note: There are very few cases that will end on this situation
|
|
// so it is not so expensive to trace a single ray from the beginning
|
|
|
|
// It was missed the 'last nodes' so, trace a ray from the beginning
|
|
if( m_accelerator->Intersect( rayAA, hitAA ) )
|
|
aOutHitColor[i] = shadeHit( aBgColorY[y], rayAA, hitAA, false, 0, is_testShadow );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define DISP_FACTOR 0.075f
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_render_trace_block( GLubyte *ptrPBO ,
|
|
signed int iBlock )
|
|
{
|
|
// Initialize ray packets
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
const SFVEC2UI &blockPos = m_blockPositions[iBlock];
|
|
const SFVEC2I blockPosI = SFVEC2I( blockPos.x + m_xoffset,
|
|
blockPos.y + m_yoffset );
|
|
|
|
RAYPACKET blockPacket( m_settings.CameraGet(),
|
|
(SFVEC2F)blockPosI + SFVEC2F(DISP_FACTOR, DISP_FACTOR),
|
|
SFVEC2F(DISP_FACTOR, DISP_FACTOR) // Displacement random factor
|
|
);
|
|
|
|
HITINFO_PACKET hitPacket_X0Y0[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
HITINFO_PACKET_init( hitPacket_X0Y0 );
|
|
|
|
// Calculate background gradient color
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
SFVEC3F bgColor[RAYPACKET_DIM];// Store a vertical gradient color
|
|
|
|
for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
const float posYfactor = (float)(blockPosI.y + y) / (float)m_windowSize.y;
|
|
|
|
bgColor[y] = m_BgColorTop_LinearRGB * SFVEC3F(posYfactor) +
|
|
m_BgColorBot_LinearRGB * ( SFVEC3F(1.0f) - SFVEC3F(posYfactor) );
|
|
}
|
|
|
|
// Intersect ray packets (calculate the intersection with rays and objects)
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
if( !m_accelerator->Intersect( blockPacket, hitPacket_X0Y0 ) )
|
|
{
|
|
|
|
// If block is empty then set shades and continue
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
{
|
|
for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
const SFVEC3F &outColor = bgColor[y];
|
|
|
|
const unsigned int yBlockPos = blockPos.y + y;
|
|
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x )
|
|
{
|
|
m_postshader_ssao.SetPixelData( blockPos.x + x,
|
|
yBlockPos,
|
|
SFVEC3F( 0.0f ),
|
|
outColor,
|
|
SFVEC3F( 0.0f ),
|
|
0,
|
|
1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
// This will set the output color to be displayed
|
|
// If post processing is enabled, it will not reflect the final result
|
|
// (as the final color will be computed on post processing)
|
|
// but it is used for report progress
|
|
|
|
const bool isFinalColor = !m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING );
|
|
|
|
for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
const SFVEC3F &outColor = bgColor[y];
|
|
|
|
const unsigned int yConst = blockPos.x +
|
|
( (y + blockPos.y) * m_realBufferSize.x);
|
|
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x )
|
|
{
|
|
GLubyte *ptr = &ptrPBO[ (yConst + x) * 4 ];
|
|
|
|
rt_final_color( ptr, outColor, isFinalColor );
|
|
}
|
|
}
|
|
|
|
// There is nothing more here to do.. there are no hits ..
|
|
// just background so continue
|
|
return;
|
|
}
|
|
|
|
|
|
SFVEC3F hitColor_X0Y0[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
// Shade original (0, 0) hits ("paint" the intersected objects)
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
rt_shades_packet( bgColor,
|
|
blockPacket.m_ray,
|
|
hitPacket_X0Y0,
|
|
m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS ),
|
|
hitColor_X0Y0 );
|
|
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_ANTI_ALIASING ) )
|
|
{
|
|
SFVEC3F hitColor_AA_X1Y1[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
|
|
// Intersect one blockPosI + (0.5, 0.5) used for anti aliasing calculation
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
HITINFO_PACKET hitPacket_AA_X1Y1[RAYPACKET_RAYS_PER_PACKET];
|
|
HITINFO_PACKET_init( hitPacket_AA_X1Y1 );
|
|
|
|
RAYPACKET blockPacket_AA_X1Y1( m_settings.CameraGet(),
|
|
(SFVEC2F)blockPosI + SFVEC2F(0.5f, 0.5f),
|
|
SFVEC2F(DISP_FACTOR, DISP_FACTOR) // Displacement random factor
|
|
);
|
|
|
|
if( !m_accelerator->Intersect( blockPacket_AA_X1Y1, hitPacket_AA_X1Y1 ) )
|
|
{
|
|
// Missed all the package
|
|
for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
const SFVEC3F &outColor = bgColor[y];
|
|
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
|
|
{
|
|
hitColor_AA_X1Y1[i] = outColor;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rt_shades_packet( bgColor,
|
|
blockPacket_AA_X1Y1.m_ray,
|
|
hitPacket_AA_X1Y1,
|
|
m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS ),
|
|
hitColor_AA_X1Y1
|
|
);
|
|
}
|
|
|
|
SFVEC3F hitColor_AA_X1Y0[RAYPACKET_RAYS_PER_PACKET];
|
|
SFVEC3F hitColor_AA_X0Y1[RAYPACKET_RAYS_PER_PACKET];
|
|
SFVEC3F hitColor_AA_X0Y1_half[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
|
|
{
|
|
const SFVEC3F color_average = ( hitColor_X0Y0[i] +
|
|
hitColor_AA_X1Y1[i] ) * SFVEC3F(0.5f);
|
|
|
|
hitColor_AA_X1Y0[i] = color_average;
|
|
hitColor_AA_X0Y1[i] = color_average;
|
|
hitColor_AA_X0Y1_half[i] = color_average;
|
|
}
|
|
|
|
RAY blockRayPck_AA_X1Y0[RAYPACKET_RAYS_PER_PACKET];
|
|
RAY blockRayPck_AA_X0Y1[RAYPACKET_RAYS_PER_PACKET];
|
|
RAY blockRayPck_AA_X1Y1_half[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
|
|
(SFVEC2F)blockPosI + SFVEC2F(0.5f - DISP_FACTOR, DISP_FACTOR),
|
|
SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
|
|
blockRayPck_AA_X1Y0 );
|
|
|
|
RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
|
|
(SFVEC2F)blockPosI + SFVEC2F(DISP_FACTOR, 0.5f - DISP_FACTOR),
|
|
SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
|
|
blockRayPck_AA_X0Y1 );
|
|
|
|
RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
|
|
(SFVEC2F)blockPosI + SFVEC2F(0.25f - DISP_FACTOR, 0.25f - DISP_FACTOR),
|
|
SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
|
|
blockRayPck_AA_X1Y1_half );
|
|
|
|
rt_trace_AA_packet( bgColor,
|
|
hitPacket_X0Y0, hitPacket_AA_X1Y1,
|
|
blockRayPck_AA_X1Y0,
|
|
hitColor_AA_X1Y0 );
|
|
|
|
rt_trace_AA_packet( bgColor,
|
|
hitPacket_X0Y0, hitPacket_AA_X1Y1,
|
|
blockRayPck_AA_X0Y1,
|
|
hitColor_AA_X0Y1 );
|
|
|
|
rt_trace_AA_packet( bgColor,
|
|
hitPacket_X0Y0, hitPacket_AA_X1Y1,
|
|
blockRayPck_AA_X1Y1_half,
|
|
hitColor_AA_X0Y1_half );
|
|
|
|
// Average the result
|
|
for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
|
|
{
|
|
hitColor_X0Y0[i] = ( hitColor_X0Y0[i] +
|
|
hitColor_AA_X1Y1[i] +
|
|
hitColor_AA_X1Y0[i] +
|
|
hitColor_AA_X0Y1[i] +
|
|
hitColor_AA_X0Y1_half[i]
|
|
) * SFVEC3F(1.0f / 5.0f);
|
|
}
|
|
}
|
|
|
|
|
|
// Copy results to the next stage
|
|
// /////////////////////////////////////////////////////////////////////
|
|
|
|
GLubyte *ptr = &ptrPBO[ ( blockPos.x +
|
|
(blockPos.y * m_realBufferSize.x) ) * 4 ];
|
|
|
|
const uint32_t ptrInc = (m_realBufferSize.x - RAYPACKET_DIM) * 4;
|
|
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
{
|
|
SFVEC2I bPos;
|
|
bPos.y = blockPos.y;
|
|
|
|
for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
bPos.x = blockPos.x;
|
|
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
|
|
{
|
|
const SFVEC3F &hColor = hitColor_X0Y0[i];
|
|
|
|
if( hitPacket_X0Y0[i].m_hitresult == true )
|
|
m_postshader_ssao.SetPixelData( bPos.x, bPos.y,
|
|
hitPacket_X0Y0[i].m_HitInfo.m_HitNormal,
|
|
hColor,
|
|
blockPacket.m_ray[i].at(
|
|
hitPacket_X0Y0[i].m_HitInfo.m_tHit ),
|
|
hitPacket_X0Y0[i].m_HitInfo.m_tHit,
|
|
hitPacket_X0Y0[i].m_HitInfo.m_ShadowFactor );
|
|
else
|
|
m_postshader_ssao.SetPixelData( bPos.x, bPos.y,
|
|
SFVEC3F( 0.0f ),
|
|
hColor,
|
|
SFVEC3F( 0.0f ),
|
|
0,
|
|
1.0f );
|
|
|
|
rt_final_color( ptr, hColor, false );
|
|
|
|
bPos.x++;
|
|
ptr += 4;
|
|
}
|
|
|
|
ptr += ptrInc;
|
|
bPos.y++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
|
|
{
|
|
rt_final_color( ptr, hitColor_X0Y0[i], true );
|
|
ptr += 4;
|
|
}
|
|
|
|
ptr += ptrInc;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_render_post_process_shade( GLubyte *ptrPBO,
|
|
REPORTER *aStatusTextReporter )
|
|
{
|
|
(void)ptrPBO; // unused
|
|
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
{
|
|
if( aStatusTextReporter )
|
|
aStatusTextReporter->Report( _("Rendering: Post processing shader") );
|
|
|
|
std::atomic<size_t> nextBlock( 0 );
|
|
std::atomic<size_t> threadsFinished( 0 );
|
|
|
|
size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
|
|
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
|
|
{
|
|
std::thread t = std::thread( [&]()
|
|
{
|
|
for( size_t y = nextBlock.fetch_add( 1 );
|
|
y < m_realBufferSize.y;
|
|
y = nextBlock.fetch_add( 1 ) )
|
|
{
|
|
SFVEC3F *ptr = &m_shaderBuffer[ y * m_realBufferSize.x ];
|
|
|
|
for( signed int x = 0; x < (int)m_realBufferSize.x; ++x )
|
|
{
|
|
*ptr = m_postshader_ssao.Shade( SFVEC2I( x, y ) );
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
threadsFinished++;
|
|
} );
|
|
|
|
t.detach();
|
|
}
|
|
|
|
while( threadsFinished < parallelThreadCount )
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
|
|
|
// Set next state
|
|
m_rt_render_state = RT_RENDER_STATE_POST_PROCESS_BLUR_AND_FINISH;
|
|
}
|
|
else
|
|
{
|
|
// As this was an invalid state, set to finish
|
|
m_rt_render_state = RT_RENDER_STATE_FINISH;
|
|
}
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::rt_render_post_process_blur_finish( GLubyte *ptrPBO,
|
|
REPORTER *aStatusTextReporter )
|
|
{
|
|
(void)aStatusTextReporter; //unused
|
|
|
|
if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
{
|
|
// Now blurs the shader result and compute the final color
|
|
std::atomic<size_t> nextBlock( 0 );
|
|
std::atomic<size_t> threadsFinished( 0 );
|
|
|
|
size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
|
|
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
|
|
{
|
|
std::thread t = std::thread( [&]()
|
|
{
|
|
for( size_t y = nextBlock.fetch_add( 1 );
|
|
y < m_realBufferSize.y;
|
|
y = nextBlock.fetch_add( 1 ) )
|
|
{
|
|
GLubyte *ptr = &ptrPBO[ y * m_realBufferSize.x * 4 ];
|
|
|
|
const SFVEC3F *ptrShaderY0 =
|
|
&m_shaderBuffer[ glm::max((int)y - 2, 0) * m_realBufferSize.x ];
|
|
const SFVEC3F *ptrShaderY1 =
|
|
&m_shaderBuffer[ glm::max((int)y - 1, 0) * m_realBufferSize.x ];
|
|
const SFVEC3F *ptrShaderY2 =
|
|
&m_shaderBuffer[ y * m_realBufferSize.x ];
|
|
const SFVEC3F *ptrShaderY3 =
|
|
&m_shaderBuffer[ glm::min((int)y + 1, (int)(m_realBufferSize.y - 1)) *
|
|
m_realBufferSize.x ];
|
|
const SFVEC3F *ptrShaderY4 =
|
|
&m_shaderBuffer[ glm::min((int)y + 2, (int)(m_realBufferSize.y - 1)) *
|
|
m_realBufferSize.x ];
|
|
|
|
for( signed int x = 0; x < (int)m_realBufferSize.x; ++x )
|
|
{
|
|
// This #if should be 1, it is here that can be used for debug proposes during development
|
|
#if 1
|
|
int idx = x > 1 ? -2 : 0;
|
|
SFVEC3F bluredShadeColor = ptrShaderY0[idx] * 1.0f / 273.0f +
|
|
ptrShaderY1[idx] * 4.0f / 273.0f +
|
|
ptrShaderY2[idx] * 7.0f / 273.0f +
|
|
ptrShaderY3[idx] * 4.0f / 273.0f +
|
|
ptrShaderY4[idx] * 1.0f / 273.0f;
|
|
|
|
idx = x > 0 ? -1 : 0;
|
|
bluredShadeColor += ptrShaderY0[idx] * 4.0f / 273.0f +
|
|
ptrShaderY1[idx] * 16.0f / 273.0f +
|
|
ptrShaderY2[idx] * 26.0f / 273.0f +
|
|
ptrShaderY3[idx] * 16.0f / 273.0f +
|
|
ptrShaderY4[idx] * 4.0f / 273.0f;
|
|
|
|
bluredShadeColor += (*ptrShaderY0) * 7.0f / 273.0f +
|
|
(*ptrShaderY1) * 26.0f / 273.0f +
|
|
(*ptrShaderY2) * 41.0f / 273.0f +
|
|
(*ptrShaderY3) * 26.0f / 273.0f +
|
|
(*ptrShaderY4) * 7.0f / 273.0f;
|
|
|
|
idx = (x < (int)m_realBufferSize.x - 1) ? 1 : 0;
|
|
bluredShadeColor += ptrShaderY0[idx] * 4.0f / 273.0f +
|
|
ptrShaderY1[idx] *16.0f / 273.0f +
|
|
ptrShaderY2[idx] *26.0f / 273.0f +
|
|
ptrShaderY3[idx] *16.0f / 273.0f +
|
|
ptrShaderY4[idx] * 4.0f / 273.0f;
|
|
|
|
idx = (x < (int)m_realBufferSize.x - 2) ? 2 : 0;
|
|
bluredShadeColor += ptrShaderY0[idx] * 1.0f / 273.0f +
|
|
ptrShaderY1[idx] * 4.0f / 273.0f +
|
|
ptrShaderY2[idx] * 7.0f / 273.0f +
|
|
ptrShaderY3[idx] * 4.0f / 273.0f +
|
|
ptrShaderY4[idx] * 1.0f / 273.0f;
|
|
|
|
// process next pixel
|
|
++ptrShaderY0;
|
|
++ptrShaderY1;
|
|
++ptrShaderY2;
|
|
++ptrShaderY3;
|
|
++ptrShaderY4;
|
|
|
|
#ifdef USE_SRGB_SPACE
|
|
const SFVEC3F originColor = convertLinearToSRGB( m_postshader_ssao.GetColorAtNotProtected( SFVEC2I( x,y ) ) );
|
|
#else
|
|
const SFVEC3F originColor = m_postshader_ssao.GetColorAtNotProtected( SFVEC2I( x,y ) );
|
|
#endif
|
|
|
|
const SFVEC3F shadedColor = m_postshader_ssao.ApplyShadeColor( SFVEC2I( x,y ), originColor, bluredShadeColor );
|
|
#else
|
|
// Debug code
|
|
//const SFVEC3F shadedColor = SFVEC3F( 1.0f ) -
|
|
// m_shaderBuffer[ y * m_realBufferSize.x + x];
|
|
const SFVEC3F shadedColor = m_shaderBuffer[ y * m_realBufferSize.x + x ];
|
|
#endif
|
|
|
|
rt_final_color( ptr, shadedColor, false );
|
|
|
|
ptr += 4;
|
|
}
|
|
}
|
|
|
|
threadsFinished++;
|
|
} );
|
|
|
|
t.detach();
|
|
}
|
|
|
|
while( threadsFinished < parallelThreadCount )
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
|
|
|
|
|
// Debug code
|
|
//m_postshader_ssao.DebugBuffersOutputAsImages();
|
|
}
|
|
|
|
// End rendering
|
|
m_rt_render_state = RT_RENDER_STATE_FINISH;
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::render_preview( GLubyte *ptrPBO )
|
|
{
|
|
m_isPreview = true;
|
|
|
|
std::atomic<size_t> nextBlock( 0 );
|
|
std::atomic<size_t> threadsFinished( 0 );
|
|
|
|
size_t parallelThreadCount = std::min<size_t>(
|
|
std::max<size_t>( std::thread::hardware_concurrency(), 2 ),
|
|
m_blockPositions.size() );
|
|
for( size_t ii = 0; ii < parallelThreadCount; ++ii )
|
|
{
|
|
std::thread t = std::thread( [&]()
|
|
{
|
|
for( size_t iBlock = nextBlock.fetch_add( 1 );
|
|
iBlock < m_blockPositionsFast.size();
|
|
iBlock = nextBlock.fetch_add( 1 ) )
|
|
{
|
|
const SFVEC2UI &windowPosUI = m_blockPositionsFast[ iBlock ];
|
|
const SFVEC2I windowsPos = SFVEC2I( windowPosUI.x + m_xoffset,
|
|
windowPosUI.y + m_yoffset );
|
|
|
|
RAYPACKET blockPacket( m_settings.CameraGet(), windowsPos, 4 );
|
|
|
|
HITINFO_PACKET hitPacket[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
// Initialize hitPacket with a "not hit" information
|
|
for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
|
|
{
|
|
hitPacket[i].m_HitInfo.m_tHit = std::numeric_limits<float>::infinity();
|
|
hitPacket[i].m_HitInfo.m_acc_node_info = 0;
|
|
hitPacket[i].m_hitresult = false;
|
|
}
|
|
|
|
// Intersect packet block
|
|
m_accelerator->Intersect( blockPacket, hitPacket );
|
|
|
|
|
|
// Calculate background gradient color
|
|
// /////////////////////////////////////////////////////////////////////
|
|
SFVEC3F bgColor[RAYPACKET_DIM];
|
|
|
|
for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
|
|
{
|
|
const float posYfactor = (float)(windowsPos.y + y * 4.0f) / (float)m_windowSize.y;
|
|
|
|
bgColor[y] = (SFVEC3F)m_settings.m_BgColorTop * SFVEC3F(posYfactor) +
|
|
(SFVEC3F)m_settings.m_BgColorBot * ( SFVEC3F(1.0f) - SFVEC3F(posYfactor) );
|
|
}
|
|
|
|
CCOLORRGB hitColorShading[RAYPACKET_RAYS_PER_PACKET];
|
|
|
|
for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
|
|
{
|
|
const SFVEC3F bhColorY = bgColor[i / RAYPACKET_DIM];
|
|
|
|
if( hitPacket[i].m_hitresult == true )
|
|
{
|
|
const SFVEC3F hitColor = shadeHit( bhColorY,
|
|
blockPacket.m_ray[i],
|
|
hitPacket[i].m_HitInfo,
|
|
false,
|
|
0,
|
|
false );
|
|
|
|
hitColorShading[i] = CCOLORRGB( hitColor );
|
|
}
|
|
else
|
|
hitColorShading[i] = bhColorY;
|
|
}
|
|
|
|
CCOLORRGB cLRB_old[(RAYPACKET_DIM - 1)];
|
|
|
|
for( unsigned int y = 0; y < (RAYPACKET_DIM - 1); ++y )
|
|
{
|
|
|
|
const SFVEC3F bgColorY = bgColor[y];
|
|
const CCOLORRGB bgColorYRGB = CCOLORRGB( bgColorY );
|
|
|
|
// This stores cRTB from the last block to be reused next time in a cLTB pixel
|
|
CCOLORRGB cRTB_old;
|
|
|
|
//RAY cRTB_ray;
|
|
//HITINFO cRTB_hitInfo;
|
|
|
|
for( unsigned int x = 0; x < (RAYPACKET_DIM - 1); ++x )
|
|
{
|
|
// pxl 0 pxl 1 pxl 2 pxl 3 pxl 4
|
|
// x0 x1 ...
|
|
// .---------------------------.
|
|
// y0 | cLT | cxxx | cLRT | cxxx | cRT |
|
|
// | cxxx | cLTC | cxxx | cRTC | cxxx |
|
|
// | cLTB | cxxx | cC | cxxx | cRTB |
|
|
// | cxxx | cLBC | cxxx | cRBC | cxxx |
|
|
// '---------------------------'
|
|
// y1 | cLB | cxxx | cLRB | cxxx | cRB |
|
|
|
|
const unsigned int iLT = ((x + 0) + RAYPACKET_DIM * (y + 0));
|
|
const unsigned int iRT = ((x + 1) + RAYPACKET_DIM * (y + 0));
|
|
const unsigned int iLB = ((x + 0) + RAYPACKET_DIM * (y + 1));
|
|
const unsigned int iRB = ((x + 1) + RAYPACKET_DIM * (y + 1));
|
|
|
|
// !TODO: skip when there are no hits
|
|
|
|
|
|
const CCOLORRGB &cLT = hitColorShading[ iLT ];
|
|
const CCOLORRGB &cRT = hitColorShading[ iRT ];
|
|
const CCOLORRGB &cLB = hitColorShading[ iLB ];
|
|
const CCOLORRGB &cRB = hitColorShading[ iRB ];
|
|
|
|
// Trace and shade cC
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cC = bgColorYRGB;
|
|
|
|
const SFVEC3F &oriLT = blockPacket.m_ray[ iLT ].m_Origin;
|
|
const SFVEC3F &oriRB = blockPacket.m_ray[ iRB ].m_Origin;
|
|
|
|
const SFVEC3F &dirLT = blockPacket.m_ray[ iLT ].m_Dir;
|
|
const SFVEC3F &dirRB = blockPacket.m_ray[ iRB ].m_Dir;
|
|
|
|
SFVEC3F oriC;
|
|
SFVEC3F dirC;
|
|
|
|
HITINFO centerHitInfo;
|
|
centerHitInfo.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
bool hittedC = false;
|
|
|
|
if( (hitPacket[ iLT ].m_hitresult == true) ||
|
|
(hitPacket[ iRT ].m_hitresult == true) ||
|
|
(hitPacket[ iLB ].m_hitresult == true) ||
|
|
(hitPacket[ iRB ].m_hitresult == true) )
|
|
{
|
|
|
|
oriC = ( oriLT + oriRB ) * 0.5f;
|
|
dirC = glm::normalize( ( dirLT + dirRB ) * 0.5f );
|
|
|
|
// Trace the center ray
|
|
RAY centerRay;
|
|
centerRay.Init( oriC, dirC );
|
|
|
|
const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;
|
|
|
|
if( nodeLT != 0 )
|
|
hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeLT );
|
|
|
|
if( ( nodeRT != 0 ) &&
|
|
( nodeRT != nodeLT ) )
|
|
hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeRT );
|
|
|
|
if( ( nodeLB != 0 ) &&
|
|
( nodeLB != nodeLT ) &&
|
|
( nodeLB != nodeRT ) )
|
|
hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeLB );
|
|
|
|
if( ( nodeRB != 0 ) &&
|
|
( nodeRB != nodeLB ) &&
|
|
( nodeRB != nodeLT ) &&
|
|
( nodeRB != nodeRT ) )
|
|
hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeRB );
|
|
|
|
if( hittedC )
|
|
cC = CCOLORRGB( shadeHit( bgColorY, centerRay, centerHitInfo, false, 0, false ) );
|
|
else
|
|
{
|
|
centerHitInfo.m_tHit = std::numeric_limits<float>::infinity();
|
|
hittedC = m_accelerator->Intersect( centerRay, centerHitInfo );
|
|
|
|
if( hittedC )
|
|
cC = CCOLORRGB( shadeHit( bgColorY,
|
|
centerRay,
|
|
centerHitInfo,
|
|
false,
|
|
0,
|
|
false ) );
|
|
}
|
|
}
|
|
|
|
// Trace and shade cLRT
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cLRT = bgColorYRGB;
|
|
|
|
const SFVEC3F &oriRT = blockPacket.m_ray[ iRT ].m_Origin;
|
|
const SFVEC3F &dirRT = blockPacket.m_ray[ iRT ].m_Dir;
|
|
|
|
if( y == 0 )
|
|
{
|
|
// Trace the center ray
|
|
RAY rayLRT;
|
|
rayLRT.Init( ( oriLT + oriRT ) * 0.5f,
|
|
glm::normalize( ( dirLT + dirRT ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoLRT;
|
|
hitInfoLRT.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( hitPacket[ iLT ].m_hitresult &&
|
|
hitPacket[ iRT ].m_hitresult &&
|
|
(hitPacket[ iLT ].m_HitInfo.pHitObject == hitPacket[ iRT ].m_HitInfo.pHitObject) )
|
|
{
|
|
hitInfoLRT.pHitObject = hitPacket[ iLT ].m_HitInfo.pHitObject;
|
|
hitInfoLRT.m_tHit = ( hitPacket[ iLT ].m_HitInfo.m_tHit +
|
|
hitPacket[ iRT ].m_HitInfo.m_tHit ) * 0.5f;
|
|
hitInfoLRT.m_HitNormal =
|
|
glm::normalize( ( hitPacket[ iLT ].m_HitInfo.m_HitNormal +
|
|
hitPacket[ iRT ].m_HitInfo.m_HitNormal ) * 0.5f );
|
|
|
|
cLRT = CCOLORRGB( shadeHit( bgColorY, rayLRT, hitInfoLRT, false, 0, false ) );
|
|
cLRT = BlendColor( cLRT, BlendColor( cLT, cRT) );
|
|
}
|
|
else
|
|
{
|
|
if( hitPacket[ iLT ].m_hitresult ||
|
|
hitPacket[ iRT ].m_hitresult ) // If any hits
|
|
{
|
|
const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;
|
|
|
|
bool hittedLRT = false;
|
|
|
|
if( nodeLT != 0 )
|
|
hittedLRT |= m_accelerator->Intersect( rayLRT, hitInfoLRT, nodeLT );
|
|
|
|
if( ( nodeRT != 0 ) &&
|
|
( nodeRT != nodeLT ) )
|
|
hittedLRT |= m_accelerator->Intersect( rayLRT,
|
|
hitInfoLRT,
|
|
nodeRT );
|
|
|
|
if( hittedLRT )
|
|
cLRT = CCOLORRGB( shadeHit( bgColorY,
|
|
rayLRT,
|
|
hitInfoLRT,
|
|
false,
|
|
0,
|
|
false ) );
|
|
else
|
|
{
|
|
hitInfoLRT.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( m_accelerator->Intersect( rayLRT,hitInfoLRT ) )
|
|
cLRT = CCOLORRGB( shadeHit( bgColorY,
|
|
rayLRT,
|
|
hitInfoLRT,
|
|
false,
|
|
0,
|
|
false ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
cLRT = cLRB_old[x];
|
|
|
|
|
|
// Trace and shade cLTB
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cLTB = bgColorYRGB;
|
|
|
|
if( x == 0 )
|
|
{
|
|
const SFVEC3F &oriLB = blockPacket.m_ray[ iLB ].m_Origin;
|
|
const SFVEC3F &dirLB = blockPacket.m_ray[ iLB ].m_Dir;
|
|
|
|
// Trace the center ray
|
|
RAY rayLTB;
|
|
rayLTB.Init( ( oriLT + oriLB ) * 0.5f,
|
|
glm::normalize( ( dirLT + dirLB ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoLTB;
|
|
hitInfoLTB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( hitPacket[ iLT ].m_hitresult &&
|
|
hitPacket[ iLB ].m_hitresult &&
|
|
( hitPacket[ iLT ].m_HitInfo.pHitObject ==
|
|
hitPacket[ iLB ].m_HitInfo.pHitObject ) )
|
|
{
|
|
hitInfoLTB.pHitObject = hitPacket[ iLT ].m_HitInfo.pHitObject;
|
|
hitInfoLTB.m_tHit = ( hitPacket[ iLT ].m_HitInfo.m_tHit +
|
|
hitPacket[ iLB ].m_HitInfo.m_tHit ) * 0.5f;
|
|
hitInfoLTB.m_HitNormal =
|
|
glm::normalize( ( hitPacket[ iLT ].m_HitInfo.m_HitNormal +
|
|
hitPacket[ iLB ].m_HitInfo.m_HitNormal ) * 0.5f );
|
|
cLTB = CCOLORRGB( shadeHit( bgColorY, rayLTB, hitInfoLTB, false, 0, false ) );
|
|
cLTB = BlendColor( cLTB, BlendColor( cLT, cLB) );
|
|
}
|
|
else
|
|
{
|
|
if( hitPacket[ iLT ].m_hitresult ||
|
|
hitPacket[ iLB ].m_hitresult ) // If any hits
|
|
{
|
|
const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;
|
|
|
|
bool hittedLTB = false;
|
|
|
|
if( nodeLT != 0 )
|
|
hittedLTB |= m_accelerator->Intersect( rayLTB,
|
|
hitInfoLTB,
|
|
nodeLT );
|
|
|
|
if( ( nodeLB != 0 ) &&
|
|
( nodeLB != nodeLT ) )
|
|
hittedLTB |= m_accelerator->Intersect( rayLTB,
|
|
hitInfoLTB,
|
|
nodeLB );
|
|
|
|
if( hittedLTB )
|
|
cLTB = CCOLORRGB( shadeHit( bgColorY,
|
|
rayLTB,
|
|
hitInfoLTB,
|
|
false,
|
|
0,
|
|
false ) );
|
|
else
|
|
{
|
|
hitInfoLTB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( m_accelerator->Intersect( rayLTB, hitInfoLTB ) )
|
|
cLTB = CCOLORRGB( shadeHit( bgColorY,
|
|
rayLTB,
|
|
hitInfoLTB,
|
|
false,
|
|
0,
|
|
false ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
cLTB = cRTB_old;
|
|
|
|
|
|
// Trace and shade cRTB
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cRTB = bgColorYRGB;
|
|
|
|
// Trace the center ray
|
|
RAY rayRTB;
|
|
rayRTB.Init( ( oriRT + oriRB ) * 0.5f,
|
|
glm::normalize( ( dirRT + dirRB ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoRTB;
|
|
hitInfoRTB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( hitPacket[ iRT ].m_hitresult &&
|
|
hitPacket[ iRB ].m_hitresult &&
|
|
( hitPacket[ iRT ].m_HitInfo.pHitObject ==
|
|
hitPacket[ iRB ].m_HitInfo.pHitObject ) )
|
|
{
|
|
hitInfoRTB.pHitObject = hitPacket[ iRT ].m_HitInfo.pHitObject;
|
|
|
|
hitInfoRTB.m_tHit = ( hitPacket[ iRT ].m_HitInfo.m_tHit +
|
|
hitPacket[ iRB ].m_HitInfo.m_tHit ) * 0.5f;
|
|
|
|
hitInfoRTB.m_HitNormal =
|
|
glm::normalize( ( hitPacket[ iRT ].m_HitInfo.m_HitNormal +
|
|
hitPacket[ iRB ].m_HitInfo.m_HitNormal ) * 0.5f );
|
|
|
|
cRTB = CCOLORRGB( shadeHit( bgColorY, rayRTB, hitInfoRTB, false, 0, false ) );
|
|
cRTB = BlendColor( cRTB, BlendColor( cRT, cRB) );
|
|
}
|
|
else
|
|
{
|
|
if( hitPacket[ iRT ].m_hitresult ||
|
|
hitPacket[ iRB ].m_hitresult ) // If any hits
|
|
{
|
|
const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;
|
|
|
|
bool hittedRTB = false;
|
|
|
|
if( nodeRT != 0 )
|
|
hittedRTB |= m_accelerator->Intersect( rayRTB, hitInfoRTB, nodeRT );
|
|
|
|
if( ( nodeRB != 0 ) &&
|
|
( nodeRB != nodeRT ) )
|
|
hittedRTB |= m_accelerator->Intersect( rayRTB, hitInfoRTB, nodeRB );
|
|
|
|
if( hittedRTB )
|
|
cRTB = CCOLORRGB( shadeHit( bgColorY,
|
|
rayRTB,
|
|
hitInfoRTB,
|
|
false,
|
|
0,
|
|
false) );
|
|
else
|
|
{
|
|
hitInfoRTB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( m_accelerator->Intersect( rayRTB, hitInfoRTB ) )
|
|
cRTB = CCOLORRGB( shadeHit( bgColorY,
|
|
rayRTB,
|
|
hitInfoRTB,
|
|
false,
|
|
0,
|
|
false ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
cRTB_old = cRTB;
|
|
|
|
|
|
// Trace and shade cLRB
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cLRB = bgColorYRGB;
|
|
|
|
const SFVEC3F &oriLB = blockPacket.m_ray[ iLB ].m_Origin;
|
|
const SFVEC3F &dirLB = blockPacket.m_ray[ iLB ].m_Dir;
|
|
|
|
// Trace the center ray
|
|
RAY rayLRB;
|
|
rayLRB.Init( ( oriLB + oriRB ) * 0.5f,
|
|
glm::normalize( ( dirLB + dirRB ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoLRB;
|
|
hitInfoLRB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( hitPacket[ iLB ].m_hitresult &&
|
|
hitPacket[ iRB ].m_hitresult &&
|
|
( hitPacket[ iLB ].m_HitInfo.pHitObject ==
|
|
hitPacket[ iRB ].m_HitInfo.pHitObject ) )
|
|
{
|
|
hitInfoLRB.pHitObject = hitPacket[ iLB ].m_HitInfo.pHitObject;
|
|
|
|
hitInfoLRB.m_tHit = ( hitPacket[ iLB ].m_HitInfo.m_tHit +
|
|
hitPacket[ iRB ].m_HitInfo.m_tHit ) * 0.5f;
|
|
|
|
hitInfoLRB.m_HitNormal =
|
|
glm::normalize( ( hitPacket[ iLB ].m_HitInfo.m_HitNormal +
|
|
hitPacket[ iRB ].m_HitInfo.m_HitNormal ) * 0.5f );
|
|
|
|
cLRB = CCOLORRGB( shadeHit( bgColorY, rayLRB, hitInfoLRB, false, 0, false ) );
|
|
cLRB = BlendColor( cLRB, BlendColor( cLB, cRB) );
|
|
}
|
|
else
|
|
{
|
|
if( hitPacket[ iLB ].m_hitresult ||
|
|
hitPacket[ iRB ].m_hitresult ) // If any hits
|
|
{
|
|
const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;
|
|
const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;
|
|
|
|
bool hittedLRB = false;
|
|
|
|
if( nodeLB != 0 )
|
|
hittedLRB |= m_accelerator->Intersect( rayLRB, hitInfoLRB, nodeLB );
|
|
|
|
if( ( nodeRB != 0 ) &&
|
|
( nodeRB != nodeLB ) )
|
|
hittedLRB |= m_accelerator->Intersect( rayLRB, hitInfoLRB, nodeRB );
|
|
|
|
if( hittedLRB )
|
|
cLRB = CCOLORRGB( shadeHit( bgColorY, rayLRB, hitInfoLRB, false, 0, false ) );
|
|
else
|
|
{
|
|
hitInfoLRB.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( m_accelerator->Intersect( rayLRB, hitInfoLRB ) )
|
|
cLRB = CCOLORRGB( shadeHit( bgColorY,
|
|
rayLRB,
|
|
hitInfoLRB,
|
|
false,
|
|
0,
|
|
false ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
cLRB_old[x] = cLRB;
|
|
|
|
|
|
// Trace and shade cLTC
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cLTC = BlendColor( cLT , cC );
|
|
|
|
if( hitPacket[ iLT ].m_hitresult || hittedC )
|
|
{
|
|
// Trace the center ray
|
|
RAY rayLTC;
|
|
rayLTC.Init( ( oriLT + oriC ) * 0.5f,
|
|
glm::normalize( ( dirLT + dirC ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoLTC;
|
|
hitInfoLTC.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
bool hitted = false;
|
|
|
|
if( hittedC )
|
|
hitted = centerHitInfo.pHitObject->Intersect( rayLTC, hitInfoLTC );
|
|
else
|
|
if( hitPacket[ iLT ].m_hitresult )
|
|
hitted = hitPacket[ iLT ].m_HitInfo.pHitObject->Intersect( rayLTC,
|
|
hitInfoLTC );
|
|
|
|
if( hitted )
|
|
cLTC = CCOLORRGB( shadeHit( bgColorY, rayLTC, hitInfoLTC, false, 0, false ) );
|
|
}
|
|
|
|
|
|
// Trace and shade cRTC
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cRTC = BlendColor( cRT , cC );
|
|
|
|
if( hitPacket[ iRT ].m_hitresult || hittedC )
|
|
{
|
|
// Trace the center ray
|
|
RAY rayRTC;
|
|
rayRTC.Init( ( oriRT + oriC ) * 0.5f,
|
|
glm::normalize( ( dirRT + dirC ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoRTC;
|
|
hitInfoRTC.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
bool hitted = false;
|
|
|
|
if( hittedC )
|
|
hitted = centerHitInfo.pHitObject->Intersect( rayRTC, hitInfoRTC );
|
|
else
|
|
if( hitPacket[ iRT ].m_hitresult )
|
|
hitted = hitPacket[ iRT ].m_HitInfo.pHitObject->Intersect( rayRTC,
|
|
hitInfoRTC );
|
|
|
|
if( hitted )
|
|
cRTC = CCOLORRGB( shadeHit( bgColorY, rayRTC, hitInfoRTC, false, 0, false ) );
|
|
}
|
|
|
|
|
|
// Trace and shade cLBC
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cLBC = BlendColor( cLB , cC );
|
|
|
|
if( hitPacket[ iLB ].m_hitresult || hittedC )
|
|
{
|
|
// Trace the center ray
|
|
RAY rayLBC;
|
|
rayLBC.Init( ( oriLB + oriC ) * 0.5f,
|
|
glm::normalize( ( dirLB + dirC ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoLBC;
|
|
hitInfoLBC.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
bool hitted = false;
|
|
|
|
if( hittedC )
|
|
hitted = centerHitInfo.pHitObject->Intersect( rayLBC, hitInfoLBC );
|
|
else
|
|
if( hitPacket[ iLB ].m_hitresult )
|
|
hitted = hitPacket[ iLB ].m_HitInfo.pHitObject->Intersect( rayLBC,
|
|
hitInfoLBC );
|
|
|
|
if( hitted )
|
|
cLBC = CCOLORRGB( shadeHit( bgColorY, rayLBC, hitInfoLBC, false, 0, false ) );
|
|
}
|
|
|
|
|
|
// Trace and shade cRBC
|
|
// /////////////////////////////////////////////////////////////
|
|
CCOLORRGB cRBC = BlendColor( cRB , cC );
|
|
|
|
if( hitPacket[ iRB ].m_hitresult || hittedC )
|
|
{
|
|
// Trace the center ray
|
|
RAY rayRBC;
|
|
rayRBC.Init( ( oriRB + oriC ) * 0.5f,
|
|
glm::normalize( ( dirRB + dirC ) * 0.5f ) );
|
|
|
|
HITINFO hitInfoRBC;
|
|
hitInfoRBC.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
bool hitted = false;
|
|
|
|
if( hittedC )
|
|
hitted = centerHitInfo.pHitObject->Intersect( rayRBC, hitInfoRBC );
|
|
else
|
|
if( hitPacket[ iRB ].m_hitresult )
|
|
hitted = hitPacket[ iRB ].m_HitInfo.pHitObject->Intersect( rayRBC,
|
|
hitInfoRBC );
|
|
|
|
if( hitted )
|
|
cRBC = CCOLORRGB( shadeHit( bgColorY, rayRBC, hitInfoRBC, false, 0, false ) );
|
|
}
|
|
|
|
|
|
// Set pixel colors
|
|
// /////////////////////////////////////////////////////////////
|
|
|
|
GLubyte *ptr = &ptrPBO[ (4 * x + m_blockPositionsFast[iBlock].x +
|
|
m_realBufferSize.x *
|
|
(m_blockPositionsFast[iBlock].y + 4 * y)) * 4 ];
|
|
SetPixel( ptr + 0, cLT );
|
|
SetPixel( ptr + 4, BlendColor( cLT, cLRT, cLTC ) );
|
|
SetPixel( ptr + 8, cLRT );
|
|
SetPixel( ptr + 12, BlendColor( cLRT, cRT, cRTC ) );
|
|
|
|
ptr += m_realBufferSize.x * 4;
|
|
SetPixel( ptr + 0, BlendColor( cLT , cLTB, cLTC ) );
|
|
SetPixel( ptr + 4, BlendColor( cLTC, BlendColor( cLT , cC ) ) );
|
|
SetPixel( ptr + 8, BlendColor( cC, BlendColor( cLRT, cLTC, cRTC ) ) );
|
|
SetPixel( ptr + 12, BlendColor( cRTC, BlendColor( cRT , cC ) ) );
|
|
|
|
ptr += m_realBufferSize.x * 4;
|
|
SetPixel( ptr + 0, cLTB );
|
|
SetPixel( ptr + 4, BlendColor( cC, BlendColor( cLTB, cLTC, cLBC ) ) );
|
|
SetPixel( ptr + 8, cC );
|
|
SetPixel( ptr + 12, BlendColor( cC, BlendColor( cRTB, cRTC, cRBC ) ) );
|
|
|
|
ptr += m_realBufferSize.x * 4;
|
|
SetPixel( ptr + 0, BlendColor( cLB , cLTB, cLBC ) );
|
|
SetPixel( ptr + 4, BlendColor( cLBC, BlendColor( cLB , cC ) ) );
|
|
SetPixel( ptr + 8, BlendColor( cC, BlendColor( cLRB, cLBC, cRBC ) ) );
|
|
SetPixel( ptr + 12, BlendColor( cRBC, BlendColor( cRB , cC ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
threadsFinished++;
|
|
} );
|
|
|
|
t.detach();
|
|
}
|
|
|
|
while( threadsFinished < parallelThreadCount )
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
|
}
|
|
|
|
|
|
#define USE_EXPERIMENTAL_SOFT_SHADOWS 1
|
|
|
|
SFVEC3F C3D_RENDER_RAYTRACING::shadeHit( const SFVEC3F &aBgColor,
|
|
const RAY &aRay,
|
|
HITINFO &aHitInfo,
|
|
bool aIsInsideObject,
|
|
unsigned int aRecursiveLevel,
|
|
bool is_testShadow ) const
|
|
{
|
|
if( aRecursiveLevel > 2 )
|
|
return SFVEC3F( 0.0f );
|
|
|
|
SFVEC3F hitPoint = aHitInfo.m_HitPoint;
|
|
|
|
if( !m_isPreview )
|
|
hitPoint += aHitInfo.m_HitNormal * m_settings.GetNonCopperLayerThickness3DU() * 1.0f;
|
|
|
|
const CMATERIAL *objMaterial = aHitInfo.pHitObject->GetMaterial();
|
|
wxASSERT( objMaterial != NULL );
|
|
|
|
const SFVEC3F diffuseColorObj = aHitInfo.pHitObject->GetDiffuseColor( aHitInfo );
|
|
|
|
SFVEC3F outColor = objMaterial->GetEmissiveColor();
|
|
|
|
const LIST_LIGHT &lightList = m_lights.GetList();
|
|
|
|
#if USE_EXPERIMENTAL_SOFT_SHADOWS
|
|
const bool is_aa_enabled = m_settings.GetFlag( FL_RENDER_RAYTRACING_ANTI_ALIASING ) &&
|
|
(!m_isPreview);
|
|
#endif
|
|
|
|
float shadow_att_factor_sum = 0.0f;
|
|
|
|
unsigned int nr_lights_that_can_cast_shadows = 0;
|
|
|
|
for( LIST_LIGHT::const_iterator ii = lightList.begin();
|
|
ii != lightList.end();
|
|
++ii )
|
|
{
|
|
const CLIGHT *light = (CLIGHT *)*ii;
|
|
|
|
SFVEC3F vectorToLight;
|
|
SFVEC3F colorOfLight;
|
|
float distToLight;
|
|
|
|
light->GetLightParameters( hitPoint, vectorToLight, colorOfLight, distToLight );
|
|
|
|
if( m_isPreview )
|
|
colorOfLight = SFVEC3F( 1.0f );
|
|
|
|
/*
|
|
if( (!m_isPreview) &&
|
|
// Little hack to make randomness to the shading and shadows
|
|
m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
vectorToLight = glm::normalize( vectorToLight +
|
|
UniformRandomHemisphereDirection() * 0.1f );
|
|
*/
|
|
|
|
const float NdotL = glm::dot( aHitInfo.m_HitNormal, vectorToLight );
|
|
|
|
// Only calc shade if the normal is facing the direction of light,
|
|
// otherwise it is in the shadow
|
|
if( NdotL >= FLT_EPSILON )
|
|
{
|
|
float shadow_att_factor_light = 1.0f;
|
|
|
|
if( is_testShadow && light->GetCastShadows() )
|
|
{
|
|
nr_lights_that_can_cast_shadows++;
|
|
#if USE_EXPERIMENTAL_SOFT_SHADOWS
|
|
if( (!is_aa_enabled) ||
|
|
|
|
// For rays that are recursive, just calculate one hit shadow
|
|
(aRecursiveLevel > 0) ||
|
|
|
|
// Only use soft shadows if using post processing
|
|
(!m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
)
|
|
{
|
|
#endif
|
|
RAY rayToLight;
|
|
rayToLight.Init( hitPoint, vectorToLight );
|
|
|
|
// Test if point is not in the shadow.
|
|
// Test for any hit from the point in the direction of light
|
|
if( m_accelerator->IntersectP( rayToLight, distToLight ) )
|
|
shadow_att_factor_light = 0.0f;
|
|
|
|
#if USE_EXPERIMENTAL_SOFT_SHADOWS
|
|
}
|
|
|
|
// Experimental softshadow calculation
|
|
else
|
|
{
|
|
|
|
const unsigned int shadow_number_of_samples = 3;
|
|
const float shadow_inc_factor = 1.0f / (float)(shadow_number_of_samples);
|
|
|
|
for( unsigned int i = 0; i < shadow_number_of_samples; ++i )
|
|
{
|
|
const SFVEC3F unifVector = UniformRandomHemisphereDirection();
|
|
const SFVEC3F disturbed_vector_to_light = glm::normalize( vectorToLight +
|
|
unifVector *
|
|
0.05f );
|
|
|
|
RAY rayToLight;
|
|
rayToLight.Init( hitPoint, disturbed_vector_to_light );
|
|
|
|
// !TODO: there are multiple ways that this tests can be
|
|
// optimized. Eg: by packing rays or to test against the
|
|
// latest hit object.
|
|
|
|
if( m_accelerator->IntersectP( rayToLight, distToLight ) )
|
|
{
|
|
shadow_att_factor_light -= shadow_inc_factor;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
shadow_att_factor_sum += shadow_att_factor_light;
|
|
}
|
|
|
|
if( !m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
|
|
{
|
|
outColor += objMaterial->Shade( aRay,
|
|
aHitInfo,
|
|
NdotL,
|
|
diffuseColorObj,
|
|
vectorToLight,
|
|
colorOfLight,
|
|
shadow_att_factor_light );
|
|
}
|
|
else
|
|
{
|
|
// This is a render hack in order to compensate for the lack of
|
|
// ambient and too much darkness when using post process shader
|
|
// It will calculate as it was not in shadow
|
|
outColor += objMaterial->Shade( aRay,
|
|
aHitInfo,
|
|
NdotL,
|
|
diffuseColorObj,
|
|
vectorToLight,
|
|
colorOfLight,
|
|
// The sampled point will be darkshaded by the post
|
|
// processing, so here it compensates to not shadow
|
|
// so much
|
|
glm::min( shadow_att_factor_light + (3.0f / 6.0f), 1.0f )
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outColor += objMaterial->GetAmbientColor();
|
|
}
|
|
|
|
// Only use the headlight for preview
|
|
if( m_isPreview )
|
|
break;
|
|
}
|
|
|
|
// Improvement: this is not taking in account the lightcolor
|
|
if( nr_lights_that_can_cast_shadows > 0 )
|
|
{
|
|
aHitInfo.m_ShadowFactor = glm::max( shadow_att_factor_sum /
|
|
(float)(nr_lights_that_can_cast_shadows * 1.0f), 0.0f );
|
|
}
|
|
else
|
|
{
|
|
aHitInfo.m_ShadowFactor = 1.0f;
|
|
}
|
|
|
|
// Clamp color to not be brighter than 1.0f
|
|
outColor = glm::min( outColor, SFVEC3F( 1.0f ) );
|
|
|
|
if( !m_isPreview )
|
|
{
|
|
// Reflections
|
|
// /////////////////////////////////////////////////////////////////////
|
|
|
|
if( !aIsInsideObject &&
|
|
(objMaterial->GetReflection() > 0.0f) &&
|
|
m_settings.GetFlag( FL_RENDER_RAYTRACING_REFLECTIONS ) )
|
|
{
|
|
const unsigned int reflection_number_of_samples = objMaterial->GetNrReflectionsSamples();
|
|
|
|
SFVEC3F sum_color = SFVEC3F(0.0f);
|
|
|
|
const SFVEC3F reflectVector = aRay.m_Dir -
|
|
2.0f * glm::dot( aRay.m_Dir, aHitInfo.m_HitNormal ) *
|
|
aHitInfo.m_HitNormal;
|
|
|
|
for( unsigned int i = 0; i < reflection_number_of_samples; ++i )
|
|
{
|
|
// Apply some randomize to the reflected vector
|
|
const SFVEC3F random_reflectVector =
|
|
glm::normalize( reflectVector +
|
|
UniformRandomHemisphereDirection() *
|
|
0.025f );
|
|
|
|
RAY reflectedRay;
|
|
reflectedRay.Init( hitPoint, random_reflectVector );
|
|
|
|
HITINFO reflectedHit;
|
|
reflectedHit.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
if( m_accelerator->Intersect( reflectedRay, reflectedHit ) )
|
|
{
|
|
sum_color += ( diffuseColorObj + objMaterial->GetSpecularColor() ) *
|
|
shadeHit( aBgColor,
|
|
reflectedRay,
|
|
reflectedHit,
|
|
false,
|
|
aRecursiveLevel + 1,
|
|
is_testShadow ) *
|
|
SFVEC3F( objMaterial->GetReflection() *
|
|
// Falloff factor
|
|
(1.0f / ( 1.0f + 0.75f * reflectedHit.m_tHit *
|
|
reflectedHit.m_tHit) ) );
|
|
}
|
|
}
|
|
|
|
outColor += (sum_color / SFVEC3F( (float)reflection_number_of_samples) );
|
|
}
|
|
|
|
|
|
// Refractions
|
|
// /////////////////////////////////////////////////////////////////////
|
|
|
|
if( (objMaterial->GetTransparency() > 0.0f) &&
|
|
m_settings.GetFlag( FL_RENDER_RAYTRACING_REFRACTIONS ) )
|
|
{
|
|
const float airIndex = 1.000293f;
|
|
const float glassIndex = 1.49f;
|
|
const float air_over_glass = airIndex / glassIndex;
|
|
const float glass_over_air = glassIndex / airIndex;
|
|
|
|
const float refractionRatio = aIsInsideObject?glass_over_air:air_over_glass;
|
|
|
|
SFVEC3F refractedVector;
|
|
|
|
if( Refract( aRay.m_Dir,
|
|
aHitInfo.m_HitNormal,
|
|
refractionRatio,
|
|
refractedVector ) )
|
|
{
|
|
const float objTransparency = objMaterial->GetTransparency();
|
|
|
|
// This increase the start point by a "fixed" factor so it will work the
|
|
// same for all distances
|
|
const SFVEC3F startPoint = aRay.at( NextFloatUp(
|
|
NextFloatUp(
|
|
NextFloatUp( aHitInfo.m_tHit ) ) ) );
|
|
|
|
const unsigned int refractions_number_of_samples = objMaterial->GetNrRefractionsSamples();
|
|
|
|
SFVEC3F sum_color = SFVEC3F(0.0f);
|
|
|
|
for( unsigned int i = 0; i < refractions_number_of_samples; ++i )
|
|
{
|
|
RAY refractedRay;
|
|
|
|
if( refractions_number_of_samples > 1 )
|
|
{
|
|
// apply some randomize to the refracted vector
|
|
const SFVEC3F randomizeRefractedVector = glm::normalize( refractedVector +
|
|
UniformRandomHemisphereDirection() *
|
|
0.15f *
|
|
(1.0f - objTransparency) );
|
|
|
|
refractedRay.Init( startPoint, randomizeRefractedVector );
|
|
}
|
|
else
|
|
{
|
|
refractedRay.Init( startPoint, refractedVector );
|
|
}
|
|
|
|
HITINFO refractedHit;
|
|
refractedHit.m_tHit = std::numeric_limits<float>::infinity();
|
|
|
|
SFVEC3F refractedColor = objMaterial->GetAmbientColor();
|
|
|
|
if( m_accelerator->Intersect( refractedRay, refractedHit ) )
|
|
{
|
|
refractedColor = shadeHit( aBgColor,
|
|
refractedRay,
|
|
refractedHit,
|
|
true,
|
|
aRecursiveLevel + 1,
|
|
false );
|
|
|
|
const SFVEC3F absorbance = ( SFVEC3F(1.0f) - diffuseColorObj ) *
|
|
(1.0f - objTransparency ) *
|
|
objMaterial->GetAbsorvance() * // Adjust falloff factor
|
|
-refractedHit.m_tHit;
|
|
|
|
const SFVEC3F transparency = SFVEC3F( expf( absorbance.r ),
|
|
expf( absorbance.g ),
|
|
expf( absorbance.b ) );
|
|
|
|
sum_color += refractedColor * transparency * objTransparency;
|
|
}
|
|
else
|
|
{
|
|
sum_color += refractedColor * objTransparency;
|
|
}
|
|
}
|
|
|
|
outColor = outColor * (1.0f - objTransparency) +
|
|
(sum_color / SFVEC3F( (float)refractions_number_of_samples) );
|
|
}
|
|
}
|
|
}
|
|
|
|
//outColor += glm::max( -glm::dot( aHitInfo.m_HitNormal, aRay.m_Dir ), 0.0f ) *
|
|
// objMaterial->GetAmbientColor();
|
|
|
|
return outColor;
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::initializeNewWindowSize()
|
|
{
|
|
opengl_init_pbo();
|
|
}
|
|
|
|
|
|
void C3D_RENDER_RAYTRACING::opengl_init_pbo()
|
|
{
|
|
if( GLEW_ARB_pixel_buffer_object )
|
|
{
|
|
m_opengl_support_vertex_buffer_object = true;
|
|
|
|
// Try to delete vbo if it was already initialized
|
|
opengl_delete_pbo();
|
|
|
|
// Learn about Pixel buffer objects at:
|
|
// http://www.songho.ca/opengl/gl_pbo.html
|
|
// http://web.eecs.umich.edu/~sugih/courses/eecs487/lectures/25-PBO+Mipmapping.pdf
|
|
// "create 2 pixel buffer objects, you need to delete them when program exits.
|
|
// glBufferDataARB with NULL pointer reserves only memory space."
|
|
|
|
// This sets the number of RGBA pixels
|
|
m_pboDataSize = m_realBufferSize.x * m_realBufferSize.y * 4;
|
|
|
|
glGenBuffersARB( 1, &m_pboId );
|
|
glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );
|
|
glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboDataSize, 0, GL_STREAM_DRAW_ARB );
|
|
glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
|
|
|
|
wxLogTrace( m_logTrace,
|
|
wxT( "C3D_RENDER_RAYTRACING:: GLEW_ARB_pixel_buffer_object is supported" ) );
|
|
}
|
|
}
|
|
|
|
|
|
bool C3D_RENDER_RAYTRACING::initializeOpenGL()
|
|
{
|
|
m_is_opengl_initialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static float distance( const SFVEC2UI& a, const SFVEC2UI& b )
|
|
{
|
|
const float dx = (float) a.x - (float) b.x;
|
|
const float dy = (float) a.y - (float) b.y;
|
|
return hypotf( dx, dy );
|
|
}
|
|
|
|
void C3D_RENDER_RAYTRACING::initialize_block_positions()
|
|
{
|
|
|
|
m_realBufferSize = SFVEC2UI( 0 );
|
|
|
|
// Calc block positions for fast preview mode
|
|
// /////////////////////////////////////////////////////////////////////
|
|
m_blockPositionsFast.clear();
|
|
|
|
unsigned int i = 0;
|
|
|
|
while(1)
|
|
{
|
|
const unsigned int mX = DecodeMorton2X(i);
|
|
const unsigned int mY = DecodeMorton2Y(i);
|
|
|
|
i++;
|
|
|
|
const SFVEC2UI blockPos( mX * 4 * RAYPACKET_DIM - mX * 4,
|
|
mY * 4 * RAYPACKET_DIM - mY * 4);
|
|
|
|
if( ( blockPos.x >= ( (unsigned int)m_windowSize.x - ( 4 * RAYPACKET_DIM + 4 ) ) ) &&
|
|
( blockPos.y >= ( (unsigned int)m_windowSize.y - ( 4 * RAYPACKET_DIM + 4 ) ) ) )
|
|
break;
|
|
|
|
if( ( blockPos.x < ( (unsigned int)m_windowSize.x - ( 4 * RAYPACKET_DIM + 4) ) ) &&
|
|
( blockPos.y < ( (unsigned int)m_windowSize.y - ( 4 * RAYPACKET_DIM + 4) ) ) )
|
|
{
|
|
m_blockPositionsFast.push_back( blockPos );
|
|
|
|
if( blockPos.x > m_realBufferSize.x )
|
|
m_realBufferSize.x = blockPos.x;
|
|
|
|
if( blockPos.y > m_realBufferSize.y )
|
|
m_realBufferSize.y = blockPos.y;
|
|
}
|
|
}
|
|
|
|
m_fastPreviewModeSize = m_realBufferSize;
|
|
|
|
m_realBufferSize.x = ((m_realBufferSize.x + RAYPACKET_DIM * 4) & RAYPACKET_INVMASK);
|
|
m_realBufferSize.y = ((m_realBufferSize.y + RAYPACKET_DIM * 4) & RAYPACKET_INVMASK);
|
|
|
|
m_xoffset = (m_windowSize.x - m_realBufferSize.x) / 2;
|
|
m_yoffset = (m_windowSize.y - m_realBufferSize.y) / 2;
|
|
|
|
m_postshader_ssao.UpdateSize( m_realBufferSize );
|
|
|
|
|
|
// Calc block positions for regular rendering. Choose an 'inside out'
|
|
// style of rendering
|
|
// /////////////////////////////////////////////////////////////////////
|
|
m_blockPositions.clear();
|
|
const int blocks_x = m_realBufferSize.x / RAYPACKET_DIM;
|
|
const int blocks_y = m_realBufferSize.y / RAYPACKET_DIM;
|
|
m_blockPositions.reserve( blocks_x * blocks_y );
|
|
|
|
for( int x = 0; x < blocks_x; ++x )
|
|
for( int y = 0; y < blocks_y; ++y )
|
|
m_blockPositions.push_back( SFVEC2UI( x * RAYPACKET_DIM, y * RAYPACKET_DIM ) );
|
|
|
|
const SFVEC2UI center( m_realBufferSize.x / 2, m_realBufferSize.y / 2 );
|
|
std::sort( m_blockPositions.begin(), m_blockPositions.end(),
|
|
[&]( const SFVEC2UI& a, const SFVEC2UI& b ) {
|
|
// Sort order: inside out.
|
|
return distance( a, center ) < distance( b, center );
|
|
} );
|
|
|
|
// Create m_shader buffer
|
|
delete[] m_shaderBuffer;
|
|
m_shaderBuffer = new SFVEC3F[m_realBufferSize.x * m_realBufferSize.y];
|
|
|
|
opengl_init_pbo();
|
|
}
|