Эффективные методы попиксельного учета ослабевания интенсивности источника света в зависимости
от расстояния до него (distance attenuation)
Если мы хотим корректно моделировать освещенность в больших сценах, то нам необходимо в попиксельном
освещении учитывать зависимость освещенности точки (точнее, количество световой энергии падающей на
единичную площадку в окрестности точки) от ее расстояния до источника света.
С точки зрения физики эта зависимость выражается следующей формулой:
At = 1 / d ^ 2
Однако непосредственное использование этой формулы далеко не всегда дает хорошо выглядящее изображение
сцены. Поэтому в стандарте OpenGL коэффициент ослабевания освещенности в зависимости от расстояния
вычисляется по ниже приведенной формуле (обобщающей предыдущую).
At = 1 / (a + b * d + c * d ^ 2)
Однако данная формула довольно сложная для попиксельной реализации (хотя ее можно реализовать в виде
фргаментной программы или программы на языке шейдеров высокого уровня, например GLSL или Cg). На практике
как правило используется приближенный способ вычисления этой зависимости.
Ниже мы рассмотрим несколько способов учета зависимости освещенности от расстояния, обладающие
своими достоинствами и недостатками.
Использование трехмерной текстуры
Поскольку коэффициент затухания зависит от вектора от точки к источнику света, т.е. является функцией
трех переменных, то можно задать эту функцию при помощи трехмерной текстуры.
При этом берется куб, описанный вокруг начала координат, достаточно размера, чтобы освещенностью на его
границе можно было пренебречь, и значения освещенности, вычисленные на сетке внутри этого куба,
заносятся в 3D текстуру.
Преимуществом этого подхода является его крайняя простота и использование всего одного текстурного
блока. Еще одним преимуществом является возможность использования различных формул для затухания,
достаточно просто вычислить значения используемой функции на сетке и записать в 3D текстуру.
Основным недостатком данного подхода является то, что использование трехмерной текстуры требует довольно
большого объема памяти, что сильно снижает эффективность обращения к ней и понижает общее
быстродействие приложения.
Для работы с трехмерной текстрой мы будем использовать расширение EXT_texture3D, поскольку используемая в
Windows версия OpenGL довольно стара и не поддерживает трехмерные текстуры.
Данное расширение помимо константы GL_TEXTURE_3D_EXT вводит также новую функцию glTexImage3DEXT,
служащую для задания трехмерной текстуры.
void glTexImage3DEXT ( GLenum target, int level, GLenum internalformat,
GLsizei width, GLsizei height, GLsizei depth,
int border, GLenum format, GLenum type, const void * pixels );
Здесь можно взять обновленную версию библиотеки libExt,
поддерживающую данное расширение.
Следующий фрагмент кода отвечает за создание трехмерной текстуры (правда для простоты здесь используется
упрощенная формула вычисления коэффициента затухания, но вы можете туда подставиьт любую формулу).
void compute1DAtten ( byte * data, int size )
{
float k = (float) size / 2.0f - 0.5f;
for ( int i = 0; i < size; i++ )
{
// get distance from center to this point
float dist = (i - k) / k;
dist = dist * dist;
if ( dist > 1 ) // clamp to [0,1]
dist = 1;
data [i] = byte ( dist*255 );
}
// make sure the color is 255 at the edges
data [0] = 255;
data [size-1] = 255;
}
unsigned create3DAttenTexture ( int size )
{
unsigned texture;
byte * data = new byte [size*size*size];
byte * atten1D = new byte [size];
int offs = 0;
compute1DAtten ( atten1D, size );
for ( int i = 0; i < size; i++ )
for ( int j = 0; j < size; j++ )
for ( int k = 0; k < size; k++ )
{
int value = atten1D [i] + atten1D [j] + atten1D [k];
if ( value > 255 )
value = 255;
data [offs++] = value;
}
glGenTextures ( 1, &texture);
glBindTexture ( GL_TEXTURE_3D_EXT, texture );
glTexImage3DEXT ( GL_TEXTURE_3D_EXT, 0, GL_INTENSITY, size, size, size,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data );
glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
delete data;
delete atten1D;
return texture;
}
Обратите внимание на то, что для текстуры уставливается режим GL_CLAMP_TO_EDGE, обеспечивающий что для
значений текстурных координат, выходящий за пределы единичного куба, будут использоваться значения
с границы текстуры.
Параметр size задает разрешение текстуры в пикселах (строится текстура размером size*size*size).
Ниже приводится фрагмент исходного кода, использующего трехмерную текстуру для моделирования
коэффициента затухания.
void drawFace ( Vertex v [], int count )
{
glBegin ( GL_QUADS );
for ( int i = 0; i < count; i++ )
{
Vector3D d = v [i].pos - light;
d = Vector3D ( 0.5, 0.5, 0.5f ) + d * (0.5 / lightRadius );
glMultiTexCoord2fv ( GL_TEXTURE0_ARB, v [i].tex );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, d );
glVertex3fv ( v [i].pos );
}
glEnd ();
}
void display ()
{
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// bind decal map to texture unit 0
glActiveTextureARB ( GL_TEXTURE0_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, decalMap );
// bind 3D attenuation map to texture unit 1
glActiveTextureARB ( GL_TEXTURE1_ARB );
glEnable ( GL_TEXTURE_3D );
glBindTexture ( GL_TEXTURE_3D, atten3DMap );
// setup register combiners
glEnable ( GL_REGISTER_COMBINERS_NV );
glCombinerParameteriNV ( GL_NUM_GENERAL_COMBINERS_NV, 1 );
// configure final combiner
// A = tex1
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_INVERT_NV, GL_RGB );
// B = tex0 (decal)
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 0
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
drawFace ( front, 4 );
drawFace ( lower, 4 );
drawFace ( upper, 4 );
drawFace ( left, 4 );
drawFace ( right, 4 );
glutSwapBuffers ();
}
Использование двух текстур
Остальные рассматриваемые способы используют приближенные формулы для вычисления коэффициента
затухания. Преимуществом этих формул является простота вычисления.
Еще одним свойством используемых приближенных формул является возможность "расщепления" зависимости -
когда можно разделить переменные (например, на (x,y) и z) и итоговая зависимость будет
являться результатом комбинирования полученных зависимостей от функций меньшего числа переменных.
Далее мы будем использовать следующую формулу для вычисления коэффициента затухания:
At = 1 - (d ^ 2)/(r ^ 2)
В этой формуле величина r является радиусом источника света - вне его освещенность от источника
считается равной нулю.
Обратите внимание, что данная формула легко расщепляется по любым из трех переменных x, y
и z:
At = 1 - ((x ^ 2 + y ^ 2)/(r ^ 2) + (z ^ 2)/(r ^ 2))
Тогда можно занести величины (x ^ 2 + y ^ 2)/(r ^ 2) и
(z ^ 2)/(r ^ 2) в двухмерную и одномерную текстуры tex0 и tex1.
Тогда итоговый коэффициент затухания можно выразить в виде следующей формулы:
At = 1 - (tex0 + tex1)
Данный подход использует два текстурных блока, однако в них расположены одна двухмерная текстура и одна
одномерная текстура, требущие заметно меньше видеопамяти, чем трехмерная текстура (при том же расзрешении).
В результате обращение к этим двум текстурам во многих случаях происходит более эффективно, чем к одной
трехмерной текстуре.
Следующий фрагмент кода создает две текстуры, используемые для вычисления затухания.
unsigned create1DAttenTexture ( int size )
{
unsigned texture;
byte * data = new byte [size];
compute1DAtten ( data, size );
glGenTextures ( 1, &texture );
glBindTexture ( GL_TEXTURE_1D, texture );
glTexImage1D ( GL_TEXTURE_1D, 0, GL_INTENSITY, size,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
delete data;
return texture;
}
unsigned create2DAttenTexture ( int size )
{
unsigned texture;
byte * data = new byte [size*size];
byte * atten1D = new byte [size];
int offs = 0;
compute1DAtten ( atten1D, size );
for ( int i = 0; i < size; i++ )
for ( int j = 0; j < size; j++ )
{
int value = atten1D [i] + atten1D [j];
if ( value > 255 )
value = 255;
data [offs++] = value;
}
glGenTextures ( 1, &texture);
glBindTexture ( GL_TEXTURE_2D, texture );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_INTENSITY, size, size,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
delete data;
delete atten1D;
return texture;
}
Ниже приводится фрагмент из программной реализации данного подхода.
void drawFace ( Vertex v [], int count )
{
glBegin ( GL_QUADS );
for ( int i = 0; i < count; i++ )
{
Vector3D d = v [i].pos - light;
d = Vector3D ( 0.5, 0.5, 0.5f ) + d * (0.5 / lightRadius );
glMultiTexCoord2fv ( GL_TEXTURE0_ARB, v [i].tex );
glMultiTexCoord1f ( GL_TEXTURE1_ARB, d.x );
glMultiTexCoord2f ( GL_TEXTURE2_ARB, d.y, d.z );
glVertex3fv ( v [i].pos );
}
glEnd ();
}
void display ()
{
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// bind decal map to texture unit 0
glActiveTextureARB ( GL_TEXTURE0_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, decalMap );
// bind 1D attenuation map to texture unit 1
glActiveTextureARB ( GL_TEXTURE1_ARB );
glEnable ( GL_TEXTURE_1D );
glBindTexture ( GL_TEXTURE_1D, atten1DMap );
// bind 2D attenuation map to texture unit 2
glActiveTextureARB ( GL_TEXTURE2_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, atten2DMap );
glActiveTextureARB ( GL_TEXTURE0_ARB );
// setup register combiners
glEnable ( GL_REGISTER_COMBINERS_NV );
glCombinerParameteriNV ( GL_NUM_GENERAL_COMBINERS_NV, 1 );
// A = texture1 (atten1D)
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE1_ARB,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B = 1
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_RGB );
// C = texture2 (atten2D)
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE2_ARB,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 1
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_RGB );
// spare0 = tex1 + tex2
glCombinerOutputNV ( GL_COMBINER0_NV, GL_RGB,
GL_DISCARD_NV, // discard AB
GL_DISCARD_NV, // discard CD
GL_SPARE0_NV, // put AB + CD into spare0
GL_NONE, GL_NONE, // no bias or scale
GL_FALSE, // per-component product for all
GL_FALSE,
GL_FALSE );
// configure final combiner
// A = 1 - spare0
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_INVERT_NV, GL_RGB );
// B = tex0 (decal)
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 0
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
drawFace ( front, 4 );
drawFace ( lower, 4 );
drawFace ( upper, 4 );
drawFace ( left, 4 );
drawFace ( right, 4 );
glutSwapBuffers ();
}
Использование расширения NV_texture_shader
Если через l обозначить нормированный вектор от точки к источнику света, то (d^2)/(r^2) = (l,l) и мы можем
переписать коэффициент зависимости в следующем виде:
At = max ( 0, 1 - (l,l) )
Вычисление по формуле этого вида легко может быть реализовано через register combiner'ы -
надо только загрузить величину l в один из регистров.
Удобно передавать вектор l через текстурные координаты для одного из текстурных блоков (т.е.
метод занимает один текстурный блок, правда никакой текстуры не требуется), а для занесения текстурных
координта в регистр использовать шейдер PASS_THROUGH_NV из расширения NV_texture_shader.
Данный шейдер просто передает текстурные координаты, усеченные по отрезку [0,1] в качестве выхода
соответствующего текстурного блока вне зависимости от наличия в нем текстуры. При этом исходный вектор
l' сначала интерполируется вдоль всей грани, а уже потом, в каждом пикселе, происходит его
усечения по отрезку.
Ниже приводится фрагмент из программной реализации данного подхода.
void drawFace ( Vertex v [], int count )
{
glBegin ( GL_QUADS );
for ( int i = 0; i < count; i++ )
{
Vector3D d = v [i].pos - light;
d = Vector3D ( 0.5, 0.5, 0.5f ) + d * (0.5 / lightRadius );
glMultiTexCoord2fv ( GL_TEXTURE0_ARB, v [i].tex );
glMultiTexCoord3fv ( GL_TEXTURE1_ARB, d );
glVertex3fv ( v [i].pos );
}
glEnd ();
}
void display ()
{
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// setup texture shader
glEnable ( GL_TEXTURE_SHADER_NV );
// unit 0 -> decal
glActiveTextureARB ( GL_TEXTURE0_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, decalMap );
glTexEnvi ( GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D );
// unit 1 -> pass through
glActiveTextureARB ( GL_TEXTURE1_ARB );
glTexEnvi ( GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_PASS_THROUGH_NV );
glActiveTextureARB ( GL_TEXTURE0_ARB );
// setup register combiners
glEnable ( GL_REGISTER_COMBINERS_NV );
glCombinerParameteriNV ( GL_NUM_GENERAL_COMBINERS_NV, 1 );
// combiner0 computes (tex1,tex1) -> spare0
// A = texture1
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE1_ARB,
GL_EXPAND_NORMAL_NV, GL_RGB );
// B = texture1
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE1_ARB,
GL_EXPAND_NORMAL_NV, GL_RGB );
// spare0 = tex1 + tex2
glCombinerOutputNV ( GL_COMBINER0_NV, GL_RGB,
GL_SPARE0_NV, // put (A,B) into spare0
GL_DISCARD_NV, // discard CD
GL_DISCARD_NV, // discard AB+CD
GL_NONE, GL_NONE, // no bias or scale
GL_TRUE, // use dot porduct for AB
GL_FALSE,
GL_FALSE );
// configure final combiner to output clamp (1 - spare0)
// A = 1 - spare0
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_INVERT_NV, GL_RGB );
// B = tex0 (decal)
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 0
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
drawFace ( front, 4 );
drawFace ( lower, 4 );
drawFace ( upper, 4 );
drawFace ( left, 4 );
drawFace ( right, 4 );
glutSwapBuffers ();
}
Использование гауссовской зависимости для вычисления затухания.
Данный подход основан на использовании для вычисления коэффициента затухания следующей формулы:
At = exp ( -k * d ^ 2 )
Здест коэффициент k характеризует скорость ослабевания интенсиваности в зависимости от
расстояния.
Как легко видно, в этой формуле также имеет место расщепление (по всем переменным), однако оно носит
не аддитивный, а мультипликативный характер (т.е. раскладываеся в произведение функций, зависящих от
отдельных переменных):
At = exp ( -k * (x ^ 2 + y ^ 2) ) * exp ( -k * z ^ 2 )
Тогда мы можем занести первый множитель в одну двухмерную текстуру (tex0), второй - в
одномерную текстуру (tex1) и в качестве коэффициента затухания использовать tex0*tex1.
Обратите внимание, что коэффициент затухания k следует выбрать достатончо большим, чтобы на
границе каждой из текстур получить нулевые значения.
Таким образом данный подход оказывается близок ко второму подходу.
Следующий фрагмент кода создает текстуры, используемые в данном подходе.
void compute1DGaussianAtten ( byte * data, int size )
{
float k = (float) size / 2.0f - 0.5f;
for ( int i = 0; i < size; i++ )
{
// get distance from center to this point
float dist = (i - k) / k;
float brightness = (float)exp ( -coeff * dist * dist );
data [i] = byte ( brightness*255 );
}
// make sure the color is 0 at the edges
data [0] = 0;
data [size-1] = 0;
}
unsigned create1DGaussianAttenTexture ( int size )
{
unsigned texture;
byte * data = new byte [size];
compute1DGaussianAtten ( data, size );
glGenTextures ( 1, &texture );
glBindTexture ( GL_TEXTURE_1D, texture );
glTexImage1D ( GL_TEXTURE_1D, 0, GL_INTENSITY, size,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
delete data;
return texture;
}
unsigned create2DGaussianAttenTexture ( int size )
{
unsigned texture;
byte * data = new byte [size*size];
byte * atten1D = new byte [size];
int offs = 0;
compute1DGaussianAtten ( atten1D, size );
for ( int i = 0; i < size; i++ )
for ( int j = 0; j < size; j++ )
{
int value = (atten1D [i] * atten1D [j]) / 255;
data [offs++] = value;
}
glGenTextures ( 1, &texture);
glBindTexture ( GL_TEXTURE_2D, texture );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_INTENSITY, size, size,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
delete data;
delete atten1D;
return texture;
}
Ниже приводится фрагмент из программной реализации данного подхода.
void display ()
{
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// bind decal map to texture unit 0
glActiveTextureARB ( GL_TEXTURE0_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, decalMap );
// bind 1D gaussian attenuation map to texture unit 1
glActiveTextureARB ( GL_TEXTURE1_ARB );
glEnable ( GL_TEXTURE_1D );
glBindTexture ( GL_TEXTURE_1D, atten1DMap );
// bind 2D gaussian attenuation map to texture unit 2
glActiveTextureARB ( GL_TEXTURE2_ARB );
glEnable ( GL_TEXTURE_2D );
glBindTexture ( GL_TEXTURE_2D, atten2DMap );
glActiveTextureARB ( GL_TEXTURE0_ARB );
// setup register combiners
glEnable ( GL_REGISTER_COMBINERS_NV );
glCombinerParameteriNV ( GL_NUM_GENERAL_COMBINERS_NV, 1 );
// A = texture1 (atten1D)
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE1_ARB,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B = texture2 (atten2D)
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE2_ARB,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 0
glCombinerInputNV ( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// spare0 = tex1 * tex2
glCombinerOutputNV ( GL_COMBINER0_NV, GL_RGB,
GL_SPARE0_NV, // put AB into spare0
GL_DISCARD_NV, // discard CD
GL_DISCARD_NV, // discard AB+CD
GL_NONE, GL_NONE, // no bias or scale
GL_FALSE, // per-component product for all
GL_FALSE,
GL_FALSE );
// configure final combiner
// A = spare0
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B = tex0 (decal)
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = 0
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
drawFace ( front, 4 );
drawFace ( lower, 4 );
drawFace ( upper, 4 );
drawFace ( left, 4 );
drawFace ( right, 4 );
glutSwapBuffers ();
}
Рис 1. Пример освещения с использованием затухания освещенности с расстоянием.
В основу данной статьи лег пример с сайта www.paulsprojects.net.
Дополнительную информацию по этой теме можно найти на
www.ronfrazier.net/apparition и
здесь.
Весь исходный код к этой статье, можно скачать
здесь.
|