Войти
ПрограммированиеСтатьиГрафика

Построение Dot 3 Bump Mapping'а

Внимание! Этот документ ещё не опубликован.

Автор:

Данная технология получения эффекта bump в настоящее время почти полностью вытеснена HLSL / GLSL, однако в определенных случаях может быть полезна. Помимо этого, в статьях на сайте легко найти данную технологию в приложении к фактически 2D-кваду. Поэтому данный материал будет общим случаем для случая 3D. При отработке программы использовался пример dot 3 bump by Kevin Harris© for DirectX 9. В то же время информацию о данном примере и технологии в Internet найти становится все сложнее, особенно в открытом доступе.

Итак, вначале, при загрузке моделей, необходимо для каждой вершины из буффера посчитать тангент

D3DXVECTOR3 ComputeTangentVector( D3DXVECTOR3 pVtxA_p, D3DXVECTOR3 pVtxA_n, FLOAT pVtxA_tu, FLOAT pVtxA_tv,
                                  D3DXVECTOR3 pVtxB_p, D3DXVECTOR3 pVtxB_n, FLOAT pVtxB_tu, FLOAT pVtxB_tv,
                                  D3DXVECTOR3 pVtxC_p, D3DXVECTOR3 pVtxC_n, FLOAT pVtxC_tu, FLOAT pVtxC_tv)
{
D3DXVECTOR3 vTangent, vAB, vAC, n, vProjAB, vProjAC;
FLOAT duAB, duAC, dvAB, dvAC;

  vAB = pVtxB_p - pVtxA_p;
  vAC = pVtxC_p - pVtxA_p;
  n   = pVtxA_n;

  vProjAB = vAB - ( D3DXVec3Dot( &n, &vAB ) * n );
  vProjAC = vAC - ( D3DXVec3Dot( &n, &vAC ) * n );

  duAB = pVtxB_tu - pVtxA_tu;
  duAC = pVtxC_tu - pVtxA_tu;
  dvAB = pVtxB_tv - pVtxA_tv;
  dvAC = pVtxC_tv - pVtxA_tv;

  if( duAC*dvAB > duAB*dvAC )
    {
        duAC = -duAC;
        duAB = -duAB;
    };

  vTangent = duAC*vProjAB - duAB*vProjAC;
  D3DXVec3Normalize( &vTangent, &vTangent );

  return vTangent;
};

и бинормаль

struct TDoublePoints
{
  D3DXVECTOR3 pnt1, pnt2;
};
...
TDoublePoints         * a_pnt;
...
  sdr_.shader->Lock( 0, sdr_.f_count * 3*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 );

//просчитываем все вершины
  for(int i=0; i<sdr_.f_count; i++){
    pVtxA_p = D3DXVECTOR3(pVertices->x,  pVertices->y,  pVertices->z);
    pVtxA_n = D3DXVECTOR3(pVertices->nx, pVertices->ny, pVertices->nz);
    pVtxA_tu = pVertices->tu1;
    pVtxA_tv = pVertices->tv1;
    pVertices++;

    pVtxB_p = D3DXVECTOR3(pVertices->x,  pVertices->y,  pVertices->z);
    pVtxB_n = D3DXVECTOR3(pVertices->nx, pVertices->ny, pVertices->nz);
    pVtxB_tu = pVertices->tu1;
    pVtxB_tv = pVertices->tv1;
    pVertices++;

    pVtxC_p = D3DXVECTOR3(pVertices->x,  pVertices->y,  pVertices->z);
    pVtxC_n = D3DXVECTOR3(pVertices->nx, pVertices->ny, pVertices->nz);
    pVtxC_tu = pVertices->tu1;
    pVtxC_tv = pVertices->tv1;
    if(! ( (i==sdr_.f_count-1) ) ) pVertices++;

//формируем коллекцию из записей, содержащих данные о тангенте и бинормали
//0
    a_pnt = new TDoublePoints;
    a_pnt->pnt1 = ComputeTangentVector( pVtxA_p, pVtxA_n, pVtxA_tu, pVtxA_tv,
                                        pVtxB_p, pVtxB_n, pVtxB_tu, pVtxB_tv,
                                        pVtxC_p, pVtxC_n, pVtxC_tu, pVtxC_tv);
    tbn->Add(a_pnt);

//1
    a_pnt = new TDoublePoints;
    a_pnt->pnt1 = ComputeTangentVector( pVtxB_p, pVtxB_n, pVtxB_tu, pVtxB_tv,
                                        pVtxA_p, pVtxA_n, pVtxA_tu, pVtxA_tv,
                                        pVtxC_p, pVtxC_n, pVtxC_tu, pVtxC_tv);
    tbn->Add(a_pnt);

//2
    a_pnt = new TDoublePoints;
    a_pnt->pnt1 = ComputeTangentVector( pVtxC_p, pVtxC_n, pVtxC_tu, pVtxC_tv,
                                        pVtxA_p, pVtxA_n, pVtxA_tu, pVtxA_tv,
                                        pVtxB_p, pVtxB_n, pVtxB_tu, pVtxB_tv);
    tbn->Add(a_pnt);
  };

  sdr_.shader->Unlock();
