Создаём свои собственные шейдеры
Valve обещала дать возможность
создавать свои собственные шейдеры под HL2. (Возможно Valve скоро выпустит этот
инструментарий и будет доступен с очередным SourceSDK update)
Создавать шейдеры очень не просто, поэтому я решил опубликовать небольшой пример.
Я использовал программу RenderMonkey (Это версия работает только с установленным DirectX 9.0c) от aTI. Скачать можно по следующему адресу: http://www2.ati.com/developer/rendermonkey/files/RenderMonkey.2004-08-03-v1.5.424.exe (46 Мб).
Все наверняка помнят следующую тех демку от aTI:
Вооружившись презентациями по этой демке, я создал упрощённую модель. Упрощённую потому, что у меня всего дишь Radeon 8500, который может выполнить не более 22 aLU (пиксельный шейдер 1.4).
Пример 100% будет работать на следующих картах:
Я создал следующую структуру проекта:
NormalMap текстура для машины (я использовал эту текстуру в разрешении 2048×2048):
Теперь переёдем непосредственно к самим шейдерам:
Пиксельный шейдер:
Вершинный шейдер:
В результате этого должно получиться следующее:
Различные цвета можно изменять. В результате этого мы можем получить различные цветовые схемы машины.
Часть 2.
Из презентаций от aTI мы увидим, что у этой машины есть также слой микрочастиц:
В Render Monkey мной был создан проект со следующей структурой:
Для тех, у кого видеоадаптеры не поддерживают выполнение шейдеров 2-ого поколения, то им необходимо воспользоваться Reference Rasterizer. В моём понимании — это модуль к DirectX, который позволяет обрабатывать шейдеры программно, причём всех моделей (Shader Model 1. x, Shader Model 2, Shader Model 3).
Этот Reference Rasterizer есть в комплекте DirectX SDK. (самый последний dxsdk_oct2004.exe)
Для слоя микрочастиц я использовал следующую текстуру:
Теперь перейдём к самим шейдерам:
Пиксельный шейдер:
При более близком рассмотрении увидим слой микрочастиц, состоящий из маленьких квадратиков:
Конечно, изображение можно сделать гораздо лучше. Здесь уже многое зависит от самой текстуры микрочастиц, которую вы используете.
Часть 3.
Вот пояснения от Valve относительно шейдеров в HL 2:
Valve принимала участие в GDC 2004, а в результате на сайте aTI появился файл D3DTutorial10_Half-Life2_Shading.pdf (который также можно скачать с сайта aTI). А пример будет строиться по подобному дереву эффектов в этой документации. В помощь приходит проектный файл Illumination.rfx, входящий в состав RM 1.6 (в нём содержится весь нужный нам код для описания подобной структуры). Разумеется, проект был немного переделан, чтоб как можно больше соответствовал структуре, описанной в документации. Также убраны все лишние объекты.
Исходный код, приводящийся в документации, я не использовал ввиду того, что это займёт не мало времени на переделку в структуру RM.
Вот структура, которую мы попытаемся сделать (Взято из D3DTutorial10_Half-Life2_Shading.pdf):
А вот как это получилось описать у меня для примера, который мы рассматриваем:
В Render Monkey проект выглядит следующим образом:
Для тех, у кого видеоадаптеры не поддерживают выполнение шейдеров 2-ого поколения, то им необходимо воспользоваться Reference Rasterizer. В моём понимании — это модуль к DirectX, который позволяет обрабатывать шейдеры программно, причём всех моделей (Shader Model 1. x, Shader Model 2, Shader Model 3).
Этот Reference Rasterizer есть в комплекте DirectX SDK.
Т.к. у меня всего лишь Radeon 8500 (PS 1.4), то мне пришлось им воспользоваться. Reference Rasterizer занимает где-то 8 Мб и его возможно установить отдельно от DX 9 SDK — достаточно скопировать нужные библиотеки в system 32.
Под PS 1.4 сделать подобный пример невозможно (даже если будет несколько проходов (pass)). Вот, например,
bump = normalize (bump * 2.0 f); занимает 4 операции, а это ¼ PS 1.4.
Теперь перейдём к самим шейдерам:
Вершинный шейдер:
float4x4 view_proj_matrix: register (c0);
float4 light_position: register (c8);
float4 eye_position: register (c9);
float4x4 view_matrix: register (c10);
float4x4 inv_view_matrix;
struct VS_INPUT_StrUCT
{
float4 position: POSITION;
float3 normal: NORMaL;
float2 texcoord0: TEXCOORD0;
float3 binormal: BINORMaL0;
float3 tangent: TaNGENT0;
};
struct VS_OUTPUT_StrUCT
{
float4 position: POSITION;
float2 bump_map: TEXCOORD0;
float3 light_vector: TEXCOORD1;
float3 half_angle: TEXCOORD2;
float3 basis1: TEXCOORD3;
float3 basis2: TEXCOORD4;
float3 basis3: TEXCOORD5;
};
//** main — Главная точка входа для вершинного шейдера
//** Returns: VS_OUTPUT_StrUCT
//**---------------------------------------------------------
VS_OUTPUT_StrUCT main (VS_INPUT_StrUCT vsInStruct)
{
VS_OUTPUT_StrUCT vsOutStruct; //** Declare the output struct
vsOutStruct.position = mul (view_proj_matrix, vsInStruct. position);
float3 position = mul (view_matrix, vsInStruct. position);
//**----------------------------------------------
//** Проход для вычисления координат текстур bump и base
//**----------------------------------------------
vsOutStruct.bump_map = vsInStruct. texcoord0;
//**--------------------------------------------
//** Подсчёт вектора света в пространстве,
//** и далее трансформация его в область текстуры.
//**--------------------------------------------
float3 temp_light_position = mul (inv_view_matrix, light_position);
float3 temp_light_vector = temp_light_position — vsInStruct. position;
vsOutStruct.light_vector.x = dot (temp_light_vector, vsInStruct. tangent);
vsOutStruct.light_vector.y = dot (temp_light_vector, vsInStruct. binormal);
vsOutStruct.light_vector.z = dot (temp_light_vector, vsInStruct. normal);
//**-------------------------------------------
//** Calculate the view vector in object space,
//** and then transform it into texture space.
//**-------------------------------------------
float3 temp_eye_position = mul (inv_view_matrix, eye_position);
float3 temp_view_vector = temp_eye_position — vsInStruct. position;
float3 temp_view_vector2;
temp_view_vector2.x = dot (temp_view_vector, vsInStruct. tangent);
temp_view_vector2.y = dot (temp_view_vector, vsInStruct. binormal);
temp_view_vector2.z = dot (temp_view_vector, vsInStruct. normal);
//**-------------------------
//** Calculate the half angle
//**-------------------------
vsOutStruct.half_angle = vsOutStruct. light_vector + temp_view_vector2;
//**-----------------------------------------
//** Construct the transpose of the tangent
//** space basis vectors, so we can transform
//** the reflection vector from texture space
//** into view space
//**-----------------------------------------
vsOutStruct.basis1.x = vsInStruct.tangent.x;
vsOutStruct.basis1.y = vsInStruct.binormal.x;
vsOutStruct.basis1.z = vsInStruct.normal.x;
vsOutStruct.basis2.x = vsInStruct.tangent.y;
vsOutStruct.basis2.y = vsInStruct.binormal.y;
vsOutStruct.basis2.z = vsInStruct.normal.y;
vsOutStruct.basis3.x = vsInStruct.tangent.z;
vsOutStruct.basis3.y = vsInStruct.binormal.z;
vsOutStruct.basis3.z = vsInStruct.normal.z;
return vsOutStruct; //** Return the resulting output struct
}
В результате у вершинного шейдера 34 операции.
Пиксельный шейдер:
float4 specular: register (c6);
float Ka: register (c7);
float Kd: register (c8);
float Ks: register (c9);
float specular_power: register (c10);
float bumpiness: register (c11);
float reflection_clarity: register (c12);
float reflectance: register (c13);
float4 ambient: register (c4);
float4 diffuse: register (c5);
float4x4 view_matrix: register (c0);
sampler base_map: register (s0);
sampler bump_map: register (s1);
sampler environment_map: register (s2);
struct PS_INPUT_StrUCT
{
float2 bump_map: TEXCOORD0;
float3 light_vector: TEXCOORD1;
float3 half_angle: TEXCOORD2;
float3 basis1: TEXCOORD3;
float3 basis2: TEXCOORD4;
float3 basis3: TEXCOORD5;
};
struct PS_OUTPUT_StrUCT
{
float4 color0: COLOR0;
};
//**---------------------------------------------------------
//** Function: main
//** Description: Declare the main entry point for the shader
//** Input: PS_INPUT_StrUCT, derived from the output of
//** the associated vertex shader
//** Returns: PS_OUTPUT_StrUCT
//**---------------------------------------------------------
PS_OUTPUT_StrUCT main (PS_INPUT_StrUCT psInStruct)
{
PS_OUTPUT_StrUCT psOutStruct;
//**----------------------------------------
//** Get the base and bump map texture coord
//**----------------------------------------
float4 bump_coord = { psInStruct.bump_map, 0.0f, reflection_clarity };
//**------------------------------------------------------
//** Retreive the base color and bump components from the
//** respective textures, based on the passed bump coords.
//**------------------------------------------------------
float3 base = tex2D (base_map, bump_coord);
float3 bump = tex2D (bump_map, bump_coord);
//**--------------------------------------------------
//** Includes MIP bias to help clairify the reflection
//**--------------------------------------------------
float3 reflection_bump = tex2Dbias (bump_map, bump_coord);
//**----------------------------------------------------
//** Normalize the passed vectors from the vertex shader
//**----------------------------------------------------
float3 normalized_light_vector = normalize (psInStruct.light_vector);
float3 normalized_half_angle = normalize (psInStruct.half_angle);
//**--------------------------------------------------------
//** «Smooth out» the bumps based on the bumpiness parameter.
//** This is simply a linear interpolation between a «flat»
//** normal and a «bumped» normal. Note that this «flat»
//** normal is based on the texture space coordinate basis.
//**--------------------------------------------------------
float3 smooth;
smooth.r = 0.5f;
smooth.g = 0.5f;
smooth.b = 1.0f;
bump = lerp (smooth, bump, bumpiness);
bump = normalize ((bump * 2.0f) — 1.0f);
reflection_bump = lerp (smooth, reflection_bump, bumpiness);
reflection_bump = normalize ((reflection_bump * 2.0f) — 1.0f);
//**---------------------------------------------
//** translate the reflection normal from texture
//** space into view space, so we can case the
//** reflection vector into an environment map.
//**---------------------------------------------
float3 reflection = reflection_bump;
reflection.x = dot (reflection_bump, psInStruct. basis1);
reflection.y = dot (reflection_bump, psInStruct. basis2);
reflection.z = dot (reflection_bump, psInStruct. basis3);
float3 reflection_vector = mul (view_matrix, reflection);
reflection_vector = normalize (reflection_vector);
//**------------------------------------------
//** Calculate the resulting reflection color.
//**------------------------------------------
// float3 reflection_color = texCUBE (environment_map, reflection_vector);
float3 reflection_color = texCUBE (environment_map, reflection_vector);
//**----------------------------------------------------------
//** The following modifiers are used to enhance the effect of
//** the basic reflection idea. Normally, specular / gloss
//** maps will take the place of these modifiers.
//**----------------------------------------------------------
float3 result_color = lerp (base, reflection_color, reflectance);
// float3 modified_specular_color = specular;// * base;
// float3 modified_specular_coefficient = Ks;// * base;
//**---------------------------------------------------------
//** These dot products are used for the lighting model
//** equations. The surface normal dotted with the light
//** vector is denoted by n_dot_l. The normal vector
//** dotted with the half angle vector is denoted by n_dot_h.
//**---------------------------------------------------------
float3 n_dot_l = dot (bump, normalized_light_vector);
float3 n_dot_h = dot (bump, normalized_half_angle);
//**--------------------------------------
//** Далее идёт формирование финального результата
//** --------------------------------------
float3 Specular_final = (specular * Ks * pow (max (0, n_dot_h), specular_power));
float3 Diffuse_final = (diffuse * Kd * max (0, n_dot_l));
float3 ambient_final = (base * ambient*Ka);
float3 bump_Cube_specular = result_color * Specular_final;
float3 Diffuse_bump_ambient = Diffuse_final + ambient_final;
psOutStruct.color0.rgb =bump_Cube_specular+Diffuse_bump_ambient;
//**------------------------------------
//** Interpolate the resulting color
//** based on the reflectance parameter.
//**------------------------------------
psOutStruct.color0.a = 1.0f; //** Set the alpha component manually
return psOutStruct; //** Return the resulting output struct
}
У пиксельного шейдера 5 5 операций .
В результате мы должны получить следующее:
Можно менять значения объектов (например, specular), при этом мы будем получать разные результаты:
В результате мы получили технологию, похожую в Unreal 3 Engine.
Рекомендую всем ознакомиться со статьёй по созданию шейдеров на gamedev. ru:
http://www.gamedev.ru/articles/read.shtml?id=10109&page=1
Автор описывает основы программирования на HLSL.
Спасибо всем за внимание.
Создавать шейдеры очень не просто, поэтому я решил опубликовать небольшой пример.
Я использовал программу RenderMonkey (Это версия работает только с установленным DirectX 9.0c) от aTI. Скачать можно по следующему адресу: http://www2.ati.com/developer/rendermonkey/files/RenderMonkey.2004-08-03-v1.5.424.exe (46 Мб).
Все наверняка помнят следующую тех демку от aTI:
Вооружившись презентациями по этой демке, я создал упрощённую модель. Упрощённую потому, что у меня всего дишь Radeon 8500, который может выполнить не более 22 aLU (пиксельный шейдер 1.4).
Пример 100% будет работать на следующих картах:
RaDEONTM X850 series | RaDEONTM 9600 series |
RaDEONTM X800 series | RaDEONTM 9500 series |
RaDEONTM X600 series | RaDEONTM 9200 series |
RaDEONTM X300 series | RaDEONTM 9100 series |
RaDEONTM 9800 series | RaDEONTM 9000 series |
RaDEONTM 9700 series | RaDEONTM 8500 series |
Я создал следующую структуру проекта:
NormalMap текстура для машины (я использовал эту текстуру в разрешении 2048×2048):
Теперь переёдем непосредственно к самим шейдерам:
Пиксельный шейдер:
sampler normalMap;
sampler showroomMap;
float4 paintColor0;
float4 paintColor2;
float4 paintColorMid;
float glossLevel;
float brightnessFactor;
float4 ps_main (//float4 Diff: COLOR0,
float2 Tex :TEXCOORD0,
float3 Tangent :TEXCOORD1,
float3 Binormal: TEXCOORD2,
float3 Normal :TEXCOORD3,
float3 View :TEXCOORD4 ): COLOR
{
float3 vNormal = tex2D (normalMap, Tex);
vNormal = 2 * vNormal — 1.0;
float3x3 mTangentToWorld = transpose (float3x3( Tangent, Binormal, Normal));
float3 vNormalWorld = mul (mTangentToWorld, vNormal);
float4 fNdotV = mul (vNormalWorld, View );
float fNdotVSq = fNdotV * fNdotV;
float4 paintColor = fNdotV * paintColor0 +
fNdotVSq * paintColorMid +
fNdotVSq * fNdotVSq * paintColor2;
float fFresnel = saturate (dot (vNormalWorld, View));
float3 vReflection = 2 * vNormalWorld * fFresnel — View;
float fEnvBias = glossLevel;
// Sample environment map using this reflection vector and bias:
float4 envMap = texCUBE ( showroomMap, float4(vReflection, fEnvBias));
// Premultiply by alpha:
envMap.rgb = envMap.rgb * envMap.a;
// Brighten the environment map sampling result:
envMap.rgb *= brightnessFactor;
// Combine result of environment map reflection with the paint
//float fEnvContribution = 1.0 — 0.5 * fFresnel;
return float4 (envMap.rgb + paintColor, 1.0);//* fEnvContribution
}
Вершинный шейдер:
float4x4 view_proj_matrix;
float4 view_position;
float4x4 inv_view_matrix;
struct VS_OUTPUT
{
float4 Pos: POSITION;
float2 Tex :TEXCOORD0;
float3 Tangent :TEXCOORD1;
float3 Binormal: TEXCOORD2;
float3 Normal :TEXCOORD3;
float3 View :TEXCOORD4;
};
VS_OUTPUT main (float4 Pos: POSITION,
float3 Normal: NORMaL,
float2 Tex :TEXCOORD,
float3 Tangent: TaNGENT,
float3 Binormal: BINORMaL
)
{
VS_OUTPUT Out = (VS_OUTPUT) 0;
Out.Pos = mul (view_proj_matrix, Pos);
Out.View = normalize (mul (inv_view_matrix, float4(0, 0, 0, 1)) — Pos );
Out.Tex = Tex;
Out.Normal = Normal;
Out.Tangent = Tangent;
Out.Binormal = Binormal;
return Out;
}
В результате этого должно получиться следующее:
Различные цвета можно изменять. В результате этого мы можем получить различные цветовые схемы машины.
Часть 2.
Из презентаций от aTI мы увидим, что у этой машины есть также слой микрочастиц:
В Render Monkey мной был создан проект со следующей структурой:
Для тех, у кого видеоадаптеры не поддерживают выполнение шейдеров 2-ого поколения, то им необходимо воспользоваться Reference Rasterizer. В моём понимании — это модуль к DirectX, который позволяет обрабатывать шейдеры программно, причём всех моделей (Shader Model 1. x, Shader Model 2, Shader Model 3).
Этот Reference Rasterizer есть в комплекте DirectX SDK. (самый последний dxsdk_oct2004.exe)
Для слоя микрочастиц я использовал следующую текстуру:
Теперь перейдём к самим шейдерам:
Пиксельный шейдер:
sampler normalMap;Вершинный шейдер :
sampler microflakeNMap;
float microflakePerturbationa;
float normalPerturbation;
float microflakePerturbation;
float4 paintColor0;
float4 paintColor2;
float4 paintColorMid;
float4 flakeLayerColor;
float4 ps_main (float4 Diff: COLOR0,
float2 Tex: TEXCOORD0,
float3 Tangent: TEXCOORD1,
float3 Binormal: TEXCOORD2,
float3 Normal: TEXCOORD3,
float3 View: TEXCOORD4,
float3 SparkleTex: TEXCOORD5): COLOR
{// Пертурбированная нормаль из карты шумов
float3 vNormal = tex2D (normalMap, Tex);
vNormal = 2 * vNormal — 1.0;
// Подсчитываем нормали для обоих слоев микрочастиц
float3 vFlakesNormal = 2 * tex2D (microflakeNMap, SparkleTex) — 1;
float3 vNp1 = microflakePerturbationa * vFlakesNormal +
normalPerturbation * vNormal;
float3 vNp2 = microflakePerturbation * (vFlakesNormal + vNormal);
float3 vView = View;
float3x3 mTangentToWorld = transpose (float3x3(Tangent, Binormal, Normal));
// Подсчитываем скалярные продукты нормализированного вектора обозревателя с нормалями для обоих слоев микрочастиц
float3 vNp1World = mul (mTangentToWorld, vNp1);
float fFresnel1 = saturate (dot (vNp1World, vView));
float3 vNp2World = mul (mTangentToWorld, vNp2);
float fFresnel2 = saturate (dot (vNp2World, vView));
// Компонуем многотональный цвет прослойки микрочастиц
float fFresnel1Sq = fFresnel1 * fFresnel1;
float4 paintColor = fFresnel1 * flakeLayerColor + fFresnel1Sq * flakeLayerColor +
fFresnel1Sq * fFresnel1Sq * flakeLayerColor+
pow (fFresnel2, 16) * flakeLayerColor;
return float4 (paintColor);
}
float4x4 view_proj_matrix;В результате мы должны получить следующее:
float4x4 inv_view_matrix;
float fFlakeTilingFactor;
struct VS_OUTPUT
{
float4 Pos: POSITION;
float2 Tex: TEXCOORD0;
float3 Tangent: TEXCOORD1;
float3 Binormal: TEXCOORD2;
float3 Normal: TEXCOORD3;
float3 View: TEXCOORD4;
float3 SparkleTex: TEXCOORD5;
};
VS_OUTPUT vs_main (float4 Pos: POSITION,
float3 Normal: NORMaL,
float2 Tex: TEXCOORD0,
float3 Tangent: TaNGENT,
float3 Binormal:BINORMaL)
{
VS_OUTPUT Out = (VS_OUTPUT) 0;
Out.Pos = mul (view_proj_matrix, Pos);
Out.View = normalize (mul (inv_view_matrix, float4(0, 0, 0, 1)) — Pos);
Out.Tex = Tex;
Out.Normal = Normal;
Out.Tangent = Tangent;
Out.Binormal = Binormal;
Out.SparkleTex = float4(Tex * fFlakeTilingFactor, 0,1);
return Out;
}
При более близком рассмотрении увидим слой микрочастиц, состоящий из маленьких квадратиков:
Конечно, изображение можно сделать гораздо лучше. Здесь уже многое зависит от самой текстуры микрочастиц, которую вы используете.
Часть 3.
Вот пояснения от Valve относительно шейдеров в HL 2:
Valve принимала участие в GDC 2004, а в результате на сайте aTI появился файл D3DTutorial10_Half-Life2_Shading.pdf (который также можно скачать с сайта aTI). А пример будет строиться по подобному дереву эффектов в этой документации. В помощь приходит проектный файл Illumination.rfx, входящий в состав RM 1.6 (в нём содержится весь нужный нам код для описания подобной структуры). Разумеется, проект был немного переделан, чтоб как можно больше соответствовал структуре, описанной в документации. Также убраны все лишние объекты.
Исходный код, приводящийся в документации, я не использовал ввиду того, что это займёт не мало времени на переделку в структуру RM.
Вот структура, которую мы попытаемся сделать (Взято из D3DTutorial10_Half-Life2_Shading.pdf):
А вот как это получилось описать у меня для примера, который мы рассматриваем:
В Render Monkey проект выглядит следующим образом:
Для тех, у кого видеоадаптеры не поддерживают выполнение шейдеров 2-ого поколения, то им необходимо воспользоваться Reference Rasterizer. В моём понимании — это модуль к DirectX, который позволяет обрабатывать шейдеры программно, причём всех моделей (Shader Model 1. x, Shader Model 2, Shader Model 3).
Этот Reference Rasterizer есть в комплекте DirectX SDK.
Т.к. у меня всего лишь Radeon 8500 (PS 1.4), то мне пришлось им воспользоваться. Reference Rasterizer занимает где-то 8 Мб и его возможно установить отдельно от DX 9 SDK — достаточно скопировать нужные библиотеки в system 32.
Под PS 1.4 сделать подобный пример невозможно (даже если будет несколько проходов (pass)). Вот, например,
bump = normalize (bump * 2.0 f); занимает 4 операции, а это ¼ PS 1.4.
Теперь перейдём к самим шейдерам:
Вершинный шейдер:
float4x4 view_proj_matrix: register (c0);
float4 light_position: register (c8);
float4 eye_position: register (c9);
float4x4 view_matrix: register (c10);
float4x4 inv_view_matrix;
struct VS_INPUT_StrUCT
{
float4 position: POSITION;
float3 normal: NORMaL;
float2 texcoord0: TEXCOORD0;
float3 binormal: BINORMaL0;
float3 tangent: TaNGENT0;
};
struct VS_OUTPUT_StrUCT
{
float4 position: POSITION;
float2 bump_map: TEXCOORD0;
float3 light_vector: TEXCOORD1;
float3 half_angle: TEXCOORD2;
float3 basis1: TEXCOORD3;
float3 basis2: TEXCOORD4;
float3 basis3: TEXCOORD5;
};
//** main — Главная точка входа для вершинного шейдера
//** Returns: VS_OUTPUT_StrUCT
//**---------------------------------------------------------
VS_OUTPUT_StrUCT main (VS_INPUT_StrUCT vsInStruct)
{
VS_OUTPUT_StrUCT vsOutStruct; //** Declare the output struct
vsOutStruct.position = mul (view_proj_matrix, vsInStruct. position);
float3 position = mul (view_matrix, vsInStruct. position);
//**----------------------------------------------
//** Проход для вычисления координат текстур bump и base
//**----------------------------------------------
vsOutStruct.bump_map = vsInStruct. texcoord0;
//**--------------------------------------------
//** Подсчёт вектора света в пространстве,
//** и далее трансформация его в область текстуры.
//**--------------------------------------------
float3 temp_light_position = mul (inv_view_matrix, light_position);
float3 temp_light_vector = temp_light_position — vsInStruct. position;
vsOutStruct.light_vector.x = dot (temp_light_vector, vsInStruct. tangent);
vsOutStruct.light_vector.y = dot (temp_light_vector, vsInStruct. binormal);
vsOutStruct.light_vector.z = dot (temp_light_vector, vsInStruct. normal);
//**-------------------------------------------
//** Calculate the view vector in object space,
//** and then transform it into texture space.
//**-------------------------------------------
float3 temp_eye_position = mul (inv_view_matrix, eye_position);
float3 temp_view_vector = temp_eye_position — vsInStruct. position;
float3 temp_view_vector2;
temp_view_vector2.x = dot (temp_view_vector, vsInStruct. tangent);
temp_view_vector2.y = dot (temp_view_vector, vsInStruct. binormal);
temp_view_vector2.z = dot (temp_view_vector, vsInStruct. normal);
//**-------------------------
//** Calculate the half angle
//**-------------------------
vsOutStruct.half_angle = vsOutStruct. light_vector + temp_view_vector2;
//**-----------------------------------------
//** Construct the transpose of the tangent
//** space basis vectors, so we can transform
//** the reflection vector from texture space
//** into view space
//**-----------------------------------------
vsOutStruct.basis1.x = vsInStruct.tangent.x;
vsOutStruct.basis1.y = vsInStruct.binormal.x;
vsOutStruct.basis1.z = vsInStruct.normal.x;
vsOutStruct.basis2.x = vsInStruct.tangent.y;
vsOutStruct.basis2.y = vsInStruct.binormal.y;
vsOutStruct.basis2.z = vsInStruct.normal.y;
vsOutStruct.basis3.x = vsInStruct.tangent.z;
vsOutStruct.basis3.y = vsInStruct.binormal.z;
vsOutStruct.basis3.z = vsInStruct.normal.z;
return vsOutStruct; //** Return the resulting output struct
}
В результате у вершинного шейдера 34 операции.
Пиксельный шейдер:
float4 specular: register (c6);
float Ka: register (c7);
float Kd: register (c8);
float Ks: register (c9);
float specular_power: register (c10);
float bumpiness: register (c11);
float reflection_clarity: register (c12);
float reflectance: register (c13);
float4 ambient: register (c4);
float4 diffuse: register (c5);
float4x4 view_matrix: register (c0);
sampler base_map: register (s0);
sampler bump_map: register (s1);
sampler environment_map: register (s2);
struct PS_INPUT_StrUCT
{
float2 bump_map: TEXCOORD0;
float3 light_vector: TEXCOORD1;
float3 half_angle: TEXCOORD2;
float3 basis1: TEXCOORD3;
float3 basis2: TEXCOORD4;
float3 basis3: TEXCOORD5;
};
struct PS_OUTPUT_StrUCT
{
float4 color0: COLOR0;
};
//**---------------------------------------------------------
//** Function: main
//** Description: Declare the main entry point for the shader
//** Input: PS_INPUT_StrUCT, derived from the output of
//** the associated vertex shader
//** Returns: PS_OUTPUT_StrUCT
//**---------------------------------------------------------
PS_OUTPUT_StrUCT main (PS_INPUT_StrUCT psInStruct)
{
PS_OUTPUT_StrUCT psOutStruct;
//**----------------------------------------
//** Get the base and bump map texture coord
//**----------------------------------------
float4 bump_coord = { psInStruct.bump_map, 0.0f, reflection_clarity };
//**------------------------------------------------------
//** Retreive the base color and bump components from the
//** respective textures, based on the passed bump coords.
//**------------------------------------------------------
float3 base = tex2D (base_map, bump_coord);
float3 bump = tex2D (bump_map, bump_coord);
//**--------------------------------------------------
//** Includes MIP bias to help clairify the reflection
//**--------------------------------------------------
float3 reflection_bump = tex2Dbias (bump_map, bump_coord);
//**----------------------------------------------------
//** Normalize the passed vectors from the vertex shader
//**----------------------------------------------------
float3 normalized_light_vector = normalize (psInStruct.light_vector);
float3 normalized_half_angle = normalize (psInStruct.half_angle);
//**--------------------------------------------------------
//** «Smooth out» the bumps based on the bumpiness parameter.
//** This is simply a linear interpolation between a «flat»
//** normal and a «bumped» normal. Note that this «flat»
//** normal is based on the texture space coordinate basis.
//**--------------------------------------------------------
float3 smooth;
smooth.r = 0.5f;
smooth.g = 0.5f;
smooth.b = 1.0f;
bump = lerp (smooth, bump, bumpiness);
bump = normalize ((bump * 2.0f) — 1.0f);
reflection_bump = lerp (smooth, reflection_bump, bumpiness);
reflection_bump = normalize ((reflection_bump * 2.0f) — 1.0f);
//**---------------------------------------------
//** translate the reflection normal from texture
//** space into view space, so we can case the
//** reflection vector into an environment map.
//**---------------------------------------------
float3 reflection = reflection_bump;
reflection.x = dot (reflection_bump, psInStruct. basis1);
reflection.y = dot (reflection_bump, psInStruct. basis2);
reflection.z = dot (reflection_bump, psInStruct. basis3);
float3 reflection_vector = mul (view_matrix, reflection);
reflection_vector = normalize (reflection_vector);
//**------------------------------------------
//** Calculate the resulting reflection color.
//**------------------------------------------
// float3 reflection_color = texCUBE (environment_map, reflection_vector);
float3 reflection_color = texCUBE (environment_map, reflection_vector);
//**----------------------------------------------------------
//** The following modifiers are used to enhance the effect of
//** the basic reflection idea. Normally, specular / gloss
//** maps will take the place of these modifiers.
//**----------------------------------------------------------
float3 result_color = lerp (base, reflection_color, reflectance);
// float3 modified_specular_color = specular;// * base;
// float3 modified_specular_coefficient = Ks;// * base;
//**---------------------------------------------------------
//** These dot products are used for the lighting model
//** equations. The surface normal dotted with the light
//** vector is denoted by n_dot_l. The normal vector
//** dotted with the half angle vector is denoted by n_dot_h.
//**---------------------------------------------------------
float3 n_dot_l = dot (bump, normalized_light_vector);
float3 n_dot_h = dot (bump, normalized_half_angle);
//**--------------------------------------
//** Далее идёт формирование финального результата
//** --------------------------------------
float3 Specular_final = (specular * Ks * pow (max (0, n_dot_h), specular_power));
float3 Diffuse_final = (diffuse * Kd * max (0, n_dot_l));
float3 ambient_final = (base * ambient*Ka);
float3 bump_Cube_specular = result_color * Specular_final;
float3 Diffuse_bump_ambient = Diffuse_final + ambient_final;
psOutStruct.color0.rgb =bump_Cube_specular+Diffuse_bump_ambient;
//**------------------------------------
//** Interpolate the resulting color
//** based on the reflectance parameter.
//**------------------------------------
psOutStruct.color0.a = 1.0f; //** Set the alpha component manually
return psOutStruct; //** Return the resulting output struct
}
У пиксельного шейдера 5 5 операций .
В результате мы должны получить следующее:
Можно менять значения объектов (например, specular), при этом мы будем получать разные результаты:
В результате мы получили технологию, похожую в Unreal 3 Engine.
Рекомендую всем ознакомиться со статьёй по созданию шейдеров на gamedev. ru:
http://www.gamedev.ru/articles/read.shtml?id=10109&page=1
Автор описывает основы программирования на HLSL.
Спасибо всем за внимание.