Возможно вы искали: 'Platypus 2'

May 11 2025 07:13:32
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96001592
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18342
• Обзор The Walking ... 18785
• Обзор DMC: Devil M... 19861
• Обзор на игру Valk... 15866
• Обзор на игру Stars! 17752
• Обзор на Far Cry 3 17933
• Обзор на Resident ... 16011
• Обзор на Chivalry:... 17494
• Обзор на игру Kerb... 17969
• Обзор игры 007: Fr... 16600
Превью о играх
• Превью к игре Comp... 17944
• Превью о игре Mage... 14447
• Превью Incredible ... 14704
• Превью Firefall 13460
• Превью Dead Space 3 16324
• Превью о игре SimC... 14713
• Превью к игре Fuse 15430
• Превью Red Orche... 15531
• Превью Gothic 3 16331
• Превью Black & W... 17342
Главная » Статьи » Всё о XNA » Меташейдеры

Меташейдеры

Теория

Что такое метапрограммирование?

Если программирование подразумевает собой реализацию алгоритмов обработки данных на каком-либо языке, то метапрограммирование это реализация алгоритмов для работы с самими программами, представленными в виде исходного кода или еще как то. Проще говоря, метапрограммирование – это создание программ, создающих другие программы или манипулирующих ими и даже собой.

C# является ООП языком и предоставляет возможности практически на прямую использовать формальное описание объектной модели предметной области решаемой задачи. Среди основных понятий и принципов ООП отметим следующие – Абстракция данных, Инкапсуляция, Наследование, Полиморфизм.

Полиморфизм рассмотрим немного подробнее, так как именно его преимущества лежат в основе предлагаемого подхода. Полиморфным будет тот код, который при вызове какой либо функции с определенным (одним и тем же) именем использует различные типы передаваемых аргументов, что обеспечивает выполнение разного конечного кода. На практике это значит, что у нас есть возможность выполнять контекстно-зависимый код, где контекст определяется типом аргумента вызываемой функции. Собственно это и есть то преимущество полиморфизма, которое позволит нам создавать HLSL шейдеры на языке C#.

Полиморфизм глазами C#

Тут за примером ходить далеко не придется, полиморфизм используется практически везде. Возьмите рефлектор и откройте декларацию класса System.IO.BinaryWriter. У метода Write(..) есть 17 вариантов реализации. Кроме этого – вот еще небольшой пример для наглядности –

Предположим у нас есть структура для описания вершин –

1
2
3
4
5
6
struct VertexPositionNormalTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
}

 

Теперь нам необходимо сохранить ее в бинарном виде. Для этого добавим еще «немного полиморфизма» в стандартный BinaryWriter. Для добавления используем наследование.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBinaryWriter : BinaryWriter
{
public void Write(Vector2 v2)
{
Write(v2.X);
Write(v2.Y);
}
public void Write(Vector3 v3)
{
Write(v3.X);
Write(v3.Y);
Write(v3.Z);
}
}

 

MyBinaryWriter уже содержит 19 вариантов реализации метода Write(..).  Наша структура, теперь умеющая сохранять себя, будет выглядеть так –

1
2
3
4
5
6
7
8
9
10
11
12
13
struct VertexPositionNormalTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
 
public void Write(MyBinaryWriter writer)
{
writer.Write(Position);
writer.Write(Normal);
writer.Write(TextureCoordinate);
}
}

 

Получилось наглядно и работать с этим удобно будет...

Одной из разновидностей полиморфизма в C# является перегрузка операторов. XNA использует это для обеспечения наглядности при работе с векторными и матричными типами данных.

Сравните два метода, выполняющих одни и те же вычисления -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Matrix GetTransform1(ref Matrix world, ref Matrix view, ref Matrix proj)
{
Matrix result;
 
Matrix.Multiply(ref world, ref view, out result);
Matrix.Multiply(ref result, ref proj, out result);
 
return result;
}
 
Matrix GetTransform2(Matrix world, Matrix view, Matrix proj)
{
return world * view * proj;
}

 

Эти методы равнозначны. Только первый из них реализован с учетом производительности (порядка 20% будет выигрыш). А второй с учетом наглядности и использует перегруженный оператор умножения.

См. рефлектор, struct Matrix - public static Matrix operator *(Matrix matrix1, Matrix matrix2);

