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?).