Урок 10. Различные шейдеры Direct3D10

06.08.2012

В предыдущем уроке мы произвели обзор языка HLSL. В этом уроке мы разберем пару конкретных примеров шейдеров.

Шейдеры

В этом уроке, как и в предыдущем мы не будем разбирать код приложения. Мы рассмотрим код самих шейдеров. Единственный случай, когда мы обратимся к коду приложения – это когда мы будем назначать различные шейдеры различным объектам. В приложении два вида шейдеров и вы должны представлять, как назначать их объектам, наподобие материалов.

Шейдер для эффекта bump

Эффект bump широко используется  для детализации поверхности модели текстурой выпуклостей, еще называемой normalmap. Шейдер для этого эффекта на рисунке имеет шар под номером два. Рассмотрим код пиксельного шейдера этого эффекта:

//------------------------------------------------------------------------------------
// Pixel Shader
//------------------------------------------------------------------------------------
float4 PS1( VS_OUTPUT input ) : SV_Target
{
      input.Tex.y*=4.0f;
      float3 bump=txDiffuse1.Sample(samLinear,input.Tex)*2.0f - 1.0f;
      float shine=dot((float3)input.LI,normalize(bump)); shine+=dot((float3)input.LJ,normalize(bump));
      float4 ambient=txDiffuse2.Sample(samLinear,input.Tex)*input.Diffuse;
      return ambient*(shine*0.5f)+pow(shine,4) * input.Diffuse*0.1f+ambient*0.5f+float4(0.1f,0,0.1f,0);
}

Во первых, на что стоит обратить внимание здесь, это то, что в шейдере используется две текстуры – одна это обычная текстура поверхности txDiffuse2 и другая текстура для образования выпуклой фактуры поверхности txDiffuse1. Эта текстура имеет опрделенный формат наложения цветов, расположенный таким образом, чтобы цвет из этой текстуры мог использоваться как вектор. Таким образом, основная операция освещения для эффекта bump, это вычисление параметра shine, который получается умножением двух векторов – первый вектор input.LI- это вектор от текущей вершины до точки камеры, — а второй вектор берется из цвета текстуры. При этом используется два источника освещения, таким образом передается два вектора для них LI и LJ.

Вершинный шейдер для двух эффектов из этого урока один и тот же. Надо сказать, что создание общего вершинного шейдера для нескольких пиксельных шейдеров это распространенная и весьма полезная практика. Пиксельныйшейдер становится материалом и объединяется с вершинным в разделе technique:

technique10 Render1
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS1() ) );
    }
}

Шейдер для эффекта металлической поверхности

Следующий шейдер имеет более интересный код а также более эффектный вид, хотя первое и второе напрямую не связано. Во первых, нужно сказать пару слов о том, чем металлическая поверхность отличается от обычной. Для металлических предметов характерен блеск. Это происходит потому, что кристаллическая решетка металлов плотная и таким образом, отражает свет и не пропускает его через себя. Таким образом, металлическая поверхность является зеркалом. Единственное отличие – это то, что металл может быть шероховатым, и чем больше шероховатость, тем меньше металл блестит. Однако зеркальные свойства сохраняются, только в очень рассеянном виде.

Таким образом, главное что отличает металлический эффект освещения от обычного, диффузного – это то, что правило – чем прямее угол падения луча на предмет, тем он светлее – для металлов не сохраняется. Здесь действует другое правило – участок металлического предмета светлее всего в тех местах, где луч, проведенный из точки зрения наблюдателя к поверхности металла, отражаясь попадет на источник освещения. И наоборот, если такой луч попадет не на источник света, а куда-нибудь еще, то значит в этих местах металл темный.

Зная координаты источника освещения, координаты текущей точки модели, нормаль к этой точке, и позицию наблюдателя, можно вычислить коэффициент блеска металла в текущей точке.

//------------------------------------------------------------------------------------
// Vertex Shader
//------------------------------------------------------------------------------------
VS_OUTPUT VS( float4 Pos : POSITION, float3 Normal : NORMAL, float2 Tex: TEXCOORD)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul( Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
      output.Color=float4(0.0f,0.0f,0.0f,0);
      float4 lviewvec=normalize(vView-Pos);
      float4 lviewvec=mul(View,float4(0,0,1,0));     
 
      for (int i=0; i<2; i++)
      {
      float3 lpos=(float3)vLightPosition[i];
      float a=dot(normalize(Normal),normalize(lpos-(float3)Pos));
      float b=dot(normalize(Normal),(float3)lviewvec);
      float c=1.0f/(1.0f+(a-b)*(a-b)*5); c=pow(c,5);
      output.Color+=vLightColor[i]*c;
      }
      output.L=lviewvec;
}