Меташейдеры для XNA

Основная идея очень проста – что если в перегруженных операторах выполнять не сами вычисления, а попытаться сохранить их логику? Очевидно, что для формального описания логики вычислений нам понадобится ее объектная модель. Тут нужна модель, описывающая как ход этих вычислений, так и любой другой ход программы, возможный в рамках представляемого языка, в нашем случае HLSL. Благо рассматриваемый нами язык не так богат на возможности и классов из System.CodeDom нам вполне хватит.

Рассмотрим простейший пиксельный шейдер, складывающий диффузную и спекулярную компоненты освещения –

1
2
3
4
float4 PSBasic(in float4 Diffuse: COLOR0, in float4 Specular : COLOR1) : COLOR
{
return Diffuse + Specular;
}

 


Для написания метапрограммы, результатом которой будет аналогичный код - определим новый тип данных float4 и перегрузим оператор сложения для него.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct float4
{
public CodeExpression Expression;
 
public float4(CodeExpression expression)
{
this.Expression = expression;
}
 
public static float4 operator +(float4 left, float4 right)
{
CodeBinaryOperatorExpression expression;
 
expression = new CodeBinaryOperatorExpression(
left.Expression, CodeBinaryOperatorType.Add, right.Expression);
 
return new float4(expression);
}
 
public static implicit operator float4(string argName)
{
return new float4(new CodeArgumentReferenceExpression(argName));
}
 
public override string ToString()
{
CSharpCodeProvider cSharp = new CSharpCodeProvider();
 
StringBuilder hlslCode = new StringBuilder();
StringWriter writer = new StringWriter(hlslCode);
CodeGeneratorOptions options = new CodeGeneratorOptions();
 
cSharp.GenerateCodeFromExpression(Expression, writer, options);
 
return hlslCode.ToString();
}
}

 

Обратите внимание на реализацию метода ToString(). Мы используем CSharpCodeProvider, для теста этого достаточно но для более сложных вариантов понадобится свой HlslCodeProvider наследованный от базового.

Теперь мы можем во время исполнения видеть логику хода программы.


И главное можно не только наблюдать ее но и анализировать. Для более удобного анализа есть смысл построить свой CodeDom для Hlsl. Из которого убрать все лишнее. Добавить реализацию паттерна посетитель и пр.. Но это уже детали реализации, а цель этой статьи - раскрыть торию меташейдеров.

К слову о реализации - есть такой коммерческий 3D движек Visual3D, в нем используется похожий принцип. Система материалов построена на меташейдерах. В качестве бонуса к этой статье, если хватит времени до окончания конкурса, будет предложена более грамотная реализация рассматриваемого подхода. Правильный подход поразумевает использование объектой модели конечного языка (CodeDom), как это было показано в данной статье. В Visual3D в перегруженных операторах используют обычную конкатенацию строк. В результате имеем сразу исходник Hlsl, что ограничивает возможности анализа, манипулиции и оптимизации конечного кода.

На данный момент есть полурабочий прототип, позволяющий декларировать шейдеры на шарпе используя синтаксис схожий с HLSL -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
struct VSInput
{
[POSITION]
public float4 Position;// : POSITION;
}
public struct CommonVSOutput
{
public float4 Pos_ws;
public float4 Pos_ps;
public float4 Diffuse;
public float3 Specular;
public float1 FogFactor;
}
public struct VertexLightingVSOutput
{
[POSITION] // Position in projection space
public float4 PositionPS;// : POSITION;
 
[COLOR0]
public float4 Diffuse;// : COLOR0;
 
[COLOR1]// Specular.rgb and fog factor
public float4 Specular;// : COLOR1;
}
 
public struct VertexLightingPSInput
{
[COLOR0]
public float4 Diffuse;// : COLOR0;
[COLOR1]
public float4 Specular;// : COLOR1;
}
public struct VertexLightingPSInputTx
{
[COLOR0]
public float4 Diffuse;
[COLOR1]
public float4 Specular;
[TEXCOORD0]
public float2 TexCoord;
}
 
public float1 FogStart;
public float1 FogEnd;
public float1 FogEnabled;
public float3 FogColor; //: register(c3);
 
public texture BasicTexture;
 
private sampler TextureSampler = sampler_state(
"BasicTexture",
MipFilter.Linear,
MinFilter.Linear,
MagFilter.Linear);
 
