diff --git a/base/renderprogs/_manifest.lua b/base/renderprogs/_manifest.lua index f7b7c19e..6c98de7a 100644 --- a/base/renderprogs/_manifest.lua +++ b/base/renderprogs/_manifest.lua @@ -15,6 +15,8 @@ return "builtin/debug/shadowDebug_skinned.vs.hlsl", "builtin/debug/octahedron.ps.hlsl", "builtin/debug/octahedron.vs.hlsl", + "builtin/debug/lightgrid.ps.hlsl", + "builtin/debug/lightgrid.vs.hlsl", "builtin/fog/blendLight.ps.hlsl", "builtin/fog/blendLight.vs.hlsl", @@ -40,6 +42,8 @@ return "builtin/lighting/ambient_lighting.vs.hlsl", "builtin/lighting/ambient_lighting_IBL.ps.hlsl", "builtin/lighting/ambient_lighting_IBL.vs.hlsl", + "builtin/lighting/ambient_lightgrid_IBL.ps.hlsl", + "builtin/lighting/ambient_lightgrid_IBL.vs.hlsl", "builtin/lighting/interaction.ps.hlsl", "builtin/lighting/interaction.vs.hlsl", "builtin/lighting/interactionAmbient.ps.hlsl", diff --git a/base/renderprogs/builtin/debug/lightgrid.ps.hlsl b/base/renderprogs/builtin/debug/lightgrid.ps.hlsl new file mode 100644 index 00000000..44cb95f9 --- /dev/null +++ b/base/renderprogs/builtin/debug/lightgrid.ps.hlsl @@ -0,0 +1,129 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. +Copyright (C) 2021 Robert Beckebans + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code 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 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "renderprogs/global.inc.hlsl" + + +// *INDENT-OFF* +uniform sampler2D samp0 : register(s0); // texture 0 is octahedron cube map atlas + +struct PS_IN { + float4 position : VPOS; + float3 texcoord0 : TEXCOORD0_centroid; + float3 texcoord1 : TEXCOORD1_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; +// *INDENT-ON* + + +int3 GetBaseGridCoord( float3 origin ) +{ + float3 lightGridOrigin = rpGlobalLightOrigin.xyz; + float3 lightGridSize = rpJitterTexScale.xyz; + int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z ); + + int3 pos; + + float3 lightOrigin = origin - lightGridOrigin; + for( int i = 0; i < 3; i++ ) + { + float v; + + v = lightOrigin[i] * ( 1.0f / lightGridSize[i] ); + pos[i] = int( floor( v ) ); + + if( pos[i] < 0 ) + { + pos[i] = 0; + } + else if( pos[i] >= lightGridBounds[i] - 1 ) + { + pos[i] = lightGridBounds[i] - 1; + } + } + + return pos; +} + +void main( PS_IN fragment, out PS_OUT result ) +{ + const int LIGHTGRID_IRRADIANCE_SIZE = 32; + + float3 globalPosition = fragment.texcoord0.xyz; + float3 globalNormal = normalize( fragment.texcoord1 ); + + float2 normalizedOctCoord = octEncode( globalNormal ); + float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + + float3 lightGridOrigin = rpGlobalLightOrigin.xyz; + float3 lightGridSize = rpJitterTexScale.xyz; + int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z ); + + float invXZ = ( 1.0 / ( lightGridBounds[0] * lightGridBounds[2] ) ); + float invY = ( 1.0 / lightGridBounds[1] ); + + normalizedOctCoordZeroOne.x *= invXZ; + normalizedOctCoordZeroOne.y *= invY; + + int3 gridStep; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + int3 gridCoord = GetBaseGridCoord( globalPosition ); + + normalizedOctCoordZeroOne.x += ( gridCoord[0] * gridStep[0] + gridCoord[2] * gridStep[1] ) * invXZ; + normalizedOctCoordZeroOne.y += ( gridCoord[1] * invY ); + + // offset by one pixel border bleed size for linear filtering +#if 1 + // rpScreenCorrectionFactor.x = probeSize - borderSize, e.g. ( 18 - 2 ) = 16 + // rpScreenCorrectionFactor.y = probeSize including border, e.g = 18 + // rpScreenCorrectionFactor.z = borderSize e.g = 2 + // rpScreenCorrectionFactor.w = probeSize factor accounting account offset border, e.g = ( 16 / 18 ) = 0.8888 + float2 octCoordNormalizedToTextureDimensions = normalizedOctCoordZeroOne * rpScreenCorrectionFactor.w; + + float2 probeTopLeftPosition; + probeTopLeftPosition.x = ( gridCoord[0] * gridStep[0] + gridCoord[2] * gridStep[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5; + probeTopLeftPosition.y = ( gridCoord[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5; + + float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw; + + normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions; +#endif + + float4 envMap = texture( samp0, normalizedOctCoordZeroOne, 0 ); + + result.color = float4( envMap.xyz, 1.0f ) * 1.0 * fragment.color; +} diff --git a/base/renderprogs/builtin/debug/lightgrid.vs.hlsl b/base/renderprogs/builtin/debug/lightgrid.vs.hlsl new file mode 100644 index 00000000..07a117c7 --- /dev/null +++ b/base/renderprogs/builtin/debug/lightgrid.vs.hlsl @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. +Copyright (C) 2021 Robert Beckebans + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code 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 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "renderprogs/global.inc.hlsl" + + +uniform matrices_ubo { float4 matrices[408]; }; + +// *INDENT-OFF* +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float3 texcoord1 : TEXCOORD1; + float4 color : COLOR0; +}; +// *INDENT-ON* + +void main( VS_IN vertex, out VS_OUT result ) +{ + float4 vNormal = vertex.normal * 2.0 - 1.0; + +#if defined( USE_GPU_SKINNING ) + + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + int joint = int( vertex.color.x * 255.1 * 3.0 ); + matX = matrices[int( joint + 0 )] * w0; + matY = matrices[int( joint + 1 )] * w0; + matZ = matrices[int( joint + 2 )] * w0; + + joint = int( vertex.color.y * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w1; + matY += matrices[int( joint + 1 )] * w1; + matZ += matrices[int( joint + 2 )] * w1; + + joint = int( vertex.color.z * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w2; + matY += matrices[int( joint + 1 )] * w2; + matZ += matrices[int( joint + 2 )] * w2; + + joint = int( vertex.color.w * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w3; + matY += matrices[int( joint + 1 )] * w3; + matZ += matrices[int( joint + 2 )] * w3; + + float3 normal; + normal.x = dot3( matX, vNormal ); + normal.y = dot3( matY, vNormal ); + normal.z = dot3( matZ, vNormal ); + normal = normalize( normal ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + +#else + + float4 modelPosition = vertex.position; + float4 normal = vNormal; + +#endif + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + result.texcoord1.x = dot3( normal, rpModelMatrixX ); + result.texcoord1.y = dot3( normal, rpModelMatrixY ); + result.texcoord1.z = dot3( normal, rpModelMatrixZ ); + + float4 worldPosition; + worldPosition.x = dot4( modelPosition, rpModelMatrixX ); + worldPosition.y = dot4( modelPosition, rpModelMatrixY ); + worldPosition.z = dot4( modelPosition, rpModelMatrixZ ); + worldPosition.w = dot4( modelPosition, rpModelMatrixW ); + result.texcoord0 = worldPosition.xyz; + + result.color = sRGBAToLinearRGBA( rpColor ); +} diff --git a/base/renderprogs/builtin/debug/octahedron.ps.hlsl b/base/renderprogs/builtin/debug/octahedron.ps.hlsl index 8ef576de..0ad3c269 100644 --- a/base/renderprogs/builtin/debug/octahedron.ps.hlsl +++ b/base/renderprogs/builtin/debug/octahedron.ps.hlsl @@ -58,7 +58,19 @@ void main( PS_IN fragment, out PS_OUT result ) float2 normalizedOctCoord = octEncode( reflectionVector ); float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; - float4 envMap = tex2D( samp0, normalizedOctCoordZeroOne ); + // offset by one pixel border bleed size for linear filtering +#if 0 + float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne * ( rpCascadeDistances.x - float( 2.0 ) ) ) / rpCascadeDistances.xy; - result.color = float4( sRGBToLinearRGB( envMap.xyz ), 1.0f ) * fragment.color; + float2 probeTopLeftPosition = float2( 1.0, 1.0 ); + float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw; + + normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions; +#endif + + //normalizedOctCoordZeroOne = TextureCoordFromDirection( reflectionVector, 0, int( rpCascadeDistances.x ), int( rpCascadeDistances.y ), int( rpCascadeDistances.x ) - 2 ); + + float4 envMap = texture( samp0, normalizedOctCoordZeroOne, 0 ); + + result.color = float4( envMap.xyz, 1.0f ) * fragment.color * 1.0; } diff --git a/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.ps.hlsl b/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.ps.hlsl new file mode 100644 index 00000000..d4b0c4d1 --- /dev/null +++ b/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.ps.hlsl @@ -0,0 +1,435 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. +Copyright (C) 2013-2021 Robert Beckebans + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code 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 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "renderprogs/global.inc.hlsl" + +#include "renderprogs/BRDF.inc.hlsl" + + +// *INDENT-OFF* +uniform sampler2D samp0 : register(s0); // texture 0 is the per-surface normal map +uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface specular or roughness/metallic/AO mixer map +uniform sampler2D samp2 : register(s2); // texture 2 is the per-surface baseColor map +uniform sampler2D samp3 : register(s3); // texture 3 is the BRDF LUT +uniform sampler2D samp4 : register(s4); // texture 4 is SSAO + +uniform sampler2D samp7 : register(s7); // texture 7 is the irradiance cube map +uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map 1 +uniform sampler2D samp9 : register(s9); // texture 9 is the radiance cube map 2 +uniform sampler2D samp10 : register(s10); // texture 10 is the radiance cube map 3 + +struct PS_IN +{ + half4 position : VPOS; + half4 texcoord0 : TEXCOORD0_centroid; + half4 texcoord1 : TEXCOORD1_centroid; + half4 texcoord2 : TEXCOORD2_centroid; + half4 texcoord3 : TEXCOORD3_centroid; + half4 texcoord4 : TEXCOORD4_centroid; + half4 texcoord5 : TEXCOORD5_centroid; + half4 texcoord6 : TEXCOORD6_centroid; + half4 texcoord7 : TEXCOORD7_centroid; + half4 color : COLOR0; +}; + +struct PS_OUT +{ + half4 color : COLOR; +}; +// *INDENT-ON* + + +// RB: TODO OPTIMIZE +// this is a straight port of idBounds::RayIntersection +bool AABBRayIntersection( float3 b[2], float3 start, float3 dir, out float scale ) +{ + int i, ax0, ax1, ax2, side, inside; + float f; + float3 hit; + + ax0 = -1; + inside = 0; + for( i = 0; i < 3; i++ ) + { + if( start[i] < b[0][i] ) + { + side = 0; + } + else if( start[i] > b[1][i] ) + { + side = 1; + } + else + { + inside++; + continue; + } + if( dir[i] == 0.0f ) + { + continue; + } + + f = ( start[i] - b[side][i] ); + + if( ax0 < 0 || abs( f ) > abs( scale * dir[i] ) ) + { + scale = - ( f / dir[i] ); + ax0 = i; + } + } + + if( ax0 < 0 ) + { + scale = 0.0f; + + // return true if the start point is inside the bounds + return ( inside == 3 ); + } + + ax1 = ( ax0 + 1 ) % 3; + ax2 = ( ax0 + 2 ) % 3; + hit[ax1] = start[ax1] + scale * dir[ax1]; + hit[ax2] = start[ax2] + scale * dir[ax2]; + + return ( hit[ax1] >= b[0][ax1] && hit[ax1] <= b[1][ax1] && + hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] ); +} + +void main( PS_IN fragment, out PS_OUT result ) +{ + half4 bumpMap = tex2D( samp0, fragment.texcoord0.xy ); + half4 YCoCG = tex2D( samp2, fragment.texcoord1.xy ); + half4 specMapSRGB = tex2D( samp1, fragment.texcoord2.xy ); + half4 specMap = sRGBAToLinearRGBA( specMapSRGB ); + + half3 diffuseMap = sRGBToLinearRGB( ConvertYCoCgToRGB( YCoCG ) ); + + half3 localNormal; +#if defined(USE_NORMAL_FMT_RGB8) + localNormal.xy = bumpMap.rg - 0.5; +#else + localNormal.xy = bumpMap.wy - 0.5; +#endif + localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) ); + localNormal = normalize( localNormal ); + + float3 globalNormal; + globalNormal.x = dot3( localNormal, fragment.texcoord4 ); + globalNormal.y = dot3( localNormal, fragment.texcoord5 ); + globalNormal.z = dot3( localNormal, fragment.texcoord6 ); + globalNormal = normalize( globalNormal ); + + float3 globalPosition = fragment.texcoord7.xyz; + + float3 globalView = normalize( rpGlobalEyePos.xyz - globalPosition ); + + float3 reflectionVector = globalNormal * dot3( globalView, globalNormal ); + reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalView ); + +#if 0 + // parallax box correction using portal area bounds + float hitScale = 0.0; + float3 bounds[2]; + bounds[0].x = rpWobbleSkyX.x; + bounds[0].y = rpWobbleSkyX.y; + bounds[0].z = rpWobbleSkyX.z; + + bounds[1].x = rpWobbleSkyY.x; + bounds[1].y = rpWobbleSkyY.y; + bounds[1].z = rpWobbleSkyY.z; + + // global fragment position + float3 rayStart = fragment.texcoord7.xyz; + + // we can't start inside the box so move this outside and use the reverse path + rayStart += reflectionVector * 10000.0; + + // only do a box <-> ray intersection test if we use a local cubemap + if( ( rpWobbleSkyX.w > 0.0 ) && AABBRayIntersection( bounds, rayStart, -reflectionVector, hitScale ) ) + { + float3 hitPoint = rayStart - reflectionVector * hitScale; + + // rpWobbleSkyZ is cubemap center + reflectionVector = hitPoint - rpWobbleSkyZ.xyz; + } +#endif + + half vDotN = saturate( dot3( globalView, globalNormal ) ); + +#if defined( USE_PBR ) + const half metallic = specMapSRGB.g; + const half roughness = specMapSRGB.r; + const half glossiness = 1.0 - roughness; + + // the vast majority of real-world materials (anything not metal or gems) have F(0°) + // values in a very narrow range (~0.02 - 0.08) + + // approximate non-metals with linear RGB 0.04 which is 0.08 * 0.5 (default in UE4) + const half3 dielectricColor = half3( 0.04 ); + + // derive diffuse and specular from albedo(m) base color + const half3 baseColor = diffuseMap; + + half3 diffuseColor = baseColor * ( 1.0 - metallic ); + half3 specularColor = lerp( dielectricColor, baseColor, metallic ); + +#if defined( DEBUG_PBR ) + diffuseColor = half3( 0.0, 0.0, 0.0 ); + specularColor = half3( 0.0, 1.0, 0.0 ); +#endif + + float3 kS = Fresnel_SchlickRoughness( specularColor, vDotN, roughness ); + float3 kD = ( float3( 1.0, 1.0, 1.0 ) - kS ) * ( 1.0 - metallic ); + +#else + const float roughness = EstimateLegacyRoughness( specMapSRGB.rgb ); + + half3 diffuseColor = diffuseMap; + half3 specularColor = specMap.rgb; + +#if defined( DEBUG_PBR ) + diffuseColor = half3( 0.0, 0.0, 0.0 ); + specularColor = half3( 1.0, 0.0, 0.0 ); +#endif + + float3 kS = Fresnel_SchlickRoughness( specularColor, vDotN, roughness ); + + // NOTE: metalness is missing + float3 kD = ( float3( 1.0, 1.0, 1.0 ) - kS ); + +#endif + + //diffuseColor = half3( 1.0, 1.0, 1.0 ); + //diffuseColor = half3( 0.0, 0.0, 0.0 ); + + // calculate the screen texcoord in the 0.0 to 1.0 range + //float2 screenTexCoord = vposToScreenPosTexCoord( fragment.position.xy ); + float2 screenTexCoord = fragment.position.xy * rpWindowCoord.xy; + + float ao = 1.0; + ao = tex2D( samp4, screenTexCoord ).r; + //diffuseColor.rgb *= ao; + + // evaluate diffuse IBL + + float2 normalizedOctCoord = octEncode( globalNormal ); + float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + +// lightgrid atlas + + //float3 lightGridOrigin = float3( -192.0, -128.0, 0 ); + //float3 lightGridSize = float3( 64.0, 64.0, 128.0 ); + //int3 lightGridBounds = int3( 7, 7, 3 ); + + float3 lightGridOrigin = rpGlobalLightOrigin.xyz; + float3 lightGridSize = rpJitterTexScale.xyz; + int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z ); + + float invXZ = ( 1.0 / ( lightGridBounds[0] * lightGridBounds[2] ) ); + float invY = ( 1.0 / lightGridBounds[1] ); + + normalizedOctCoordZeroOne.x *= invXZ; + normalizedOctCoordZeroOne.y *= invY; + + int3 gridCoord; + float3 frac; + float3 lightOrigin = globalPosition - lightGridOrigin; + + for( int i = 0; i < 3; i++ ) + { + float v; + + // walls can be sampled behind the grid sometimes so avoid negative weights + v = max( 0, lightOrigin[i] * ( 1.0 / lightGridSize[i] ) ); + gridCoord[i] = int( floor( v ) ); + frac[ i ] = v - gridCoord[ i ]; + + /* + if( gridCoord[i] < 0 ) + { + gridCoord[i] = 0; + } + else + */ + if( gridCoord[i] >= lightGridBounds[i] - 1 ) + { + gridCoord[i] = lightGridBounds[i] - 1; + } + } + + // trilerp the light value + int3 gridStep; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + float totalFactor = 0.0; + float3 irradiance = float3( 0.0, 0.0, 0.0 ); + + /* + for( int i = 0; i < 8; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + if( i & ( 1 << j ) ) + + results in these offsets + */ + const float3 cornerOffsets[8] = float3[]( + float3( 0.0, 0.0, 0.0 ), + float3( 1.0, 0.0, 0.0 ), + float3( 0.0, 2.0, 0.0 ), + float3( 1.0, 2.0, 0.0 ), + float3( 0.0, 0.0, 4.0 ), + float3( 1.0, 0.0, 4.0 ), + float3( 0.0, 2.0, 4.0 ), + float3( 1.0, 2.0, 4.0 ) ); + + for( int i = 0; i < 8; i++ ) + { + float factor = 1.0; + + int3 gridCoord2 = gridCoord; + + for( int j = 0; j < 3; j++ ) + { + if( cornerOffsets[ i ][ j ] > 0.0 ) + { + factor *= frac[ j ]; + + gridCoord2[ j ] += 1; + } + else + { + factor *= ( 1.0 - frac[ j ] ); + } + } + + // build P + //float3 P = lightGridOrigin + ( gridCoord2[0] * gridStep[0] + gridCoord2[1] * gridStep[1] + gridCoord2[2] * gridStep[2] ); + + float2 atlasOffset; + + atlasOffset.x = ( gridCoord2[0] * gridStep[0] + gridCoord2[2] * gridStep[1] ) * invXZ; + atlasOffset.y = ( gridCoord2[1] * invY ); + + // offset by one pixel border bleed size for linear filtering +#if 1 + // rpScreenCorrectionFactor.w = probeSize factor accounting account offset border, e.g = ( 16 / 18 ) = 0.8888 + float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne + atlasOffset ) * rpScreenCorrectionFactor.w; + + // skip by default 2 pixels for each grid cell and offset the start position by (1,1) + // rpScreenCorrectionFactor.z = borderSize e.g = 2 + float2 probeTopLeftPosition; + probeTopLeftPosition.x = ( gridCoord2[0] * gridStep[0] + gridCoord2[2] * gridStep[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5; + probeTopLeftPosition.y = ( gridCoord2[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5; + + float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw; + + float2 atlasCoord = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions; +#else + float2 atlasCoord = normalizedOctCoordZeroOne + atlasOffset; +#endif + + float3 color = texture( samp7, atlasCoord, 0 ).rgb; + + if( ( color.r + color.g + color.b ) < 0.0001 ) + { + // ignore samples in walls + continue; + } + + irradiance += color * factor; + totalFactor += factor; + } + + if( totalFactor > 0.0 && totalFactor < 0.9999 ) + { + totalFactor = 1.0 / totalFactor; + + irradiance *= totalFactor; + } + +// lightgrid atlas + + + float3 diffuseLight = ( kD * irradiance * diffuseColor ) * ao * ( rpDiffuseModifier.xyz * 1.0 ); + + // evaluate specular IBL + + // should be 8 = numMips - 1, 256^2 = 9 mips + const float MAX_REFLECTION_LOD = 10.0; + float mip = clamp( ( roughness * MAX_REFLECTION_LOD ), 0.0, MAX_REFLECTION_LOD ); + //float mip = 0.0; + + normalizedOctCoord = octEncode( reflectionVector ); + normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + + float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.x; + radiance += textureLod( samp9, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.y; + radiance += textureLod( samp10, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.z; + //radiance = float3( 0.0 ); + + // RB: HACK dim down room radiance by better local irradiance brightness + //float luma = PhotoLuma( irradiance ); + //float luma = dot( irradiance, LUMINANCE_LINEAR.rgb ); + //float luma = length( irradiance.rgb ); + //radiance *= ( luma * rpSpecularModifier.x * 3.0 ); + + float2 envBRDF = texture( samp3, float2( max( vDotN, 0.0 ), roughness ) ).rg; + +#if 0 + result.color.rgb = float3( envBRDF.x, envBRDF.y, 0.0 ); + result.color.w = fragment.color.a; + return; +#endif + + float specAO = ComputeSpecularAO( vDotN, ao, roughness ); + float3 specularLight = radiance * ( kS * envBRDF.x + float3( envBRDF.y ) ) * specAO * ( rpSpecularModifier.xyz * 0.5 ); + +#if 1 + // Marmoset Horizon Fade trick + const half horizonFade = 1.3; + half horiz = saturate( 1.0 + horizonFade * saturate( dot3( reflectionVector, globalNormal ) ) ); + horiz *= horiz; + //horiz = clamp( horiz, 0.0, 1.0 ); +#endif + + half3 lightColor = sRGBToLinearRGB( rpAmbientColor.rgb ); + //half3 lightColor = ( rpAmbientColor.rgb ); + + //result.color.rgb = diffuseLight; + //result.color.rgb = diffuseLight * lightColor; + //result.color.rgb = specularLight; + result.color.rgb = ( diffuseLight + specularLight * horiz ) * lightColor * fragment.color.rgb; + //result.color.rgb = localNormal.xyz * 0.5 + 0.5; + //result.color.rgb = float3( ao ); + result.color.w = fragment.color.a; +} diff --git a/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.vs.hlsl b/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.vs.hlsl new file mode 100644 index 00000000..d405e89f --- /dev/null +++ b/base/renderprogs/builtin/lighting/ambient_lightgrid_IBL.vs.hlsl @@ -0,0 +1,196 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. +Copyright (C) 2013-2015 Robert Beckebans + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code 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 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "renderprogs/global.inc.hlsl" + + +#if defined( USE_GPU_SKINNING ) +uniform matrices_ubo { float4 matrices[408]; }; +#endif + +// *INDENT-OFF* +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 texcoord3 : TEXCOORD3; + float4 texcoord4 : TEXCOORD4; + float4 texcoord5 : TEXCOORD5; + float4 texcoord6 : TEXCOORD6; + float4 texcoord7 : TEXCOORD7; + float4 color : COLOR0; +}; +// *INDENT-ON* + +void main( VS_IN vertex, out VS_OUT result ) +{ + + float4 vNormal = vertex.normal * 2.0 - 1.0; + float4 vTangent = vertex.tangent * 2.0 - 1.0; + float3 vBitangent = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w; + +#if defined( USE_GPU_SKINNING ) + //-------------------------------------------------------------- + // GPU transformation of the normal / tangent / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + int joint = int( vertex.color.x * 255.1 * 3.0 ); + matX = matrices[int( joint + 0 )] * w0; + matY = matrices[int( joint + 1 )] * w0; + matZ = matrices[int( joint + 2 )] * w0; + + joint = int( vertex.color.y * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w1; + matY += matrices[int( joint + 1 )] * w1; + matZ += matrices[int( joint + 2 )] * w1; + + joint = int( vertex.color.z * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w2; + matY += matrices[int( joint + 1 )] * w2; + matZ += matrices[int( joint + 2 )] * w2; + + joint = int( vertex.color.w * 255.1 * 3.0 ); + matX += matrices[int( joint + 0 )] * w3; + matY += matrices[int( joint + 1 )] * w3; + matZ += matrices[int( joint + 2 )] * w3; + + float3 normal; + normal.x = dot3( matX, vNormal ); + normal.y = dot3( matY, vNormal ); + normal.z = dot3( matZ, vNormal ); + normal = normalize( normal ); + + float3 tangent; + tangent.x = dot3( matX, vTangent ); + tangent.y = dot3( matY, vTangent ); + tangent.z = dot3( matZ, vTangent ); + tangent = normalize( tangent ); + + float3 bitangent; + bitangent.x = dot3( matX, vBitangent ); + bitangent.y = dot3( matY, vBitangent ); + bitangent.z = dot3( matZ, vBitangent ); + bitangent = normalize( bitangent ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + +#else + float4 modelPosition = vertex.position; + float3 normal = vNormal.xyz; + float3 tangent = vTangent.xyz; + float3 bitangent = vBitangent.xyz; +#endif + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f ); + + //-------------------------------------------------------------- + + + //# textures 0 takes the base coordinates by the texture matrix + result.texcoord0 = defaultTexCoord; + result.texcoord0.x = dot4( vertex.texcoord.xy, rpBumpMatrixS ); + result.texcoord0.y = dot4( vertex.texcoord.xy, rpBumpMatrixT ); + + //# textures 1 takes the base coordinates by the texture matrix + result.texcoord1 = defaultTexCoord; + result.texcoord1.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS ); + result.texcoord1.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT ); + + //# textures 2 takes the base coordinates by the texture matrix + result.texcoord2 = defaultTexCoord; + result.texcoord2.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS ); + result.texcoord2.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT ); + + //# calculate normalized vector to viewer in R1 + //result.texcoord3 = modelPosition; + + float4 toEye = normalize( rpLocalViewOrigin - modelPosition ); + + result.texcoord3.x = dot3( toEye, rpModelMatrixX ); + result.texcoord3.y = dot3( toEye, rpModelMatrixY ); + result.texcoord3.z = dot3( toEye, rpModelMatrixZ ); + + result.texcoord4.x = dot3( tangent, rpModelMatrixX ); + result.texcoord5.x = dot3( tangent, rpModelMatrixY ); + result.texcoord6.x = dot3( tangent, rpModelMatrixZ ); + + result.texcoord4.y = dot3( bitangent, rpModelMatrixX ); + result.texcoord5.y = dot3( bitangent, rpModelMatrixY ); + result.texcoord6.y = dot3( bitangent, rpModelMatrixZ ); + + result.texcoord4.z = dot3( normal, rpModelMatrixX ); + result.texcoord5.z = dot3( normal, rpModelMatrixY ); + result.texcoord6.z = dot3( normal, rpModelMatrixZ ); + + float4 worldPosition; + worldPosition.x = dot4( modelPosition, rpModelMatrixX ); + worldPosition.y = dot4( modelPosition, rpModelMatrixY ); + worldPosition.z = dot4( modelPosition, rpModelMatrixZ ); + worldPosition.w = dot4( modelPosition, rpModelMatrixW ); + result.texcoord7 = worldPosition; + +#if defined( USE_GPU_SKINNING ) + // for joint transformation of the tangent space, we use color and + // color2 for weighting information, so hopefully there aren't any + // effects that need vertex color... + result.color = float4( 1.0f, 1.0f, 1.0f, 1.0f ); +#else + //# generate the vertex color, which can be 1.0, color, or 1.0 - color + //# for 1.0 : env[16] = 0, env[17] = 1 + //# for color : env[16] = 1, env[17] = 0 + //# for 1.0-color : env[16] = -1, env[17] = 1 + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +#endif +} \ No newline at end of file diff --git a/base/renderprogs/builtin/lighting/ambient_lighting_IBL.ps.hlsl b/base/renderprogs/builtin/lighting/ambient_lighting_IBL.ps.hlsl index a99daa64..c35b5dcc 100644 --- a/base/renderprogs/builtin/lighting/ambient_lighting_IBL.ps.hlsl +++ b/base/renderprogs/builtin/lighting/ambient_lighting_IBL.ps.hlsl @@ -40,7 +40,9 @@ uniform sampler2D samp3 : register(s3); // texture 3 is the BRDF LUT uniform sampler2D samp4 : register(s4); // texture 4 is SSAO uniform sampler2D samp7 : register(s7); // texture 7 is the irradiance cube map -uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map +uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map 1 +uniform sampler2D samp9 : register(s9); // texture 9 is the radiance cube map 2 +uniform sampler2D samp10 : register(s10); // texture 10 is the radiance cube map 3 struct PS_IN { @@ -62,6 +64,7 @@ struct PS_OUT }; // *INDENT-ON* + // RB: TODO OPTIMIZE // this is a straight port of idBounds::RayIntersection bool AABBRayIntersection( float3 b[2], float3 start, float3 dir, out float scale ) @@ -118,6 +121,26 @@ bool AABBRayIntersection( float3 b[2], float3 start, float3 dir, out float scale hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] ); } + +float2 OctTexCoord( float3 worldDir ) +{ + float2 normalizedOctCoord = octEncode( worldDir ); + float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + + // offset by one pixel border bleed size for linear filtering +#if 0 + // texcoord sizes in rpCascadeDistances are not valid + float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne * ( rpCascadeDistances.x - float( 2.0 ) ) ) / rpCascadeDistances.xy; + + float2 probeTopLeftPosition = float2( 1.0, 1.0 ); + float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw; + + normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions; +#endif + + return normalizedOctCoordZeroOne; +} + void main( PS_IN fragment, out PS_OUT result ) { half4 bumpMap = tex2D( samp0, fragment.texcoord0.xy ); @@ -144,15 +167,14 @@ void main( PS_IN fragment, out PS_OUT result ) float3 globalPosition = fragment.texcoord7.xyz; - // RB: rpGlobalLightOrigin is global view origin - float3 globalEye = normalize( rpGlobalLightOrigin.xyz - globalPosition ); + float3 globalView = normalize( rpGlobalEyePos.xyz - globalPosition ); - float3 reflectionVector = globalNormal * dot3( globalEye, globalNormal ); - reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalEye ); + float3 reflectionVector = globalNormal * dot3( globalView, globalNormal ); + reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalView ); -#if 1 +#if 0 // parallax box correction using portal area bounds - float hitScale; + float hitScale = 0.0; float3 bounds[2]; bounds[0].x = rpWobbleSkyX.x; bounds[0].y = rpWobbleSkyX.y; @@ -178,7 +200,7 @@ void main( PS_IN fragment, out PS_OUT result ) } #endif - half vDotN = saturate( dot3( globalEye, globalNormal ) ); + half vDotN = saturate( dot3( globalView, globalNormal ) ); #if defined( USE_PBR ) const half metallic = specMapSRGB.g; @@ -236,8 +258,7 @@ void main( PS_IN fragment, out PS_OUT result ) // evaluate diffuse IBL - float2 normalizedOctCoord = octEncode( globalNormal ); - float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + float2 normalizedOctCoordZeroOne = OctTexCoord( globalNormal ); float3 irradiance = tex2D( samp7, normalizedOctCoordZeroOne ).rgb; float3 diffuseLight = ( kD * irradiance * diffuseColor ) * ao * ( rpDiffuseModifier.xyz * 1.0 ); @@ -249,10 +270,11 @@ void main( PS_IN fragment, out PS_OUT result ) float mip = clamp( ( roughness * MAX_REFLECTION_LOD ), 0.0, MAX_REFLECTION_LOD ); //float mip = 0.0; - normalizedOctCoord = octEncode( reflectionVector ); - normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5; + normalizedOctCoordZeroOne = OctTexCoord( reflectionVector ); - float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb; + float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.x; + radiance += textureLod( samp9, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.y; + radiance += textureLod( samp10, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.z; //radiance = float3( 0.0 ); float2 envBRDF = texture( samp3, float2( max( vDotN, 0.0 ), roughness ) ).rg; @@ -266,7 +288,7 @@ void main( PS_IN fragment, out PS_OUT result ) float specAO = ComputeSpecularAO( vDotN, ao, roughness ); float3 specularLight = radiance * ( kS * envBRDF.x + float3( envBRDF.y ) ) * specAO * ( rpSpecularModifier.xyz * 0.5 ); -#if 0 +#if 1 // Marmoset Horizon Fade trick const half horizonFade = 1.3; half horiz = saturate( 1.0 + horizonFade * saturate( dot3( reflectionVector, globalNormal ) ) ); @@ -275,11 +297,12 @@ void main( PS_IN fragment, out PS_OUT result ) #endif half3 lightColor = sRGBToLinearRGB( rpAmbientColor.rgb ); + //half3 lightColor = ( rpAmbientColor.rgb ); //result.color.rgb = diffuseLight; //result.color.rgb = diffuseLight * lightColor; //result.color.rgb = specularLight; - result.color.rgb = ( diffuseLight + specularLight ) * lightColor * fragment.color.rgb; + result.color.rgb = ( diffuseLight + specularLight * horiz ) * lightColor * fragment.color.rgb; //result.color.rgb = localNormal.xyz * 0.5 + 0.5; //result.color.rgb = float3( ao ); result.color.w = fragment.color.a; diff --git a/base/renderprogs/builtin/post/postprocess.ps.hlsl b/base/renderprogs/builtin/post/postprocess.ps.hlsl index 4945bc08..6f59362f 100644 --- a/base/renderprogs/builtin/post/postprocess.ps.hlsl +++ b/base/renderprogs/builtin/post/postprocess.ps.hlsl @@ -66,7 +66,7 @@ struct PS_OUT #define USE_CAS 0 #define USE_DITHERING 1 -#define Dithering_QuantizationSteps 8.0 // 8.0 = 2 ^ 3 quantization bits +#define Dithering_QuantizationSteps 16.0 // 8.0 = 2 ^ 3 quantization bits #define Dithering_NoiseBoost 1.0 #define Dithering_Wide 1.0 #define DITHER_IN_LINEAR_SPACE 0 diff --git a/neo/framework/DeclManager.cpp b/neo/framework/DeclManager.cpp index c5fded1d..d2a08eae 100644 --- a/neo/framework/DeclManager.cpp +++ b/neo/framework/DeclManager.cpp @@ -2040,7 +2040,7 @@ struct evarPrefix_t const char* prefix; }; -const evarPrefix_t EvarPrefixes[] = +static const evarPrefix_t EvarPrefixes[] = { { EVAR_STRING, "editor_var " }, { EVAR_INT, "editor_int " }, @@ -2053,15 +2053,15 @@ const evarPrefix_t EvarPrefixes[] = { EVAR_SOUND, "editor_snd "} }; -const int NumEvarPrefixes = sizeof( EvarPrefixes ) / sizeof( evarPrefix_t ); +static const int NumEvarPrefixes = sizeof( EvarPrefixes ) / sizeof( evarPrefix_t ); -typedef struct evar_s +struct LocalEvar_t { int type; idStr fullname; idStr name; idStr desc; -} evar_t; +}; #include // idDeclModelDef @@ -2091,15 +2091,15 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) bool exportModels = false; - if( !idStr::Icmp( args.Argv( 1 ), "models" ) ) + if( !idStr::Icmp( args.Argv( 1 ), "nomodels" ) ) { - exportModels = true; - common->Printf( "exporting entity decls to FGDs with models:\n" ); + exportModels = false; + common->Printf( "exporting entity decls to FGDs without models:\n" ); } else { - exportModels = false; - common->Printf( "exporting entity decls to FGDs:\n" ); + exportModels = true; + common->Printf( "exporting entity decls to FGDs with models:\n" ); } if( exportModels ) @@ -2361,14 +2361,14 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) } // collect editor specific spawn flags - idList evars; + idList evars; for( int i = 0; i < NumEvarPrefixes; i++ ) { kv = decl->dict.MatchPrefix( EvarPrefixes[i].prefix ); while( kv ) { - evar_t ev; + LocalEvar_t ev; ev.fullname = kv->GetKey(); kv->GetKey().Right( kv->GetKey().Length() - strlen( EvarPrefixes[i].prefix ), ev.name ); ev.desc = kv->GetValue(); @@ -2386,7 +2386,7 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) // add missing property to control the radius - evar_t ev; + LocalEvar_t ev; ev.fullname = "editor_int light"; ev.name = "light"; ev.desc = "light radius"; @@ -2401,7 +2401,7 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) { // entities with dynamic models - evar_t ev; + LocalEvar_t ev; ev.fullname = "editor_model model"; ev.name = "model"; ev.desc = "Model Selection (ex mapobjects/model.obj)"; @@ -2437,7 +2437,7 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) //} // is it an editor var or a regular spawn argument? - evar_t* ev = nullptr; + LocalEvar_t* ev = nullptr; int vc = evars.Num(); for( int j = 0; j < vc; j++ ) { @@ -2464,7 +2464,7 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) // add editor_vars that aren't already covered by the default vars for( int i = 0; i < evars.Num(); i++ ) { - const evar_t* ev = &evars[ i ]; + const LocalEvar_t* ev = &evars[ i ]; const idKeyValue* kv2 = dictToWrite.FindKey( ev->name ); if( !kv2 ) @@ -2538,7 +2538,7 @@ void idDeclManagerLocal::ExportDeclsToTrenchBroom_f( const idCmdArgs& args ) kv = dictToWrite.GetKeyVal( i ); // is it an editor var or a regular spawn argument? - evar_t* ev = nullptr; + LocalEvar_t* ev = nullptr; int vc = evars.Num(); for( int j = 0; j < vc; j++ ) { diff --git a/neo/idlib/math/SphericalHarmonics.h b/neo/idlib/math/SphericalHarmonics.h index a8937261..5a33bfa8 100644 --- a/neo/idlib/math/SphericalHarmonics.h +++ b/neo/idlib/math/SphericalHarmonics.h @@ -26,6 +26,10 @@ SOFTWARE. #ifndef __MATH_SPHERICAL_HARMONICS_H__ #define __MATH_SPHERICAL_HARMONICS_H__ +// RB: there is a very good talk by Yuriy O'Donnell that explains the the functions used in this library +// Precomputed Global Illumination in Frostbite (GDC 2018) +// https://www.gdcvault.com/play/1025214/Precomputed-Global-Illumination-in + // https://graphics.stanford.edu/papers/envmap/envmap.pdf template diff --git a/neo/renderer/Framebuffer.h b/neo/renderer/Framebuffer.h index 2069b794..2fc7e8cf 100644 --- a/neo/renderer/Framebuffer.h +++ b/neo/renderer/Framebuffer.h @@ -35,7 +35,7 @@ static const int MAX_SSAO_BUFFERS = 2; static const int MAX_HIERARCHICAL_ZBUFFERS = 6; // native resolution + 5 MIP LEVELS static const int RADIANCE_CUBEMAP_SIZE = 256; -static const int IRRADIANCE_CUBEMAP_SIZE = 128; +static const int IRRADIANCE_CUBEMAP_SIZE = 30 + 2; #if 1 static int shadowMapResolutions[MAX_SHADOWMAP_RESOLUTIONS] = { 2048, 1024, 512, 512, 256 }; diff --git a/neo/renderer/Image.h b/neo/renderer/Image.h index 619fcc72..df10abeb 100644 --- a/neo/renderer/Image.h +++ b/neo/renderer/Image.h @@ -309,6 +309,11 @@ public: return opts.height; } + idVec2i GetUploadResolution() const + { + return idVec2i( opts.width, opts.height ); + } + void SetReferencedOutsideLevelLoad() { referencedOutsideLevelLoad = true; diff --git a/neo/renderer/Image_load.cpp b/neo/renderer/Image_load.cpp index c92f04c2..56f49946 100644 --- a/neo/renderer/Image_load.cpp +++ b/neo/renderer/Image_load.cpp @@ -751,7 +751,7 @@ void idImage::Reload( bool force ) if( !force ) { ID_TIME_T current; - if( cubeFiles != CF_2D ) + if( cubeFiles == CF_NATIVE || cubeFiles == CF_CAMERA ) { R_LoadCubeImages( imgName, cubeFiles, NULL, NULL, ¤t ); } diff --git a/neo/renderer/OpenGL/Image_GL.cpp b/neo/renderer/OpenGL/Image_GL.cpp index 80a8dad4..ac447078 100644 --- a/neo/renderer/OpenGL/Image_GL.cpp +++ b/neo/renderer/OpenGL/Image_GL.cpp @@ -615,70 +615,62 @@ void idImage::AllocImage() GL_CheckErrors(); PurgeImage(); - int sRGB = r_useSRGB.GetInteger(); - switch( opts.format ) { case FMT_RGBA8: - //internalFormat = GL_RGBA8; - //internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ? GL_SRGB8_ALPHA8 : GL_RGBA8; - internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ? GL_SRGB8_ALPHA8 : GL_RGBA8; + internalFormat = GL_RGBA8; dataFormat = GL_RGBA; dataType = GL_UNSIGNED_BYTE; break; + case FMT_XRGB8: - internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ? GL_SRGB : GL_RGB; + internalFormat = GL_RGB; dataFormat = GL_RGBA; dataType = GL_UNSIGNED_BYTE; break; + case FMT_RGB565: - //internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ? GL_SRGB : GL_RGB; internalFormat = GL_RGB; dataFormat = GL_RGB; dataType = GL_UNSIGNED_SHORT_5_6_5; break; + case FMT_ALPHA: -#if 1 - if( ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ) - { - internalFormat = GL_SRGB; - dataFormat = GL_RED; - } - else -#endif - { - internalFormat = GL_R8; - dataFormat = GL_RED; - } + internalFormat = GL_R8; + dataFormat = GL_RED; dataType = GL_UNSIGNED_BYTE; break; + case FMT_L8A8: internalFormat = GL_RG8; dataFormat = GL_RG; dataType = GL_UNSIGNED_BYTE; break; + case FMT_LUM8: internalFormat = GL_R8; dataFormat = GL_RED; dataType = GL_UNSIGNED_BYTE; break; + case FMT_INT8: internalFormat = GL_R8; dataFormat = GL_RED; dataType = GL_UNSIGNED_BYTE; break; + case FMT_DXT1: - internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) ) ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - //internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; dataFormat = GL_RGBA; dataType = GL_UNSIGNED_BYTE; break; + case FMT_DXT5: - internalFormat = ( glConfig.sRGBFramebufferAvailable && ( sRGB == 1 || sRGB == 3 ) && opts.colorFormat != CFM_YCOCG_DXT5 && opts.colorFormat != CFM_NORMAL_DXT5 ) ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - //internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; dataFormat = GL_RGBA; dataType = GL_UNSIGNED_BYTE; break; + case FMT_DEPTH: internalFormat = GL_DEPTH_COMPONENT; dataFormat = GL_DEPTH_COMPONENT; @@ -731,7 +723,6 @@ void idImage::AllocImage() internalFormat = GL_R11F_G11F_B10F; dataFormat = GL_RGB; dataType = GL_UNSIGNED_INT_10F_11F_11F_REV; - //dataType = GL_FLOAT; break; default: diff --git a/neo/renderer/OpenGL/RenderBackend_GL.cpp b/neo/renderer/OpenGL/RenderBackend_GL.cpp index 05560191..d741bd33 100644 --- a/neo/renderer/OpenGL/RenderBackend_GL.cpp +++ b/neo/renderer/OpenGL/RenderBackend_GL.cpp @@ -276,10 +276,6 @@ static void R_CheckPortableExtensions() glConfig.seamlessCubeMapAvailable = GLEW_ARB_seamless_cube_map != 0; r_useSeamlessCubeMap.SetModified(); // the CheckCvars() next frame will enable / disable it - // GL_ARB_framebuffer_sRGB - glConfig.sRGBFramebufferAvailable = GLEW_ARB_framebuffer_sRGB != 0; - r_useSRGB.SetModified(); // the CheckCvars() next frame will enable / disable it - // GL_ARB_vertex_buffer_object if( glConfig.driverType == GLDRV_OPENGL_MESA_CORE_PROFILE ) { @@ -1423,21 +1419,6 @@ void idRenderBackend::GL_Clear( bool color, bool depth, bool stencil, byte stenc glClear( clearFlags ); // RB begin - /* - if( r_useHDR.GetBool() && clearHDR && globalFramebuffers.hdrFBO != NULL ) - { - bool isDefaultFramebufferActive = Framebuffer::IsDefaultFramebufferActive(); - - globalFramebuffers.hdrFBO->Bind(); - glClear( clearFlags ); - - if( isDefaultFramebufferActive ) - { - Framebuffer::Unbind(); - } - } - */ - if( r_useHDR.GetBool() && clearHDR ) { bool isDefaultFramebufferActive = Framebuffer::IsDefaultFramebufferActive(); @@ -1534,21 +1515,6 @@ void idRenderBackend::CheckCVars() } } - if( r_useSRGB.IsModified() ) - { - r_useSRGB.ClearModified(); - if( glConfig.sRGBFramebufferAvailable ) - { - if( r_useSRGB.GetBool() && r_useSRGB.GetInteger() != 3 ) - { - glEnable( GL_FRAMEBUFFER_SRGB ); - } - else - { - glDisable( GL_FRAMEBUFFER_SRGB ); - } - } - } // SRS - Enable SDL-driven vync changes without restart for UNIX-like OSs #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) @@ -1570,7 +1536,6 @@ void idRenderBackend::CheckCVars() } #endif // SRS end - if( r_antiAliasing.IsModified() ) { switch( r_antiAliasing.GetInteger() ) diff --git a/neo/renderer/OpenGL/RenderDebug_GL.cpp b/neo/renderer/OpenGL/RenderDebug_GL.cpp index 3641abae..e235835c 100644 --- a/neo/renderer/OpenGL/RenderDebug_GL.cpp +++ b/neo/renderer/OpenGL/RenderDebug_GL.cpp @@ -3,7 +3,7 @@ Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. -Copyright (C) 2014-2016 Robert Beckebans +Copyright (C) 2014-2021 Robert Beckebans Copyright (C) 2014-2016 Kot in Action Creative Artel Copyright (C) 2016-2017 Dustin Land @@ -1702,9 +1702,38 @@ void idRenderBackend::DBG_ShowLights() ============== RB_ShowViewEnvprobes -Visualize all environemnt probes used in the current scene +Visualize all environment probes used in the current scene ============== */ +class idSort_DebugCompareViewEnvprobe : public idSort_Quick< RenderEnvprobeLocal*, idSort_DebugCompareViewEnvprobe > +{ + idVec3 viewOrigin; + +public: + idSort_DebugCompareViewEnvprobe( const idVec3& origin ) + { + viewOrigin = origin; + } + + int Compare( RenderEnvprobeLocal* const& a, RenderEnvprobeLocal* const& b ) const + { + float adist = ( viewOrigin - a->parms.origin ).LengthSqr(); + float bdist = ( viewOrigin - b->parms.origin ).LengthSqr(); + + if( adist < bdist ) + { + return -1; + } + + if( adist > bdist ) + { + return 1; + } + + return 0; + } +}; + void idRenderBackend::DBG_ShowViewEnvprobes() { if( !r_showViewEnvprobes.GetInteger() ) @@ -1719,16 +1748,15 @@ void idRenderBackend::DBG_ShowViewEnvprobes() { count++; - renderProgManager.BindShader_Octahedron(); + renderProgManager.BindShader_DebugOctahedron(); GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_DEPTHMASK ); GL_Color( 1.0f, 1.0f, 1.0f ); - float modelMatrix[16]; - idMat3 axis; axis.Identity(); + float modelMatrix[16]; R_AxisToModelMatrix( axis, vProbe->globalOrigin, modelMatrix ); idRenderMatrix modelRenderMatrix; @@ -1757,16 +1785,26 @@ void idRenderBackend::DBG_ShowViewEnvprobes() renderProgManager.SetUniformValue( RENDERPARM_LOCALVIEWORIGIN, localViewOrigin.ToFloatPtr() ); // rpLocalViewOrigin + idVec4 textureSize; + GL_SelectTexture( 0 ); if( r_showViewEnvprobes.GetInteger() >= 2 ) { vProbe->irradianceImage->Bind(); + + idVec2i res = vProbe->irradianceImage->GetUploadResolution(); + textureSize.Set( res.x, res.y, 1.0f / res.x, 1.0f / res.y ); } else { vProbe->radianceImage->Bind(); + + idVec2i res = vProbe->radianceImage->GetUploadResolution(); + textureSize.Set( res.x, res.y, 1.0f / res.x, 1.0f / res.y ); } + renderProgManager.SetUniformValue( RENDERPARM_CASCADEDISTANCES, textureSize.ToFloatPtr() ); + DrawElementsWithCounters( &zeroOneSphereSurface ); // non-hidden lines @@ -1785,6 +1823,457 @@ void idRenderBackend::DBG_ShowViewEnvprobes() } #endif } + + + //if( r_showViewEnvprobes.GetInteger() >= 3 ) + if( tr.primaryWorld ) + { + /* + idList viewEnvprobes; + for( viewEnvprobe_t* vProbe = viewDef->viewEnvprobes; vProbe != NULL; vProbe = vProbe->next ) + { + viewEnvprobes.AddUnique( vProbe ); + } + */ + + idList viewEnvprobes; + for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ ) + { + RenderEnvprobeLocal* vProbe = tr.primaryWorld->envprobeDefs[i]; + if( vProbe ) + { + viewEnvprobes.AddUnique( vProbe ); + } + } + + if( viewEnvprobes.Num() == 0 ) + { + return; + } + + idVec3 testOrigin = viewDef->renderView.vieworg; + //testOrigin += viewDef->renderView.viewaxis[0] * 150.0f; + //testOrigin -= viewDef->renderView.viewaxis[2] * 16.0f; + + // sort by distance + viewEnvprobes.SortWithTemplate( idSort_DebugCompareViewEnvprobe( testOrigin ) ); + + // draw 3 nearest probes + renderProgManager.BindShader_Color(); + + const int numColors = 3; + static idVec4 colors[numColors] = { colorRed, colorGreen, colorBlue }; + + // form a triangle of the 3 closest probes + idVec3 verts[3]; + for( int i = 0; i < 3; i++ ) + { + verts[i] = viewEnvprobes[0]->parms.origin; + } + + for( int i = 0; i < viewEnvprobes.Num() && i < 3; i++ ) + { + RenderEnvprobeLocal* vProbe = viewEnvprobes[i]; + + verts[i] = vProbe->parms.origin; + } + + idVec3 closest = R_ClosestPointPointTriangle( testOrigin, verts[0], verts[1], verts[2] ); + idVec3 barycentricWeights; + + // find the barycentric coordinates + float denom = idWinding::TriangleArea( verts[0], verts[1], verts[2] ); + if( denom == 0 ) + { + // all points at same location + barycentricWeights.Set( 1, 0, 0 ); + } + else + { + float a, b, c; + + a = idWinding::TriangleArea( closest, verts[1], verts[2] ) / denom; + b = idWinding::TriangleArea( closest, verts[2], verts[0] ) / denom; + c = idWinding::TriangleArea( closest, verts[0], verts[1] ) / denom; + + barycentricWeights.Set( a, b, c ); + } + + idMat3 axis; + axis.Identity(); + + for( int i = 0; i < viewEnvprobes.Num() && i < 3; i++ ) + { + RenderEnvprobeLocal* vProbe = viewEnvprobes[i]; + + verts[i] = vProbe->parms.origin; + + //GL_Color( colors[i] ); + + idVec4 color = Lerp( colorBlack, colors[i], barycentricWeights[i] ); + GL_Color( color ); + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( vProbe->parms.origin, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 16.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + } + + // draw closest hit + { + GL_Color( colorYellow ); + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( closest, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 4.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + } + } +} + +void idRenderBackend::DBG_ShowLightGrid() +{ + if( r_showLightGrid.GetInteger() <= 0 || !tr.primaryWorld ) + { + return; + } + + // all volumes are expressed in world coordinates + + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_DEPTHMASK ); + GL_Color( 1.0f, 1.0f, 1.0f ); + + idMat3 axis; + axis.Identity(); + + // only show current area + int cameraArea = tr.primaryWorld->PointInArea( viewDef->renderView.vieworg ); + if( cameraArea == -1 && r_showLightGrid.GetInteger() < 3 ) + { + return; + } + + const int numColors = 7; + static idVec4 colors[numColors] = { colorBrown, colorBlue, colorCyan, colorGreen, colorYellow, colorRed, colorWhite }; + + for( int a = 0; a < tr.primaryWorld->NumAreas(); a++ ) + { + if( r_showLightGrid.GetInteger() < 3 && ( cameraArea != a ) ) + { + continue; + } + + portalArea_t* area = &tr.primaryWorld->portalAreas[a]; + + // use rpGlobalLightOrigin for lightGrid center + idVec4 lightGridOrigin( area->lightGrid.lightGridOrigin.x, area->lightGrid.lightGridOrigin.y, area->lightGrid.lightGridOrigin.z, 1.0f ); + idVec4 lightGridSize( area->lightGrid.lightGridSize.x, area->lightGrid.lightGridSize.y, area->lightGrid.lightGridSize.z, 1.0f ); + idVec4 lightGridBounds( area->lightGrid.lightGridBounds[0], area->lightGrid.lightGridBounds[1], area->lightGrid.lightGridBounds[2], 1.0f ); + + renderProgManager.SetUniformValue( RENDERPARM_GLOBALLIGHTORIGIN, lightGridOrigin.ToFloatPtr() ); + renderProgManager.SetUniformValue( RENDERPARM_JITTERTEXSCALE, lightGridSize.ToFloatPtr() ); + renderProgManager.SetUniformValue( RENDERPARM_JITTERTEXOFFSET, lightGridBounds.ToFloatPtr() ); + + // individual probe sizes on the atlas image + idVec4 probeSize; + probeSize[0] = area->lightGrid.imageSingleProbeSize - area->lightGrid.imageBorderSize; + probeSize[1] = area->lightGrid.imageSingleProbeSize; + probeSize[2] = area->lightGrid.imageBorderSize; + probeSize[3] = float( area->lightGrid.imageSingleProbeSize - area->lightGrid.imageBorderSize ) / area->lightGrid.imageSingleProbeSize; + renderProgManager.SetUniformValue( RENDERPARM_SCREENCORRECTIONFACTOR, probeSize.ToFloatPtr() ); // rpScreenCorrectionFactor + + for( int i = 0; i < area->lightGrid.lightGridPoints.Num(); i++ ) + { + lightGridPoint_t* gridPoint = &area->lightGrid.lightGridPoints[i]; + if( !gridPoint->valid && r_showLightGrid.GetInteger() < 3 ) + { + continue; + } + + idVec3 distanceToCam = gridPoint->origin - viewDef->renderView.vieworg; + if( distanceToCam.LengthSqr() > ( 1024 * 1024 ) && r_showLightGrid.GetInteger() < 3 ) + { + continue; + } + +#if 0 + if( i > 53 ) + { + break; + } +#endif + + // move center into the cube so we can void using negative results with GetBaseGridCoord + idVec3 gridPointOrigin = gridPoint->origin + idVec3( 4, 4, 4 ); + + idVec4 localViewOrigin( 1.0f ); + idVec4 globalViewOrigin; + globalViewOrigin.x = viewDef->renderView.vieworg.x; + globalViewOrigin.y = viewDef->renderView.vieworg.y; + globalViewOrigin.z = viewDef->renderView.vieworg.z; + globalViewOrigin.w = 1.0f; + + float modelMatrix[16]; + R_AxisToModelMatrix( axis, gridPointOrigin, modelMatrix ); + + R_GlobalPointToLocal( modelMatrix, viewDef->renderView.vieworg, localViewOrigin.ToVec3() ); + + renderProgManager.SetUniformValue( RENDERPARM_LOCALVIEWORIGIN, localViewOrigin.ToFloatPtr() ); // rpLocalViewOrigin + + // RB: if we want to get the normals in world space so we need the model -> world matrix + idRenderMatrix modelMatrix2; + idRenderMatrix::Transpose( *( idRenderMatrix* )modelMatrix, modelMatrix2 ); + + renderProgManager.SetUniformValue( RENDERPARM_MODELMATRIX_X, &modelMatrix2[0][0] ); + renderProgManager.SetUniformValue( RENDERPARM_MODELMATRIX_Y, &modelMatrix2[1][0] ); + renderProgManager.SetUniformValue( RENDERPARM_MODELMATRIX_Z, &modelMatrix2[2][0] ); + renderProgManager.SetUniformValue( RENDERPARM_MODELMATRIX_W, &modelMatrix2[3][0] ); + + +#if 0 + renderProgManager.BindShader_Color(); + + int gridCoord[3]; + area->lightGrid.GetBaseGridCoord( gridPoint->origin, gridCoord ); + + idVec3 color = area->lightGrid.GetGridCoordDebugColor( gridCoord ); + //idVec3 color = area->lightGrid.GetProbeIndexDebugColor( i ); + //idVec4 color = colors[ i % numColors ]; + GL_Color( color ); +#else + + if( r_showLightGrid.GetInteger() == 4 || !area->lightGrid.GetIrradianceImage() ) + { + renderProgManager.BindShader_Color(); + + idVec4 color; + if( !gridPoint->valid ) + { + color = colorPurple; + } + else + { + color = colors[ a % numColors ]; + } + + GL_Color( color ); + } + else + { + renderProgManager.BindShader_DebugLightGrid(); + + GL_SelectTexture( 0 ); + area->lightGrid.GetIrradianceImage()->Bind(); + + idVec2i res = area->lightGrid.GetIrradianceImage()->GetUploadResolution(); + idVec4 textureSize( res.x, res.y, 1.0f / res.x, 1.0f / res.y ); + + renderProgManager.SetUniformValue( RENDERPARM_CASCADEDISTANCES, textureSize.ToFloatPtr() ); + } +#endif + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( gridPoint->origin, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 3.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + //DrawElementsWithCounters( &zeroOneCubeSurface ); + } + } + + if( r_showLightGrid.GetInteger() == 2 ) + { + // show 8 nearest grid points around the camera and illustrate how the trilerping works + + idVec3 lightOrigin; + int pos[3]; + int gridPointIndex; + int gridPointIndex2; + lightGridPoint_t* gridPoint; + lightGridPoint_t* gridPoint2; + float frac[3]; + int gridStep[3]; + float totalFactor; + + portalArea_t* area = &tr.primaryWorld->portalAreas[cameraArea]; + + renderProgManager.BindShader_Color(); + + lightOrigin = viewDef->renderView.vieworg; + lightOrigin += viewDef->renderView.viewaxis[0] * 150.0f; + lightOrigin -= viewDef->renderView.viewaxis[2] * 16.0f; + + // draw sample origin we want to test the grid with + { + GL_Color( colorYellow ); + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( lightOrigin, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 2.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + } + + // find base grid point + lightOrigin -= area->lightGrid.lightGridOrigin; + for( int i = 0; i < 3; i++ ) + { + float v; + + v = lightOrigin[i] * ( 1.0f / area->lightGrid.lightGridSize[i] ); + pos[i] = floor( v ); + frac[i] = v - pos[i]; + + if( pos[i] < 0 ) + { + pos[i] = 0; + } + else if( pos[i] >= area->lightGrid.lightGridBounds[i] - 1 ) + { + pos[i] = area->lightGrid.lightGridBounds[i] - 1; + } + } + + // trilerp the light value + gridStep[0] = 1; + gridStep[1] = area->lightGrid.lightGridBounds[0]; + gridStep[2] = area->lightGrid.lightGridBounds[0] * area->lightGrid.lightGridBounds[1]; + + gridPointIndex = pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + gridPoint = &area->lightGrid.lightGridPoints[ gridPointIndex ]; + + totalFactor = 0; + idVec3 cornerOffsets[8]; + + for( int i = 0; i < 8; i++ ) + { + float factor = 1.0; + + gridPoint2 = gridPoint; + gridPointIndex2 = gridPointIndex; + + for( int j = 0; j < 3; j++ ) + { + cornerOffsets[i][j] = i & ( 1 << j ); + + if( cornerOffsets[i][j] > 0.0f ) + { + factor *= frac[j]; + +#if 1 + gridPointIndex2 += gridStep[j]; + if( gridPointIndex2 < 0 || gridPointIndex2 >= area->lightGrid.lightGridPoints.Num() ) + { + // ignore values outside lightgrid + continue; + } + + gridPoint2 = &area->lightGrid.lightGridPoints[ gridPointIndex2 ]; +#else + if( pos[j] + 1 > area->lightGrid.lightGridBounds[j] - 1 ) + { + // ignore values outside lightgrid + break; + } + + gridPoint2 += gridStep[j]; +#endif + } + else + { + factor *= ( 1.0f - frac[j] ); + } + } + + if( !gridPoint2->valid ) + { + // ignore samples in walls + continue; + } + + totalFactor += factor; + + idVec4 color = Lerp( colorBlack, colorGreen, factor ); + GL_Color( color ); + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( gridPoint2->origin, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 4.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + } + + // draw main grid point where camera position snapped to + GL_Color( colorRed ); + + idRenderMatrix modelRenderMatrix; + idRenderMatrix::CreateFromOriginAxis( gridPoint->origin, axis, modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + const float size = 5.0f; + idBounds debugBounds( idVec3( -size ), idVec3( size ) ); + + idRenderMatrix inverseBaseModelProject; + idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject ); + + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + DrawElementsWithCounters( &zeroOneSphereSurface ); + } } void idRenderBackend::DBG_ShowShadowMapLODs() @@ -3125,6 +3614,11 @@ idRenderBackend::DBG_RenderDebugTools */ void idRenderBackend::DBG_RenderDebugTools( drawSurf_t** drawSurfs, int numDrawSurfs ) { + if( viewDef->renderView.rdflags & RDF_IRRADIANCE ) + { + return; + } + // don't do much if this was a 2D rendering if( !viewDef->viewEntitys ) { @@ -3158,6 +3652,7 @@ void idRenderBackend::DBG_RenderDebugTools( drawSurf_t** drawSurfs, int numDrawS DBG_ShowViewEntitys( viewDef->viewEntitys ); DBG_ShowLights(); // RB begin + DBG_ShowLightGrid(); DBG_ShowViewEnvprobes(); DBG_ShowShadowMapLODs(); DBG_ShowShadowMaps(); diff --git a/neo/renderer/RenderBackend.cpp b/neo/renderer/RenderBackend.cpp index c62aa33b..7cece8c6 100644 --- a/neo/renderer/RenderBackend.cpp +++ b/neo/renderer/RenderBackend.cpp @@ -1200,9 +1200,13 @@ const int INTERACTION_TEXUNIT_JITTER = 6; #if defined( USE_VULKAN ) const int INTERACTION_TEXUNIT_AMBIENT_CUBE1 = 5; const int INTERACTION_TEXUNIT_SPECULAR_CUBE1 = 6; + const int INTERACTION_TEXUNIT_SPECULAR_CUBE2 = 7; + const int INTERACTION_TEXUNIT_SPECULAR_CUBE3 = 8; #else const int INTERACTION_TEXUNIT_AMBIENT_CUBE1 = 7; const int INTERACTION_TEXUNIT_SPECULAR_CUBE1 = 8; + const int INTERACTION_TEXUNIT_SPECULAR_CUBE2 = 9; + const int INTERACTION_TEXUNIT_SPECULAR_CUBE3 = 10; #endif /* @@ -1323,7 +1327,7 @@ void idRenderBackend::DrawSingleInteraction( drawInteraction_t* din, bool useFas const textureUsage_t specUsage = din->specularImage->GetUsage(); // RB begin - if( useIBL ) + if( useIBL && currentSpace->useLightGrid && r_useLightGrid.GetBool() ) { idVec4 probeMins, probeMaxs, probeCenter; @@ -1344,6 +1348,108 @@ void idRenderBackend::DrawSingleInteraction( drawInteraction_t* din, bool useFas SetVertexParm( RENDERPARM_WOBBLESKY_Y, probeMaxs.ToFloatPtr() ); SetVertexParm( RENDERPARM_WOBBLESKY_Z, probeCenter.ToFloatPtr() ); + // use rpGlobalLightOrigin for lightGrid center + idVec4 lightGridOrigin( currentSpace->lightGridOrigin.x, currentSpace->lightGridOrigin.y, currentSpace->lightGridOrigin.z, 1.0f ); + idVec4 lightGridSize( currentSpace->lightGridSize.x, currentSpace->lightGridSize.y, currentSpace->lightGridSize.z, 1.0f ); + idVec4 lightGridBounds( currentSpace->lightGridBounds[0], currentSpace->lightGridBounds[1], currentSpace->lightGridBounds[2], 1.0f ); + + renderProgManager.SetUniformValue( RENDERPARM_GLOBALLIGHTORIGIN, lightGridOrigin.ToFloatPtr() ); + renderProgManager.SetUniformValue( RENDERPARM_JITTERTEXSCALE, lightGridSize.ToFloatPtr() ); + renderProgManager.SetUniformValue( RENDERPARM_JITTERTEXOFFSET, lightGridBounds.ToFloatPtr() ); + + // individual probe sizes on the atlas image + idVec4 probeSize; + probeSize[0] = currentSpace->lightGridAtlasSingleProbeSize - currentSpace->lightGridAtlasBorderSize; + probeSize[1] = currentSpace->lightGridAtlasSingleProbeSize; + probeSize[2] = currentSpace->lightGridAtlasBorderSize; + probeSize[3] = float( currentSpace->lightGridAtlasSingleProbeSize - currentSpace->lightGridAtlasBorderSize ) / currentSpace->lightGridAtlasSingleProbeSize; + renderProgManager.SetUniformValue( RENDERPARM_SCREENCORRECTIONFACTOR, probeSize.ToFloatPtr() ); // rpScreenCorrectionFactor + + // specular cubemap blend weights + renderProgManager.SetUniformValue( RENDERPARM_LOCALLIGHTORIGIN, viewDef->radianceImageBlends.ToFloatPtr() ); + + if( specUsage == TD_SPECULAR_PBR_RMAO || specUsage == TD_SPECULAR_PBR_RMAOD ) + { + // PBR path with roughness, metal and AO + if( din->surf->jointCache ) + { + renderProgManager.BindShader_ImageBasedLightGridSkinned_PBR(); + } + else + { + renderProgManager.BindShader_ImageBasedLightGrid_PBR(); + } + } + else + { + if( din->surf->jointCache ) + { + renderProgManager.BindShader_ImageBasedLightGridSkinned(); + } + else + { + renderProgManager.BindShader_ImageBasedLightGrid(); + } + } + + GL_SelectTexture( INTERACTION_TEXUNIT_FALLOFF ); + globalImages->brdfLutImage->Bind(); + + GL_SelectTexture( INTERACTION_TEXUNIT_PROJECTION ); +#if defined( USE_VULKAN ) + globalImages->whiteImage->Bind(); +#else + if( !r_useSSAO.GetBool() ) + { + globalImages->whiteImage->Bind(); + } + else + { + globalImages->ambientOcclusionImage[0]->Bind(); + } +#endif + + GL_SelectTexture( INTERACTION_TEXUNIT_AMBIENT_CUBE1 ); + currentSpace->lightGridAtlasImage->Bind(); + + idVec2i res = currentSpace->lightGridAtlasImage->GetUploadResolution(); + idVec4 textureSize( res.x, res.y, 1.0f / res.x, 1.0f / res.y ); + + renderProgManager.SetUniformValue( RENDERPARM_CASCADEDISTANCES, textureSize.ToFloatPtr() ); + + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE1 ); + viewDef->radianceImages[0]->Bind(); + + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE2 ); + viewDef->radianceImages[1]->Bind(); + + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE3 ); + viewDef->radianceImages[2]->Bind(); + } + else if( useIBL ) + { + idVec4 probeMins, probeMaxs, probeCenter; + + probeMins[0] = viewDef->globalProbeBounds[0][0]; + probeMins[1] = viewDef->globalProbeBounds[0][1]; + probeMins[2] = viewDef->globalProbeBounds[0][2]; + probeMins[3] = viewDef->globalProbeBounds.IsCleared() ? 0.0f : 1.0f; + + probeMaxs[0] = viewDef->globalProbeBounds[1][0]; + probeMaxs[1] = viewDef->globalProbeBounds[1][1]; + probeMaxs[2] = viewDef->globalProbeBounds[1][2]; + probeMaxs[3] = 0.0f; + + idVec3 center = viewDef->globalProbeBounds.GetCenter(); + probeCenter.Set( center.x, center.y, center.z, 1.0f ); + + SetVertexParm( RENDERPARM_WOBBLESKY_X, probeMins.ToFloatPtr() ); + SetVertexParm( RENDERPARM_WOBBLESKY_Y, probeMaxs.ToFloatPtr() ); + SetVertexParm( RENDERPARM_WOBBLESKY_Z, probeCenter.ToFloatPtr() ); + + // specular cubemap blend weights + renderProgManager.SetUniformValue( RENDERPARM_LOCALLIGHTORIGIN, viewDef->radianceImageBlends.ToFloatPtr() ); + if( specUsage == TD_SPECULAR_PBR_RMAO || specUsage == TD_SPECULAR_PBR_RMAOD ) { // PBR path with roughness, metal and AO @@ -1385,26 +1491,17 @@ void idRenderBackend::DrawSingleInteraction( drawInteraction_t* din, bool useFas } #endif - // TODO bind the 3 closest probes GL_SelectTexture( INTERACTION_TEXUNIT_AMBIENT_CUBE1 ); - if( viewDef->irradianceImage ) - { - viewDef->irradianceImage->Bind(); - } - else - { - globalImages->defaultUACIrradianceCube->Bind(); - } + viewDef->irradianceImage->Bind(); GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE1 ); - if( viewDef->radianceImage ) - { - viewDef->radianceImage->Bind(); - } - else - { - globalImages->defaultUACRadianceCube->Bind(); - } + viewDef->radianceImages[0]->Bind(); + + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE2 ); + viewDef->radianceImages[1]->Bind(); + + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR_CUBE3 ); + viewDef->radianceImages[2]->Bind(); } else if( setInteractionShader ) { @@ -2229,10 +2326,6 @@ void idRenderBackend::AmbientPass( const drawSurf_t* const* drawSurfs, int numDr renderProgManager.SetRenderParm( RENDERPARM_AMBIENT_COLOR, ambientColor.ToFloatPtr() ); - // use rpGlobalLightOrigin for camera center - idVec4 globalViewOrigin( viewDef->renderView.vieworg.x, viewDef->renderView.vieworg.y, viewDef->renderView.vieworg.z, 1.0f ); - SetVertexParm( RENDERPARM_GLOBALLIGHTORIGIN, globalViewOrigin.ToFloatPtr() ); - // setup renderparms assuming we will be drawing trivial surfaces first RB_SetupForFastPathInteractions( diffuseColor, specularColor ); @@ -2318,16 +2411,6 @@ void idRenderBackend::AmbientPass( const drawSurf_t* const* drawSurfs, int numDr R_GlobalPointToLocal( drawSurf->space->modelMatrix, viewDef->renderView.vieworg, localViewOrigin.ToVec3() ); SetVertexParm( RENDERPARM_LOCALVIEWORIGIN, localViewOrigin.ToFloatPtr() ); - //if( !isWorldModel ) - //{ - // // tranform the light direction into model local space - // idVec3 globalLightDirection( 0.0f, 0.0f, -1.0f ); // HACK - // idVec4 localLightDirection( 0.0f ); - // R_GlobalVectorToLocal( drawSurf->space->modelMatrix, globalLightDirection, localLightDirection.ToVec3() ); - // - // SetVertexParm( RENDERPARM_LOCALLIGHTORIGIN, localLightDirection.ToFloatPtr() ); - //} - // RB: if we want to store the normals in world space so we need the model -> world matrix idRenderMatrix modelMatrix; idRenderMatrix::Transpose( *( idRenderMatrix* )drawSurf->space->modelMatrix, modelMatrix ); @@ -2340,27 +2423,6 @@ void idRenderBackend::AmbientPass( const drawSurf_t* const* drawSurfs, int numDr SetVertexParms( RENDERPARM_MODELVIEWMATRIX_X, modelViewMatrixTranspose, 4 ); } -#if 0 - if( !isWorldModel ) - { - idVec4 directedColor; - directedColor.x = drawSurf->space->gridDirectedLight.x; - directedColor.y = drawSurf->space->gridDirectedLight.y; - directedColor.z = drawSurf->space->gridDirectedLight.z; - directedColor.w = 1; - - idVec4 ambientColor; - ambientColor.x = drawSurf->space->gridAmbientLight.x; - ambientColor.y = drawSurf->space->gridAmbientLight.y; - ambientColor.z = drawSurf->space->gridAmbientLight.z; - ambientColor.w = 1; - - renderProgManager.SetRenderParm( RENDERPARM_COLOR, directedColor.ToFloatPtr() ); - renderProgManager.SetRenderParm( RENDERPARM_AMBIENT_COLOR, ambientColor.ToFloatPtr() ); - } - float ambientBoost = r_useHDR.GetBool() ? 1.5 : 1.0; -#endif - /* uint64 surfGLState = 0; diff --git a/neo/renderer/RenderBackend.h b/neo/renderer/RenderBackend.h index 975290ba..6ec134ce 100644 --- a/neo/renderer/RenderBackend.h +++ b/neo/renderer/RenderBackend.h @@ -427,6 +427,7 @@ private: void DBG_ShowDominantTris( drawSurf_t** drawSurfs, int numDrawSurfs ); void DBG_ShowEdges( drawSurf_t** drawSurfs, int numDrawSurfs ); void DBG_ShowLights(); + void DBG_ShowLightGrid(); // RB void DBG_ShowViewEnvprobes(); // RB void DBG_ShowShadowMapLODs(); // RB void DBG_ShowPortals(); diff --git a/neo/renderer/RenderCommon.h b/neo/renderer/RenderCommon.h index a2381609..35d21fb3 100644 --- a/neo/renderer/RenderCommon.h +++ b/neo/renderer/RenderCommon.h @@ -450,6 +450,17 @@ struct viewEntity_t // be linked to the lights or added to the drawsurf list in a serial code section drawSurf_t* drawSurfs; + // RB: use light grid of the best area this entity is in + bool useLightGrid; + idImage* lightGridAtlasImage; + int lightGridAtlasSingleProbeSize; // including border + int lightGridAtlasBorderSize; + + idVec3 lightGridOrigin; + idVec3 lightGridSize; + int lightGridBounds[3]; + // RB end + // R_AddSingleModel will build a chain of parameters here to setup shadow volumes staticShadowVolumeParms_t* staticShadowVolumes; dynamicShadowVolumeParms_t* dynamicShadowVolumes; @@ -498,7 +509,28 @@ struct calcEnvprobeParms_t idStr filename; // output - halfFloat_t* outBuffer; // HDR R11G11B11F packed atlas + halfFloat_t* outBuffer; // HDR R11G11B11F packed octahedron atlas + int time; // execution time in milliseconds +}; + + + +static const int LIGHTGRID_IRRADIANCE_BORDER_SIZE = 2; // one pixel border all around the octahedron so 2 on each side +static const int LIGHTGRID_IRRADIANCE_SIZE = 30 + LIGHTGRID_IRRADIANCE_BORDER_SIZE; + +struct calcLightGridPointParms_t +{ + // input + byte* radiance[6]; // HDR RGB16F standard OpenGL cubemap sides + int gridCoord[3]; + + int outWidth; // LIGHTGRID_IRRADIANCE_SIZE + int outHeight; + + // output + SphericalHarmonicsT shRadiance; // L3 Spherical Harmonics + + halfFloat_t* outBuffer; // HDR R11G11B11F octahedron LIGHTGRID_IRRADIANCE_SIZE^2 int time; // execution time in milliseconds }; // RB end @@ -616,8 +648,8 @@ struct viewDef_t idBounds globalProbeBounds; idRenderMatrix inverseBaseEnvProbeProject; // the matrix for deforming the 'zeroOneCubeModel' to exactly cover the environent probe volume in world space idImage* irradianceImage; // cubemap image used for diffuse IBL by backend - idImage* radianceImage; // cubemap image used for specular IBL by backend - // RB end + idImage* radianceImages[3]; // cubemap image used for specular IBL by backend + idVec4 radianceImageBlends; // blending weights }; @@ -858,6 +890,7 @@ public: virtual void RenderCommandBuffers( const emptyCommand_t* commandBuffers ); virtual void TakeScreenshot( int width, int height, const char* fileName, int downSample, renderView_t* ref, int exten ); + virtual byte* CaptureRenderToBuffer( int width, int height, renderView_t* ref ); virtual void CropRenderSize( int width, int height ); virtual void CaptureRenderToImage( const char* imageName, bool clearColorAfterCopy = false ); virtual void CaptureRenderToFile( const char* fileName, bool fixAlpha ); @@ -940,6 +973,8 @@ public: unsigned short gammaTable[256]; // brightness / gamma modify this + idMat3 cubeAxis[6]; // RB + srfTriangles_t* unitSquareTriangles; srfTriangles_t* zeroOneCubeTriangles; srfTriangles_t* zeroOneSphereTriangles; @@ -956,8 +991,9 @@ public: idParallelJobList* frontEndJobList; // RB irradiance and GGX background jobs - idParallelJobList* envprobeJobList; - idList irradianceJobs; + idParallelJobList* envprobeJobList; + idList envprobeJobs; + idList lightGridJobs; idRenderBackend backend; @@ -1029,7 +1065,6 @@ extern idCVar r_useShadowDepthBounds; // use depth bounds test on individual sh extern idCVar r_useShadowMapping; // use shadow mapping instead of stencil shadows extern idCVar r_useHalfLambertLighting; // use Half-Lambert lighting instead of classic Lambert extern idCVar r_useHDR; -extern idCVar r_useSRGB; extern idCVar r_useSeamlessCubeMap; // RB end @@ -1175,6 +1210,9 @@ extern idCVar r_useHierarchicalDepthBuffer; extern idCVar r_usePBR; extern idCVar r_pbrDebug; extern idCVar r_showViewEnvprobes; +extern idCVar r_showLightGrid; // show Quake 3 style light grid points + +extern idCVar r_useLightGrid; extern idCVar r_exposure; // RB end @@ -1353,6 +1391,85 @@ RENDERWORLD_PORTALS viewEntity_t* R_SetEntityDefViewEntity( idRenderEntityLocal* def ); viewLight_t* R_SetLightDefViewLight( idRenderLightLocal* def ); +/* +============================================================ + +RENDERWORLD_ENVPROBES + +============================================================ +*/ + +void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float result[3], float& u, float& v ); +void R_SampleCubeMapHDR16F( const idVec3& dir, int size, halfFloat_t* buffers[6], float result[3], float& u, float& v ); + +idVec2 NormalizedOctCoord( int x, int y, const int probeSideLength ); + +class CommandlineProgressBar +{ +private: + size_t tics = 0; + size_t nextTicCount = 0; + int count = 0; + int expectedCount = 0; + +public: + CommandlineProgressBar( int _expectedCount ) + { + expectedCount = _expectedCount; + } + + void Start() + { + common->Printf( "0%% 10 20 30 40 50 60 70 80 90 100%%\n" ); + common->Printf( "|----|----|----|----|----|----|----|----|----|----|\n" ); + + common->UpdateScreen( false ); + } + + void Increment() + { + if( ( count + 1 ) >= nextTicCount ) + { + size_t ticsNeeded = ( size_t )( ( ( double )( count + 1 ) / expectedCount ) * 50.0 ); + + do + { + common->Printf( "*" ); + } + while( ++tics < ticsNeeded ); + + nextTicCount = ( size_t )( ( tics / 50.0 ) * expectedCount ); + if( count == ( expectedCount - 1 ) ) + { + if( tics < 51 ) + { + common->Printf( "*" ); + } + common->Printf( "\n" ); + } + + common->UpdateScreen( false ); + } + + count++; + } + + void Reset() + { + count = 0; + tics = 0; + nextTicCount = 0; + } + + void Reset( int expected ) + { + expectedCount = expected; + count = 0; + tics = 0; + nextTicCount = 0; + } +}; + /* ==================================================================== @@ -1501,6 +1618,9 @@ void R_InitDrawSurfFromTri( drawSurf_t& ds, srfTriangles_t& tri ); // time, rather than being re-created each frame in the frame temporary buffers. void R_CreateStaticBuffersForTri( srfTriangles_t& tri ); +// RB +idVec3 R_ClosestPointPointTriangle( const idVec3& point, const idVec3& vertex1, const idVec3& vertex2, const idVec3& vertex3 ); + // deformable meshes precalculate as much as possible from a base frame, then generate // complete srfTriangles_t from just a new set of vertexes struct deformInfo_t @@ -1559,7 +1679,6 @@ struct localTrace_t localTrace_t R_LocalTrace( const idVec3& start, const idVec3& end, const float radius, const srfTriangles_t* tri ); - /* ============================================================ @@ -1590,6 +1709,9 @@ void RB_DrawBounds( const idBounds& bounds ); void RB_ShutdownDebugTools(); void RB_SetVertexColorParms( stageVertexColor_t svc ); + + + //============================================= #include "ResolutionScale.h" diff --git a/neo/renderer/RenderProgs.cpp b/neo/renderer/RenderProgs.cpp index d3aadac4..329df96f 100644 --- a/neo/renderer/RenderProgs.cpp +++ b/neo/renderer/RenderProgs.cpp @@ -109,10 +109,17 @@ void idRenderProgManager::Init() { BUILTIN_VERTEX_COLOR, "builtin/vertex_color", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_AMBIENT_LIGHTING, "builtin/lighting/ambient_lighting", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_AMBIENT_LIGHTING_SKINNED, "builtin/lighting/ambient_lighting", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_AMBIENT_LIGHTING_IBL, "builtin/lighting/ambient_lighting_IBL", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_AMBIENT_LIGHTING_IBL_SKINNED, "builtin/lighting/ambient_lighting_IBL", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_AMBIENT_LIGHTING_IBL_PBR, "builtin/lighting/ambient_lighting_IBL", "_PBR", BIT( USE_PBR ), false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_AMBIENT_LIGHTING_IBL_PBR_SKINNED, "builtin/lighting/ambient_lighting_IBL", "_PBR_skinned", BIT( USE_GPU_SKINNING | USE_PBR ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + + { BUILTIN_AMBIENT_LIGHTGRID_IBL, "builtin/lighting/ambient_lightgrid_IBL", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_AMBIENT_LIGHTGRID_IBL_SKINNED, "builtin/lighting/ambient_lightgrid_IBL", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR, "builtin/lighting/ambient_lightgrid_IBL", "_PBR", BIT( USE_PBR ), false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR_SKINNED, "builtin/lighting/ambient_lightgrid_IBL", "_PBR_skinned", BIT( USE_GPU_SKINNING | USE_PBR ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_SMALL_GEOMETRY_BUFFER, "builtin/gbuffer", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_SMALL_GEOMETRY_BUFFER_SKINNED, "builtin/gbuffer", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, // RB end @@ -154,8 +161,12 @@ void idRenderProgManager::Init() { BUILTIN_PBR_INTERACTION_SHADOW_MAPPING_PARALLEL, "builtin/lighting/interactionSM", "_parallel_PBR", BIT( LIGHT_PARALLEL ) | BIT( USE_PBR ), false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_PBR_INTERACTION_SHADOW_MAPPING_PARALLEL_SKINNED, "builtin/lighting/interactionSM", "_parallel_skinned_PBR", BIT( USE_GPU_SKINNING ) | BIT( LIGHT_PARALLEL ) | BIT( USE_PBR ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, - { BUILTIN_OCTAHEDRON, "builtin/debug/octahedron", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, - { BUILTIN_OCTAHEDRON_SKINNED, "builtin/debug/octahedron", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + // debug stuff + { BUILTIN_DEBUG_LIGHTGRID, "builtin/debug/lightgrid", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_DEBUG_LIGHTGRID_SKINNED, "builtin/debug/lightgrid", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + + { BUILTIN_DEBUG_OCTAHEDRON, "builtin/debug/octahedron", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + { BUILTIN_DEBUG_OCTAHEDRON_SKINNED, "builtin/debug/octahedron", "_skinned", BIT( USE_GPU_SKINNING ), true, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, // RB end { BUILTIN_ENVIRONMENT, "builtin/legacy/environment", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, @@ -272,10 +283,16 @@ void idRenderProgManager::Init() renderProgs[builtinShaders[BUILTIN_SHADOW_DEBUG_SKINNED]].usesJoints = true; renderProgs[builtinShaders[BUILTIN_FOG_SKINNED]].usesJoints = true; // RB begin - renderProgs[builtinShaders[BUILTIN_OCTAHEDRON_SKINNED]].usesJoints = true; + renderProgs[builtinShaders[BUILTIN_DEBUG_LIGHTGRID_SKINNED]].usesJoints = true; + renderProgs[builtinShaders[BUILTIN_DEBUG_OCTAHEDRON_SKINNED]].usesJoints = true; renderProgs[builtinShaders[BUILTIN_AMBIENT_LIGHTING_SKINNED]].usesJoints = true; + renderProgs[builtinShaders[BUILTIN_AMBIENT_LIGHTING_IBL_SKINNED]].usesJoints = true; renderProgs[builtinShaders[BUILTIN_AMBIENT_LIGHTING_IBL_PBR_SKINNED]].usesJoints = true; + + renderProgs[builtinShaders[BUILTIN_AMBIENT_LIGHTGRID_IBL_SKINNED]].usesJoints = true; + renderProgs[builtinShaders[BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR_SKINNED]].usesJoints = true; + renderProgs[builtinShaders[BUILTIN_SMALL_GEOMETRY_BUFFER_SKINNED]].usesJoints = true; renderProgs[builtinShaders[BUILTIN_INTERACTION_SHADOW_MAPPING_SPOT_SKINNED]].usesJoints = true; renderProgs[builtinShaders[BUILTIN_INTERACTION_SHADOW_MAPPING_POINT_SKINNED]].usesJoints = true; diff --git a/neo/renderer/RenderProgs.h b/neo/renderer/RenderProgs.h index e2f1076e..3709606e 100644 --- a/neo/renderer/RenderProgs.h +++ b/neo/renderer/RenderProgs.h @@ -147,7 +147,6 @@ enum renderParm_t // RB begin RENDERPARM_AMBIENT_COLOR, -// RENDERPARM_GLOBALVIEWORIGIN, RENDERPARM_GLOBALLIGHTORIGIN, RENDERPARM_JITTERTEXSCALE, RENDERPARM_JITTERTEXOFFSET, @@ -311,6 +310,28 @@ public: BindShader_Builtin( BUILTIN_AMBIENT_LIGHTING_IBL_PBR_SKINNED ); } + + void BindShader_ImageBasedLightGrid() + { + BindShader_Builtin( BUILTIN_AMBIENT_LIGHTGRID_IBL ); + } + + void BindShader_ImageBasedLightGridSkinned() + { + BindShader_Builtin( BUILTIN_AMBIENT_LIGHTGRID_IBL_SKINNED ); + } + + void BindShader_ImageBasedLightGrid_PBR() + { + BindShader_Builtin( BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR ); + } + + void BindShader_ImageBasedLightGridSkinned_PBR() + { + BindShader_Builtin( BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR_SKINNED ); + } + + void BindShader_SmallGeometryBuffer() { BindShader_Builtin( BUILTIN_SMALL_GEOMETRY_BUFFER ); @@ -451,14 +472,24 @@ public: BindShader_Builtin( BUILTIN_PBR_INTERACTION_SHADOW_MAPPING_PARALLEL_SKINNED ); } - void BindShader_Octahedron() + void BindShader_DebugLightGrid() { - BindShader_Builtin( BUILTIN_OCTAHEDRON ); + BindShader_Builtin( BUILTIN_DEBUG_LIGHTGRID ); } - void BindShader_OctahedronSkinned() + void BindShader_DebugLightGridSkinned() { - BindShader_Builtin( BUILTIN_OCTAHEDRON_SKINNED ); + BindShader_Builtin( BUILTIN_DEBUG_LIGHTGRID_SKINNED ); + } + + void BindShader_DebugOctahedron() + { + BindShader_Builtin( BUILTIN_DEBUG_OCTAHEDRON ); + } + + void BindShader_DebugOctahedronSkinned() + { + BindShader_Builtin( BUILTIN_DEBUG_OCTAHEDRON_SKINNED ); } // RB end @@ -731,10 +762,17 @@ private: BUILTIN_VERTEX_COLOR, BUILTIN_AMBIENT_LIGHTING, BUILTIN_AMBIENT_LIGHTING_SKINNED, + BUILTIN_AMBIENT_LIGHTING_IBL, BUILTIN_AMBIENT_LIGHTING_IBL_SKINNED, BUILTIN_AMBIENT_LIGHTING_IBL_PBR, BUILTIN_AMBIENT_LIGHTING_IBL_PBR_SKINNED, + + BUILTIN_AMBIENT_LIGHTGRID_IBL, + BUILTIN_AMBIENT_LIGHTGRID_IBL_SKINNED, + BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR, + BUILTIN_AMBIENT_LIGHTGRID_IBL_PBR_SKINNED, + BUILTIN_SMALL_GEOMETRY_BUFFER, BUILTIN_SMALL_GEOMETRY_BUFFER_SKINNED, // RB end @@ -767,8 +805,11 @@ private: BUILTIN_PBR_INTERACTION_SHADOW_MAPPING_PARALLEL, BUILTIN_PBR_INTERACTION_SHADOW_MAPPING_PARALLEL_SKINNED, - BUILTIN_OCTAHEDRON, - BUILTIN_OCTAHEDRON_SKINNED, + BUILTIN_DEBUG_LIGHTGRID, + BUILTIN_DEBUG_LIGHTGRID_SKINNED, + + BUILTIN_DEBUG_OCTAHEDRON, + BUILTIN_DEBUG_OCTAHEDRON_SKINNED, // RB end BUILTIN_ENVIRONMENT, BUILTIN_ENVIRONMENT_SKINNED, diff --git a/neo/renderer/RenderProgs_embedded.h b/neo/renderer/RenderProgs_embedded.h index bdd1c269..0d517972 100644 --- a/neo/renderer/RenderProgs_embedded.h +++ b/neo/renderer/RenderProgs_embedded.h @@ -2627,9 +2627,21 @@ static const cgShaderDef_t cg_renderprogs[] = " float2 normalizedOctCoord = octEncode( reflectionVector );\n" " float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" "\n" - " float4 envMap = tex2D( samp0, normalizedOctCoordZeroOne );\n" + " // offset by one pixel border bleed size for linear filtering\n" + "#if 0\n" + " float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne * ( rpCascadeDistances.x - float( 2.0 ) ) ) / rpCascadeDistances.xy;\n" "\n" - " result.color = float4( sRGBToLinearRGB( envMap.xyz ), 1.0f ) * fragment.color;\n" + " float2 probeTopLeftPosition = float2( 1.0, 1.0 );\n" + " float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw;\n" + "\n" + " normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions;\n" + "#endif\n" + "\n" + " //normalizedOctCoordZeroOne = TextureCoordFromDirection( reflectionVector, 0, int( rpCascadeDistances.x ), int( rpCascadeDistances.y ), int( rpCascadeDistances.x ) - 2 );\n" + "\n" + " float4 envMap = texture( samp0, normalizedOctCoordZeroOne, 0 );\n" + "\n" + " result.color = float4( envMap.xyz, 1.0f ) * fragment.color * 1.0;\n" "}\n" "\n" @@ -2761,6 +2773,273 @@ static const cgShaderDef_t cg_renderprogs[] = }, + { + "renderprogs/builtin/debug/lightgrid.ps.hlsl", + "/*\n" + "===========================================================================\n" + "\n" + "Doom 3 BFG Edition GPL Source Code\n" + "Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.\n" + "Copyright (C) 2021 Robert Beckebans\n" + "\n" + "This file is part of the Doom 3 BFG Edition GPL Source Code (\"Doom 3 BFG Edition Source Code\").\n" + "\n" + "Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with Doom 3 BFG Edition Source Code. If not, see .\n" + "\n" + "In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.\n" + "\n" + "If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.\n" + "\n" + "===========================================================================\n" + "*/\n" + "\n" + "#include \"renderprogs/global.inc.hlsl\"\n" + "\n" + "\n" + "// *INDENT-OFF*\n" + "uniform sampler2D samp0 : register(s0); // texture 0 is octahedron cube map atlas\n" + "\n" + "struct PS_IN {\n" + " float4 position : VPOS;\n" + " float3 texcoord0 : TEXCOORD0_centroid;\n" + " float3 texcoord1 : TEXCOORD1_centroid;\n" + " float4 color : COLOR0;\n" + "};\n" + "\n" + "struct PS_OUT {\n" + " float4 color : COLOR;\n" + "};\n" + "// *INDENT-ON*\n" + "\n" + "\n" + "int3 GetBaseGridCoord( float3 origin )\n" + "{\n" + " float3 lightGridOrigin = rpGlobalLightOrigin.xyz;\n" + " float3 lightGridSize = rpJitterTexScale.xyz;\n" + " int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z );\n" + "\n" + " int3 pos;\n" + "\n" + " float3 lightOrigin = origin - lightGridOrigin;\n" + " for( int i = 0; i < 3; i++ )\n" + " {\n" + " float v;\n" + "\n" + " v = lightOrigin[i] * ( 1.0f / lightGridSize[i] );\n" + " pos[i] = int( floor( v ) );\n" + "\n" + " if( pos[i] < 0 )\n" + " {\n" + " pos[i] = 0;\n" + " }\n" + " else if( pos[i] >= lightGridBounds[i] - 1 )\n" + " {\n" + " pos[i] = lightGridBounds[i] - 1;\n" + " }\n" + " }\n" + "\n" + " return pos;\n" + "}\n" + "\n" + "void main( PS_IN fragment, out PS_OUT result )\n" + "{\n" + " const int LIGHTGRID_IRRADIANCE_SIZE = 32;\n" + "\n" + " float3 globalPosition = fragment.texcoord0.xyz;\n" + " float3 globalNormal = normalize( fragment.texcoord1 );\n" + "\n" + " float2 normalizedOctCoord = octEncode( globalNormal );\n" + " float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + "\n" + " float3 lightGridOrigin = rpGlobalLightOrigin.xyz;\n" + " float3 lightGridSize = rpJitterTexScale.xyz;\n" + " int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z );\n" + "\n" + " float invXZ = ( 1.0 / ( lightGridBounds[0] * lightGridBounds[2] ) );\n" + " float invY = ( 1.0 / lightGridBounds[1] );\n" + "\n" + " normalizedOctCoordZeroOne.x *= invXZ;\n" + " normalizedOctCoordZeroOne.y *= invY;\n" + "\n" + " int3 gridStep;\n" + "\n" + " gridStep[0] = 1;\n" + " gridStep[1] = lightGridBounds[0];\n" + " gridStep[2] = lightGridBounds[0] * lightGridBounds[1];\n" + "\n" + " int3 gridCoord = GetBaseGridCoord( globalPosition );\n" + "\n" + " normalizedOctCoordZeroOne.x += ( gridCoord[0] * gridStep[0] + gridCoord[2] * gridStep[1] ) * invXZ;\n" + " normalizedOctCoordZeroOne.y += ( gridCoord[1] * invY );\n" + "\n" + " // offset by one pixel border bleed size for linear filtering\n" + "#if 1\n" + " // rpScreenCorrectionFactor.x = probeSize - borderSize, e.g. ( 18 - 2 ) = 16\n" + " // rpScreenCorrectionFactor.y = probeSize including border, e.g = 18\n" + " // rpScreenCorrectionFactor.z = borderSize e.g = 2\n" + " // rpScreenCorrectionFactor.w = probeSize factor accounting account offset border, e.g = ( 16 / 18 ) = 0.8888\n" + " float2 octCoordNormalizedToTextureDimensions = normalizedOctCoordZeroOne * rpScreenCorrectionFactor.w;\n" + "\n" + " float2 probeTopLeftPosition;\n" + " probeTopLeftPosition.x = ( gridCoord[0] * gridStep[0] + gridCoord[2] * gridStep[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5;\n" + " probeTopLeftPosition.y = ( gridCoord[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5;\n" + "\n" + " float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw;\n" + "\n" + " normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions;\n" + "#endif\n" + "\n" + " float4 envMap = texture( samp0, normalizedOctCoordZeroOne, 0 );\n" + "\n" + " result.color = float4( envMap.xyz, 1.0f ) * 1.0 * fragment.color;\n" + "}\n" + "\n" + + }, + + { + "renderprogs/builtin/debug/lightgrid.vs.hlsl", + "/*\n" + "===========================================================================\n" + "\n" + "Doom 3 BFG Edition GPL Source Code\n" + "Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.\n" + "Copyright (C) 2021 Robert Beckebans\n" + "\n" + "This file is part of the Doom 3 BFG Edition GPL Source Code (\"Doom 3 BFG Edition Source Code\").\n" + "\n" + "Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with Doom 3 BFG Edition Source Code. If not, see .\n" + "\n" + "In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.\n" + "\n" + "If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.\n" + "\n" + "===========================================================================\n" + "*/\n" + "\n" + "#include \"renderprogs/global.inc.hlsl\"\n" + "\n" + "\n" + "uniform matrices_ubo { float4 matrices[408]; };\n" + "\n" + "// *INDENT-OFF*\n" + "struct VS_IN {\n" + " float4 position : POSITION;\n" + " float2 texcoord : TEXCOORD0;\n" + " float4 normal : NORMAL;\n" + " float4 tangent : TANGENT;\n" + " float4 color : COLOR0;\n" + " float4 color2 : COLOR1;\n" + "};\n" + "\n" + "struct VS_OUT {\n" + " float4 position : POSITION;\n" + " float3 texcoord0 : TEXCOORD0;\n" + " float3 texcoord1 : TEXCOORD1;\n" + " float4 color : COLOR0;\n" + "};\n" + "// *INDENT-ON*\n" + "\n" + "void main( VS_IN vertex, out VS_OUT result )\n" + "{\n" + " float4 vNormal = vertex.normal * 2.0 - 1.0;\n" + "\n" + "#if defined( USE_GPU_SKINNING )\n" + "\n" + " //--------------------------------------------------------------\n" + " // GPU transformation of the normal / binormal / bitangent\n" + " //\n" + " // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 )\n" + " //--------------------------------------------------------------\n" + " const float w0 = vertex.color2.x;\n" + " const float w1 = vertex.color2.y;\n" + " const float w2 = vertex.color2.z;\n" + " const float w3 = vertex.color2.w;\n" + "\n" + " float4 matX, matY, matZ; // must be float4 for vec4\n" + " int joint = int( vertex.color.x * 255.1 * 3.0 );\n" + " matX = matrices[int( joint + 0 )] * w0;\n" + " matY = matrices[int( joint + 1 )] * w0;\n" + " matZ = matrices[int( joint + 2 )] * w0;\n" + "\n" + " joint = int( vertex.color.y * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w1;\n" + " matY += matrices[int( joint + 1 )] * w1;\n" + " matZ += matrices[int( joint + 2 )] * w1;\n" + "\n" + " joint = int( vertex.color.z * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w2;\n" + " matY += matrices[int( joint + 1 )] * w2;\n" + " matZ += matrices[int( joint + 2 )] * w2;\n" + "\n" + " joint = int( vertex.color.w * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w3;\n" + " matY += matrices[int( joint + 1 )] * w3;\n" + " matZ += matrices[int( joint + 2 )] * w3;\n" + "\n" + " float3 normal;\n" + " normal.x = dot3( matX, vNormal );\n" + " normal.y = dot3( matY, vNormal );\n" + " normal.z = dot3( matZ, vNormal );\n" + " normal = normalize( normal );\n" + "\n" + " float4 modelPosition;\n" + " modelPosition.x = dot4( matX, vertex.position );\n" + " modelPosition.y = dot4( matY, vertex.position );\n" + " modelPosition.z = dot4( matZ, vertex.position );\n" + " modelPosition.w = 1.0;\n" + "\n" + "#else\n" + "\n" + " float4 modelPosition = vertex.position;\n" + " float4 normal = vNormal;\n" + "\n" + "#endif\n" + "\n" + " result.position.x = dot4( modelPosition, rpMVPmatrixX );\n" + " result.position.y = dot4( modelPosition, rpMVPmatrixY );\n" + " result.position.z = dot4( modelPosition, rpMVPmatrixZ );\n" + " result.position.w = dot4( modelPosition, rpMVPmatrixW );\n" + "\n" + " result.texcoord1.x = dot3( normal, rpModelMatrixX );\n" + " result.texcoord1.y = dot3( normal, rpModelMatrixY );\n" + " result.texcoord1.z = dot3( normal, rpModelMatrixZ );\n" + "\n" + " float4 worldPosition;\n" + " worldPosition.x = dot4( modelPosition, rpModelMatrixX );\n" + " worldPosition.y = dot4( modelPosition, rpModelMatrixY );\n" + " worldPosition.z = dot4( modelPosition, rpModelMatrixZ );\n" + " worldPosition.w = dot4( modelPosition, rpModelMatrixW );\n" + " result.texcoord0 = worldPosition.xyz;\n" + "\n" + " result.color = sRGBAToLinearRGBA( rpColor );\n" + "}\n" + "\n" + + }, + { "renderprogs/builtin/fog/blendLight.ps.hlsl", "/*\n" @@ -4528,7 +4807,9 @@ static const cgShaderDef_t cg_renderprogs[] = "uniform sampler2D samp4 : register(s4); // texture 4 is SSAO\n" "\n" "uniform sampler2D samp7 : register(s7); // texture 7 is the irradiance cube map\n" - "uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map\n" + "uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map 1\n" + "uniform sampler2D samp9 : register(s9); // texture 9 is the radiance cube map 2\n" + "uniform sampler2D samp10 : register(s10); // texture 10 is the radiance cube map 3\n" "\n" "struct PS_IN \n" "{\n" @@ -4550,6 +4831,7 @@ static const cgShaderDef_t cg_renderprogs[] = "};\n" "// *INDENT-ON*\n" "\n" + "\n" "// RB: TODO OPTIMIZE\n" "// this is a straight port of idBounds::RayIntersection\n" "bool AABBRayIntersection( float3 b[2], float3 start, float3 dir, out float scale )\n" @@ -4606,6 +4888,26 @@ static const cgShaderDef_t cg_renderprogs[] = " hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] );\n" "}\n" "\n" + "\n" + "float2 OctTexCoord( float3 worldDir )\n" + "{\n" + " float2 normalizedOctCoord = octEncode( worldDir );\n" + " float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + "\n" + " // offset by one pixel border bleed size for linear filtering\n" + "#if 0\n" + " // texcoord sizes in rpCascadeDistances are not valid\n" + " float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne * ( rpCascadeDistances.x - float( 2.0 ) ) ) / rpCascadeDistances.xy;\n" + "\n" + " float2 probeTopLeftPosition = float2( 1.0, 1.0 );\n" + " float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw;\n" + "\n" + " normalizedOctCoordZeroOne.xy = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions;\n" + "#endif\n" + "\n" + " return normalizedOctCoordZeroOne;\n" + "}\n" + "\n" "void main( PS_IN fragment, out PS_OUT result )\n" "{\n" " half4 bumpMap = tex2D( samp0, fragment.texcoord0.xy );\n" @@ -4632,15 +4934,14 @@ static const cgShaderDef_t cg_renderprogs[] = "\n" " float3 globalPosition = fragment.texcoord7.xyz;\n" "\n" - " // RB: rpGlobalLightOrigin is global view origin\n" - " float3 globalEye = normalize( rpGlobalLightOrigin.xyz - globalPosition );\n" + " float3 globalView = normalize( rpGlobalEyePos.xyz - globalPosition );\n" "\n" - " float3 reflectionVector = globalNormal * dot3( globalEye, globalNormal );\n" - " reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalEye );\n" + " float3 reflectionVector = globalNormal * dot3( globalView, globalNormal );\n" + " reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalView );\n" "\n" - "#if 1\n" + "#if 0\n" " // parallax box correction using portal area bounds\n" - " float hitScale;\n" + " float hitScale = 0.0;\n" " float3 bounds[2];\n" " bounds[0].x = rpWobbleSkyX.x;\n" " bounds[0].y = rpWobbleSkyX.y;\n" @@ -4666,7 +4967,7 @@ static const cgShaderDef_t cg_renderprogs[] = " }\n" "#endif\n" "\n" - " half vDotN = saturate( dot3( globalEye, globalNormal ) );\n" + " half vDotN = saturate( dot3( globalView, globalNormal ) );\n" "\n" "#if defined( USE_PBR )\n" " const half metallic = specMapSRGB.g;\n" @@ -4724,8 +5025,7 @@ static const cgShaderDef_t cg_renderprogs[] = "\n" " // evaluate diffuse IBL\n" "\n" - " float2 normalizedOctCoord = octEncode( globalNormal );\n" - " float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + " float2 normalizedOctCoordZeroOne = OctTexCoord( globalNormal );\n" "\n" " float3 irradiance = tex2D( samp7, normalizedOctCoordZeroOne ).rgb;\n" " float3 diffuseLight = ( kD * irradiance * diffuseColor ) * ao * ( rpDiffuseModifier.xyz * 1.0 );\n" @@ -4737,10 +5037,11 @@ static const cgShaderDef_t cg_renderprogs[] = " float mip = clamp( ( roughness * MAX_REFLECTION_LOD ), 0.0, MAX_REFLECTION_LOD );\n" " //float mip = 0.0;\n" "\n" - " normalizedOctCoord = octEncode( reflectionVector );\n" - " normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + " normalizedOctCoordZeroOne = OctTexCoord( reflectionVector );\n" "\n" - " float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb;\n" + " float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.x;\n" + " radiance += textureLod( samp9, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.y;\n" + " radiance += textureLod( samp10, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.z;\n" " //radiance = float3( 0.0 );\n" "\n" " float2 envBRDF = texture( samp3, float2( max( vDotN, 0.0 ), roughness ) ).rg;\n" @@ -4754,7 +5055,7 @@ static const cgShaderDef_t cg_renderprogs[] = " float specAO = ComputeSpecularAO( vDotN, ao, roughness );\n" " float3 specularLight = radiance * ( kS * envBRDF.x + float3( envBRDF.y ) ) * specAO * ( rpSpecularModifier.xyz * 0.5 );\n" "\n" - "#if 0\n" + "#if 1\n" " // Marmoset Horizon Fade trick\n" " const half horizonFade = 1.3;\n" " half horiz = saturate( 1.0 + horizonFade * saturate( dot3( reflectionVector, globalNormal ) ) );\n" @@ -4763,11 +5064,12 @@ static const cgShaderDef_t cg_renderprogs[] = "#endif\n" "\n" " half3 lightColor = sRGBToLinearRGB( rpAmbientColor.rgb );\n" + " //half3 lightColor = ( rpAmbientColor.rgb );\n" "\n" " //result.color.rgb = diffuseLight;\n" " //result.color.rgb = diffuseLight * lightColor;\n" " //result.color.rgb = specularLight;\n" - " result.color.rgb = ( diffuseLight + specularLight ) * lightColor * fragment.color.rgb;\n" + " result.color.rgb = ( diffuseLight + specularLight * horiz ) * lightColor * fragment.color.rgb;\n" " //result.color.rgb = localNormal.xyz * 0.5 + 0.5;\n" " //result.color.rgb = float3( ao );\n" " result.color.w = fragment.color.a;\n" @@ -4981,6 +5283,648 @@ static const cgShaderDef_t cg_renderprogs[] = }, + { + "renderprogs/builtin/lighting/ambient_lightgrid_IBL.ps.hlsl", + "/*\n" + "===========================================================================\n" + "\n" + "Doom 3 BFG Edition GPL Source Code\n" + "Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.\n" + "Copyright (C) 2013-2021 Robert Beckebans\n" + "\n" + "This file is part of the Doom 3 BFG Edition GPL Source Code (\"Doom 3 BFG Edition Source Code\").\n" + "\n" + "Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with Doom 3 BFG Edition Source Code. If not, see .\n" + "\n" + "In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.\n" + "\n" + "If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.\n" + "\n" + "===========================================================================\n" + "*/\n" + "\n" + "#include \"renderprogs/global.inc.hlsl\"\n" + "\n" + "#include \"renderprogs/BRDF.inc.hlsl\"\n" + "\n" + "\n" + "// *INDENT-OFF*\n" + "uniform sampler2D samp0 : register(s0); // texture 0 is the per-surface normal map\n" + "uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface specular or roughness/metallic/AO mixer map\n" + "uniform sampler2D samp2 : register(s2); // texture 2 is the per-surface baseColor map \n" + "uniform sampler2D samp3 : register(s3); // texture 3 is the BRDF LUT\n" + "uniform sampler2D samp4 : register(s4); // texture 4 is SSAO\n" + "\n" + "uniform sampler2D samp7 : register(s7); // texture 7 is the irradiance cube map\n" + "uniform sampler2D samp8 : register(s8); // texture 8 is the radiance cube map 1\n" + "uniform sampler2D samp9 : register(s9); // texture 9 is the radiance cube map 2\n" + "uniform sampler2D samp10 : register(s10); // texture 10 is the radiance cube map 3\n" + "\n" + "struct PS_IN \n" + "{\n" + " half4 position : VPOS;\n" + " half4 texcoord0 : TEXCOORD0_centroid;\n" + " half4 texcoord1 : TEXCOORD1_centroid;\n" + " half4 texcoord2 : TEXCOORD2_centroid;\n" + " half4 texcoord3 : TEXCOORD3_centroid;\n" + " half4 texcoord4 : TEXCOORD4_centroid;\n" + " half4 texcoord5 : TEXCOORD5_centroid;\n" + " half4 texcoord6 : TEXCOORD6_centroid;\n" + " half4 texcoord7 : TEXCOORD7_centroid;\n" + " half4 color : COLOR0;\n" + "};\n" + "\n" + "struct PS_OUT\n" + "{\n" + " half4 color : COLOR;\n" + "};\n" + "// *INDENT-ON*\n" + "\n" + "\n" + "// RB: TODO OPTIMIZE\n" + "// this is a straight port of idBounds::RayIntersection\n" + "bool AABBRayIntersection( float3 b[2], float3 start, float3 dir, out float scale )\n" + "{\n" + " int i, ax0, ax1, ax2, side, inside;\n" + " float f;\n" + " float3 hit;\n" + "\n" + " ax0 = -1;\n" + " inside = 0;\n" + " for( i = 0; i < 3; i++ )\n" + " {\n" + " if( start[i] < b[0][i] )\n" + " {\n" + " side = 0;\n" + " }\n" + " else if( start[i] > b[1][i] )\n" + " {\n" + " side = 1;\n" + " }\n" + " else\n" + " {\n" + " inside++;\n" + " continue;\n" + " }\n" + " if( dir[i] == 0.0f )\n" + " {\n" + " continue;\n" + " }\n" + "\n" + " f = ( start[i] - b[side][i] );\n" + "\n" + " if( ax0 < 0 || abs( f ) > abs( scale * dir[i] ) )\n" + " {\n" + " scale = - ( f / dir[i] );\n" + " ax0 = i;\n" + " }\n" + " }\n" + "\n" + " if( ax0 < 0 )\n" + " {\n" + " scale = 0.0f;\n" + "\n" + " // return true if the start point is inside the bounds\n" + " return ( inside == 3 );\n" + " }\n" + "\n" + " ax1 = ( ax0 + 1 ) % 3;\n" + " ax2 = ( ax0 + 2 ) % 3;\n" + " hit[ax1] = start[ax1] + scale * dir[ax1];\n" + " hit[ax2] = start[ax2] + scale * dir[ax2];\n" + "\n" + " return ( hit[ax1] >= b[0][ax1] && hit[ax1] <= b[1][ax1] &&\n" + " hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] );\n" + "}\n" + "\n" + "void main( PS_IN fragment, out PS_OUT result )\n" + "{\n" + " half4 bumpMap = tex2D( samp0, fragment.texcoord0.xy );\n" + " half4 YCoCG = tex2D( samp2, fragment.texcoord1.xy );\n" + " half4 specMapSRGB = tex2D( samp1, fragment.texcoord2.xy );\n" + " half4 specMap = sRGBAToLinearRGBA( specMapSRGB );\n" + "\n" + " half3 diffuseMap = sRGBToLinearRGB( ConvertYCoCgToRGB( YCoCG ) );\n" + "\n" + " half3 localNormal;\n" + "#if defined(USE_NORMAL_FMT_RGB8)\n" + " localNormal.xy = bumpMap.rg - 0.5;\n" + "#else\n" + " localNormal.xy = bumpMap.wy - 0.5;\n" + "#endif\n" + " localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) );\n" + " localNormal = normalize( localNormal );\n" + "\n" + " float3 globalNormal;\n" + " globalNormal.x = dot3( localNormal, fragment.texcoord4 );\n" + " globalNormal.y = dot3( localNormal, fragment.texcoord5 );\n" + " globalNormal.z = dot3( localNormal, fragment.texcoord6 );\n" + " globalNormal = normalize( globalNormal );\n" + "\n" + " float3 globalPosition = fragment.texcoord7.xyz;\n" + "\n" + " float3 globalView = normalize( rpGlobalEyePos.xyz - globalPosition );\n" + "\n" + " float3 reflectionVector = globalNormal * dot3( globalView, globalNormal );\n" + " reflectionVector = normalize( ( reflectionVector * 2.0f ) - globalView );\n" + "\n" + "#if 0\n" + " // parallax box correction using portal area bounds\n" + " float hitScale = 0.0;\n" + " float3 bounds[2];\n" + " bounds[0].x = rpWobbleSkyX.x;\n" + " bounds[0].y = rpWobbleSkyX.y;\n" + " bounds[0].z = rpWobbleSkyX.z;\n" + "\n" + " bounds[1].x = rpWobbleSkyY.x;\n" + " bounds[1].y = rpWobbleSkyY.y;\n" + " bounds[1].z = rpWobbleSkyY.z;\n" + "\n" + " // global fragment position\n" + " float3 rayStart = fragment.texcoord7.xyz;\n" + "\n" + " // we can't start inside the box so move this outside and use the reverse path\n" + " rayStart += reflectionVector * 10000.0;\n" + "\n" + " // only do a box <-> ray intersection test if we use a local cubemap\n" + " if( ( rpWobbleSkyX.w > 0.0 ) && AABBRayIntersection( bounds, rayStart, -reflectionVector, hitScale ) )\n" + " {\n" + " float3 hitPoint = rayStart - reflectionVector * hitScale;\n" + "\n" + " // rpWobbleSkyZ is cubemap center\n" + " reflectionVector = hitPoint - rpWobbleSkyZ.xyz;\n" + " }\n" + "#endif\n" + "\n" + " half vDotN = saturate( dot3( globalView, globalNormal ) );\n" + "\n" + "#if defined( USE_PBR )\n" + " const half metallic = specMapSRGB.g;\n" + " const half roughness = specMapSRGB.r;\n" + " const half glossiness = 1.0 - roughness;\n" + "\n" + " // the vast majority of real-world materials (anything not metal or gems) have F(0°)\n" + " // values in a very narrow range (~0.02 - 0.08)\n" + "\n" + " // approximate non-metals with linear RGB 0.04 which is 0.08 * 0.5 (default in UE4)\n" + " const half3 dielectricColor = half3( 0.04 );\n" + "\n" + " // derive diffuse and specular from albedo(m) base color\n" + " const half3 baseColor = diffuseMap;\n" + "\n" + " half3 diffuseColor = baseColor * ( 1.0 - metallic );\n" + " half3 specularColor = lerp( dielectricColor, baseColor, metallic );\n" + "\n" + "#if defined( DEBUG_PBR )\n" + " diffuseColor = half3( 0.0, 0.0, 0.0 );\n" + " specularColor = half3( 0.0, 1.0, 0.0 );\n" + "#endif\n" + "\n" + " float3 kS = Fresnel_SchlickRoughness( specularColor, vDotN, roughness );\n" + " float3 kD = ( float3( 1.0, 1.0, 1.0 ) - kS ) * ( 1.0 - metallic );\n" + "\n" + "#else\n" + " const float roughness = EstimateLegacyRoughness( specMapSRGB.rgb );\n" + "\n" + " half3 diffuseColor = diffuseMap;\n" + " half3 specularColor = specMap.rgb;\n" + "\n" + "#if defined( DEBUG_PBR )\n" + " diffuseColor = half3( 0.0, 0.0, 0.0 );\n" + " specularColor = half3( 1.0, 0.0, 0.0 );\n" + "#endif\n" + "\n" + " float3 kS = Fresnel_SchlickRoughness( specularColor, vDotN, roughness );\n" + "\n" + " // NOTE: metalness is missing\n" + " float3 kD = ( float3( 1.0, 1.0, 1.0 ) - kS );\n" + "\n" + "#endif\n" + "\n" + " //diffuseColor = half3( 1.0, 1.0, 1.0 );\n" + " //diffuseColor = half3( 0.0, 0.0, 0.0 );\n" + "\n" + " // calculate the screen texcoord in the 0.0 to 1.0 range\n" + " //float2 screenTexCoord = vposToScreenPosTexCoord( fragment.position.xy );\n" + " float2 screenTexCoord = fragment.position.xy * rpWindowCoord.xy;\n" + "\n" + " float ao = 1.0;\n" + " ao = tex2D( samp4, screenTexCoord ).r;\n" + " //diffuseColor.rgb *= ao;\n" + "\n" + " // evaluate diffuse IBL\n" + "\n" + " float2 normalizedOctCoord = octEncode( globalNormal );\n" + " float2 normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + "\n" + "// lightgrid atlas\n" + "\n" + " //float3 lightGridOrigin = float3( -192.0, -128.0, 0 );\n" + " //float3 lightGridSize = float3( 64.0, 64.0, 128.0 );\n" + " //int3 lightGridBounds = int3( 7, 7, 3 );\n" + "\n" + " float3 lightGridOrigin = rpGlobalLightOrigin.xyz;\n" + " float3 lightGridSize = rpJitterTexScale.xyz;\n" + " int3 lightGridBounds = int3( rpJitterTexOffset.x, rpJitterTexOffset.y, rpJitterTexOffset.z );\n" + "\n" + " float invXZ = ( 1.0 / ( lightGridBounds[0] * lightGridBounds[2] ) );\n" + " float invY = ( 1.0 / lightGridBounds[1] );\n" + "\n" + " normalizedOctCoordZeroOne.x *= invXZ;\n" + " normalizedOctCoordZeroOne.y *= invY;\n" + "\n" + " int3 gridCoord;\n" + " float3 frac;\n" + " float3 lightOrigin = globalPosition - lightGridOrigin;\n" + "\n" + " for( int i = 0; i < 3; i++ )\n" + " {\n" + " float v;\n" + "\n" + " // walls can be sampled behind the grid sometimes so avoid negative weights\n" + " v = max( 0, lightOrigin[i] * ( 1.0 / lightGridSize[i] ) );\n" + " gridCoord[i] = int( floor( v ) );\n" + " frac[ i ] = v - gridCoord[ i ];\n" + "\n" + " /*\n" + " if( gridCoord[i] < 0 )\n" + " {\n" + " gridCoord[i] = 0;\n" + " }\n" + " else\n" + " */\n" + " if( gridCoord[i] >= lightGridBounds[i] - 1 )\n" + " {\n" + " gridCoord[i] = lightGridBounds[i] - 1;\n" + " }\n" + " }\n" + "\n" + " // trilerp the light value\n" + " int3 gridStep;\n" + "\n" + " gridStep[0] = 1;\n" + " gridStep[1] = lightGridBounds[0];\n" + " gridStep[2] = lightGridBounds[0] * lightGridBounds[1];\n" + "\n" + " float totalFactor = 0.0;\n" + " float3 irradiance = float3( 0.0, 0.0, 0.0 );\n" + "\n" + " /*\n" + " for( int i = 0; i < 8; i++ )\n" + " {\n" + " for( int j = 0; j < 3; j++ )\n" + " {\n" + " if( i & ( 1 << j ) )\n" + "\n" + " results in these offsets\n" + " */\n" + " const float3 cornerOffsets[8] = float3[](\n" + " float3( 0.0, 0.0, 0.0 ),\n" + " float3( 1.0, 0.0, 0.0 ),\n" + " float3( 0.0, 2.0, 0.0 ),\n" + " float3( 1.0, 2.0, 0.0 ),\n" + " float3( 0.0, 0.0, 4.0 ),\n" + " float3( 1.0, 0.0, 4.0 ),\n" + " float3( 0.0, 2.0, 4.0 ),\n" + " float3( 1.0, 2.0, 4.0 ) );\n" + "\n" + " for( int i = 0; i < 8; i++ )\n" + " {\n" + " float factor = 1.0;\n" + "\n" + " int3 gridCoord2 = gridCoord;\n" + "\n" + " for( int j = 0; j < 3; j++ )\n" + " {\n" + " if( cornerOffsets[ i ][ j ] > 0.0 )\n" + " {\n" + " factor *= frac[ j ];\n" + "\n" + " gridCoord2[ j ] += 1;\n" + " }\n" + " else\n" + " {\n" + " factor *= ( 1.0 - frac[ j ] );\n" + " }\n" + " }\n" + "\n" + " // build P\n" + " //float3 P = lightGridOrigin + ( gridCoord2[0] * gridStep[0] + gridCoord2[1] * gridStep[1] + gridCoord2[2] * gridStep[2] );\n" + "\n" + " float2 atlasOffset;\n" + "\n" + " atlasOffset.x = ( gridCoord2[0] * gridStep[0] + gridCoord2[2] * gridStep[1] ) * invXZ;\n" + " atlasOffset.y = ( gridCoord2[1] * invY );\n" + "\n" + " // offset by one pixel border bleed size for linear filtering\n" + "#if 1\n" + " // rpScreenCorrectionFactor.w = probeSize factor accounting account offset border, e.g = ( 16 / 18 ) = 0.8888\n" + " float2 octCoordNormalizedToTextureDimensions = ( normalizedOctCoordZeroOne + atlasOffset ) * rpScreenCorrectionFactor.w;\n" + "\n" + " // skip by default 2 pixels for each grid cell and offset the start position by (1,1)\n" + " // rpScreenCorrectionFactor.z = borderSize e.g = 2\n" + " float2 probeTopLeftPosition;\n" + " probeTopLeftPosition.x = ( gridCoord2[0] * gridStep[0] + gridCoord2[2] * gridStep[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5;\n" + " probeTopLeftPosition.y = ( gridCoord2[1] ) * rpScreenCorrectionFactor.z + rpScreenCorrectionFactor.z * 0.5;\n" + "\n" + " float2 normalizedProbeTopLeftPosition = probeTopLeftPosition * rpCascadeDistances.zw;\n" + "\n" + " float2 atlasCoord = normalizedProbeTopLeftPosition + octCoordNormalizedToTextureDimensions;\n" + "#else\n" + " float2 atlasCoord = normalizedOctCoordZeroOne + atlasOffset;\n" + "#endif\n" + "\n" + " float3 color = texture( samp7, atlasCoord, 0 ).rgb;\n" + "\n" + " if( ( color.r + color.g + color.b ) < 0.0001 )\n" + " {\n" + " // ignore samples in walls\n" + " continue;\n" + " }\n" + "\n" + " irradiance += color * factor;\n" + " totalFactor += factor;\n" + " }\n" + "\n" + " if( totalFactor > 0.0 && totalFactor < 0.9999 )\n" + " {\n" + " totalFactor = 1.0 / totalFactor;\n" + "\n" + " irradiance *= totalFactor;\n" + " }\n" + "\n" + "// lightgrid atlas\n" + "\n" + "\n" + " float3 diffuseLight = ( kD * irradiance * diffuseColor ) * ao * ( rpDiffuseModifier.xyz * 1.0 );\n" + "\n" + " // evaluate specular IBL\n" + "\n" + " // should be 8 = numMips - 1, 256^2 = 9 mips\n" + " const float MAX_REFLECTION_LOD = 10.0;\n" + " float mip = clamp( ( roughness * MAX_REFLECTION_LOD ), 0.0, MAX_REFLECTION_LOD );\n" + " //float mip = 0.0;\n" + "\n" + " normalizedOctCoord = octEncode( reflectionVector );\n" + " normalizedOctCoordZeroOne = ( normalizedOctCoord + float2( 1.0 ) ) * 0.5;\n" + "\n" + " float3 radiance = textureLod( samp8, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.x;\n" + " radiance += textureLod( samp9, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.y;\n" + " radiance += textureLod( samp10, normalizedOctCoordZeroOne, mip ).rgb * rpLocalLightOrigin.z;\n" + " //radiance = float3( 0.0 );\n" + "\n" + " // RB: HACK dim down room radiance by better local irradiance brightness\n" + " //float luma = PhotoLuma( irradiance );\n" + " //float luma = dot( irradiance, LUMINANCE_LINEAR.rgb );\n" + " //float luma = length( irradiance.rgb );\n" + " //radiance *= ( luma * rpSpecularModifier.x * 3.0 );\n" + "\n" + " float2 envBRDF = texture( samp3, float2( max( vDotN, 0.0 ), roughness ) ).rg;\n" + "\n" + "#if 0\n" + " result.color.rgb = float3( envBRDF.x, envBRDF.y, 0.0 );\n" + " result.color.w = fragment.color.a;\n" + " return;\n" + "#endif\n" + "\n" + " float specAO = ComputeSpecularAO( vDotN, ao, roughness );\n" + " float3 specularLight = radiance * ( kS * envBRDF.x + float3( envBRDF.y ) ) * specAO * ( rpSpecularModifier.xyz * 0.5 );\n" + "\n" + "#if 1\n" + " // Marmoset Horizon Fade trick\n" + " const half horizonFade = 1.3;\n" + " half horiz = saturate( 1.0 + horizonFade * saturate( dot3( reflectionVector, globalNormal ) ) );\n" + " horiz *= horiz;\n" + " //horiz = clamp( horiz, 0.0, 1.0 );\n" + "#endif\n" + "\n" + " half3 lightColor = sRGBToLinearRGB( rpAmbientColor.rgb );\n" + " //half3 lightColor = ( rpAmbientColor.rgb );\n" + "\n" + " //result.color.rgb = diffuseLight;\n" + " //result.color.rgb = diffuseLight * lightColor;\n" + " //result.color.rgb = specularLight;\n" + " result.color.rgb = ( diffuseLight + specularLight * horiz ) * lightColor * fragment.color.rgb;\n" + " //result.color.rgb = localNormal.xyz * 0.5 + 0.5;\n" + " //result.color.rgb = float3( ao );\n" + " result.color.w = fragment.color.a;\n" + "}\n" + "\n" + + }, + + { + "renderprogs/builtin/lighting/ambient_lightgrid_IBL.vs.hlsl", + "/*\n" + "===========================================================================\n" + "\n" + "Doom 3 BFG Edition GPL Source Code\n" + "Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.\n" + "Copyright (C) 2013-2015 Robert Beckebans\n" + "\n" + "This file is part of the Doom 3 BFG Edition GPL Source Code (\"Doom 3 BFG Edition Source Code\").\n" + "\n" + "Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with Doom 3 BFG Edition Source Code. If not, see .\n" + "\n" + "In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.\n" + "\n" + "If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.\n" + "\n" + "===========================================================================\n" + "*/\n" + "\n" + "#include \"renderprogs/global.inc.hlsl\"\n" + "\n" + "\n" + "#if defined( USE_GPU_SKINNING )\n" + "uniform matrices_ubo { float4 matrices[408]; };\n" + "#endif\n" + "\n" + "// *INDENT-OFF*\n" + "struct VS_IN {\n" + " float4 position : POSITION;\n" + " float2 texcoord : TEXCOORD0;\n" + " float4 normal : NORMAL;\n" + " float4 tangent : TANGENT;\n" + " float4 color : COLOR0;\n" + " float4 color2 : COLOR1;\n" + "};\n" + "\n" + "struct VS_OUT {\n" + " float4 position : POSITION;\n" + " float4 texcoord0 : TEXCOORD0;\n" + " float4 texcoord1 : TEXCOORD1;\n" + " float4 texcoord2 : TEXCOORD2;\n" + " float4 texcoord3 : TEXCOORD3;\n" + " float4 texcoord4 : TEXCOORD4;\n" + " float4 texcoord5 : TEXCOORD5;\n" + " float4 texcoord6 : TEXCOORD6;\n" + " float4 texcoord7 : TEXCOORD7;\n" + " float4 color : COLOR0;\n" + "};\n" + "// *INDENT-ON*\n" + "\n" + "void main( VS_IN vertex, out VS_OUT result )\n" + "{\n" + "\n" + " float4 vNormal = vertex.normal * 2.0 - 1.0;\n" + " float4 vTangent = vertex.tangent * 2.0 - 1.0;\n" + " float3 vBitangent = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w;\n" + "\n" + "#if defined( USE_GPU_SKINNING )\n" + " //--------------------------------------------------------------\n" + " // GPU transformation of the normal / tangent / bitangent\n" + " //\n" + " // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 )\n" + " //--------------------------------------------------------------\n" + " const float w0 = vertex.color2.x;\n" + " const float w1 = vertex.color2.y;\n" + " const float w2 = vertex.color2.z;\n" + " const float w3 = vertex.color2.w;\n" + "\n" + " float4 matX, matY, matZ; // must be float4 for vec4\n" + " int joint = int( vertex.color.x * 255.1 * 3.0 );\n" + " matX = matrices[int( joint + 0 )] * w0;\n" + " matY = matrices[int( joint + 1 )] * w0;\n" + " matZ = matrices[int( joint + 2 )] * w0;\n" + "\n" + " joint = int( vertex.color.y * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w1;\n" + " matY += matrices[int( joint + 1 )] * w1;\n" + " matZ += matrices[int( joint + 2 )] * w1;\n" + "\n" + " joint = int( vertex.color.z * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w2;\n" + " matY += matrices[int( joint + 1 )] * w2;\n" + " matZ += matrices[int( joint + 2 )] * w2;\n" + "\n" + " joint = int( vertex.color.w * 255.1 * 3.0 );\n" + " matX += matrices[int( joint + 0 )] * w3;\n" + " matY += matrices[int( joint + 1 )] * w3;\n" + " matZ += matrices[int( joint + 2 )] * w3;\n" + "\n" + " float3 normal;\n" + " normal.x = dot3( matX, vNormal );\n" + " normal.y = dot3( matY, vNormal );\n" + " normal.z = dot3( matZ, vNormal );\n" + " normal = normalize( normal );\n" + "\n" + " float3 tangent;\n" + " tangent.x = dot3( matX, vTangent );\n" + " tangent.y = dot3( matY, vTangent );\n" + " tangent.z = dot3( matZ, vTangent );\n" + " tangent = normalize( tangent );\n" + "\n" + " float3 bitangent;\n" + " bitangent.x = dot3( matX, vBitangent );\n" + " bitangent.y = dot3( matY, vBitangent );\n" + " bitangent.z = dot3( matZ, vBitangent );\n" + " bitangent = normalize( bitangent );\n" + "\n" + " float4 modelPosition;\n" + " modelPosition.x = dot4( matX, vertex.position );\n" + " modelPosition.y = dot4( matY, vertex.position );\n" + " modelPosition.z = dot4( matZ, vertex.position );\n" + " modelPosition.w = 1.0;\n" + "\n" + "#else\n" + " float4 modelPosition = vertex.position;\n" + " float3 normal = vNormal.xyz;\n" + " float3 tangent = vTangent.xyz;\n" + " float3 bitangent = vBitangent.xyz;\n" + "#endif\n" + "\n" + " result.position.x = dot4( modelPosition, rpMVPmatrixX );\n" + " result.position.y = dot4( modelPosition, rpMVPmatrixY );\n" + " result.position.z = dot4( modelPosition, rpMVPmatrixZ );\n" + " result.position.w = dot4( modelPosition, rpMVPmatrixW );\n" + "\n" + " float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f );\n" + "\n" + " //--------------------------------------------------------------\n" + "\n" + "\n" + " //# textures 0 takes the base coordinates by the texture matrix\n" + " result.texcoord0 = defaultTexCoord;\n" + " result.texcoord0.x = dot4( vertex.texcoord.xy, rpBumpMatrixS );\n" + " result.texcoord0.y = dot4( vertex.texcoord.xy, rpBumpMatrixT );\n" + "\n" + " //# textures 1 takes the base coordinates by the texture matrix\n" + " result.texcoord1 = defaultTexCoord;\n" + " result.texcoord1.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS );\n" + " result.texcoord1.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT );\n" + "\n" + " //# textures 2 takes the base coordinates by the texture matrix\n" + " result.texcoord2 = defaultTexCoord;\n" + " result.texcoord2.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS );\n" + " result.texcoord2.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT );\n" + "\n" + " //# calculate normalized vector to viewer in R1\n" + " //result.texcoord3 = modelPosition;\n" + "\n" + " float4 toEye = normalize( rpLocalViewOrigin - modelPosition );\n" + "\n" + " result.texcoord3.x = dot3( toEye, rpModelMatrixX );\n" + " result.texcoord3.y = dot3( toEye, rpModelMatrixY );\n" + " result.texcoord3.z = dot3( toEye, rpModelMatrixZ );\n" + "\n" + " result.texcoord4.x = dot3( tangent, rpModelMatrixX );\n" + " result.texcoord5.x = dot3( tangent, rpModelMatrixY );\n" + " result.texcoord6.x = dot3( tangent, rpModelMatrixZ );\n" + "\n" + " result.texcoord4.y = dot3( bitangent, rpModelMatrixX );\n" + " result.texcoord5.y = dot3( bitangent, rpModelMatrixY );\n" + " result.texcoord6.y = dot3( bitangent, rpModelMatrixZ );\n" + "\n" + " result.texcoord4.z = dot3( normal, rpModelMatrixX );\n" + " result.texcoord5.z = dot3( normal, rpModelMatrixY );\n" + " result.texcoord6.z = dot3( normal, rpModelMatrixZ );\n" + "\n" + " float4 worldPosition;\n" + " worldPosition.x = dot4( modelPosition, rpModelMatrixX );\n" + " worldPosition.y = dot4( modelPosition, rpModelMatrixY );\n" + " worldPosition.z = dot4( modelPosition, rpModelMatrixZ );\n" + " worldPosition.w = dot4( modelPosition, rpModelMatrixW );\n" + " result.texcoord7 = worldPosition;\n" + "\n" + "#if defined( USE_GPU_SKINNING )\n" + " // for joint transformation of the tangent space, we use color and\n" + " // color2 for weighting information, so hopefully there aren't any\n" + " // effects that need vertex color...\n" + " result.color = float4( 1.0f, 1.0f, 1.0f, 1.0f );\n" + "#else\n" + " //# generate the vertex color, which can be 1.0, color, or 1.0 - color\n" + " //# for 1.0 : env[16] = 0, env[17] = 1\n" + " //# for color : env[16] = 1, env[17] = 0\n" + " //# for 1.0-color : env[16] = -1, env[17] = 1\n" + " result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd;\n" + "#endif\n" + "}\n" + + }, + { "renderprogs/builtin/lighting/interaction.ps.hlsl", "/*\n" @@ -7428,7 +8372,7 @@ static const cgShaderDef_t cg_renderprogs[] = "#define USE_CAS 0\n" "\n" "#define USE_DITHERING 1\n" - "#define Dithering_QuantizationSteps 8.0 // 8.0 = 2 ^ 3 quantization bits\n" + "#define Dithering_QuantizationSteps 16.0 // 8.0 = 2 ^ 3 quantization bits\n" "#define Dithering_NoiseBoost 1.0\n" "#define Dithering_Wide 1.0\n" "#define DITHER_IN_LINEAR_SPACE 0\n" diff --git a/neo/renderer/RenderSystem.h b/neo/renderer/RenderSystem.h index d37ed5e6..88fcfa18 100644 --- a/neo/renderer/RenderSystem.h +++ b/neo/renderer/RenderSystem.h @@ -199,7 +199,6 @@ struct glconfig_t bool anisotropicFilterAvailable; bool textureLODBiasAvailable; bool seamlessCubeMapAvailable; - bool sRGBFramebufferAvailable; bool vertexBufferObjectAvailable; bool mapBufferRangeAvailable; bool vertexArrayObjectAvailable; @@ -405,6 +404,9 @@ public: // markers. Use WriteRender() instead. virtual void TakeScreenshot( int width, int height, const char* fileName, int samples, struct renderView_s* ref, int exten ) = 0; + // RB + virtual byte* CaptureRenderToBuffer( int width, int height, renderView_t* ref ) = 0; + // the render output can be cropped down to a subset of the real screen, as // for save-game reviews and split-screen multiplayer. Users of the renderer // will not know the actual pixel size of the area they are rendering to diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index 0ee37996..4ea43e8d 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -94,7 +94,6 @@ idCVar r_useNodeCommonChildren( "r_useNodeCommonChildren", "1", CVAR_RENDERER | idCVar r_useShadowSurfaceScissor( "r_useShadowSurfaceScissor", "1", CVAR_RENDERER | CVAR_BOOL, "scissor shadows by the scissor rect of the interaction surfaces" ); idCVar r_useCachedDynamicModels( "r_useCachedDynamicModels", "1", CVAR_RENDERER | CVAR_BOOL, "cache snapshots of dynamic models" ); idCVar r_useSeamlessCubeMap( "r_useSeamlessCubeMap", "1", CVAR_RENDERER | CVAR_BOOL, "use ARB_seamless_cube_map if available" ); -idCVar r_useSRGB( "r_useSRGB", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_ARCHIVE, "1 = both texture and framebuffer, 2 = framebuffer only, 3 = texture only" ); idCVar r_maxAnisotropicFiltering( "r_maxAnisotropicFiltering", "8", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "limit aniso filtering" ); idCVar r_useTrilinearFiltering( "r_useTrilinearFiltering", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Extra quality filtering" ); // RB: not used anymore @@ -271,9 +270,9 @@ idCVar r_shadowMapSunDepthBiasScale( "r_shadowMapSunDepthBiasScale", "0.999991", // RB: HDR parameters #if defined( USE_VULKAN ) - idCVar r_useHDR( "r_useHDR", "0", CVAR_RENDERER | CVAR_ROM | CVAR_STATIC | CVAR_BOOL, "use high dynamic range rendering" ); + idCVar r_useHDR( "r_useHDR", "0", CVAR_RENDERER | CVAR_ROM | CVAR_STATIC | CVAR_BOOL, "Can't be changed, is broken on Vulkan backend" ); #else - idCVar r_useHDR( "r_useHDR", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use high dynamic range rendering" ); + idCVar r_useHDR( "r_useHDR", "1", CVAR_RENDERER | CVAR_ROM | CVAR_STATIC | CVAR_BOOL, "Can't be changed: Use high dynamic range rendering" ); #endif idCVar r_hdrAutoExposure( "r_hdrAutoExposure", "0", CVAR_RENDERER | CVAR_BOOL, "EXPENSIVE: enables adapative HDR tone mapping otherwise the exposure is derived by r_exposure" ); @@ -313,6 +312,9 @@ idCVar r_useHierarchicalDepthBuffer( "r_useHierarchicalDepthBuffer", "1", CVAR_R idCVar r_usePBR( "r_usePBR", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use PBR and Image Based Lighting instead of old Quake 4 style ambient lighting" ); idCVar r_pbrDebug( "r_pbrDebug", "0", CVAR_RENDERER | CVAR_INTEGER, "show which materials have PBR support (green = PBR, red = oldschool D3)" ); idCVar r_showViewEnvprobes( "r_showViewEnvprobes", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = displays the bounding boxes of all view environment probes, 2 = show irradiance" ); +idCVar r_showLightGrid( "r_showLightGrid", "0", CVAR_RENDERER | CVAR_INTEGER, "show Quake 3 style light grid points" ); + +idCVar r_useLightGrid( "r_useLightGrid", "1", CVAR_RENDERER | CVAR_BOOL, "" ); idCVar r_exposure( "r_exposure", "0.5", CVAR_ARCHIVE | CVAR_RENDERER | CVAR_FLOAT, "HDR exposure or LDR brightness [0.0 .. 1.0]", 0.0f, 1.0f ); // RB end @@ -751,7 +753,7 @@ void R_ReadTiledPixels( int width, int height, byte* buffer, renderView_t* ref = if( ref && ref->rdflags & RDF_IRRADIANCE ) { // * 2 = sizeof( half float ) - temp = ( byte* )R_StaticAlloc( RADIANCE_CUBEMAP_SIZE * RADIANCE_CUBEMAP_SIZE * 3 * 2 ); + //temp = ( byte* )R_StaticAlloc( RADIANCE_CUBEMAP_SIZE * RADIANCE_CUBEMAP_SIZE * 3 * 2 ); } else { @@ -778,8 +780,12 @@ void R_ReadTiledPixels( int width, int height, byte* buffer, renderView_t* ref = int originalNativeWidth = glConfig.nativeScreenWidth; int originalNativeHeight = glConfig.nativeScreenHeight; - glConfig.nativeScreenWidth = sysWidth; - glConfig.nativeScreenHeight = sysHeight; + + //if( !ref || ( ref && !( ref->rdflags & RDF_IRRADIANCE ) ) ) + { + glConfig.nativeScreenWidth = sysWidth; + glConfig.nativeScreenHeight = sysHeight; + } #endif // disable scissor, so we don't need to adjust all those rects @@ -878,8 +884,11 @@ void R_ReadTiledPixels( int width, int height, byte* buffer, renderView_t* ref = // discard anything currently on the list tr.SwapCommandBuffers( NULL, NULL, NULL, NULL, NULL, NULL ); - glConfig.nativeScreenWidth = originalNativeWidth; - glConfig.nativeScreenHeight = originalNativeHeight; + if( !ref || ( ref && !( ref->rdflags & RDF_IRRADIANCE ) ) ) + { + glConfig.nativeScreenWidth = originalNativeWidth; + glConfig.nativeScreenHeight = originalNativeHeight; + } #endif r_useScissor.SetBool( true ); @@ -1024,6 +1033,33 @@ void idRenderSystemLocal::TakeScreenshot( int width, int height, const char* fil takingScreenshot = false; } +// RB begin +byte* idRenderSystemLocal::CaptureRenderToBuffer( int width, int height, renderView_t* ref ) +{ + byte* buffer; + + takingScreenshot = true; + + int pix = width * height; + const int bufferSize = pix * 3 + 18; + + // HDR only for now + //if( exten == EXR ) + { + buffer = ( byte* )R_StaticAlloc( pix * 3 * 2 ); + } + //else if( exten == PNG ) + //{ + // buffer = ( byte* )R_StaticAlloc( pix * 3 ); + //} + + R_ReadTiledPixels( width, height, buffer, ref ); + + takingScreenshot = false; + + return buffer; +} + /* ================== R_ScreenshotFilename @@ -1283,11 +1319,6 @@ void R_EnvShot_f( const idCmdArgs& args ) //============================================================================ -static idMat3 cubeAxis[6]; - - - - void R_TransformCubemap( const char* orgDirection[6], const char* orgDir, const char* destDirection[6], const char* destDir, const char* baseName ) { idStr fullname; @@ -1707,6 +1738,7 @@ void idRenderSystemLocal::Clear() guiRecursionLevel = 0; guiModel = NULL; memset( gammaTable, 0, sizeof( gammaTable ) ); + memset( &cubeAxis, 0, sizeof( cubeAxis ) ); // RB takingScreenshot = false; if( unitSquareTriangles != NULL ) @@ -1737,7 +1769,8 @@ void idRenderSystemLocal::Clear() // RB envprobeJobList = NULL; - irradianceJobs.Clear(); + envprobeJobs.Clear(); + lightGridJobs.Clear(); } /* @@ -1921,7 +1954,7 @@ static srfTriangles_t* R_MakeZeroOneSphereTris() verts[ numVerts ].SetTexCoord( s * S, r * R ); verts[ numVerts ].xyz = idVec3( x, y, z ) * radius; - verts[ numVerts ].SetNormal( -x, -y, -z ); + verts[ numVerts ].SetNormal( x, y, z ); verts[ numVerts ].SetColor( 0xffffffff ); numVerts++; @@ -2053,6 +2086,38 @@ void idRenderSystemLocal::Init() identitySpace.modelMatrix[1 * 4 + 1] = 1.0f; identitySpace.modelMatrix[2 * 4 + 2] = 1.0f; + // set cubemap axis for cubemap sampling tools + + // +X + cubeAxis[0][0][0] = 1; + cubeAxis[0][1][2] = 1; + cubeAxis[0][2][1] = 1; + + // -X + cubeAxis[1][0][0] = -1; + cubeAxis[1][1][2] = -1; + cubeAxis[1][2][1] = 1; + + // +Y + cubeAxis[2][0][1] = 1; + cubeAxis[2][1][0] = -1; + cubeAxis[2][2][2] = -1; + + // -Y + cubeAxis[3][0][1] = -1; + cubeAxis[3][1][0] = -1; + cubeAxis[3][2][2] = 1; + + // +Z + cubeAxis[4][0][2] = 1; + cubeAxis[4][1][0] = -1; + cubeAxis[4][2][1] = 1; + + // -Z + cubeAxis[5][0][2] = -1; + cubeAxis[5][1][0] = 1; + cubeAxis[5][2][1] = 1; + // make sure the tr.unitSquareTriangles data is current in the vertex / index cache if( unitSquareTriangles == NULL ) { diff --git a/neo/renderer/RenderWorld_defs.cpp b/neo/renderer/RenderWorld_defs.cpp index d48b6f0f..bbac89d2 100644 --- a/neo/renderer/RenderWorld_defs.cpp +++ b/neo/renderer/RenderWorld_defs.cpp @@ -769,7 +769,7 @@ void R_DeriveEnvprobeData( RenderEnvprobeLocal* probe ) // TODO get preconvolved cubemaps fullname.Format( "env/%s/envprobe%i_amb", basename.c_str(), probeIndex ); - probe->irradianceImage = globalImages->ImageFromFile( fullname, TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN ); + probe->irradianceImage = globalImages->ImageFromFile( fullname, TF_LINEAR, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN ); fullname.Format( "env/%s/envprobe%i_spec", basename.c_str(), probeIndex ); probe->radianceImage = globalImages->ImageFromFile( fullname, TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN ); diff --git a/neo/renderer/RenderWorld_envprobes.cpp b/neo/renderer/RenderWorld_envprobes.cpp index e3c53460..0ada648b 100644 --- a/neo/renderer/RenderWorld_envprobes.cpp +++ b/neo/renderer/RenderWorld_envprobes.cpp @@ -171,7 +171,6 @@ void idRenderWorldLocal::AddAreaViewEnvprobes( int areaNum, const portalStack_t* R_SampleCubeMapHDR ================== */ -static idMat3 cubeAxis[6]; static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" }; void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float result[3], float& u, float& v ) @@ -208,8 +207,8 @@ void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float re axis = 5; } - float fx = ( dir * cubeAxis[axis][1] ) / ( dir * cubeAxis[axis][0] ); - float fy = ( dir * cubeAxis[axis][2] ) / ( dir * cubeAxis[axis][0] ); + float fx = ( dir * tr.cubeAxis[axis][1] ) / ( dir * tr.cubeAxis[axis][0] ); + float fy = ( dir * tr.cubeAxis[axis][2] ) / ( dir * tr.cubeAxis[axis][0] ); fx = -fx; fy = -fy; @@ -252,56 +251,74 @@ void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float re r11g11b10f_to_float3( tmp.i, result ); } -class CommandlineProgressBar +void R_SampleCubeMapHDR16F( const idVec3& dir, int size, halfFloat_t* buffers[6], float result[3], float& u, float& v ) { -private: - size_t tics = 0; - size_t nextTicCount = 0; - int count = 0; - int expectedCount = 0; + float adir[3]; + int axis, x, y; -public: - CommandlineProgressBar( int _expectedCount ) + adir[0] = fabs( dir[0] ); + adir[1] = fabs( dir[1] ); + adir[2] = fabs( dir[2] ); + + if( dir[0] >= adir[1] && dir[0] >= adir[2] ) { - expectedCount = _expectedCount; + axis = 0; + } + else if( -dir[0] >= adir[1] && -dir[0] >= adir[2] ) + { + axis = 1; + } + else if( dir[1] >= adir[0] && dir[1] >= adir[2] ) + { + axis = 2; + } + else if( -dir[1] >= adir[0] && -dir[1] >= adir[2] ) + { + axis = 3; + } + else if( dir[2] >= adir[1] && dir[2] >= adir[2] ) + { + axis = 4; + } + else + { + axis = 5; } - void Start() - { - common->Printf( "0%% 10 20 30 40 50 60 70 80 90 100%%\n" ); - common->Printf( "|----|----|----|----|----|----|----|----|----|----|\n" ); + float fx = ( dir * tr.cubeAxis[axis][1] ) / ( dir * tr.cubeAxis[axis][0] ); + float fy = ( dir * tr.cubeAxis[axis][2] ) / ( dir * tr.cubeAxis[axis][0] ); - common->UpdateScreen( false ); + fx = -fx; + fy = -fy; + x = size * 0.5 * ( fx + 1 ); + y = size * 0.5 * ( fy + 1 ); + if( x < 0 ) + { + x = 0; + } + else if( x >= size ) + { + x = size - 1; + } + if( y < 0 ) + { + y = 0; + } + else if( y >= size ) + { + y = size - 1; } - void Increment() - { - if( ( count + 1 ) >= nextTicCount ) - { - size_t ticsNeeded = ( size_t )( ( ( double )( count + 1 ) / expectedCount ) * 50.0 ); + u = x; + v = y; - do - { - common->Printf( "*" ); - } - while( ++tics < ticsNeeded ); + // unpack RGB16F to 3 floats + result[0] = F16toF32( buffers[axis][( y * size + x ) * 3 + 0] ); + result[1] = F16toF32( buffers[axis][( y * size + x ) * 3 + 1] ); + result[2] = F16toF32( buffers[axis][( y * size + x ) * 3 + 2] ); +} - nextTicCount = ( size_t )( ( tics / 50.0 ) * expectedCount ); - if( count == ( expectedCount - 1 ) ) - { - if( tics < 51 ) - { - common->Printf( "*" ); - } - common->Printf( "\n" ); - } - common->UpdateScreen( false ); - } - - count++; - } -}; // http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html @@ -432,14 +449,41 @@ idVec2 IntegrateBRDF( float NdotV, float roughness, int sampleCount ) // Compute normalized oct coord, mapping top left of top left pixel to (-1,-1) -idVec2 NormalizedOctCoord( int x, int y, const int probeSideLength ) +idVec2 NormalizedOctCoord( int x, int y, const int probeWithBorderSide ) { - const int margin = 0; +#if 0 + // 1 pixel border + const int margin = 1; - int probeWithBorderSide = probeSideLength + margin; + int probeSideLength = Max( 2, probeWithBorderSide - ( margin * 2 ) ); + + idVec2 octFragCoord; + octFragCoord.x = idMath::ClampInt( 0, probeSideLength - 1, x - margin ); + octFragCoord.y = idMath::ClampInt( 0, probeSideLength - 1, y - margin ); + + return ( idVec2( octFragCoord ) ) * ( 2.0f / float( probeSideLength ) ) - idVec2( 1.0f, 1.0f ); +#else + + const int margin = 2; + + // RB: FIXME - margin * 2 is wrong but looks better + // figure out why + int probeSideLength = Max( 2, probeWithBorderSide - ( margin * 2 ) ); idVec2 octFragCoord = idVec2( ( x - margin ) % probeWithBorderSide, ( y - margin ) % probeWithBorderSide ); + // Add back the half pixel to get pixel center normalized coordinates + return ( idVec2( octFragCoord ) + idVec2( 0.5f, 0.5f ) ) * ( 2.0f / float( probeSideLength ) ) - idVec2( 1.0f, 1.0f ); + +#endif +} + +static inline idVec2 NormalizedOctCoordNoBorder( int x, int y, const int probeWithBorderSide ) +{ + int probeSideLength = probeWithBorderSide; + + idVec2 octFragCoord = idVec2( x % probeWithBorderSide, y % probeWithBorderSide ); + // Add back the half pixel to get pixel center normalized coordinates return ( idVec2( octFragCoord ) + idVec2( 0.5f, 0.5f ) ) * ( 2.0f / float( probeSideLength ) ) - idVec2( 1.0f, 1.0f ); } @@ -562,10 +606,10 @@ void CalculateIrradianceJob( calcEnvprobeParms_t* parms ) progressBar.Start(); } - // build L4 Spherical Harmonics from source image - SphericalHarmonicsT shRadiance; + // build L3 Spherical Harmonics from source image + SphericalHarmonicsT shRadiance; - for( int i = 0; i < shSize( 4 ); i++ ) + for( int i = 0; i < shSize( 3 ); i++ ) { shRadiance[i].Zero(); } @@ -582,7 +626,7 @@ void CalculateIrradianceJob( calcEnvprobeParms_t* parms ) { for( int y = dstRect.y; y < ( dstRect.y + dstRect.w ); y++ ) { - idVec2 octCoord = NormalizedOctCoord( x, y, dstRect.z ); + idVec2 octCoord = NormalizedOctCoordNoBorder( x, y, dstRect.z ); // convert UV coord to 3D direction idVec3 dir; @@ -647,7 +691,7 @@ void CalculateIrradianceJob( calcEnvprobeParms_t* parms ) float texelArea = CubemapTexelSolidAngle( uu, vv, invDstSize ); - const SphericalHarmonicsT& sh = shEvaluate<4>( dir ); + const SphericalHarmonicsT& sh = shEvaluate<3>( dir ); bool shValid = true; for( int i = 0; i < 25; i++ ) @@ -694,11 +738,11 @@ void CalculateIrradianceJob( calcEnvprobeParms_t* parms ) if( mip > 0 ) { // move back to [0, 1] coords - octCoord = NormalizedOctCoord( x - dstRect.x, y - dstRect.y, dstRect.z ); + octCoord = NormalizedOctCoordNoBorder( x - dstRect.x, y - dstRect.y, dstRect.z ); } else { - octCoord = NormalizedOctCoord( x, y, dstRect.z ); + octCoord = NormalizedOctCoordNoBorder( x, y, dstRect.z ); } // convert UV coord to 3D direction @@ -709,10 +753,10 @@ void CalculateIrradianceJob( calcEnvprobeParms_t* parms ) idVec3 outColor( 0, 0, 0 ); #if 1 - // generate ambient colors by evaluating the L4 Spherical Harmonics - SphericalHarmonicsT shDirection = shEvaluate<4>( dir ); + // generate ambient colors by evaluating the L3 Spherical Harmonics + SphericalHarmonicsT shDirection = shEvaluate<3>( dir ); - idVec3 sampleIrradianceSh = shEvaluateDiffuse( shRadiance, dir ) / idMath::PI; + idVec3 sampleIrradianceSh = shEvaluateDiffuse( shRadiance, dir ) / idMath::PI; outColor[0] = Max( 0.0f, sampleIrradianceSh.x ); outColor[1] = Max( 0.0f, sampleIrradianceSh.y ); @@ -805,11 +849,11 @@ void CalculateRadianceJob( calcEnvprobeParms_t* parms ) if( mip > 0 ) { // move back to [0, 1] coords - octCoord = NormalizedOctCoord( x - dstRect.x, y - dstRect.y, dstRect.z ); + octCoord = NormalizedOctCoordNoBorder( x - dstRect.x, y - dstRect.y, dstRect.z ); } else { - octCoord = NormalizedOctCoord( x, y, dstRect.z ); + octCoord = NormalizedOctCoordNoBorder( x, y, dstRect.z ); } // convert UV coord to 3D direction @@ -921,7 +965,7 @@ void R_MakeAmbientMap( const char* baseName, const char* suffix, int outSize, bo jobParms->outHeight = outSize; jobParms->outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( outSize * outSize * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE ); - tr.irradianceJobs.Append( jobParms ); + tr.envprobeJobs.Append( jobParms ); if( useThreads ) { @@ -957,7 +1001,7 @@ void R_MakeAmbientMap( const char* baseName, const char* suffix, int outSize, bo } } -CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL ) +CONSOLE_COMMAND( bakeEnvironmentProbes, "Bake environment probes", NULL ) { idStr fullname; idStr baseName; @@ -990,38 +1034,6 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL const viewDef_t primary = *tr.primaryView; - memset( &cubeAxis, 0, sizeof( cubeAxis ) ); - - // +X - cubeAxis[0][0][0] = 1; - cubeAxis[0][1][2] = 1; - cubeAxis[0][2][1] = 1; - - // -X - cubeAxis[1][0][0] = -1; - cubeAxis[1][1][2] = -1; - cubeAxis[1][2][1] = 1; - - // +Y - cubeAxis[2][0][1] = 1; - cubeAxis[2][1][0] = -1; - cubeAxis[2][2][2] = -1; - - // -Y - cubeAxis[3][0][1] = -1; - cubeAxis[3][1][0] = -1; - cubeAxis[3][2][2] = 1; - - // +Z - cubeAxis[4][0][2] = 1; - cubeAxis[4][1][0] = -1; - cubeAxis[4][2][1] = 1; - - // -Z - cubeAxis[5][0][2] = -1; - cubeAxis[5][1][0] = 1; - cubeAxis[5][2][1] = 1; - //-------------------------------------------- // CAPTURE SCENE LIGHTING TO CUBEMAPS //-------------------------------------------- @@ -1042,7 +1054,7 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL ref.fov_x = ref.fov_y = 90; ref.vieworg = def->parms.origin; - ref.viewaxis = cubeAxis[j]; + ref.viewaxis = tr.cubeAxis[j]; extension = envDirection[ j ]; fullname.Format( "env/%s/envprobe%i%s", baseName.c_str(), i, extension ); @@ -1082,9 +1094,9 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL tr.envprobeJobList->Wait(); } - for( int j = 0; j < tr.irradianceJobs.Num(); j++ ) + for( int j = 0; j < tr.envprobeJobs.Num(); j++ ) { - calcEnvprobeParms_t* job = tr.irradianceJobs[ j ]; + calcEnvprobeParms_t* job = tr.envprobeJobs[ j ]; R_WriteEXR( job->filename, ( byte* )job->outBuffer, 3, job->outWidth, job->outHeight, "fs_basepath" ); @@ -1103,11 +1115,26 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL delete job; } - tr.irradianceJobs.Clear(); + tr.envprobeJobs.Clear(); int end = Sys_Milliseconds(); common->Printf( "convolved probes in %5.1f seconds\n\n", ( end - start ) * 0.001f ); + + //-------------------------------------------- + // LOAD CONVOLVED OCTAHEDRONS INTO THE GPU + //-------------------------------------------- + for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ ) + { + RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i]; + if( def == NULL ) + { + continue; + } + + def->irradianceImage->Reload( false ); + def->radianceImage->Reload( false ); + } } /* diff --git a/neo/renderer/RenderWorld_lightgrid.cpp b/neo/renderer/RenderWorld_lightgrid.cpp new file mode 100644 index 00000000..a69a0b90 --- /dev/null +++ b/neo/renderer/RenderWorld_lightgrid.cpp @@ -0,0 +1,1560 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. +Copyright (C) 2013-2021 Robert Beckebans + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code 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 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code 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 Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "RenderCommon.h" + +#define LGRID_FILE_EXT "lightgrid" +#define LGRID_BINARYFILE_EXT "blightgrid" +#define LGRID_FILEID "LGRID" +const byte LGRID_VERSION = 3; + +#define STORE_LIGHTGRID_SHDATA 0 + +static const byte BLGRID_VERSION = 4; +static const unsigned int BLGRID_MAGIC = ( 'P' << 24 ) | ( 'R' << 16 ) | ( 'O' << 8 ) | BLGRID_VERSION; + + +static const int MAX_LIGHTGRID_ATLAS_SIZE = 2048; +static const int MAX_AREA_LIGHTGRID_POINTS = ( MAX_LIGHTGRID_ATLAS_SIZE / LIGHTGRID_IRRADIANCE_SIZE ) * ( MAX_LIGHTGRID_ATLAS_SIZE / LIGHTGRID_IRRADIANCE_SIZE ); + +LightGrid::LightGrid() +{ + lightGridSize.Set( 64, 64, 128 ); + area = -1; + + irradianceImage = NULL; + imageSingleProbeSize = LIGHTGRID_IRRADIANCE_SIZE; + imageBorderSize = LIGHTGRID_IRRADIANCE_BORDER_SIZE; +} + +void LightGrid::SetupLightGrid( const idBounds& bounds, const char* mapName, const idRenderWorld* world, int _area, int limit ) +{ + //idLib::Printf( "----- SetupLightGrid -----\n" ); + + lightGridSize.Set( 64, 64, 128 ); + lightGridPoints.Clear(); + + area = _area; + + imageSingleProbeSize = LIGHTGRID_IRRADIANCE_SIZE; + imageBorderSize = LIGHTGRID_IRRADIANCE_BORDER_SIZE; + + idVec3 maxs; + int j = 0; + + int maxGridPoints = MAX_AREA_LIGHTGRID_POINTS; + if( limit >= 100 && limit < MAX_AREA_LIGHTGRID_POINTS ) + { + maxGridPoints = limit; + } + + int numGridPoints = maxGridPoints + 1; + while( numGridPoints > maxGridPoints ) + { + for( int i = 0; i < 3; i++ ) + { + lightGridOrigin[i] = lightGridSize[i] * ceil( bounds[0][i] / lightGridSize[i] ); + maxs[i] = lightGridSize[i] * floor( bounds[1][i] / lightGridSize[i] ); + lightGridBounds[i] = ( maxs[i] - lightGridOrigin[i] ) / lightGridSize[i] + 1; + } + + numGridPoints = lightGridBounds[0] * lightGridBounds[1] * lightGridBounds[2]; + + if( numGridPoints > maxGridPoints ) + { + lightGridSize[ j++ % 3 ] += 16.0f; + } + } + + if( numGridPoints > 0 ) + { + lightGridPoints.SetNum( numGridPoints ); + + idLib::Printf( "\narea %i (%i x %i x %i) = %i grid points \n", area, lightGridBounds[0], lightGridBounds[1], lightGridBounds[2], numGridPoints ); + idLib::Printf( "area %i grid size (%i %i %i)\n", area, ( int )lightGridSize[0], ( int )lightGridSize[1], ( int )lightGridSize[2] ); + idLib::Printf( "area %i grid bounds (%i %i %i)\n", area, ( int )lightGridBounds[0], ( int )lightGridBounds[1], ( int )lightGridBounds[2] ); + idLib::Printf( "area %i %9u x %" PRIuSIZE " = lightGridSize = (%.2fMB)\n", area, numGridPoints, sizeof( lightGridPoint_t ), ( float )( lightGridPoints.MemoryUsed() ) / ( 1024.0f * 1024.0f ) ); + + CalculateLightGridPointPositions( world, area ); + } +} + +void LightGrid::GetBaseGridCoord( const idVec3& origin, int gridCoord[3] ) +{ + int pos[3]; + + idVec3 lightOrigin = origin - lightGridOrigin; + for( int i = 0; i < 3; i++ ) + { + float v; + + v = lightOrigin[i] * ( 1.0f / lightGridSize[i] ); + pos[i] = floor( v ); + + if( pos[i] < 0 ) + { + pos[i] = 0; + } + else if( pos[i] >= lightGridBounds[i] - 1 ) + { + pos[i] = lightGridBounds[i] - 1; + } + + gridCoord[i] = pos[i]; + } +} + +int LightGrid::GridCoordToProbeIndex( int gridCoord[3] ) +{ + int gridStep[3]; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + int gridPointIndex = gridCoord[0] * gridStep[0] + gridCoord[1] * gridStep[1] + gridCoord[2] * gridStep[2]; + + return gridPointIndex; +} + +void LightGrid::ProbeIndexToGridCoord( const int probeIndex, int gridCoord[3] ) +{ +#if 1 + // slow brute force method only for debugging + int gridStep[3]; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + gridCoord[0] = 0; + gridCoord[1] = 0; + gridCoord[2] = 0; + + int p = 0; + for( int i = 0; i < lightGridBounds[0]; i += 1 ) + { + for( int j = 0; j < lightGridBounds[1]; j += 1 ) + { + for( int k = 0; k < lightGridBounds[2]; k += 1 ) + { + if( probeIndex == p ) + { + gridCoord[0] = i; + gridCoord[1] = j; + gridCoord[2] = k; + + return; + } + + p++; + } + } + } +#else + + gridPoints + + GetBaseGridCoord() +#endif +} + +idVec3 LightGrid::GetGridCoordDebugColor( int gridCoord[3] ) +{ + idVec3 color( colorGold.x, colorGold.y, colorGold.z ); + +#if 0 + color.x = float( gridCoord[0] & 1 ); + color.y = float( gridCoord[1] & 1 ); + color.z = float( gridCoord[2] & 1 ); + + //color *= ( 1.0f / Max( color.x + color.y + color.z, 0.01f ) ); + //color = color * 0.6f + idVec3( 0.2f ); + +#else + int gridStep[3]; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + int gridPointIndex = gridCoord[0] * gridStep[0] + gridCoord[1] * gridStep[1] + gridCoord[2] * gridStep[2]; + + const int numColors = 7; + static idVec4 colors[numColors] = { colorBlack, colorBlue, colorCyan, colorGreen, colorYellow, colorRed, colorWhite }; + + color.x = colors[ gridPointIndex % numColors ].x; + color.y = colors[ gridPointIndex % numColors ].y; + color.z = colors[ gridPointIndex % numColors ].z; +#endif + + return color; +} + +idVec3 LightGrid::GetProbeIndexDebugColor( const int probeIndex ) +{ + idVec3 color( colorGold.x, colorGold.y, colorGold.z ); + + int gridCoord[3]; + ProbeIndexToGridCoord( probeIndex, gridCoord ); + + return GetGridCoordDebugColor( gridCoord ); +} + +int LightGrid::CountValidGridPoints() const +{ + int validCount = 0; + + for( int i = 0; i < lightGridPoints.Num(); i += 1 ) + { + if( lightGridPoints[i].valid > 0 ) + { + validCount++; + } + } + + return validCount; +} + +void LightGrid::CalculateLightGridPointPositions( const idRenderWorld* world, int area ) +{ + // calculate grid point positions + int gridStep[3]; + int pos[3]; + idVec3 posFloat; + + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + int invalidCount = 0; + int p = 0; + + for( int i = 0; i < lightGridBounds[0]; i += 1 ) + { + for( int j = 0; j < lightGridBounds[1]; j += 1 ) + { + for( int k = 0; k < lightGridBounds[2]; k += 1 ) + { + pos[0] = i; + pos[1] = j; + pos[2] = k; + + posFloat[0] = i * lightGridSize[0]; + posFloat[1] = j * lightGridSize[1]; + posFloat[2] = k * lightGridSize[2]; + + lightGridPoint_t* gridPoint = &lightGridPoints[ pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2] ]; + + gridPoint->origin = lightGridOrigin + posFloat; + + gridPoint->valid = ( world->PointInArea( gridPoint->origin ) != -1 ); + if( !gridPoint->valid ) + { + idVec3 origin; + idVec3 baseOrigin; + int step; + + baseOrigin = gridPoint->origin; +#if 1 + // RB: do what q3map1 did - try to nudge the origin around to find a valid point + for( step = 9; step <= 18; step += 9 ) + { + for( int c = 0; c < 8; c++ ) + { + origin = baseOrigin; + if( c & 1 ) + { + origin[0] += step; + } + else + { + origin[0] -= step; + } + if( c & 2 ) + { + origin[1] += step; + } + else + { + origin[1] -= step; + } + if( c & 4 ) + { + origin[2] += step; + } + else + { + origin[2] -= step; + } + + if( world->PointInArea( origin ) != -1 ) + { + // point is not in the void + gridPoint->valid = true; + gridPoint->origin = origin; + break; + } + } + + if( i != 8 ) + { + break; + } + } +#endif + + /* + if( step > 18 ) + { + // can't find a valid point at all + for( i = 0; i < 3; i++ ) + { + gridPoint->ambient[i] = 0; + gridPoint->directed[i] = 0; + } + gridPoint->latLong[0] = 0; + gridPoint->latLong[1] = 0; + return; + } + */ + + if( !gridPoint->valid ) + { + invalidCount++; + } + } + + p++; + } + } + } + + //validGridPoints = p - invalidCount; + + idLib::Printf( "area %i: %i of %i grid points in empty space (%.2f%%)\n", area, invalidCount, lightGridPoints.Num(), ( ( float ) invalidCount / lightGridPoints.Num() ) * 100 ); +} + +void idRenderWorldLocal::SetupLightGrid() +{ + idLib::Printf( "----- SetupLightGrid -----\n" ); + + idStrStatic< MAX_OSPATH > baseName = mapName; + baseName.StripFileExtension(); + + idStr filename; + filename.Format( "%s.lightgrid", baseName.c_str() ); + bool loaded = LoadLightGridFile( filename ); + if( loaded ) + { + LoadLightGridImages(); + } + else + { + int totalGridPoints = 0; + + for( int i = 0; i < numPortalAreas; i++ ) + { + portalArea_t* area = &portalAreas[i]; + + area->lightGrid.SetupLightGrid( area->globalBounds, mapName, this, i, -1 ); + + totalGridPoints += area->lightGrid.CountValidGridPoints(); + } + + idLib::Printf( "----------------------------------\n" ); + idLib::Printf( "Total valid light grid points %i\n", totalGridPoints ); + } +} + +void idRenderWorldLocal::LoadLightGridImages() +{ + idLib::Printf( "----- LoadLightGridImages -----\n" ); + + idStrStatic< MAX_OSPATH > baseName = mapName; + baseName.StripFileExtension(); + + idStr filename; + + // try to load existing lightgrid image data + for( int i = 0; i < numPortalAreas; i++ ) + { + portalArea_t* area = &portalAreas[i]; + + if( !area->lightGrid.irradianceImage ) + { + filename.Format( "env/%s/area%i_lightgrid_amb", baseName.c_str(), i ); + area->lightGrid.irradianceImage = globalImages->ImageFromFile( filename, TF_LINEAR, TR_CLAMP, TD_R11G11B10F, CF_2D ); + } + else + { + area->lightGrid.irradianceImage->Reload( false ); + } + } +} + +/* +=============================================================================== + +Reading / Writing of light grids files + +=============================================================================== +*/ + +void idRenderWorldLocal::WriteLightGridsToFile( const char* filename ) +{ + idFile* fp; + idStr name; + + name = filename; + name.SetFileExtension( LGRID_FILE_EXT ); + + common->Printf( "writing %s\n", name.c_str() ); + fp = fileSystem->OpenFileWrite( name, "fs_basepath" ); + if( !fp ) + { + common->Warning( "idCollisionModelManagerLocal::WriteCollisionModelsToFile: Error opening file %s\n", name.c_str() ); + return; + } + + // write file id and version + fp->WriteFloatString( "%s \"%i\"\n\n", LGRID_FILEID, BLGRID_VERSION ); + + // write the map file crc + //fp->WriteFloatString( "%u\n\n", mapFileCRC ); + + for( int i = 0; i < numPortalAreas; i++ ) + { + portalArea_t* area = &portalAreas[i]; + + WriteLightGrid( fp, area->lightGrid ); + } + + fileSystem->CloseFile( fp ); +} + +void idRenderWorldLocal::WriteLightGrid( idFile* fp, const LightGrid& lightGrid ) +{ + fp->WriteFloatString( "lightGridPoints { /* area = */ %i /* numLightGridPoints = */ %i /* imageSingleProbeSize = */ %i /* imageBorderSize = */ %i \n", lightGrid.area, lightGrid.lightGridPoints.Num(), lightGrid.imageSingleProbeSize, lightGrid.imageBorderSize ); + + fp->WriteFloatString( "/* gridMins */ " ); + fp->WriteFloatString( "\t ( %f %f %f )\n", lightGrid.lightGridOrigin[0], lightGrid.lightGridOrigin[1], lightGrid.lightGridOrigin[2] ); + + fp->WriteFloatString( "/* gridSize */ " ); + fp->WriteFloatString( "\t ( %f %f %f )\n", lightGrid.lightGridSize[0], lightGrid.lightGridSize[1], lightGrid.lightGridSize[2] ); + + fp->WriteFloatString( "/* gridBounds */ " ); + fp->WriteFloatString( "%i %i %i\n\n", lightGrid.lightGridBounds[0], lightGrid.lightGridBounds[1], lightGrid.lightGridBounds[2] ); + + for( int i = 0 ; i < lightGrid.lightGridPoints.Num() ; i++ ) + { + const lightGridPoint_t* gridPoint = &lightGrid.lightGridPoints[i]; + + fp->WriteFloatString( "/* lgp %i */ %d ( %f %f %f )", i, ( int )gridPoint->valid, gridPoint->origin[0], gridPoint->origin[1], gridPoint->origin[2] ); + +#if STORE_LIGHTGRID_SHDATA + // spherical harmonic + fp->WriteFloatString( "( " ); + + for( int j = 0; j < shSize( 3 ); j++ ) + { + fp->WriteFloatString( "%f %f %f ", gridPoint->shRadiance[j][0], gridPoint->shRadiance[j][1], gridPoint->shRadiance[j][2] ); + } + + fp->WriteFloatString( ")\n" ); +#endif + } + + fp->WriteFloatString( "}\n\n" ); +} + + +bool idRenderWorldLocal::LoadLightGridFile( const char* name ) +{ + idToken token; + idLexer* src; + //unsigned int crc; + + // load it + idStrStatic< MAX_OSPATH > fileName = name; + + // check for generated file + idStrStatic< MAX_OSPATH > generatedFileName = fileName; + generatedFileName.Insert( "generated/", 0 ); + generatedFileName.SetFileExtension( LGRID_BINARYFILE_EXT ); + + // if we are reloading the same map, check the timestamp + // and try to skip all the work + ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( fileName ); + + // see if we have a generated version of this + bool loaded = false; + +#if 1 + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + if( file != NULL ) + { + int numEntries = 0; + int magic = 0; + ID_TIME_T storedTimeStamp; + file->ReadBig( magic ); + file->ReadBig( storedTimeStamp ); + file->ReadBig( numEntries ); + if( ( magic == BLGRID_MAGIC ) && ( sourceTimeStamp == storedTimeStamp ) && ( numEntries > 0 ) ) + { + loaded = true; + + for( int i = 0; i < numEntries; i++ ) + { + idStrStatic< MAX_OSPATH > type; + file->ReadString( type ); + type.ToLower(); + + if( type == "lightgridpoints" ) + { + ReadBinaryLightGridPoints( file ); + } + else + { + idLib::Error( "Binary proc file failed, unexpected type %s\n", type.c_str() ); + } + } + } + } +#endif + + if( !loaded ) + { + fileName.SetFileExtension( LGRID_FILE_EXT ); + src = new( TAG_RENDER ) idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if( !src->IsLoaded() ) + { + delete src; + return false; + } + + int numEntries = 0; + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + if( outputFile != NULL ) + { + int magic = BLGRID_MAGIC; + outputFile->WriteBig( magic ); + outputFile->WriteBig( sourceTimeStamp ); + outputFile->WriteBig( numEntries ); + } + + if( !src->ExpectTokenString( LGRID_FILEID ) ) + { + common->Warning( "%s is not an CM file.", fileName.c_str() ); + delete src; + return false; + } + + int lightGridVersion = 0; + if( !src->ReadToken( &token ) ) + { + src->Error( "couldn't read expected integer" ); + delete src; + return false; + } + if( token.type == TT_PUNCTUATION && token == "-" ) + { + src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + lightGridVersion = -( ( signed int ) token.GetIntValue() ); + } + else if( token.type != TT_NUMBER && token.subtype != TT_STRING && token.subtype != TT_NAME ) + { + src->Error( "expected integer value or string, found '%s'", token.c_str() ); + } + + if( token.type == TT_NUMBER ) + { + lightGridVersion = token.GetIntValue(); + } + else if( token.type == TT_STRING || token.subtype == TT_NAME ) + { + lightGridVersion = atoi( token ); + } + + if( lightGridVersion != LGRID_VERSION ) + { + common->Warning( "%s has version %i instead of %i", fileName.c_str(), lightGridVersion, LGRID_VERSION ); + delete src; + return false; + } + + //if( !src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) + //{ + // common->Warning( "%s has no map file CRC", fileName.c_str() ); + // delete src; + // return false; + //} + + //crc = token.GetUnsignedLongValue(); + //if( mapFileCRC && crc != mapFileCRC ) + //{ + // common->Printf( "%s is out of date\n", fileName.c_str() ); + // delete src; + // return false; + //} + + // parse the file + while( 1 ) + { + if( !src->ReadToken( &token ) ) + { + break; + } + + if( token == "lightGridPoints" ) + { + ParseLightGridPoints( src, outputFile ); + + numEntries++; + continue; + } + + src->Error( "idRenderWorldLocal::LoadLightGridFile: bad token \"%s\"", token.c_str() ); + } + + delete src; + + if( outputFile != NULL ) + { + outputFile->Seek( 0, FS_SEEK_SET ); + + int magic = BLGRID_MAGIC; + outputFile->WriteBig( magic ); + outputFile->WriteBig( sourceTimeStamp ); + outputFile->WriteBig( numEntries ); + } + } + + return true; +} + +void idRenderWorldLocal::ParseLightGridPoints( idLexer* src, idFile* fileOut ) +{ + src->ExpectTokenString( "{" ); + + int areaIndex = src->ParseInt(); + if( areaIndex < 0 || areaIndex >= NumAreas() ) + { + src->Error( "ParseLightGridPoints: bad area index %i", areaIndex ); + return; + } + + int numLightGridPoints = src->ParseInt(); + if( numLightGridPoints < 0 ) + { + src->Error( "ParseLightGridPoints: bad numLightGridPoints" ); + return; + } + + int imageProbeSize = src->ParseInt(); + if( imageProbeSize < 8 ) + { + src->Error( "ParseLightGridPoints: bad single image probe size %i", imageProbeSize ); + return; + } + + int imageBorderSize = src->ParseInt(); + if( imageBorderSize < 0 ) + { + src->Error( "ParseLightGridPoints: bad probe border size %i", imageBorderSize ); + return; + } + + if( fileOut != NULL ) + { + // write out the type so the binary reader knows what to instantiate + fileOut->WriteString( "lightGridPoints" ); + } + + portalArea_t* area = &portalAreas[areaIndex]; + area->lightGrid.area = areaIndex; + + area->lightGrid.imageSingleProbeSize = imageProbeSize; + area->lightGrid.imageBorderSize = imageBorderSize; + + // gridMins + src->Parse1DMatrix( 3, area->lightGrid.lightGridOrigin.ToFloatPtr() ); + src->Parse1DMatrix( 3, area->lightGrid.lightGridSize.ToFloatPtr() ); + for( int i = 0; i < 3; i++ ) + { + area->lightGrid.lightGridBounds[i] = src->ParseInt(); + } + + area->lightGrid.lightGridPoints.SetNum( numLightGridPoints ); + + idLib::Printf( "\narea %i (%i x %i x %i) = %i grid points \n", areaIndex, area->lightGrid.lightGridBounds[0], area->lightGrid.lightGridBounds[1], area->lightGrid.lightGridBounds[2], numLightGridPoints ); + idLib::Printf( "area %i grid size (%i %i %i)\n", areaIndex, ( int )area->lightGrid.lightGridSize[0], ( int )area->lightGrid.lightGridSize[1], ( int )area->lightGrid.lightGridSize[2] ); + idLib::Printf( "area %i grid bounds (%i %i %i)\n", areaIndex, ( int )area->lightGrid.lightGridBounds[0], ( int )area->lightGrid.lightGridBounds[1], ( int )area->lightGrid.lightGridBounds[2] ); + idLib::Printf( "area %i %9u x %" PRIuSIZE " = lightGridSize = (%.2fMB)\n", areaIndex, numLightGridPoints, sizeof( lightGridPoint_t ), ( float )( area->lightGrid.lightGridPoints.MemoryUsed() ) / ( 1024.0f * 1024.0f ) ); + idLib::Printf( "area %i probe size (%i %i)\n", areaIndex, imageProbeSize, imageBorderSize ); + + if( fileOut != NULL ) + { + fileOut->WriteBig( areaIndex ); + fileOut->WriteBig( numLightGridPoints ); + fileOut->WriteBig( imageProbeSize ); + fileOut->WriteBig( imageBorderSize ); + fileOut->WriteBig( area->lightGrid.lightGridOrigin ); + fileOut->WriteBig( area->lightGrid.lightGridSize ); + fileOut->WriteBigArray( area->lightGrid.lightGridBounds, 3 ); + } + + for( int i = 0; i < numLightGridPoints; i++ ) + { + lightGridPoint_t* gridPoint = &area->lightGrid.lightGridPoints[i]; + + gridPoint->valid = src->ParseInt(); + + src->Parse1DMatrix( 3, gridPoint->origin.ToFloatPtr() ); +#if STORE_LIGHTGRID_SHDATA + src->Parse1DMatrix( shSize( 3 ) * 3, gridPoint->shRadiance[0].ToFloatPtr() ); +#endif + + if( fileOut != NULL ) + { + fileOut->WriteBig( gridPoint->valid ); + fileOut->WriteBig( gridPoint->origin ); + +#if STORE_LIGHTGRID_SHDATA + fileOut->WriteBigArray( gridPoint->shRadiance[0].ToFloatPtr(), shSize( 3 ) * 3 ); +#endif + } + } + + src->ExpectTokenString( "}" ); +} + +void idRenderWorldLocal::ReadBinaryLightGridPoints( idFile* file ) +{ + int areaIndex; + file->ReadBig( areaIndex ); + + if( areaIndex < 0 || areaIndex >= NumAreas() ) + { + idLib::Error( "ReadBinaryLightGridPoints: bad area index %i", areaIndex ); + return; + } + + int numLightGridPoints = 0; + file->ReadBig( numLightGridPoints ); + if( numLightGridPoints < 0 ) + { + idLib::Error( "ReadBinaryLightGridPoints: bad numLightGridPoints" ); + return; + } + + int imageProbeSize = 0; + file->ReadBig( imageProbeSize ); + if( imageProbeSize < 0 ) + { + idLib::Error( "ReadBinaryLightGridPoints: bad imageProbeSize %i", imageProbeSize ); + return; + } + + int imageBorderSize = 0; + file->ReadBig( imageBorderSize ); + if( imageBorderSize < 0 ) + { + idLib::Error( "ReadBinaryLightGridPoints: bad imageBorderSize %i", imageBorderSize ); + return; + } + + portalArea_t* area = &portalAreas[areaIndex]; + area->lightGrid.area = areaIndex; + + area->lightGrid.imageSingleProbeSize = imageProbeSize; + area->lightGrid.imageBorderSize = imageBorderSize; + + // gridMins + file->ReadBig( area->lightGrid.lightGridOrigin ); + file->ReadBig( area->lightGrid.lightGridSize ); + file->ReadBigArray( area->lightGrid.lightGridBounds, 3 ); + + area->lightGrid.lightGridPoints.SetNum( numLightGridPoints ); + + idLib::Printf( "\narea %i (%i x %i x %i) = %i grid points \n", areaIndex, area->lightGrid.lightGridBounds[0], area->lightGrid.lightGridBounds[1], area->lightGrid.lightGridBounds[2], numLightGridPoints ); + idLib::Printf( "area %i grid size (%i %i %i)\n", areaIndex, ( int )area->lightGrid.lightGridSize[0], ( int )area->lightGrid.lightGridSize[1], ( int )area->lightGrid.lightGridSize[2] ); + idLib::Printf( "area %i grid bounds (%i %i %i)\n", areaIndex, ( int )area->lightGrid.lightGridBounds[0], ( int )area->lightGrid.lightGridBounds[1], ( int )area->lightGrid.lightGridBounds[2] ); + idLib::Printf( "area %i %9u x %" PRIuSIZE " = lightGridSize = (%.2fMB)\n", areaIndex, numLightGridPoints, sizeof( lightGridPoint_t ), ( float )( area->lightGrid.lightGridPoints.MemoryUsed() ) / ( 1024.0f * 1024.0f ) ); + idLib::Printf( "area %i probe size (%i %i)\n", areaIndex, imageProbeSize, imageBorderSize ); + + for( int i = 0; i < numLightGridPoints; i++ ) + { + lightGridPoint_t* gridPoint = &area->lightGrid.lightGridPoints[i]; + + file->ReadBig( gridPoint->valid ); + file->ReadBig( gridPoint->origin ); + + //file->ReadBigArray( gridPoint->shRadiance[0].ToFloatPtr(), shSize( 3 ) * 3 ); + } +} + +/* +=============================================================================== + +Baking light grids files + +=============================================================================== +*/ + +static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" }; + +/// http://www.mpia-hd.mpg.de/~mathar/public/mathar20051002.pdf +/// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ +static inline float AreaElement( float _x, float _y ) +{ + return atan2f( _x * _y, sqrtf( _x * _x + _y * _y + 1.0f ) ); +} + +/// u and v should be center adressing and in [-1.0 + invSize.. 1.0 - invSize] range. +static inline float CubemapTexelSolidAngle( float u, float v, float _invFaceSize ) +{ + // Specify texel area. + const float x0 = u - _invFaceSize; + const float x1 = u + _invFaceSize; + const float y0 = v - _invFaceSize; + const float y1 = v + _invFaceSize; + + // Compute solid angle of texel area. + const float solidAngle = AreaElement( x1, y1 ) + - AreaElement( x0, y1 ) + - AreaElement( x1, y0 ) + + AreaElement( x0, y0 ) + ; + + return solidAngle; +} + +static inline idVec3 MapXYSToDirection( uint64 x, uint64 y, uint64 s, uint64 width, uint64 height ) +{ + float u = ( ( x + 0.5f ) / float( width ) ) * 2.0f - 1.0f; + float v = ( ( y + 0.5f ) / float( height ) ) * 2.0f - 1.0f; + v *= -1.0f; + + idVec3 dir( 0, 0, 0 ); + + // +x, -x, +y, -y, +z, -z + switch( s ) + { + case 0: + dir = idVec3( 1.0f, v, -u ); + break; + case 1: + dir = idVec3( -1.0f, v, u ); + break; + case 2: + dir = idVec3( u, 1.0f, -v ); + break; + case 3: + dir = idVec3( u, -1.0f, v ); + break; + case 4: + dir = idVec3( u, v, 1.0f ); + break; + case 5: + dir = idVec3( -u, v, -1.0f ); + break; + } + + dir.Normalize(); + + return dir; +} + +void CalculateLightGridPointJob( calcLightGridPointParms_t* parms ) +{ + halfFloat_t* buffers[6]; + + int start = Sys_Milliseconds(); + + for( int i = 0; i < 6; i++ ) + { + buffers[ i ] = ( halfFloat_t* ) parms->radiance[ i ]; + } + + const float invDstSize = 1.0f / float( parms->outHeight ); + + const int numMips = idMath::BitsForInteger( parms->outHeight ); + + const idVec2i sourceImageSize( parms->outHeight, parms->outHeight ); + + // build L3 Spherical Harmonics from source image + SphericalHarmonicsT shRadiance; + + for( int i = 0; i < shSize( 3 ); i++ ) + { + shRadiance[i].Zero(); + } + + // build SH by iterating over all cubemap pixels + + for( int side = 0; side < 6; side++ ) + { + for( int x = 0; x < sourceImageSize.x; x++ ) + { + for( int y = 0; y < sourceImageSize.y; y++ ) + { + // convert UV coord to 3D direction + idVec3 dir = MapXYSToDirection( x, y, side, sourceImageSize.x, sourceImageSize.y ); + + float u, v; + idVec3 radiance; + R_SampleCubeMapHDR16F( dir, parms->outHeight, buffers, &radiance[0], u, v ); + + //radiance = dir * 0.5 + idVec3( 0.5f, 0.5f, 0.5f ); + + // convert from [0 .. size-1] to [-1.0 + invSize .. 1.0 - invSize] + const float uu = 2.0f * ( u * invDstSize ) - 1.0f; + const float vv = 2.0f * ( v * invDstSize ) - 1.0f; + + float texelArea = CubemapTexelSolidAngle( uu, vv, invDstSize ); + + const SphericalHarmonicsT& sh = shEvaluate<3>( dir ); + + bool shValid = true; + for( int i = 0; i < shSize( 3 ); i++ ) + { + if( IsNAN( sh[i] ) ) + { + shValid = false; + break; + } + } + + if( shValid ) + { + shAddWeighted( shRadiance, sh, radiance * texelArea ); + } + } + } + } + + for( int i = 0; i < shSize( 3 ); i++ ) + { + parms->shRadiance[i] = shRadiance[i]; + } + + // reset image to black + for( int x = 0; x < parms->outWidth; x++ ) + { + for( int y = 0; y < parms->outHeight; y++ ) + { + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 0] = F32toF16( 0 ); + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 1] = F32toF16( 0 ); + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 2] = F32toF16( 0 ); + } + } + + for( int x = 0; x < parms->outWidth; x++ ) + { + for( int y = 0; y < parms->outHeight; y++ ) + { + idVec2 octCoord = NormalizedOctCoord( x, y, parms->outWidth ); + + // convert UV coord to 3D direction + idVec3 dir; + + dir.FromOctahedral( octCoord ); + + idVec3 outColor( 0, 0, 0 ); + +#if 1 + // generate ambient colors by evaluating the L3 Spherical Harmonics + SphericalHarmonicsT shDirection = shEvaluate<3>( dir ); + + idVec3 sampleIrradianceSh = shEvaluateDiffuse( shRadiance, dir ) / idMath::PI; + + outColor[0] = Max( 0.0f, sampleIrradianceSh.x ); + outColor[1] = Max( 0.0f, sampleIrradianceSh.y ); + outColor[2] = Max( 0.0f, sampleIrradianceSh.z ); +#else + // generate ambient colors using Monte Carlo method + for( int s = 0; s < parms->samples; s++ ) + { + idVec2 Xi = Hammersley2D( s, parms->samples ); + idVec3 H = ImportanceSampleGGX( Xi, dir, 0.95f ); + + float u, v; + idVec3 radiance; + R_SampleCubeMapHDR( H, parms->outHeight, buffers, &radiance[0], u, v ); + + outColor[0] += radiance[0]; + outColor[1] += radiance[1]; + outColor[2] += radiance[2]; + } + + outColor[0] /= parms->samples; + outColor[1] /= parms->samples; + outColor[2] /= parms->samples; +#endif + + //outColor = dir * 0.5 + idVec3( 0.5f, 0.5f, 0.5f ); + + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 0] = F32toF16( outColor[0] ); + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 1] = F32toF16( outColor[1] ); + parms->outBuffer[( y * parms->outWidth + x ) * 3 + 2] = F32toF16( outColor[2] ); + } + } + + int end = Sys_Milliseconds(); + + parms->time = end - start; +} + +REGISTER_PARALLEL_JOB( CalculateLightGridPointJob, "CalculateLightGridPointJob" ); + +#if 0 +void R_MakeAmbientGridPoint( const char* baseName, const char* suffix, int outSize, bool deleteTempFiles, bool useThreads ) +{ + idStr fullname; + renderView_t ref; + viewDef_t primary; + byte* buffers[6]; + int width = 0, height = 0; + + // read all of the images + for( int i = 0 ; i < 6 ; i++ ) + { + fullname.Format( "env/%s%s.exr", baseName, envDirection[i] ); + + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + + R_LoadImage( fullname, &buffers[i], &width, &height, NULL, true, NULL ); + if( !buffers[i] ) + { + common->Printf( "loading %s failed.\n", fullname.c_str() ); + for( i-- ; i >= 0 ; i-- ) + { + Mem_Free( buffers[i] ); + } + return; + } + } + + // set up the job + calcLightGridPointParms_t* jobParms = new calcLightGridPointParms_t; + + for( int i = 0; i < 6; i++ ) + { + jobParms->buffers[ i ] = buffers[ i ]; + } + +// jobParms->samples = 1000; +// jobParms->filename.Format( "env/%s%s.exr", baseName, suffix ); + +// jobParms->printProgress = !useThreads; + + jobParms->outWidth = int( outSize * 1.5f ); + jobParms->outHeight = outSize; + jobParms->outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( outSize * outSize * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE ); + + tr.lightGridJobs.Append( jobParms ); + + if( useThreads ) + { + tr.envprobeJobList->AddJob( ( jobRun_t )CalculateLightGridPointJob, jobParms ); + } + else + { + CalculateLightGridPointJob( jobParms ); + } + + if( deleteTempFiles ) + { + for( int i = 0 ; i < 6 ; i++ ) + { + fullname.Format( "env/%s%s.exr", baseName, envDirection[i] ); + + fileSystem->RemoveFile( fullname ); + } + } +} +#endif + +CONSOLE_COMMAND( bakeLightGrids, "Bake irradiance/vis light grid data", NULL ) +{ + idStr baseName; + idStr filename; + renderView_t ref; + int blends; + const char* extension; + int captureSize; + + static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" }; + + if( args.Argc() != 1 && args.Argc() != 2 ) + { + common->Printf( "USAGE: bakeLightGrids [limit] (limit is max probes per BSP area)\n" ); + return; + } + + if( !tr.primaryWorld ) + { + common->Printf( "No primary world loaded.\n" ); + return; + } + + bool useThreads = false; + + baseName = tr.primaryWorld->mapName; + baseName.StripFileExtension(); + + captureSize = RADIANCE_CUBEMAP_SIZE; + blends = 1; + + if( !tr.primaryView ) + { + common->Printf( "No primary view.\n" ); + return; + } + + int limit = MAX_AREA_LIGHTGRID_POINTS; + if( args.Argc() >= 2 ) + { + limit = atoi( args.Argv( 1 ) ); + } + + idLib::Printf( "Using limit = %i\n", limit ); + + const viewDef_t primary = *tr.primaryView; + + //-------------------------------------------- + // CAPTURE SCENE LIGHTING TO CUBEMAPS + //-------------------------------------------- + + int totalStart = Sys_Milliseconds(); + + for( int a = 0; a < tr.primaryWorld->NumAreas(); a++ ) + { + portalArea_t* area = &tr.primaryWorld->portalAreas[a]; + + //int numGridPoints = Min( area->lightGrid.lightGridPoints.Num(), limit ); + //if( numGridPoints == 0 ) + + //int numGridPoints = area->lightGrid.lightGridPoints.Num(); + //if( numGridPoints == 0 || numGridPoints > limit ) + //{ + // continue; + //} + + area->lightGrid.SetupLightGrid( area->globalBounds, tr.primaryWorld->mapName, tr.primaryWorld, a, limit ); + +#if 1 + int numGridPoints = area->lightGrid.CountValidGridPoints(); + if( numGridPoints == 0 ) + { + continue; + } + + idLib::Printf( "Shooting %i grid probes area %i...\n", numGridPoints, a ); + + CommandlineProgressBar progressBar( numGridPoints ); + if( !useThreads ) + { + progressBar.Start(); + } + + int start = Sys_Milliseconds(); + + int gridStep[3]; + + gridStep[0] = 1; + gridStep[1] = area->lightGrid.lightGridBounds[0]; + gridStep[2] = area->lightGrid.lightGridBounds[0] * area->lightGrid.lightGridBounds[1]; + + int gridCoord[3]; + + for( int i = 0; i < area->lightGrid.lightGridBounds[0]; i += 1 ) + { + for( int j = 0; j < area->lightGrid.lightGridBounds[1]; j += 1 ) + { + for( int k = 0; k < area->lightGrid.lightGridBounds[2]; k += 1 ) + { + gridCoord[0] = i; + gridCoord[1] = j; + gridCoord[2] = k; + + lightGridPoint_t* gridPoint = &area->lightGrid.lightGridPoints[ gridCoord[0] * gridStep[0] + gridCoord[1] * gridStep[1] + gridCoord[2] * gridStep[2] ]; + if( !gridPoint->valid ) + { + //progressBar.Increment(); + continue; + } + + calcLightGridPointParms_t* jobParms = new calcLightGridPointParms_t; + jobParms->gridCoord[0] = i; + jobParms->gridCoord[1] = j; + jobParms->gridCoord[2] = k; + + for( int side = 0; side < 6; side++ ) + { + ref = primary.renderView; + + ref.rdflags = RDF_NOAMBIENT | RDF_IRRADIANCE; + ref.fov_x = ref.fov_y = 90; + + ref.vieworg = gridPoint->origin; + ref.viewaxis = tr.cubeAxis[ side ]; + + extension = envDirection[ side ]; + + //tr.TakeScreenshot( size, size, fullname, blends, &ref, EXR ); + byte* float16FRGB = tr.CaptureRenderToBuffer( captureSize, captureSize, &ref ); + + jobParms->radiance[ side ] = float16FRGB; + } + + tr.lightGridJobs.Append( jobParms ); + + progressBar.Increment(); + } + } + } + + int end = Sys_Milliseconds(); + + common->Printf( "captured light grid radiance for area %i in %5.1f seconds\n\n", a, ( end - start ) * 0.001f ); + + //-------------------------------------------- + // GENERATE IRRADIANCE + //-------------------------------------------- + + if( !useThreads ) + { + progressBar.Reset( tr.lightGridJobs.Num() ); + progressBar.Start(); + } + + start = Sys_Milliseconds(); + + for( int j = 0; j < tr.lightGridJobs.Num(); j++ ) + { + calcLightGridPointParms_t* jobParms = tr.lightGridJobs[ j ]; + + jobParms->outWidth = LIGHTGRID_IRRADIANCE_SIZE; + jobParms->outHeight = LIGHTGRID_IRRADIANCE_SIZE; + jobParms->outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( LIGHTGRID_IRRADIANCE_SIZE * LIGHTGRID_IRRADIANCE_SIZE * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE ); + + if( useThreads ) + { + tr.envprobeJobList->AddJob( ( jobRun_t )CalculateLightGridPointJob, jobParms ); + } + else + { + CalculateLightGridPointJob( jobParms ); + progressBar.Increment(); + } + } + + if( useThreads ) + { + //tr.envprobeJobList->Submit(); + tr.envprobeJobList->Submit( NULL, JOBLIST_PARALLELISM_MAX_CORES ); + tr.envprobeJobList->Wait(); + } + + + + int atlasWidth = area->lightGrid.lightGridBounds[0] * area->lightGrid.lightGridBounds[2] * LIGHTGRID_IRRADIANCE_SIZE; + int atlasHeight = area->lightGrid.lightGridBounds[1] * LIGHTGRID_IRRADIANCE_SIZE; + + idTempArray irradianceAtlas( atlasWidth * atlasHeight * 3 ); + + // fill everything with solid black + for( int i = 0; i < ( atlasWidth * atlasHeight ); i++ ) + { + irradianceAtlas[i * 3 + 0] = F32toF16( 0.0f ); + irradianceAtlas[i * 3 + 1] = F32toF16( 0.0f ); + irradianceAtlas[i * 3 + 2] = F32toF16( 0.0f ); + } + + for( int j = 0; j < tr.lightGridJobs.Num(); j++ ) + { + calcLightGridPointParms_t* job = tr.lightGridJobs[ j ]; + + for( int x = 0; x < LIGHTGRID_IRRADIANCE_SIZE; x++ ) + { + for( int y = 0; y < LIGHTGRID_IRRADIANCE_SIZE; y++ ) + { + int xx = x + ( job->gridCoord[0] * gridStep[0] + job->gridCoord[2] * gridStep[1] ) * LIGHTGRID_IRRADIANCE_SIZE; + int yy = y + job->gridCoord[1] * LIGHTGRID_IRRADIANCE_SIZE; + + irradianceAtlas[( yy * atlasWidth + xx ) * 3 + 0] = job->outBuffer[( y * LIGHTGRID_IRRADIANCE_SIZE + x ) * 3 + 0]; + irradianceAtlas[( yy * atlasWidth + xx ) * 3 + 1] = job->outBuffer[( y * LIGHTGRID_IRRADIANCE_SIZE + x ) * 3 + 1]; + irradianceAtlas[( yy * atlasWidth + xx ) * 3 + 2] = job->outBuffer[( y * LIGHTGRID_IRRADIANCE_SIZE + x ) * 3 + 2]; + } + } + + // backup SH L3 data + lightGridPoint_t* gridPoint = &area->lightGrid.lightGridPoints[ job->gridCoord[0] * gridStep[0] + job->gridCoord[1] * gridStep[1] + job->gridCoord[2] * gridStep[2] ]; + for( int i = 0; i < shSize( 3 ); i++ ) + { + gridPoint->shRadiance[i] = job->shRadiance[i]; + } + + for( int i = 0; i < 6; i++ ) + { + if( job->radiance[i] ) + { + Mem_Free( job->radiance[i] ); + } + } + + Mem_Free( job->outBuffer ); + + delete job; + } + + filename.Format( "env/%s/area%i_lightgrid_amb.exr", baseName.c_str(), a ); + + R_WriteEXR( filename.c_str(), irradianceAtlas.Ptr(), 3, atlasWidth, atlasHeight, "fs_basepath" ); + + tr.lightGridJobs.Clear(); + + end = Sys_Milliseconds(); + + common->Printf( "computed light grid irradiance for area %i in %5.1f seconds\n\n", a, ( end - start ) * 0.001f ); +#endif + } + + + int totalEnd = Sys_Milliseconds(); + + common->Printf( "Baked light grid irradiance in %5.1f minutes\n\n", ( totalEnd - totalStart ) / ( 1000.0f * 60 ) ); + + // everything went ok so let's save the configurations to disc + // so we can load the texture atlases with the correct subdivisions next time + filename.Format( "%s.lightgrid", baseName.c_str() ); + tr.primaryWorld->WriteLightGridsToFile( filename ); + + tr.primaryWorld->LoadLightGridImages(); +} + +#if 0 +// straight port of Quake 3 / XreaL +void idRenderWorldLocal::SetupEntityGridLighting( idRenderEntityLocal* def ) +{ + // lighting calculations +#if 0 + if( def->lightgridCalculated ) + { + return; + } + + def->lightgridCalculated = true; +#endif + + if( lightGridPoints.Num() > 0 ) + { + idVec3 lightOrigin; + int pos[3]; + int i, j; + int gridPointIndex; + lightGridPoint_t* gridPoint; + lightGridPoint_t* gridPoint2; + float frac[3]; + int gridStep[3]; + idVec3 direction; + idVec3 direction2; + float lattitude; + float longitude; + float totalFactor; + +#if 0 + if( forcedOrigin ) + { + VectorCopy( forcedOrigin, lightOrigin ); + } + else + { + if( ent->e.renderfx & RF_LIGHTING_ORIGIN ) + { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } + else + { + VectorCopy( ent->e.origin, lightOrigin ); + } + } +#else + // some models, like empty particles have no volume +#if 1 + lightOrigin = def->parms.origin; +#else + if( def->referenceBounds.IsCleared() ) + { + lightOrigin = def->parms.origin; + } + else + { + lightOrigin = def->volumeMidPoint; + } +#endif + +#endif + + lightOrigin -= lightGridOrigin; + for( i = 0; i < 3; i++ ) + { + float v; + + v = lightOrigin[i] * ( 1.0f / lightGridSize[i] ); + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if( pos[i] < 0 ) + { + pos[i] = 0; + } + else if( pos[i] >= lightGridBounds[i] - 1 ) + { + pos[i] = lightGridBounds[i] - 1; + } + } + + def->ambientLight.Zero(); + def->directedLight.Zero(); + direction.Zero(); + + // trilerp the light value + gridStep[0] = 1; + gridStep[1] = lightGridBounds[0]; + gridStep[2] = lightGridBounds[0] * lightGridBounds[1]; + + gridPointIndex = pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + gridPoint = &lightGridPoints[ gridPointIndex ]; + + totalFactor = 0; + for( i = 0; i < 8; i++ ) + { + float factor; + + factor = 1.0; + gridPoint2 = gridPoint; + for( int j = 0; j < 3; j++ ) + { + if( i & ( 1 << j ) ) + { + factor *= frac[j]; + +#if 1 + gridPointIndex2 += gridStep[j]; + if( gridPointIndex2 < 0 || gridPointIndex2 >= area->lightGrid.lightGridPoints.Num() ) + { + // ignore values outside lightgrid + continue; + } + + gridPoint2 = &area->lightGrid.lightGridPoints[ gridPointIndex2 ]; +#else + if( pos[j] + 1 > area->lightGrid.lightGridBounds[j] - 1 ) + { + // ignore values outside lightgrid + break; + } + + gridPoint2 += gridStep[j]; +#endif + } + else + { + factor *= ( 1.0f - frac[j] ); + } + } + + if( !( gridPoint2->ambient[0] + gridPoint2->ambient[1] + gridPoint2->ambient[2] ) ) + { + continue; // ignore samples in walls + } + + totalFactor += factor; + + def->ambientLight[0] += factor * gridPoint2->ambient[0] * ( 1.0f / 255.0f ); + def->ambientLight[1] += factor * gridPoint2->ambient[1] * ( 1.0f / 255.0f ); + def->ambientLight[2] += factor * gridPoint2->ambient[2] * ( 1.0f / 255.0f ); + + def->directedLight[0] += factor * gridPoint2->directed[0] * ( 1.0f / 255.0f ); + def->directedLight[1] += factor * gridPoint2->directed[1] * ( 1.0f / 255.0f ); + def->directedLight[2] += factor * gridPoint2->directed[2] * ( 1.0f / 255.0f ); + + lattitude = DEG2RAD( gridPoint2->latLong[1] * ( 360.0f / 255.0f ) ); + longitude = DEG2RAD( gridPoint2->latLong[0] * ( 360.0f / 255.0f ) ); + + direction2[0] = idMath::Cos( lattitude ) * idMath::Sin( longitude ); + direction2[1] = idMath::Sin( lattitude ) * idMath::Sin( longitude ); + direction2[2] = idMath::Cos( longitude ); + + direction += ( direction2 * factor ); + + //direction += ( gridPoint2->dir * factor ); + } + +#if 1 + if( totalFactor > 0 && totalFactor < 0.99 ) + { + totalFactor = 1.0f / totalFactor; + def->ambientLight *= totalFactor; + def->directedLight *= totalFactor; + } +#endif + + def->ambientLight[0] = idMath::ClampFloat( 0, 1, def->ambientLight[0] ); + def->ambientLight[1] = idMath::ClampFloat( 0, 1, def->ambientLight[1] ); + def->ambientLight[2] = idMath::ClampFloat( 0, 1, def->ambientLight[2] ); + + def->directedLight[0] = idMath::ClampFloat( 0, 1, def->directedLight[0] ); + def->directedLight[1] = idMath::ClampFloat( 0, 1, def->directedLight[1] ); + def->directedLight[2] = idMath::ClampFloat( 0, 1, def->directedLight[2] ); + + def->lightDir = direction; + def->lightDir.Normalize(); + +#if 0 + if( VectorLength( ent->ambientLight ) < r_forceAmbient->value ) + { + ent->ambientLight[0] = r_forceAmbient->value; + ent->ambientLight[1] = r_forceAmbient->value; + ent->ambientLight[2] = r_forceAmbient->value; + } +#endif + } +} + +#endif \ No newline at end of file diff --git a/neo/renderer/RenderWorld_load.cpp b/neo/renderer/RenderWorld_load.cpp index 8fd2de60..214cda72 100644 --- a/neo/renderer/RenderWorld_load.cpp +++ b/neo/renderer/RenderWorld_load.cpp @@ -858,6 +858,7 @@ bool idRenderWorldLocal::InitFromMap( const char* name ) TouchWorldModels(); AddWorldModelEntities(); ClearPortalStates(); + SetupLightGrid(); return true; } common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" ); @@ -1047,6 +1048,7 @@ bool idRenderWorldLocal::InitFromMap( const char* name ) AddWorldModelEntities(); ClearPortalStates(); + SetupLightGrid(); // done! return true; diff --git a/neo/renderer/RenderWorld_local.h b/neo/renderer/RenderWorld_local.h index 7881eb8f..512d2438 100644 --- a/neo/renderer/RenderWorld_local.h +++ b/neo/renderer/RenderWorld_local.h @@ -3,7 +3,7 @@ Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. -Copyright (C) 2014-2016 Robert Beckebans +Copyright (C) 2014-2021 Robert Beckebans Copyright (C) 2014-2016 Kot in Action Creative Artel This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). @@ -59,6 +59,57 @@ typedef struct doublePortal_s struct doublePortal_s* nextFoggedPortal; } doublePortal_t; +// RB: added Quake 3 style light grid +// however this 2021 version features Spherical Harmonics instead of ambient + directed color +struct lightGridPoint_t +{ + idVec3 origin; // not saved to .proc + byte valid; // is not in the void + + SphericalHarmonicsT shRadiance; // L4 Spherical Harmonics +}; + +class LightGrid +{ +public: + idVec3 lightGridOrigin; + idVec3 lightGridSize; + int lightGridBounds[3]; + + idList lightGridPoints; + + int area; + idImage* irradianceImage; + int imageSingleProbeSize; // including border + int imageBorderSize; + + LightGrid(); + + // setup light grid for given world bounds + void SetupLightGrid( const idBounds& bounds, const char* baseName, const idRenderWorld* world, int _area, int limit ); + + void GetBaseGridCoord( const idVec3& origin, int gridCoord[3] ); + + int GridCoordToProbeIndex( int gridCoord[3] ); + void ProbeIndexToGridCoord( const int probeIndex, int gridCoord[3] ); + + idVec3 GetGridCoordDebugColor( int gridCoord[3] ); + idVec3 GetProbeIndexDebugColor( const int probeIndex ); + + int CountValidGridPoints() const; + + idImage* GetIrradianceImage() const + { + return irradianceImage; + } + + // fetch grid lighting on a per object basis + void SetupEntityGridLighting( idRenderEntityLocal* def ); + +private: + void CalculateLightGridPointPositions( const idRenderWorld* world, int area ); +}; +// RB end typedef struct portalArea_s { @@ -68,6 +119,8 @@ typedef struct portalArea_s idBounds globalBounds; // RB: AABB of the BSP area used for light grid density + LightGrid lightGrid; + int viewCount; // set by R_FindViewLightsAndEntities portal_t* portals; // never changes after load areaReference_t entityRefs; // head/tail of doubly linked list, may change @@ -337,6 +390,24 @@ public: //------------------------------- // tr_light.c void CreateLightDefInteractions( idRenderLightLocal* const ldef, const int renderViewID ); + +// RB begin + + //-------------------------- + // RenderWorld_lightgrid.cpp + +//private: + void SetupLightGrid(); + + void WriteLightGridsToFile( const char* filename ); + void WriteLightGrid( idFile* fp, const LightGrid& lightGrid ); + + bool LoadLightGridFile( const char* name ); + void LoadLightGridImages(); + + void ParseLightGridPoints( idLexer* src, idFile* fileOut ); + void ReadBinaryLightGridPoints( idFile* file ); +// RB end }; // if an entity / light combination has been evaluated and found to not genrate any surfaces or shadows, diff --git a/neo/renderer/tr_frontend_addmodels.cpp b/neo/renderer/tr_frontend_addmodels.cpp index 3d09e054..c73e8fd4 100644 --- a/neo/renderer/tr_frontend_addmodels.cpp +++ b/neo/renderer/tr_frontend_addmodels.cpp @@ -373,6 +373,9 @@ void R_AddSingleModel( viewEntity_t* vEntity ) vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; + // RB + vEntity->useLightGrid = false; + // globals we really should pass in... const viewDef_t* viewDef = tr.viewDef; @@ -549,6 +552,32 @@ void R_AddSingleModel( viewEntity_t* vEntity ) } } + // RB: use first valid lightgrid + for( areaReference_t* ref = entityDef->entityRefs; ref != NULL; ref = ref->ownerNext ) + { + idImage* lightGridImage = ref->area->lightGrid.GetIrradianceImage(); + + if( ref->area->lightGrid.lightGridPoints.Num() && lightGridImage && !lightGridImage->IsDefaulted() ) + { + vEntity->useLightGrid = true; + vEntity->lightGridAtlasImage = lightGridImage; + vEntity->lightGridAtlasSingleProbeSize = ref->area->lightGrid.imageSingleProbeSize; + vEntity->lightGridAtlasBorderSize = ref->area->lightGrid.imageBorderSize; + + for( int i = 0; i < 3; i++ ) + { + vEntity->lightGridOrigin[i] = ref->area->lightGrid.lightGridOrigin[i]; + vEntity->lightGridSize[i] = ref->area->lightGrid.lightGridSize[i]; + vEntity->lightGridBounds[i] = ref->area->lightGrid.lightGridBounds[i]; + } + + break; + } + } + + + // RB end + //--------------------------- // copy matrix related stuff for back-end use // and setup a render matrix for faster culling diff --git a/neo/renderer/tr_frontend_main.cpp b/neo/renderer/tr_frontend_main.cpp index e9199c01..82a60885 100644 --- a/neo/renderer/tr_frontend_main.cpp +++ b/neo/renderer/tr_frontend_main.cpp @@ -454,6 +454,121 @@ static void R_SetupSplitFrustums( viewDef_t* viewDef ) } } } + +class idSort_CompareEnvprobe : public idSort_Quick< RenderEnvprobeLocal*, idSort_CompareEnvprobe > +{ + idVec3 viewOrigin; + +public: + idSort_CompareEnvprobe( const idVec3& origin ) + { + viewOrigin = origin; + } + + int Compare( RenderEnvprobeLocal* const& a, RenderEnvprobeLocal* const& b ) const + { + float adist = ( viewOrigin - a->parms.origin ).LengthSqr(); + float bdist = ( viewOrigin - b->parms.origin ).LengthSqr(); + + if( adist < bdist ) + { + return -1; + } + + if( adist > bdist ) + { + return 1; + } + + return 0; + } +}; + +static void R_FindClosestEnvironmentProbes() +{ + // set safe defaults + tr.viewDef->globalProbeBounds.Clear(); + + tr.viewDef->irradianceImage = globalImages->defaultUACIrradianceCube; + tr.viewDef->radianceImageBlends.Set( 1, 0, 0, 0 ); + for( int i = 0; i < 3; i++ ) + { + tr.viewDef->radianceImages[i] = globalImages->defaultUACRadianceCube; + } + + // early out + if( tr.viewDef->areaNum == -1 || tr.viewDef->isSubview ) + { + return; + } + + idList viewEnvprobes; + for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ ) + { + RenderEnvprobeLocal* vProbe = tr.primaryWorld->envprobeDefs[i]; + if( vProbe ) + { + viewEnvprobes.AddUnique( vProbe ); + } + } + + if( viewEnvprobes.Num() == 0 ) + { + return; + } + + idVec3 testOrigin = tr.viewDef->renderView.vieworg; + + // sort by distance + // RB: each Doom 3 level has ~50 - 150 probes so this should be ok for each frame + viewEnvprobes.SortWithTemplate( idSort_CompareEnvprobe( testOrigin ) ); + + RenderEnvprobeLocal* nearest = viewEnvprobes[0]; + tr.viewDef->globalProbeBounds = nearest->globalProbeBounds; + tr.viewDef->irradianceImage = nearest->irradianceImage; + + // form a triangle of the 3 closest probes + idVec3 verts[3]; + for( int i = 0; i < 3; i++ ) + { + verts[i] = viewEnvprobes[0]->parms.origin; + } + + for( int i = 0; i < viewEnvprobes.Num() && i < 3; i++ ) + { + RenderEnvprobeLocal* vProbe = viewEnvprobes[i]; + + verts[i] = vProbe->parms.origin; + } + + idVec3 closest = R_ClosestPointPointTriangle( testOrigin, verts[0], verts[1], verts[2] ); + idVec3 bary; + + // find the barycentric coordinates + float denom = idWinding::TriangleArea( verts[0], verts[1], verts[2] ); + if( denom == 0 ) + { + // all points at same location + bary.Set( 1, 0, 0 ); + } + else + { + float a, b, c; + + a = idWinding::TriangleArea( closest, verts[1], verts[2] ) / denom; + b = idWinding::TriangleArea( closest, verts[2], verts[0] ) / denom; + c = idWinding::TriangleArea( closest, verts[0], verts[1] ) / denom; + + bary.Set( a, b, c ); + } + + tr.viewDef->radianceImageBlends.Set( bary.x, bary.y, bary.z, 0.0f ); + + for( int i = 0; i < viewEnvprobes.Num() && i < 3; i++ ) + { + tr.viewDef->radianceImages[i] = viewEnvprobes[i]->radianceImage; + } +} // RB end /* @@ -539,32 +654,8 @@ void R_RenderView( viewDef_t* parms ) } } - // RB: find closest environment probe - if( tr.viewDef->areaNum != -1 && !tr.viewDef->isSubview ) - { - float bestDist = idMath::INFINITY; - - tr.viewDef->globalProbeBounds.Clear(); - - tr.viewDef->irradianceImage = globalImages->defaultUACIrradianceCube; - tr.viewDef->radianceImage = globalImages->defaultUACRadianceCube; - - for( viewEnvprobe_t* vProbe = tr.viewDef->viewEnvprobes; vProbe != NULL; vProbe = vProbe->next ) - { - float dist = ( tr.viewDef->renderView.vieworg - vProbe->globalOrigin ).Length(); - if( ( dist < bestDist ) ) - { - if( vProbe->irradianceImage->IsLoaded() && !vProbe->irradianceImage->IsDefaulted() ) - { - tr.viewDef->globalProbeBounds = vProbe->globalProbeBounds; - tr.viewDef->irradianceImage = vProbe->irradianceImage; - tr.viewDef->radianceImage = vProbe->radianceImage; - - bestDist = dist; - } - } - } - } + // RB: find closest environment probes so we can interpolate between them in the ambient shaders + R_FindClosestEnvironmentProbes(); // write everything needed to the demo file if( common->WriteDemo() ) diff --git a/neo/renderer/tr_trisurf.cpp b/neo/renderer/tr_trisurf.cpp index 8800676e..d77554a7 100644 --- a/neo/renderer/tr_trisurf.cpp +++ b/neo/renderer/tr_trisurf.cpp @@ -2367,4 +2367,82 @@ static void SetUpMikkTSpaceContext( SMikkTSpaceContext* context ) context->m_pInterface = &mikkTSpaceInterface.mkInterface; } -// SP end \ No newline at end of file +// SP end + + +// RB: Determines the closest point between a point and a triangle +idVec3 R_ClosestPointPointTriangle( const idVec3& point, const idVec3& vertex1, const idVec3& vertex2, const idVec3& vertex3 ) +{ + idVec3 result; + + // Source: Real-Time Collision Detection by Christer Ericson + // Reference: Page 136 + + // check if P in vertex region outside A + idVec3 ab = vertex2 - vertex1; + idVec3 ac = vertex3 - vertex1; + idVec3 ap = point - vertex1; + + float d1 = ( ab * ap ); + float d2 = ( ac * ap ); + if( d1 <= 0.0f && d2 <= 0.0f ) + { + result = vertex1; //Barycentric coordinates (1,0,0) + return result; + } + + // Check if P in vertex region outside B + idVec3 bp = point - vertex2; + float d3 = ( ab * bp ); + float d4 = ( ac * bp ); + if( d3 >= 0.0f && d4 <= d3 ) + { + result = vertex2; // barycentric coordinates (0,1,0) + return result; + } + + // Check if P in edge region of AB, if so return projection of P onto AB + float vc = d1 * d4 - d3 * d2; + if( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) + { + float v = d1 / ( d1 - d3 ); + result = vertex1 + v * ab; //Barycentric coordinates (1-v,v,0) + return result; + } + + // Check if P in vertex region outside C + idVec3 cp = point - vertex3; + float d5 = ( ab * cp ); + float d6 = ( ac * cp ); + if( d6 >= 0.0f && d5 <= d6 ) + { + result = vertex3; //Barycentric coordinates (0,0,1) + return result; + } + + // Check if P in edge region of AC, if so return projection of P onto AC + float vb = d5 * d2 - d1 * d6; + if( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) + { + float w = d2 / ( d2 - d6 ); + result = vertex1 + w * ac; //Barycentric coordinates (1-w,0,w) + return result; + } + + // Check if P in edge region of BC, if so return projection of P onto BC + float va = d3 * d6 - d5 * d4; + if( va <= 0.0f && ( d4 - d3 ) >= 0.0f && ( d5 - d6 ) >= 0.0f ) + { + float w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); + result = vertex2 + w * ( vertex3 - vertex2 ); //Barycentric coordinates (0,1-w,w) + return result; + } + + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + float denom = 1.0f / ( va + vb + vc ); + float v2 = vb * denom; + float w2 = vc * denom; + result = vertex1 + ab * v2 + ac * w2; //= u*vertex1 + v*vertex2 + w*vertex3, u = va * denom = 1.0f - v - w + + return result; +} \ No newline at end of file diff --git a/neo/tools/compilers/dmap/map.cpp b/neo/tools/compilers/dmap/map.cpp index e2024788..82a41a50 100644 --- a/neo/tools/compilers/dmap/map.cpp +++ b/neo/tools/compilers/dmap/map.cpp @@ -3,7 +3,7 @@ Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. -Copyright (C) 2013-2015 Robert Beckebans +Copyright (C) 2013-2021 Robert Beckebans This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).