前几天,想看看AMD以前的技术演示,看到了这个demo。可惜没有AMD的显卡,看不到的实际效果。网上资料找了半天没有找到,昨天刚刚在桌面上发现了一个ppt,Technology Behind AMD’s “Leo Demo”。去年在GDC上发布的ppt,关于leo Demo的光照。
Leo Demo没有选择延迟渲染,原因有三点,一是材质太复杂,二是灯光总类都,三是支持半透明。其中用到了light culling,思想是将世界坐标系内的灯光在屏幕坐标系内进行分割,分成一个个小的tile,每个tile存储自己的光照信息。其实和战地3里的方法差不多,只不过战地3用的是延迟渲染。
然后在渲染像素的时候找到对应的tile取里面的光照信息,完成光照计算,输出结果。其中很重要的一点事使用了UAV(unorder access view)来存储光照信息,没有这个功能是无法实现的。其实很简单,看看就可以了。
代码如下:
// 1. prepare
float4 frustum[4];
float minZ, maxZ;
{
ConstructFrustum( frustum );
minZ = thread_REDUCE(MIN, depth );
maxZ = thread_REDUCE(MAX, depth );
ldsMinZ = SIMD_REDUCE(MIN, minZ );
ldsMaxZ = SIMD_REDUCE(MAX, maxZ );
minZ = ldsMinZ;
maxZ = ldsMaxZ;
}
_local u32 ldsNLights = 0;
__local u32 ldsLightBuffer[MAX];
// 2. overlap check, accumulate in LDS
for(int i=threadIdx; i<nLights; i+=WG_SIZE)
{
Light light = fetchAndTransform( lightBuffer[ i ] );
if( overlaps( light, frustum ) && overlaps ( light, minZ, maxZ ) )
{
AtomicAppend( ldsLightBuffer, i );
}
}
// 3. export to global
__local u32 ldsOffset;
if( threadIdx == 0 )
{
ldsOffset = AtomAdd( ldsNLights );
globalLightStart[tileIdx] = ldsOffset;
globalLightEnd[tileIdx] = ldsOffset + ldsNLights;
}
for(int i=threadIdx; i< ldsNLights; i+=WG_SIZE)
{
int dstIdx = ldsOffset + i;
globalLightIndexBuffer[dstIdx] = ldsLightBuffer[i];
}
// BaseLighting.inc // THIS INC FILE IS ALL THE COMMON LIGHTING CODE
StructuredBuffer<float4> LightParams : register(u0);
StructuredBuffer<uint> LowerBoundLights : register(u1);
StructuredBuffer<uint> UpperBoundLights : register(u2);
StructuredBuffer<int2> LightIndexBuffer : register(u3);
uint GetTileIndex(float2 screenPos)
{
float tileRes = (float)m_tileRes;
uint numCellsX = (m_width + m_tileRes - 1)/m_tileRes;
uint tileIdx = floor(screenPos.x/tileRes)+floor(screenPos.y/tileRes)*numCellsX;
return tileIdx;
}
StartHLSL BaseLightLoopBegin // THIS IS A MACRO, INCLUDED IN MATERIAL SHADERS
uint tileIdx = GetTileIndex( pixelScreenPos );
uint startIdx = LowerBoundLights[tileIdx];
uint endIdx = UppweBoundLights[tileIdx];
[loop]
for ( uint lightListIdx = startIdx; lightListIdx < endIdx; lightListIdx++ )
{
int lightIdx = LightIndexBuffer[lightListIdx];
// Set common light parameters
float ndotl = max(0, dot(normal, lightVec));
float3 directLight = 0;
float3 indirectLight = 0;
if( lightIdx >= numDirectLightsThisFrame ) {
CalculateIndirectLight(lightIdx , indirectLight);
} else {
if( IsConeLight( lightIdx ) ) { // <<== Can add more light types here
CalculateDirectSpotlight(lightIdx , directLight);
} else {
CalculateDirectSpherelight(lightIdx , directLight);
}
}
float3 incomingLight = (directLight + indirectLight)*ndotl;
float shadowTerm = CalcShadow();
EndHLSL
StartHLSL BaseLightLoopEnd
}
EndHLSL
#include "BaseLighting.inc"
float4 PS ( PSInput i ) : SV_TARGET
{
float3 totalDiffuse = 0;
float3 totalSpec = GetEnvLighting();;
$include BaseLightLoopBegin
// unique material code goes here!! Light accumulation on the pixel for a given light
// we have total incoming light and direct/indirect light components as well as material params and shadow term
// use these building blocks to integrate lighting terms
totalDiffuse += GetDiffuse(incomingLight);
totalSpec += CalcPhong(incomingLight);
$include BaseLightLoopEnd
float3 finalColor = totalDiffuse + totalSpec;
return float4( finalColor, 1 );
}