public float4x4 World; //: register(vs, c20); // 20 - 23
public float4x4 View; //: register(vs, c24); // 24 - 27
public float4x4 Projection; //: register(vs, c28); // 28 - 31
public float3 EyePosition; //: register(c4); // in world space
public float3 DiffuseColor = 1; //: register(c5) = 1;
public float1 Alpha = 1; //: register(c6) = 1;
public float3 EmissiveColor = 0; //: register(c7) = 0;
public float3 SpecularColor = 1; //: register(c8) = 1;
public float1 SpecularPower = 16; //: register(c9) = 16;
 
 
public float1 ComputeFogFactor(float1 d)
{
return clamp((d - FogStart) / (FogEnd - FogStart), 0, 1) * FogEnabled;
}
 
public CommonVSOutput ComputeCommonVSOutput(float4 position)
{
CommonVSOutput vout;
 
float4 pos_ws = mul(position, World);
float4 pos_vs = mul(pos_ws, View);
float4 pos_ps = mul(pos_vs, Projection);
vout.Pos_ws = pos_ws;
vout.Pos_ps = pos_ps;
 
vout.Diffuse = float4(DiffuseColor.rgb + EmissiveColor, Alpha);
vout.Specular = 0;
vout.FogFactor = ComputeFogFactor(length(EyePosition - pos_ws));
 
return vout;
}
 
VertexLightingVSOutput VSBasic(VSInput vin)
{
VertexLightingVSOutput vout;
 
CommonVSOutput cout = ComputeCommonVSOutput(vin.Position);
 
vout.PositionPS = cout.Pos_ps;
vout.Diffuse = cout.Diffuse;
vout.Specular = float4(cout.Specular, cout.FogFactor);
 
return vout;
}
 
[return: COLOR]
public float4 PSBasic(VertexLightingPSInput pin)// : COLOR
{
float4 color = pin.Diffuse + float4(pin.Specular.rgb, 0);
color.rgb = lerp(color.rgb, FogColor, pin.Specular.w);
 
return color;
}
 
[return:COLOR]
public float4 PSBasicTx(VertexLightingPSInputTx pin)// : COLOR
{
float4 color = tex2D(TextureSampler, pin.TexCoord) *
pin.Diffuse + float4(pin.Specular.rgb, 0);
 
color.rgb = lerp(color.rgb, FogColor, pin.Specular.w);
return color;
}

 

Это фрагмет шейдера BasicEffect из XNA framework. Логика выполнения функций отражена в перегруженных операторах и встроенных методах, таких как tex2D(), lerp(), mul(). Эти встроенные методы (Intrinsic Functions) определены в базовом классе шейдера и возвращают результат в виде MethodInvokeExpression. Все остальное (параметры, самплеры, техники) получаем используя рефлексию.

Тесты

Текущий прототип не может пока генерировать весь код HLSL эффекта из C# класса деларирующего шейдер. Но получить код отдельно взятых функций можно. Сравните то что получилось из вершинного шейдера объявленного выше -

HLSL код сгенерированный из С# (см. выше - строки 67-100)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
VertexLightingVSOutput VSBasic(VSInput vin)
{
VertexLightingVSOutput vout;
 
vout.PositionPS = mul(mul(mul(vin.Position, World), View), Projection);
 
vout.Diffuse = float4((DiffuseColor.rgb + EmissiveColor), Alpha);
 
vout.Specular = float4(0,0,0, clamp(((length((EyePosition - mul(vin.Position, World)))
- FogStart) / (FogEnd - FogStart)), 0, 1) * FogEnabled);
 
return vout;
}
 

 


Вершинный шейдер как будто стал короче )). Хотя на самом деле - появилось лишнее умножение - mul(vin.Position, World). На дебаг режим компиляции HLSL это повлияет, но в релизе оптимизатор это заметит и уберет.
Полученный HLSL код визуально смотриться короче просто потому, что тут развернуты все вызовы локальных функций - ComputeCommonVSOutput(..) и ComputeFogFactor(..). Разворачивание локальных функций происходит автоматом, это даже не фича, специально реализованная, это следствие самого подхода к кодогенерации.

