Опубликован: 08.07.2007 | Доступ: свободный | Студентов: 1431 / 183 | Оценка: 4.43 / 4.02 | Длительность: 13:47:00
Специальности: Программист
Лекция 8:

Использование шейдеров с помощью языка HLSL. Графический процессор

Теперь необходимо рассмотреть каким образом происходит установка значений констант в шейдере из программы. Как мы уже видели, при вызове метода компиляции шейдера (D3DXCompileShaderFromFile), в последнюю переменную данной функции помещается ссылка на так называемую таблицу констант. Именно с помощью данного указателя и происходит присваивание значений константам в шейдере. Реализуется это с помощью вызова методов SetXXX интерфейса ID3DXConstantTable, где XXX – "заменяется" на следующие выражения: Bool, Float, Int, Matrix, Vector. Данные методы имеют три параметра: первый – указатель на устройство вывода, второй – наименование константы в шейдере, и третий – устанавливаемое значение. Так, например, установка значения для матрицы преобразования (WorldViewProj) в приведенном выше примере осуществляется следующим образом.

C++
D3DXMATRIX matWorld, matView, matProj, tmp;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 
   1.0f, 100.0f );
D3DXVECTOR3 positionCamera, targetPoint, worldUp;
positionCamera = D3DXVECTOR3(2.0f, 2.0f, -2.0f);
targetPoint = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
worldUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&matView, &positionCamera, &targetPoint, 
   &worldUp);
D3DXMatrixRotationY(&matWorld, angle);
tmp = matWorld * matView * matProj;
ConstantTable->SetMatrix( device, "WorldViewProj", &tmp );
Pascal
var
  matWorld, matView, matProj, tmp: TD3DMatrix;
  positionCamera, targetPoint, worldUp : TD3DXVector3;
...
  positionCamera:=D3DXVector3(2,2,-2);
  targetPoint:=D3DXVector3(0,0,0);
  worldUp:=D3DXVector3(0,1,0);
  D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, 
    worldUp);
  D3DXMatrixPerspectiveFovLH(matProj, PI/4, 1, 1, 100);
  D3DXMatrixRotationY(matWorld, angle);
  D3DXMatrixMultiply(tmp, matWorld, matView);
  D3DXMatrixMultiply(tmp, tmp, matProj);
  ConstantTable.SetMatrix(device, 'WorldViewProj', tmp);

Ниже приведены примеры вызова каждого метода.

C++
LPD3DXCONSTANTTABLE ConstantTable = NULL;

bool b = true;
ConstantTable->SetBool( device, "flag", b );

float f = 3.14f;
ConstantTable->SetFloat( device, "pi", f );

int x = 4;
ConstantTable->SetInt( device, "num", x );

D3DXMATRIX m;
...
ConstantTable->SetMatrix( device, "mat", &m );

D3DXVECTOR4 v(1.0f, 2.0f, 3.0f, 4.0f);
ConstantTable->SetVector( device, "vec", &v );
Pascal
var b: Boolean;
    f: Single;
    x: Integer;
    m: TD3DMatrix;
    v: TD3DXVector4;
    ConstantTable: ID3DXConstantTable;
...
b := true;
ConstantTable.SetBool(device, 'flag', b);

f:=3.14;
ConstantTable.SetFloat(device, 'pi', f);

x := 4;
ConstantTable.SetInt(device, 'num', x);

m._11:=1; ...
ConstantTable.SetMatrix(device, 'mat', m);

v := D3DXVector4(1,2,3,4);
ConstantTable.SetVector(device, 'vec', v);

В качестве примера использования вершинных шейдеров рассмотрим реализацию эффекта скручивания трехмерного объекта вдоль одной из координатных осей. Данный эффект осуществляется с помощью смешения двух матриц преобразования ( matrix blending ). Основная идея такого преобразования объекта, заданного своими вершинами, может быть выражена с помощью следующей формулы: v_{i}=(M_{1}(1-\alpha_{i})+M_{2} \alpha_{i})v_{i}, где v_{i} - координаты вершины, \alpha_{i}  [0,1] - вес вершины, M_{1}M_{2} - матрицы преобразования. Как правило, вес вершине \alpha_{i} приписывается линейно изменяющийся вдоль одной из осей. В результате, на часть точек объекта большее влияние оказывает матрица M_{1}, на другую часть – матрица M_{2}. Пусть у нас в качестве объекта выступает единичный куб, состоящий из маленьких треугольников (их количество можно регулировать) и нижнее основание которого расположено в плоскости y=0, как показано на рисунке ниже.


В качестве веса вершины пусть выступает значение координаты y, а матрицы M_{1} и M_{2} задают матрицы поворота вокруг оси OY на углы 30 и -30 градусов соответственно. Результат скручивания объекта (куба) по приведенной выше формуле показаны ниже.


При этом код вершинного шейдера будет выглядеть следующим образом.

float4x4 M1;
float4x4 M2;

struct VS_INPUT
{
	float4 position  : POSITION;
	float4 color0    : COLOR0;
};

struct VS_OUTPUT
{
	float4 position : POSITION;
	float4 color0    : COLOR0;
};

VS_OUTPUT main( VS_INPUT IN )
{
  VS_OUTPUT OUT;
  float4x4 m = (1-IN.position.y)*M1 + IN.position.y*M2;
  OUT.position = mul( IN.position, m );
  OUT.color0 = IN.position;
  return OUT;
}

Присутствующие в шейдере матрицы преобразования M_{1} и M_{2} устанавливаются через вызывающую программу с помощью таблицы констант.

C++
D3DXMATRIX matWorld1, matWorld2, matView, matProj, M1, M2;
LPD3DXCONSTANTTABLE ConstantTable = NULL;

D3DXMatrixRotationY(&matWorld1, 30.0f*D3DX_PI/4);
M1 = matWorld1 * matView * matProj;
ConstantTable->SetMatrix( device, "M1", &M1 );

D3DXMatrixRotationY(&matWorld2, -30.0f*D3DX_PI/4);
M2 = matWorld2 * matView * matProj;
ConstantTable->SetMatrix( device, "M2", &M2 );
Pascal
var
  matWorld1, matWorld2, matView, matProj, M1, M2: TD3DMatrix;
  ConstantTable: ID3DXConstantTable;
...
D3DXMatrixRotationY(matWorld1, 30*pi/180);
D3DXMatrixMultiply(M1, matWorld1, matView); 
      // M1 = matWorld1 * matView
D3DXMatrixMultiply(M1, M1, matProj); // M1 = M1 * matProj
ConstantTable.SetMatrix(device, 'M1', M1);

D3DXMatrixRotationY(matWorld2, - 30*pi/180);
D3DXMatrixMultiply(M2, matWorld2, matView);
D3DXMatrixMultiply(M2, M2, matProj);
ConstantTable.SetMatrix(device, 'M2', M2);

Следует заметить, что подобное преобразование никак не зависит от степени детализации (количества треугольников) исходного единичного куба. Кроме того, искажению может быть подвергнут абсолютно любой трехмерный объект. Ниже представлен пример трансформации чайника с помощью метода смешения матриц преобразования.


Рассмотрим теперь необходимые шаги для работы с пиксельным шейдером. В отличие от вершинных шейдеров, пиксельные шейдеры не могут эмулироваться центральным процессором. Поэтому если видеокарта не поддерживает пиксельных шейдеров, значит обработка элементарных фрагментов (пикселей) будет производится по жестко заданному правилу. Как мы уже говорили, пиксельный шейдер представляет собой небольшую программу (процедуру) для обработки каждого пикселя. Первым шагом необходимо объявить переменную интерфейсного типа IDirect3DPixelShader9, которая отвечает за работу пиксельного шейдера из программы.

C++
LPDIRECT3DPIXELSHADER9 PixelShader = NULL;
Pascal var PixelShader: IDirect3DPixelShader9;

Компиляция пиксельного шейдера осуществляется также с помощью функции D3DXCompileShaderFromFile().

C++
LPD3DXBUFFER Code = NULL;
LPD3DXBUFFER BufferErrors = NULL;
LPD3DXCONSTANTTABLE ConstantTable = NULL;

D3DXCompileShaderFromFile( "pixel.psh", NULL, NULL, "main",     
    "ps_1_0",  0, &Code, &BufferErrors, 
       &ConstantTable );
Pascal
var
  Code: ID3DXBuffer;
  BufferErrors: ID3DXBuffer;
  ConstantTable: ID3DXConstantTable;
...

D3DXCompileShaderFromFile('pixel.psh', nil, nil, 'Main', 
   'ps_1_0', 0, @Code, @BufferErrors, 
      @ConstantTable);