Разберем код вершинного шейдера для эффекта металла. Переменная, хранящая интенсивность цвета здесь – output.Color. Эта переменная наполняется в два прохода: каждый проход для одного из источников освещения. Каждый источник освещения может иметь свой цвет. Основная операция для освещения здесь – вычисление коэффициентов a и b. Коэффициент aобозначает угол между нормалью и источником освещения. Коэффициент bобозначает угол между нормалью и точкой наблюдателя. Если углы совпадают, и эти два числа равны значит угол падения равен углу отражения, и в такой точке металл должен обладать ярким свечением. Если коэффициенты различаются, значит углы разные и источник света в данной точке и под данными углами либо не отражается совсем, либо отражается лишь слегка.

Теперь разберем пиксельныйшейдер для нашего эффекта:

//------------------------------------------------------------------------------------
// Pixel Shader 0
//------------------------------------------------------------------------------------
float4 PS0( VS_OUTPUT input ) : SV_Target
{
      float3 refl=reflect((float3)input.L,(float3)input.N);
      return input.Color+txDiffuse0.Sample(samLinear,refl)*0.8f+float4(0.1f,0,0.1f,0);
}

Так как эффект блеска источников освещения уже рассчитан, то, вообщем, мы могли бы просто сделать return input.Color; Однако металл может отражать и что-то еще кроме источников света, поэтому в этом примере добавлена карта отражения, представленная в виде кубической текстуры. Для того, чтобы осуществить выборку из кубической текстуры, — в отличие от двухмерной текстуры, в которой нужно для выборки двухкомпонентный вектор, — в кубической текстуре нужно для выборки обычный трехкомпонентный вектор. Причем этот вектор должен быть рассчитан встроенной функцией reflect. В общем, наш пиксельный шейдер для металлической поверхности получился компактным и прозрачным. Это произошло благодаря тому, что большая часть эффекта металлической поверхности была рассчитана в вершинномшейдере.

Использование нескольких шейдеров в приложении

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

В нашем примере назначение того или иного шейдера происходит при загрузке мешей.

g_MeshFromObj1=new MeshFromObj(g_pd3dDevice,g_pEffect,"Render0","torus.obj");
g_MeshFromObj2=new MeshFromObj(g_pd3dDevice,g_pEffect,"Render1","torus.obj");
g_MeshFromObj3=new MeshFromObj(g_pd3dDevice,g_pEffect,"Render2","light.obj");

В приложении загружается два объекта типа torus. При этом каждому объекту назначается своя техника. Эти техинки перечислены в разделе шейдера для материалов:

//------------------------------------------------------------------------------------
technique10 Render0
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS0() ) );
    }
}
 
//------------------------------------------------------------------------------------
technique10 Render1
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS1() ) );
    }
}

Вы можете видеть, что обе техники Render0 и Render1 используют общий вершинный шейдерVS, пиксельные шейдеры для каждой техники различаются, это PS0 и PS1.

Обзор приложения

Приложение для данного урока и исходные тексты приложения и шейдеров вы можете скачать тут. Как всегда, вы можете найти в папке с исходными кодами и само скомпилированное приложение.

В данном приложении, как всегда, при использовании эффектов освещения, в качестве основного объекта был выбран торус, так как эта фигура содержит хороший набор нормалей, соответствующих сложной трехмерной поверхности. Здесь используется два источника освещения, один из них имеет фиксированные координаты. И главное, поверхностям назначены шейдеры, дающие эффекты bump и металлической поверхности.

Заключение

Вот и подошли к завершению наши уроки по DirectX10. Мы не сомневаемся, что данные уроки были и интересными, и очень полезными в практическом смысле. Также радует тот факт, что цикл уроков завершается на такой блестящей в прямом и переносном смысле ноте. Трехмерная графика может давать хороший результат, при правильном подходе к этому вопросу.

Заполнен: DirectX10
Присвоен тэг:

avatar

Об Авторе ()

Планета назначения, JO - 8703 - IV, обозначается светлой точкой на объемном экране корабля. К тому времени, как Робоид нанялся на юпитерианский круговой рейс, ему следовало бы стать главным инженером, но после Суховодного пионерского его вышибли, внеся в черный список, и высадили в Луна-Сити за то, что вместо слежения за приборами он провел время, программируя синтезатор пищи корабля строжайше запрещенной корабельными инструкциями последовательностью синтеза пива.

Комментирование закрыто.

Наверх