//------------------------------------
  sdr_.shader->Lock( 0, sdr_.f_count * 3*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 );

  if (tbn->Count>0){
    for(int i=0; i<tbn->Count; i++){
      a_pnt = (TDoublePoints *)tbn->Items[i];

//Нормализуем тангенты
      D3DXVec3Normalize( &a_pnt->pnt1, &a_pnt->pnt1 );

//Вычисляем бинормали
      vct_ = D3DXVECTOR3(pVertices->nx, pVertices->ny, pVertices->nz);
      D3DXVec3Cross( &a_pnt->pnt2, &vct_, &a_pnt->pnt1 );
...
      if(! ( (i==sdr_.f_count-1) ) ) pVertices++;
    };
  };

  sdr_.shader->Unlock();
...

Код отрисовки с эффектом bump следующий:

...
  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3 ); 
  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );    
  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );    

  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 0);
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); 
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); 
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); 

  g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
  g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
  g_pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
  g_pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);

  g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, DWORD (False));

//Отрисовываем полигоны
  g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, sdr_.f_count);

  g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, DWORD (True));

//Возвращаем прежние установки
  g_pd3dDevice->SetTexture (0, NULL);
  g_pd3dDevice->SetTexture (1, NULL);

  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );

  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );
  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
  g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );

  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
  g_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
...

Данный алгоритм функционирует за счет того, что в поле diffuse каждой вершины обновляется информация о характере освещения - затенения. Соответственно, при каждом обновлении сцены выполняем процедуру:

void __fastcall TSpace3d::bump_shader(D3DXVECTOR3 light_pos)
//light_pos - координаты условного источника света в мировой системе координат
{
TNamedShader          sdr_;
CUSTOMVERTEX          * pVertices;
D3DXVECTOR3           vct_;
TDoublePoints         * a_pnt;

D3DXMATRIX worldInverse, invTangentMatrix;
D3DXVECTOR3 vCurrentVertex, vLightPosMS, vVertToLightMS, vVertToLightTS;

//matWorld - мировая матрица 3D - объекта, которая была ранее вычислена
  D3DXMatrixInverse(&worldInverse, NULL, &matWorld);
  D3DXVec3TransformCoord(&vLightPosMS, &light_pos, &worldInverse);

//получаем указатель на вершинный буфер и локаем его
  sdr_ = shader_library->get_full_shader(mesh_name, n_clone);

  sdr_.shader->Lock( 0, sdr_.f_count * 3*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 );

  if (tbn->Count>0){
    for(int i=0; i<tbn->Count; i++){
//для всех вершин
      a_pnt = (TDoublePoints *)tbn->Items[i];
//----------------------вычисляем направление на источник света-----------------------------
      vVertToLightMS.x = vLightPosMS.x - pVertices->x;
      vVertToLightMS.y = vLightPosMS.y - pVertices->y;
      vVertToLightMS.z = vLightPosMS.z - pVertices->z;

      D3DXVec3Normalize(&vVertToLightMS, &vVertToLightMS);
//---------------------заполняем матрицу------------------------------
      invTangentMatrix._11 = a_pnt->pnt1.x;
      invTangentMatrix._12 = a_pnt->pnt2.x;
      invTangentMatrix._13 = pVertices->nx;
      invTangentMatrix._14 = 0.0f;

      invTangentMatrix._21 = a_pnt->pnt1.y;
      invTangentMatrix._22 = a_pnt->pnt2.y;
      invTangentMatrix._23 = pVertices->ny;
      invTangentMatrix._24 = 0.0f;

      invTangentMatrix._31 = a_pnt->pnt1.z;
      invTangentMatrix._32 = a_pnt->pnt2.z;
      invTangentMatrix._33 = pVertices->nz;
      invTangentMatrix._34 = 0.0f;

      invTangentMatrix._41 = 0.0f;
      invTangentMatrix._42 = 0.0f;
      invTangentMatrix._43 = 0.0f;
      invTangentMatrix._44 = 1.0f;
//-------------------------трансформируем вектор---------------------
      D3DXVec3TransformNormal(&vVertToLightTS, &vVertToLightMS, &invTangentMatrix);
//+
      vct_ = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

      D3DXVec3Add(&vVertToLightTS, &vVertToLightTS, &vct_);
      D3DXVec3Normalize(&vVertToLightTS, &vVertToLightTS);
//+
//код между ++ - идея Вашего покорного слуги. У Кэвина Харриса этого нет и этой вставкой можно пренебречь.
//Назначение дополнительного кода - добавить освещение к полностью затененным участкам. Как бы эмулировать ambient light.
//Без этого дополнительного освещения результат рендеринга не очень удачный.

      pVertices->color = VectortoRGBA( vVertToLightTS, 1.0f );

      if(! ( (i==sdr_.f_count-1) ) ) pVertices++;
    };
  };

  sdr_.shader->Unlock();
};

DWORD VectortoRGBA( D3DXVECTOR3 v, FLOAT fHeight )
{
DWORD r, g, b, a;

  r = (DWORD)( 127.0f * v.x + 128.0f );
  g = (DWORD)( 127.0f * v.y + 128.0f );
  b = (DWORD)( 127.0f * v.z + 128.0f );
  a = (DWORD)( 255.0f * fHeight );

  return( (a<<24L) + (r<<16L) + (g<<8L) + (b<<0L) );
};

Результат рендеринга:
Dot_3_bump_mapping | Построение Dot 3 Bump Mapping'а

#bump, #C++, #DirectX 9, #Dot 3

24 мая 2014