//===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include #include #include #include #include #include "bitmap/floatbitmap.h" #include "vstdlib/vstdlib.h" #include "raytrace.h" #include "mathlib/bumpvects.h" #include "mathlib/halton.h" #include "tier0/threadtools.h" #include "tier0/progressbar.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" // In order to handle intersections with wrapped copies, we repeat the bitmap triangles this many // times #define NREPS_TILE 1 extern int n_intersection_calculations; struct SSBumpCalculationContext // what each thread needs to see { RayTracingEnvironment * m_pRtEnv; FloatBitMap_t * ret_bm; // the bitmnap we are building FloatBitMap_t const * src_bm; int nrays_to_trace_per_pixel; float bump_scale; Vector *trace_directions; // light source directions to trace Vector *normals; int min_y; // range of scanlines to computer for int max_y; uint32 m_nOptionFlags; int thread_number; }; static uintp SSBumpCalculationThreadFN( void * ctx1 ) { SSBumpCalculationContext * ctx = ( SSBumpCalculationContext * ) ctx1; RayStream ray_trace_stream_ctx; RayTracingSingleResult * rslts = new RayTracingSingleResult[ctx->ret_bm->NumCols() * ctx->nrays_to_trace_per_pixel]; for( int y = ctx->min_y; y <= ctx->max_y; y++ ) { if ( ctx->thread_number == 0 ) ReportProgress("Computing output",(1+ctx->max_y-ctx->min_y),y-ctx->min_y); for( int r = 0; r < ctx->nrays_to_trace_per_pixel; r++ ) { for( int x = 0; x < ctx->ret_bm->NumCols(); x++ ) { Vector surf_pnt( x, y, ctx->bump_scale * ctx->src_bm->Pixel( x, y, 0, 3 ) ); // move the ray origin up a hair surf_pnt.z += 0.55; Vector trace_end = surf_pnt; Vector trace_dir = ctx->trace_directions[ r ]; trace_dir *= ( 1 + NREPS_TILE * 2 ) * MAX( ctx->src_bm->NumCols(), ctx->src_bm->NumRows() ); trace_end += trace_dir; ctx->m_pRtEnv->AddToRayStream( ray_trace_stream_ctx, surf_pnt, trace_end, & ( rslts[ r + ctx->nrays_to_trace_per_pixel * ( x )] ) ); } } if ( ctx->nrays_to_trace_per_pixel ) ctx->m_pRtEnv->FinishRayStream( ray_trace_stream_ctx ); // now, all ray tracing results are in the results buffer. Determine the visible self-shadowed // bump map lighting at each vertex in each basis direction for( int x = 0; x < ctx->src_bm->NumCols(); x++ ) { int nNumChannels = ( ctx->m_nOptionFlags & SSBUMP_OPTION_NONDIRECTIONAL ) ? 1 : 3; for( int c = 0; c < nNumChannels; c++ ) { float sum_dots = 0; float sum_possible_dots = 0; Vector ldir = g_localBumpBasis[c]; float ndotl = DotProduct( ldir, ctx->normals[x + y * ctx->src_bm->NumCols()] ); if ( ndotl < 0 ) ctx->ret_bm->Pixel( x, y, 0, c ) = 0; else { if ( ctx->nrays_to_trace_per_pixel ) { RayTracingSingleResult * this_rslt = rslts + ctx->nrays_to_trace_per_pixel * ( x ); for( int r = 0; r < ctx->nrays_to_trace_per_pixel; r++ ) { float dot; if ( ctx->m_nOptionFlags & SSBUMP_OPTION_NONDIRECTIONAL ) dot = ctx->trace_directions[r].z; else dot = DotProduct( ldir, ctx->trace_directions[r] ); if ( dot > 0 ) { sum_possible_dots += dot; if ( this_rslt[r].HitID == - 1 ) sum_dots += dot; } } } else { sum_dots = sum_possible_dots = 1.0; } ctx->ret_bm->Pixel( x, y, 0, c ) = ( ndotl * sum_dots ) / sum_possible_dots; } } if ( ctx->m_nOptionFlags & SSBUMP_OPTION_NONDIRECTIONAL ) { ctx->ret_bm->Pixel( x, y, 0, 1 ) = ctx->ret_bm->Pixel( x, y, 0, 0 ); // copy height ctx->ret_bm->Pixel( x, y, 0, 2 ) = ctx->ret_bm->Pixel( x, y, 0, 0 ); // copy height ctx->ret_bm->Pixel( x, y, 0, 3 ) = ctx->ret_bm->Pixel( x, y, 0, 0 ); // copy height } else { ctx->ret_bm->Pixel( x, y, 0, 3 ) = ctx->src_bm->Pixel( x, y, 0, 3 ); // copy height } } } delete[] rslts; return 0; } void FloatBitMap_t::ComputeVertexPositionsAndNormals( float flHeightScale, Vector ** ppPosOut, Vector ** ppNormalOut ) const { Vector * verts = new Vector[NumCols() * NumRows()]; // first, calculate vertex positions for( int y = 0; y < NumRows(); y++ ) for( int x = 0; x < NumCols(); x++ ) { Vector * out = verts + x + y * NumCols(); out->x = x; out->y = y; out->z = flHeightScale * Pixel( x, y, 0, 3 ); } Vector * normals = new Vector[NumCols() * NumRows()]; // now, calculate normals, smoothed for( int y = 0; y < NumRows(); y++ ) for( int x = 0; x < NumCols(); x++ ) { // now, calculcate average normal Vector avg_normal( 0, 0, 0 ); for( int xofs =- 1;xofs <= 1;xofs++ ) for( int yofs =- 1;yofs <= 1;yofs++ ) { int x0 = ( x + xofs ); if ( x0 < 0 ) x0 += NumCols(); int y0 = ( y + yofs ); if ( y0 < 0 ) y0 += NumRows(); x0 = x0 % NumCols(); y0 = y0 % NumRows(); int x1 = ( x0 + 1 ) % NumCols(); int y1 = ( y0 + 1 ) % NumRows(); // now, form the two triangles from this vertex Vector p0 = verts[x0 + y0 * NumCols()]; Vector e1 = verts[x1 + y0 * NumCols()]; e1 -= p0; Vector e2 = verts[x0 + y1 * NumCols()]; e2 -= p0; Vector n1; CrossProduct( e1, e2, n1 ); if ( n1.z < 0 ) n1.Negate(); e1 = verts[x + y1 * NumCols()]; e1 -= p0; e2 = verts[x1 + y1 * NumCols()]; e2 -= p0; Vector n2; CrossProduct( e1, e2, n2 ); if ( n2.z < 0 ) n2.Negate(); n1.NormalizeInPlace(); n2.NormalizeInPlace(); avg_normal += n1; avg_normal += n2; } avg_normal.NormalizeInPlace(); normals[x + y * NumCols()]= avg_normal; } * ppPosOut = verts; * ppNormalOut = normals; } FloatBitMap_t * FloatBitMap_t::ComputeSelfShadowedBumpmapFromHeightInAlphaChannel( float bump_scale, int nrays_to_trace_per_pixel, uint32 nOptionFlags ) const { // first, add all the triangles from the height map to the "world". // we will make multiple copies to handle wrapping int tcnt = 1; Vector * verts; Vector * normals; ComputeVertexPositionsAndNormals( bump_scale, & verts, & normals ); RayTracingEnvironment rtEnv; rtEnv.Flags |= RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS; // save some ram if ( nrays_to_trace_per_pixel ) { rtEnv.MakeRoomForTriangles( ( 1 + 2 * NREPS_TILE ) * ( 1 + 2 * NREPS_TILE ) * 2 * NumRows() * NumCols() ); // now, add a whole mess of triangles to trace against for( int tilex =- NREPS_TILE; tilex <= NREPS_TILE; tilex++ ) for( int tiley =- NREPS_TILE; tiley <= NREPS_TILE; tiley++ ) { int min_x = 0; int max_x = NumCols() - 1; int min_y = 0; int max_y = NumRows() - 1; if ( tilex < 0 ) min_x = NumCols() / 2; if ( tilex > 0 ) max_x = NumCols() / 2; if ( tiley < 0 ) min_y = NumRows() / 2; if ( tiley > 0 ) max_y = NumRows() / 2; for( int y = min_y; y <= max_y; y++ ) for( int x = min_x; x <= max_x; x++ ) { Vector ofs( tilex * NumCols(), tiley * NumRows(), 0 ); int x1 = ( x + 1 ) % NumCols(); int y1 = ( y + 1 ) % NumRows(); Vector v0 = verts[x + y * NumCols()]; Vector v1 = verts[x1 + y * NumCols()]; Vector v2 = verts[x1 + y1 * NumCols()]; Vector v3 = verts[x + y1 * NumCols()]; v0.x = x; v0.y = y; v1.x = x + 1; v1.y = y; v2.x = x + 1; v2.y = y + 1; v3.x = x; v3.y = y + 1; v0 += ofs; v1 += ofs; v2 += ofs; v3 += ofs; rtEnv.AddTriangle( tcnt++, v0, v1, v2, Vector( 1, 1, 1 ) ); rtEnv.AddTriangle( tcnt++, v0, v3, v2, Vector( 1, 1, 1 ) ); } } //printf("added %d triangles\n",tcnt-1); ReportProgress("Creating kd-tree",0,0); rtEnv.SetupAccelerationStructure(); // ok, now we have built a structure for ray intersection. we will take advantage // of the SSE ray tracing code by intersecting rays as a batch. } // We need to calculate for each vertex (i.e. pixel) of the heightmap, how "much" of the world // it can see in each basis direction. we will do this by sampling a sphere of rays around the // vertex, and using dot-product weighting to measure the lighting contribution in each basis // direction. note that the surface normal is not used here. The surface normal will end up // being reflected in the result because of rays being blocked when they try to pass through // the planes of the triangles touching the vertex. // note that there is no reason inter-bounced lighting could not be folded into this // calculation. FloatBitMap_t * ret = new FloatBitMap_t( NumCols(), NumRows() ); Vector * trace_directions = new Vector[nrays_to_trace_per_pixel]; DirectionalSampler_t my_sphere_sampler; for( int r = 0; r < nrays_to_trace_per_pixel; r++ ) { Vector trace_dir = my_sphere_sampler.NextValue(); // trace_dir=Vector(1,0,0); trace_dir.z = fabs( trace_dir.z ); // upwards facing only trace_directions[ r ]= trace_dir; } volatile SSBumpCalculationContext ctxs[32]; ctxs[0].m_pRtEnv =& rtEnv; ctxs[0].ret_bm = ret; ctxs[0].src_bm = this; ctxs[0].nrays_to_trace_per_pixel = nrays_to_trace_per_pixel; ctxs[0].bump_scale = bump_scale; ctxs[0].trace_directions = trace_directions; ctxs[0].normals = normals; ctxs[0].min_y = 0; ctxs[0].max_y = NumRows() - 1; ctxs[0].m_nOptionFlags = nOptionFlags; int nthreads = MIN( 32, GetCPUInformation().m_nPhysicalProcessors ); ThreadHandle_t waithandles[32]; int starty = 0; int ystep = NumRows() / nthreads; for( int t = 0;t < nthreads; t++ ) { if ( t ) memcpy( ( void * ) ( & ctxs[t] ), ( void * ) & ctxs[0], sizeof( ctxs[0] ) ); ctxs[t].thread_number = t; ctxs[t].min_y = starty; if ( t != nthreads - 1 ) ctxs[t].max_y = MIN( NumRows() - 1, starty + ystep - 1 ); else ctxs[t].max_y = NumRows() - 1; waithandles[t]= CreateSimpleThread( SSBumpCalculationThreadFN, ( SSBumpCalculationContext * ) & ctxs[t] ); starty += ystep; } for( int t = 0;t < nthreads;t++ ) { ThreadJoin( waithandles[t] ); } if ( nOptionFlags & SSBUMP_MOD2X_DETAIL_TEXTURE ) { const float flOutputScale = 0.5 * ( 1.0 / .57735026 ); // normalize so that a flat normal yields 0.5 // scale output weights by color channel for( int nY = 0; nY < NumRows(); nY++ ) for( int nX = 0; nX < NumCols(); nX++ ) { float flScale = flOutputScale * ( 2.0 / 3.0 ) * ( Pixel( nX, nY, 0, 0 ) + Pixel( nX, nY, 0, 1 ) + Pixel( nX, nY, 0, 2 ) ); ret->Pixel( nX, nY, 0, 0 ) *= flScale; ret->Pixel( nX, nY, 0, 1 ) *= flScale; ret->Pixel( nX, nY, 0, 2 ) *= flScale; } } delete[] verts; delete[] trace_directions; delete[] normals; return ret; // destructor will clean up rtenv } // generate a conventional normal map from a source with height stored in alpha. FloatBitMap_t * FloatBitMap_t::ComputeBumpmapFromHeightInAlphaChannel( float flBumpScale ) const { Vector * verts; Vector * normals; ComputeVertexPositionsAndNormals( flBumpScale, & verts, & normals ); FloatBitMap_t * ret = new FloatBitMap_t( NumCols(), NumRows() ); for( int y = 0; y < NumRows(); y++ ) for( int x = 0; x < NumCols(); x++ ) { Vector const & N = normals[ x + y * NumCols() ]; ret->Pixel( x, y, 0, 0 ) = 0.5 + 0.5 * N.x; ret->Pixel( x, y, 0, 1 ) = 0.5 + 0.5 * N.y; ret->Pixel( x, y, 0, 2 ) = 0.5 + 0.5 * N.z; ret->Pixel( x, y, 0, 3 ) = Pixel( x, y, 0, 3 ); } return ret; }