Теперь вставим полученный код в fx файл, добавим техник и параметров по аналогии с оригиналом, скомпилируем и сравним ASM с дизассемблером того же эффекта но собранного полностью из оригинального HLSL.

ASM оригинального HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    vs_1_1
def c3, 0, 1, 0, 0
dcl_position v0
dp4 r1.w, v0, c23
dp4 r1.x, v0, c20
dp4 r1.y, v0, c21
dp4 r1.z, v0, c22
dp4 r0.x, r1, c24
dp4 r0.y, r1, c25
dp4 r0.z, r1, c26
add r2.xyz, -r1, c4
dp4 r0.w, r1, c27
dp3 r1.x, r2, r2
dp4 oPos.x, r0, c28
rsq r1.w, r1.x
rcp r1.w, r1.w
mov r2.w, c1.x
add r2.w, -r2.w, c2.x
add r1.w, r1.w, -c1.x
rcp r2.w, r2.w
dp4 oPos.y, r0, c29
mul r1.w, r1.w, r2.w
dp4 oPos.z, r0, c30
max r1.w, r1.w, c3.x
dp4 oPos.w, r0, c31
min r0.w, r1.w, c3.y
mov r0.xyz, c5
add oD0.xyz, r0, c7
mul oD1.w, r0.w, c0.x
mov oD0.w, c6.x
mov oD1.xyz, c3.x
 
// approximately 28 instruction slots

 

 



ASM сгенерированного HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    preshader
add c13.xyz, c2.xyz, c3.xyz
neg r1.w, c0.x
add r0.w, r1.w, c1.x
rcp c12.x, r0.w
 
// approximately 4 instructions
 
vs_1_1
def c18, 0, 1, 0, 0
dcl_position v0
dp4 r1.w, v0, c3
dp4 r1.x, v0, c0
dp4 r1.y, v0, c1
dp4 r1.z, v0, c2
dp4 r0.x, r1, c4
add r2.xyz, -r1, c17
dp4 r0.y, r1, c5
dp3 r2.x, r2, r2
dp4 r0.z, r1, c6
rsq r2.w, r2.x
dp4 r0.w, r1, c7
rcp r1.w, r2.w
dp4 oPos.x, r0, c8
add r1.w, r1.w, -c15.x
dp4 oPos.y, r0, c9
mul r1.w, r1.w, c12.x
dp4 oPos.z, r0, c10
max r1.w, r1.w, c18.x
dp4 oPos.w, r0, c11
min r0.w, r1.w, c18.y
mul oD1.w, r0.w, c14.x
mov oD0.xyz, c13
mov oD0.w, c16.x
mov oD1.xyz, c18.x
 
// approximately 24 instruction slots

 



Как видно из результата, разворачивание локальных функций дает больше возможностей для вычисления прешейдера.
Компиляция происходила в релиз режиме. Хотя HLSL компилятор сам разворачивает все вызовы, не зависимо от режима, просто потому, что ASM не поддерживает вызовов процедур. Однако не мотря на разворачивание самим компилятором - вычислить прешейдер из оригинального HLSL кода он все же не смог.

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

Плюсы и минусы

На XBox работать не будет - нет возможности компилировать HLSL код в рантайме.
При компиляции в Debug режиме можно получить более "тяжелый" HLSL/ASM код.
Если клонировать такие шейдеры без рефлексии - много ручной работы. (свой Clone() нужен везде)

Один из основных плюсов это объектно ориентированный подход в создании шейдеров.

Shawn Hargreaves (один разработчиков XNA) в своем блоге пишет -

1
2
3
4
5
"So, I have this shader that does normalmapping, 
and this other shader that does skinned animation.
How can I use them both to render an animated normalmapped character?"
 
Welcome, my friend, to one of the fundamental unsolved problems of graphics programming...

 

Комбинирование шейдеров является одной из основных нерешенных проблем программирования графики.

Что ж, я считаю что предложенный в статье подход имеет потенциал решить эту проблему. Если работа над меташейдерами будет продолжена и поддержана нами всеми - в итоге мы сможем автоматом получить normalmapping + skinned шейдер имея только его составляющие.

А еще можно без ручного кодирования биндить параметры меташейдера к EffectParameter через рефлексию. ))

На этом все..
1351 Прочтений •  [Меташейдеры] [08.08.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Меташейдеры Ukraine Vova 08.08.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка