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% будет работать на следующих картах: RaDEON™ X850 series RaDEON™ 9600 series RaDEON™ X800 series RaDEON™ 9500 series RaDEON™ X600 series RaDEON™ 9200 series RaDEON™ X300 series RaDEON™ 9100 series RaDEON™ 9800 series RaDEON™ 9000 series RaDEON™ 9700 series RaDEON™ 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 операции, а это 1/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 . Спасибо всем за внимание.
Источник: http://source-inside.ru/index.php?id=141 |