Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
Одним из наиболее распространенных методов рендеринга, позволяющих использовать большое число источников света, является так называемое отложенное освещение (Deferred Shading). При его использование удается отделить геометрическую сложность сцены от количества источников света.
При использовании этого метода все сцена выводится в так называемый G-буфер. В результате этого для каждого пиксела экрана в G-буфере хранится вся информация, необходимая для расчета освещенности данного пиксела. В число таких атрибутов обычно входят 3D-координаты, соответствующие данному пикселу, вектор нормали, цвет, светимость и т.п. На следующей таблице приводится структура G-буфера, использованная в игре STALKER: Shadow Of Chernobyl. Там G-буфер состоит из трех текстур RGBA_16F.
Таблица 1. Строение G-буфера в игре STALKER: Shadow Of Chernobyl.
Номер | Компоненты | Комментарий |
---|---|---|
0 | RGB | Единичный вектор нормали в точке (nx,ny,nz) |
0 | A | Ambient occlusion hemi |
1 | RGB | Координаты исходной точки (x,y,z) |
1 | A | Индекс материала |
2 | RGB | Диффузный цвет (dred,dgreen,dblue) |
2 | A | Интенсивность бликов gloss |
После того, как G-буфер построен, производится расчет освещения с учетом всех источников света. При этом расчет освещения фактически является вариантом постпроцессинга.
Отложенное освещение позволяет полностью отделить геометрическую сложность сцены от расчета ее освещения. При этом расчет освещения производится только для видимых фрагментов. Кроме того, имеющийся G-буфер облегчает создание ряда спецэффектов.
Однако у этого метода есть и свои недостатки. К ним в первую очередь относится высокие требования на пропускную способность памяти из-за большого объема памяти (bandwidth), выделяемого под G-буфер. Также структура G-буфера накладывает некоторые ограничения на используемые модели освещения. Кроме того, довольно сложно использовать MSAA (в отличии от традиционного, forward рендеринга).
Light Pre-Pass Rendering является модификацией отложенного освещения, смягчающей требования к памяти и предоставляющей большую гибкость в использовании материалов (правда за счет того, что геометрию необходимо выводить два раза).
Подобный подход был использован в таких играх, как Resistance 2, Uncharted, Blur и ряде других.
В простейшем варианте LPP Rendering работает следующим образом:
Рассмотрим теперь каким образом можно реализовать данный подход. В качестве G-буфера достаточно взять одну текстуру формата RGBA_16F - первые три компоненты можно использовать для хранения вектора нормали, а в альфа-компоненте - хранить zeye.
Ниже приводится вершинный шейдер для первого прохода.
varying vec3 pos;
varying vec3 n;
varying vec3 t;
varying vec3 b;
void main(void)
{
pos = vec3 ( gl_ModelViewMatrix * gl_Vertex ); // transformed point to world space
n = normalize ( gl_NormalMatrix * gl_Normal ); // transformed n
t = normalize ( gl_NormalMatrix * gl_MultiTexCoord1.xyz );
b = normalize ( gl_NormalMatrix * gl_MultiTexCoord2.xyz );
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord [0] = gl_MultiTexCoord0;
}
Следующий листинг содержит соответствующий фрагментный шейдер.
varying vec3 pos;
varying vec3 n;
varying vec3 t;
varying vec3 b;
uniform sampler2D bumpMap;
void main (void)
{
vec3 nn = 2.0*texture2D ( bumpMap, gl_TexCoord [0].xy ).xyz - vec3 ( 1.0 );
gl_FragData [0] = vec4 ( normalize ( nn.x * t + nn.y * b + nn.z * n ), pos.z );
}
В качестве буфера освещения также можно использовать текстуру формата RGBA_16F - первые три компоненты будут суммировать диффузную освещенность (для каждого из RGB каналов отдельно), а в альфа-канале будет суммироваться яркость бликов.
В этих уравнениях через n обозначена нормаль в точек, через li - единичный вектор из точки к i-му источнику света. Через IRGB,i обозначен цвет и яркость i-го источника света. atti обозначает зависимость освещенности от расстояния до i-го источника света. Через lum обозначена функция возвращающая яркость для заданного RGB-значения.
Ниже приводится фрагментный шейдер, вычисляющий освещенность (2-й проход). Для простоты здесь не учитывается зависимость освещенности от расстояние до источника света. Вершинный шейдер тривиален и не приводится.
#extension GL_ARB_texture_rectangle: enable
varying vec3 pos;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform sampler2DRect dataMap;
void main (void)
{
vec4 nz = texture2DRect ( dataMap, gl_FragCoord.xy );
vec3 n = nz.xyz;
float z = nz.a;
vec3 pp = pos * z / pos.z;
vec3 l = normalize ( lightPos - pp );
vec3 v = normalize ( -pp );
vec3 h = normalize ( l + v );
float diff = max ( 0.1, dot ( l, n ) );
float lum = dot ( lightColor, vec3 ( 0.3, 0.59, 0.11 ) );
float spec = lum * pow ( max ( 0.0, dot ( h, n ) ), 40.0 );
gl_FragColor = vec4 ( diff * lightColor.rgb, spec );
}
На последнем проходе на выводимую геометрию накладывается освещение из буфера освещения. При этом диффузное освещение просто умножается на цвет фрагмента. А вот бликовое освещение в буфере освещения хранится только как суммарная яркость, которая используется для модулирования суммарного диффузного освещения.
Для этого сначала нормируется суммарное диффузное освещение (chromaticity), а потом умножается на суммарную бликовую яркость (хранящуюся в альфа-компоненте).
На следующем листинге приводится фрагментный шейдер, соответствующий этому проходу.
//
// LPP pass 3, apply lighting G-buffer
//
#define EPS 0.001
uniform sampler2D decalMap;
uniform sampler2DRect lightMap;
void main (void)
{
vec4 c = texture2D ( decalMap, gl_TexCoord [0].xy );
vec4 l = texture2DRect ( lightMap, gl_FragCoord.xy );
vec3 diff = l.rgb;
vec3 chrom = diff / ( EPS + dot ( diff, vec3 ( 0.3, 0.59, 0.11 ) ) );
vec3 spec = chrom * l.a;
gl_FragColor = vec4 ( diff * c.xyz + 0.6 * spec, 1.0 );
}
Ниже приводится скриншот программы, использующей данный подход и всего один неподвижный источник света.
Рис 1. Простейший случай использования LPP.
Ниже приводится неполный код на С++, использованный для получения изображения на рис 1 (ряд функций, не имеющих прямого отношения к рассматриваемому методу, опущены).
#include "libExt.h"
#ifdef MACOSX
#include <GLUT/glut.h>
#else
#include <glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include "libTexture.h"
#include "TypeDefs.h"
#include "Vector3D.h"
#include "Vector2D.h"
#include "boxes.h"
#include "FrameBuffer.h"
#include "GlslProgram.h"
#include "utils.h"
#include "Camera.h"
#include "fpTexture.h"
Vector3D eye ( -0.5, -0.5, 1.5 ); // camera position
Vector3D light ( -0.5, -0.5, 1.5 ); // light position
Vector3D lightColor ( 1, 1, 1 ); // light color
unsigned stoneMap, woodMap, teapotMap, decalMap;
unsigned bumpMap, bumpMap2, bumpMap3;
GLenum format;
float angle = 0;
float yaw = 0;
float pitch = 0;
float roll = 0;
Camera camera ( eye, 0, 0, 0, 90, 0.01 ); // camera to be used
FrameBuffer buffer ( 512, 512, FrameBuffer :: depth32 );
FrameBuffer buffer2 ( 512, 512 );
GlslProgram program1; // build G-buffer
GlslProgram program2; // build light buffer
GlslProgram program3; // render scene applying lighting
void displayBoxes ();
void reshape ( int w, int h );
void drawBoxTBN ( const Vector3D& pos, const Vector3D& size, unsigned texture, bool cull )
{
float x2 = pos.x + size.x;
float y2 = pos.y + size.y;
float z2 = pos.z + size.z;
float ns = cull ? 1 : -1;
glBindTexture ( GL_TEXTURE_2D, texture );
glEnable ( GL_TEXTURE_2D );
if ( cull )
{
glCullFace ( GL_BACK );
glEnable ( GL_CULL_FACE );
}
else
glDisable ( GL_CULL_FACE );
glBegin ( GL_QUADS );
// front face
glNormal3f ( 0, 0, ns );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( 1, 0, 0 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, 1, 0 ) );
glTexCoord2f ( 0, 0 );
glVertex3f ( pos.x, pos.y, z2 );
glTexCoord2f ( size.x, 0 );
glVertex3f ( x2, pos.y, z2 );
glTexCoord2f ( size.x, size.y );
glVertex3f ( x2, y2, z2 );
glTexCoord2f ( 0, size.y );
glVertex3f ( pos.x, y2, z2 );
// back face
glNormal3f ( 0, 0, -ns );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( -1, 0, 0 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, -1, 0 ) );
glTexCoord2f ( size.x, 0 );
glVertex3f ( x2, pos.y, pos.z );
glTexCoord2f ( 0, 0 );
glVertex3f ( pos.x, pos.y, pos.z );
glTexCoord2f ( 0, size.y );
glVertex3f ( pos.x, y2, pos.z );
glTexCoord2f ( size.x, size.y );
glVertex3f ( x2, y2, pos.z );
// left face
glNormal3f ( -ns, 0, 0 );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( 0, 0, -1 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, -1, 0 ) );
glTexCoord2f ( 0, 0 );
glVertex3f ( pos.x, pos.y, pos.z );
glTexCoord2f ( 0, size.z );
glVertex3f ( pos.x, pos.y, z2 );
glTexCoord2f ( size.y, size.z );
glVertex3f ( pos.x, y2, z2 );
glTexCoord2f ( size.y, 0 );
glVertex3f ( pos.x, y2, pos.z );
// right face
glNormal3f ( ns, 0, 0 );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( 1, 0, 0 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, 1, 0 ) );
glTexCoord2f ( 0, size.z );
glVertex3f ( x2, pos.y, z2 );
glTexCoord2f ( 0, 0 );
glVertex3f ( x2, pos.y, pos.z );
glTexCoord2f ( size.y, 0 );
glVertex3f ( x2, y2, pos.z );
glTexCoord2f ( size.y, size.z );
glVertex3f ( x2, y2, z2 );
// top face
glNormal3f ( 0, ns, 0 );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( 1, 0, 0 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, 0, 1 ) );
glTexCoord2f ( 0, size.z );
glVertex3f ( pos.x, y2, z2 );
glTexCoord2f ( size.x, size.z );
glVertex3f ( x2, y2, z2 );
glTexCoord2f ( size.x, 0 );
glVertex3f ( x2, y2, pos.z );
glTexCoord2f ( 0, 0 );
glVertex3f ( pos.x, y2, pos.z );
// bottom face
glNormal3f ( 0, -ns, 0 );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, Vector3D ( -1, 0, 0 ) );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, Vector3D ( 0, 0, -1 ) );
glTexCoord2f ( size.x, size.z );
glVertex3f ( x2, pos.y, z2 );
glTexCoord2f ( 0, size.z );
glVertex3f ( pos.x, pos.y, z2 );
glTexCoord2f ( 0, 0 );
glVertex3f ( pos.x, pos.y, pos.z );
glTexCoord2f ( size.x, 0 );
glVertex3f ( x2, pos.y, pos.z );
glEnd ();
if ( cull )
glDisable ( GL_CULL_FACE );
}
void displayBoxes ()
{
glMatrixMode ( GL_MODELVIEW );
glPushMatrix ();
glActiveTextureARB ( GL_TEXTURE1_ARB );
glBindTexture ( GL_TEXTURE_2D, bumpMap );
glActiveTextureARB ( GL_TEXTURE0_ARB );
drawBoxTBN ( Vector3D ( -5, -5, 0 ), Vector3D ( 10, 10, 3 ), stoneMap, false );
glActiveTextureARB ( GL_TEXTURE1_ARB );
glBindTexture ( GL_TEXTURE_2D, bumpMap2 );
glActiveTextureARB ( GL_TEXTURE0_ARB );
drawBoxTBN ( Vector3D ( 3, 2, 0.5 ), Vector3D ( 1, 2, 2 ), decalMap, true );
drawBoxTBN ( Vector3D ( 3, -3, 0.5 ), Vector3D ( 2, 2, 1.75 ), decalMap, true );
glActiveTextureARB ( GL_TEXTURE1_ARB );
glBindTexture ( GL_TEXTURE_2D, bumpMap3 );
glActiveTextureARB ( GL_TEXTURE0_ARB );
drawBoxTBN ( Vector3D ( -3, -2, 0.5 ), Vector3D ( 1, 1, 1 ), woodMap, true );
drawBoxTBN ( Vector3D ( -3, 2, 0.5 ), Vector3D ( 1, 1, 1 ), woodMap, true );
glBindTexture ( GL_TEXTURE_2D, teapotMap );
glTranslatef ( 0.2, 1, 1.5 );
glRotatef ( angle * 45.3, 1, 0, 0 );
glRotatef ( angle * 57.2, 0, 1, 0 );
glutSolidTeapot ( 0.3 );
glPopMatrix ();
}
void display ()
{
GLenum buffers [] = { GL_COLOR_ATTACHMENT0_EXT };
// build RGBA_16F buffer with normal and z
buffer.bind ();
glDrawBuffers ( 1, buffers );
reshape ( buffer.getWidth (), buffer.getHeight () );
program1.bind ();
glClearColor ( 0, 0, 0, 0 );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
camera.apply ();
displayBoxes ();
program1.unbind ();
buffer.unbind ();
// now we have buffer with data normal and z
// build light buffer
Vector3D p [4];
camera.getPlanePolyForZ ( 1, p );
glClear ( GL_COLOR_BUFFER_BIT );
glDisable ( GL_DEPTH_TEST );
glDepthMask ( GL_FALSE );
glActiveTextureARB ( GL_TEXTURE0_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer.getColorBuffer ( 0 ) );
// render lights
buffer2.bind ();
program2.bind ();
program2.setUniformVector ( "lightPos", camera.mapFromWorld ( light ) );
program2.setUniformVector ( "lightColor", lightColor );
glBegin ( GL_QUADS );
glTexCoord2f ( buffer.getWidth (), buffer.getHeight () );
glVertex3fv ( p [0] );
glTexCoord2f ( buffer.getWidth (), 0 );
glVertex3fv ( p [1] );
glTexCoord2f ( 0, 0 );
glVertex3fv ( p [2] );
glTexCoord2f ( 0, buffer.getHeight () );
glVertex3fv ( p [3] );
glEnd ();
program2.unbind ();
buffer2.unbind ();
glEnable ( GL_DEPTH_TEST );
glDepthMask ( GL_TRUE );
// now render geometry and apply lighting buffer
reshape ( buffer.getWidth (), buffer.getHeight () );
glActiveTextureARB ( GL_TEXTURE2_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer2.getColorBuffer ( 0 ) );
glActiveTextureARB ( GL_TEXTURE0_ARB );
program3.bind ();
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
camera.apply ();
displayBoxes ();
program3.unbind ();
glActiveTextureARB ( GL_TEXTURE2_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 );
glutSwapBuffers ();
}
void reshape ( int w, int h )
{
camera.setViewSize ( w, h, 60 );
camera.apply ();
}
void key ( unsigned char key, int x, int y )
{
if ( key == 27 || key == 'q' || key == 'Q' ) // quit requested
exit ( 0 );
else
if ( key == 'w' || key == 'W' )
camera.moveBy ( camera.getViewDir () * 0.2 );
else
if ( key == 'x' || key == 'X' )
camera.moveBy ( -camera.getViewDir () * 0.2 );
else
if ( key == 'a' || key == 'A' )
camera.moveBy ( -camera.getSideDir () * 0.2 );
else
if ( key == 'd' || key == 'D' )
camera.moveBy ( camera.getSideDir () * 0.2 );
glutPostRedisplay ();
}
void specialKey ( int key, int x, int y )
{
if ( key == GLUT_KEY_UP )
yaw += M_PI / 90;
else
if ( key == GLUT_KEY_DOWN )
yaw -= M_PI / 90;
else
if ( key == GLUT_KEY_RIGHT )
roll += M_PI / 90;
else
if ( key == GLUT_KEY_LEFT )
roll -= M_PI / 90;
camera.setEulerAngles ( yaw, pitch, roll );
glutPostRedisplay ();
}
void mouseFunc ( int x, int y )
{
static int lastX = -1;
static int lastY = -1;
if ( lastX == -1 ) // not initialized
{
lastX = x;
lastY = y;
}
yaw -= (y - lastY) * 0.02;
roll += (x - lastX) * 0.02;
lastX = x;
lastY = y;
camera.setEulerAngles ( yaw, pitch, roll );
glutPostRedisplay ();
}
void animate ()
{
static float lastTime = 0.0;
float time = 0.001f * glutGet ( GLUT_ELAPSED_TIME );
angle += 2 * (time - lastTime);
lastTime = time;
glutPostRedisplay ();
}
int main ( int argc, char * argv [] )
{
// initialize glut
glutInit ( &argc, argv );
glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
glutInitWindowSize ( buffer.getWidth (), buffer.getHeight () );
// create window
glutCreateWindow ( "OpenGL Light PrePass Rendering" );
// register handlers
glutDisplayFunc ( display );
glutReshapeFunc ( reshape );
glutKeyboardFunc ( key );
glutSpecialFunc ( specialKey );
glutPassiveMotionFunc ( mouseFunc );
glutIdleFunc ( animate );
init ();
initExtensions ();
assertExtensionsSupported ( "EXT_framebuffer_object" );
decalMap = createTexture2D ( true, "wood.png" );
stoneMap = createTexture2D ( true, "brick.tga" );
teapotMap = createTexture2D ( true, "../../Textures/Oxidated.jpg" );
bumpMap = createTexture2D ( true, "brick_nm.bmp" );
bumpMap2 = createTexture2D ( true, "wood_normal.png" );
woodMap = createTexture2D ( true, "wall.jpg" );
bumpMap3 = createTexture2D ( true, "wall_bump.tga" );
if ( (format = fpRgbaFormatWithPrecision ( 16 ) ) == 0 )
{
printf ( "Floating-point textures not supported !\n" );
return 1;
}
unsigned screenMap0 = buffer.createColorRectTexture ( GL_RGBA, format );
buffer.create ();
buffer.bind ();
if ( !buffer.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, screenMap0, 0 ) )
printf ( "buffer error with color attachment\n");
if ( !buffer.isOk () )
printf ( "Error with framebuffer\n" );
buffer.unbind ();
unsigned screenMap1 = buffer2.createColorRectTexture ( GL_RGBA, format );
buffer2.create ();
buffer2.bind ();
if ( !buffer2.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, screenMap1, 0 ) )
printf ( "buffer2 error with color attachment\n");
if ( !buffer2.isOk () )
printf ( "Error with framebuffer 2\n" );
buffer2.unbind ();
if ( !program1.loadShaders ( "lpp-p1.vsh", "lpp-p1.fsh" ) )
{
printf ( "Error loading pass 1 shaders:\n%s\n", program1.getLog ().c_str () );
return 3;
}
if ( !program2.loadShaders ( "lpp-p2.vsh", "lpp-p2.fsh" ) )
{
printf ( "Error loading pass 2 shaders:\n%s\n", program2.getLog ().c_str () );
return 3;
}
if ( !program3.loadShaders ( "lpp-p3.vsh", "lpp-p3.fsh" ) )
{
printf ( "Error loading pass 3 shaders:\n%s\n", program3.getLog ().c_str () );
return 3;
}
program1.bind ();
program1.setTexture ( "decalMap", 0 );
program1.setTexture ( "bumpMap", 1 );
program1.unbind ();
program2.bind ();
program2.setTexture ( "dataMap", 0 );
program2.unbind ();
program3.bind ();
program3.setTexture ( "decalMap", 0 );
program3.setTexture ( "lightMap", 2 );
program3.unbind ();
camera.setRightHanded ( false );
glutMainLoop ();
return 0;
}
Следующий пример будет осуществлять рендеринг той же сцены, но уже освещаемой 1000 разноцветных двигающихся источников света, учитывая зависимость освещенности от расстояния. Ниже приводятся фрагменты кода на С++, из которых принципиальным является функция display.
Поскольку рисовать четырехугольник во весь экран для каждого источника света слишком затратно, то вместо этого для каждого источника света будет считаться, что его область влияния ограничена заранее заданным расстоянием MAX_DIST. Поэтому для учета вклада каждого источника света достаточно вывести нелицевые грани AABB, описанного вокруг источника.
#define NUM_LIGHTS 1000
#define MAX_DIST 1.5
Vector3D light [NUM_LIGHTS]; // light positions
Vector3D lightStart [NUM_LIGHTS]; // light start positions
Vector3D lightColor [NUM_LIGHTS]; // light colors
.
.
.
inline float rnd ()
{
return (float) rand () / (float) RAND_MAX;
}
inline float rnd ( float a, float b )
{
return a + rnd () * (b - a);
}
void initLights ()
{
for ( int i = 0; i < NUM_LIGHTS; i++ )
{
lightStart [i] = Vector3D ( rnd ( -4.5, 4.5 ), rnd ( -4.5, 4.5 ), rnd ( 0.3, 2.7 ) - 1.5 );
lightColor [i] = Vector3D ( rnd ( 0.5, 1 ), rnd ( 0.5, 1 ), rnd ( 0.5, 1 ) );
}
}
void animateLights ( float t )
{
float ca = cos ( 0.1 * t );
float sa = sin ( 0.13 * t );
for ( int i = 0; i < NUM_LIGHTS; i++ )
{
float x = lightStart [i].x;
float y = lightStart [i].y;
float z = lightStart [i].z;
light [i].x = ca * x - sa * y;
light [i].y = sa * x + ca * y;
light [i].z = 1.5 + z;
}
}
void display ()
{
GLenum buffers [] = { GL_COLOR_ATTACHMENT0_EXT };
buffer.bind (); // build RGBA_16F buffer with normal and z
glDrawBuffers ( 1, buffers );
reshape ( buffer.getWidth (), buffer.getHeight () );
program1.bind ();
glClearColor ( 0, 0, 0, 0 );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
camera.apply ();
displayBoxes ();
program1.unbind ();
buffer.unbind ();
// now we have buffer with data normal and z
// build light buffer
glDisable ( GL_DEPTH_TEST );
glDepthMask ( GL_FALSE );
glActiveTextureARB ( GL_TEXTURE0_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer.getColorBuffer ( 0 ) );
// render lights
buffer2.bind ();
program2.bind ();
glClear ( GL_COLOR_BUFFER_BIT );
glEnable ( GL_BLEND );
glBlendFunc ( GL_ONE, GL_ONE );
for ( int i = 0; i < NUM_LIGHTS; i++ )
{
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, light [i] );
glMultiTexCoord3fv ( GL_TEXTURE2_ARB, lightColor [i] );
drawBoxCull ( light [i], Vector3D ( MAX_DIST ), false );
}
program2.unbind ();
buffer2.unbind ();
glDisable ( GL_BLEND );
glEnable ( GL_DEPTH_TEST );
glDepthMask ( GL_TRUE );
// now render geometry and apply lighting buffer
reshape ( buffer.getWidth (), buffer.getHeight () );
glActiveTextureARB ( GL_TEXTURE2_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer2.getColorBuffer ( 0 ) );
glActiveTextureARB ( GL_TEXTURE0_ARB );
program3.bind ();
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
camera.apply ();
displayBoxes ();
program3.unbind ();
glActiveTextureARB ( GL_TEXTURE2_ARB );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 );
glutSwapBuffers ();
}
Ниже приводится листинг фрагментного шейдера второго прохода. Обратите внимание, что в этом примере параметры источников света передаются через текстурные координаты для 1-го и 2-го текстурных блоков.
#extension GL_ARB_texture_rectangle: enable
varying vec3 pos;
uniform sampler2DRect dataMap;
#define MAX_DIST 1.5
float atten ( float d ) // compute distance attenuation factor, 0 -> 1, MAX_DIST -> 0
{
const float offs0 = 1.0;
const float offs1 = 1.0 / (1.0 + MAX_DIST);
const float scale = 0.5 / (offs0 - offs1);
return scale * ( 1.0 / (1.0 + d) - offs1 );
}
void main (void)
{
vec3 lightPos = gl_TexCoord [1].xyz / gl_TexCoord [1].w;
vec3 lightColor = gl_TexCoord [2].xyz;
vec4 nz = texture2DRect ( dataMap, gl_FragCoord.xy );
vec3 n = nz.xyz;
vec3 pp = pos * nz.w / pos.z;
float d = length ( lightPos - pp );
if ( d > MAX_DIST ) // too far away
discard;
vec3 l = normalize ( lightPos - pp );
vec3 v = normalize ( -pp );
vec3 h = normalize ( l + v );
float at = atten ( d );
float lum = dot ( lightColor, vec3 ( 0.3, 0.59, 0.11 ) );
float diff = at * max ( 0.1, dot ( l, n ) );
float spec = diff * lum * pow ( max ( 0.001, dot ( h, n ) ), 40.0 );
gl_FragColor = vec4 ( diff * lightColor.rgb, spec );
}
Рис 2. Сцена с 1000 источников света.
Несколько полезных ссылок:
Презентация Вольфганга Энгеля про LPP.
Обсуждение LPP в блоге Вольфганга Энгеля
Еще одна реализация LPP, использующая в качестве G-буфера обычную текстуру RGB8.
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.