среда, 8 февраля 2017 г.

Разработка собственного игрового движка.




Начало разработки собственного игрового движка. Постараюсь делиться прогрессом.






четверг, 3 мая 2012 г.

Simply Compute Shader Example

Одной из главных особенностей DirectX 11 является наличие Compute Shader. У меня появилась идея написать простой пример на эту тему. Прежде всего позвольте мне сказать, что вычислительные шейдеры являются чем-то потрясающим! Они открывают так много возможностей, что сразу теряешься в идеях, где бы их опробовать в действии. В этом примере будет показано, как создать Compute Shader и затем использовать его для запуска потоков, которые просто выведут идентификатор потока в текстуру.
 Device device = new Device(DriverType.Hardware, DeviceCreationFlags.Debug, FeatureLevel.Level_11_0);
ComputeShader compute = Helper.LoadComputeShader(device, "SimpleCompute.hlsl", "main"); Texture2D uavTexture;
UnorderedAccessView computeResult = Helper.CreateUnorderedAccessView(device, 1024, 1024, Format.R8G8B8A8_UNorm, out uavTexture);
device.ImmediateContext.ComputeShader.Set(compute);
device.ImmediateContext.ComputeShader.SetUnorderedAccessView(computeResult, 0);device.ImmediateContext.Dispatch(32, 32, 1);
Texture2D.ToFile(device.ImmediateContext, uavTexture, ImageFileFormat.Png, "uav.png");

Верьте или нет, но это весь код обработчика.

Вот что именно нужно сделать в коде:
1) Создать графическое устройство DX11, с тем чтобы использовать Compute Shaders 5.0
2) Загрузить/скомпилить код HLSL в объект ComputeShader.
3) Создать объект UnorderedAccesdView  1024 x 1024, который будет использоваться для хранения данных.
4) Передать ComputeShader и UnorderedAccesdView на графическое устройство.
5) Запустить ComputeShader путем вызова метода Dispatch (32 x 32 x 1).
6) Сохранить текстуру на жесткий диск.

Код HLSL еще проще:

RWTexture2D<float4> Output;

[numthreads(32, 32, 1)]
void main( uint3 threadID : SV_DispatchThreadID )
{
    Output[threadID.xy] = float4(threadID.xy / 1024.0f, 0, 1);
}

Как вы можете видеть объект RWTexture2D используется для хранения данных. Шейдер -настроен для запуска 32 x 32 x 1 потоков в группе потоков. Это означает, что процессор запустит 32 x 32 x 1 группу  потоков, то есть  (32x32) x (32x32) x 1 потоки запустятся по отдельности. Это равняется одному потоку на пиксел в выходных данных UAV. Так в UAV, цвет устанавливается равным идентификатору потока - результат работы этого кода приводится на следующем изображении :



Довольно просто, да? Но не очень интересно. Мы могли бы легко сделать что-то подобное с pixel Shader (хотя нам пришлось бы растрировать полноэкранный квад для этого).

Мы должны попытаться сделать то, что показывает силу вычислительного шейдера. Что-то, что нельзя было сделать в шейдерах раньше. Как насчет рисования некоторых примитивов, таких как линии или круги? Для рисования линий, давайте использовать алгоритм ЦДА. Он переводится на HLSL очень легко.

void Plot(int x, int y)
{
   Output[uint2(x, y)] = float4(0, 0, 1, 1);
}

void DrawLine(float2 start, float2 end)
{
    float dydx = (end.y - start.y) / (end.x - start.x);
    float y = start.y;
    for (int x = start.x; x <= end.x; x++)    
   {
        Plot(x, round(y));        y = y + dydx;   
   }
}

Для рисования кругов давайте использовать Midpoint Circle алгоритм. Для краткости я не буду обьяснять его сейчас. Для этого в моей главной функции CS, я просто добавлю этот код:

if (threadID.x == 1023 && threadID.y == 1023)
{
    DrawLine(float2(0, 0), float2(1024, 1024));
    DrawLine(float2(0, 1023), float2(1023, 0));
    DrawCircle(512, 512, 250);
    DrawCircle(0, 512, 250);
}

Результат этого кода приводится на следующем рисунке:



Я подозреваю, что вам, возможно покажется довольно странным, написание шейдера, который рисует примитивы. Но этот пример определенно помогает проиллюстрировать мощь Compute Shader.

Effects in DirectX 11

Я никогда не поверю, что Microsoft сделали доброе дело, так усложнив DirectX11. Effects framework - API которое поддерживало загрузку и использование файлов эффектов сгруппированных в HLSL код, рендерринг через passes и techniques – больше не являются неотъемлемой частью D3DX. Вместо этого они предоставили исходный код этой библиотеки, и это означает, что вы можете скомпилировать его самостоятельно!

Итак сделаем это: Зайдите в DX SDK подпапку "Samples\C++\Effects11", откройте "Effects11_*.sln".

Чтобы использовать эффекты API в своем проекте вы должны включить этот заголовок: "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Inc\D3dx11effect.h" and link with this lib: "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Debug\D3DX11EffectsD.lib (Debug) or "YOUR_DX_SDK_PATH\Samples\C++\Effects11\Release\D3DX11Effects.lib" (Release), as well as with "d3dcompiler.lib" (в обеих конфигурациях).

Вот пример того, как загружается эффект из файла. Вы должны сначала скомпилировать исходный код из файла или из памяти в бинарный эффект (ID3D10Blob), а затем создать реальный объект эффекта из него.

// Compile effect from HLSL file into binary Blob in memory
ID3D10Blob *effectBlob = 0, *errorsBlob = 0;
HRESULT hr = D3DX11CompileFromFile("Effect1.fx", 0, 0, 0, "fx_5_0", 0, 0, 0, &effectBlob, &errorsBlob, 0);
assert(SUCCEEDED(hr) && effectBlob);
if (errorsBlob) errorsBlob->Release();
// Create D3DX11 effect from compiled binary memory block
ID3DX11Effect *g_Effect;
hr = D3DX11CreateEffectFromMemory(effectBlob->GetBufferPointer(), effectBlob->GetBufferSize(), 0, g_Dev, &g_Effect);
assert(SUCCEEDED(hr));
effectBlob->Release();

Одного эффекта не достаточно. Вам нужно получить объект, который представляет "pass" чтобы использовать его. Если вы получите технику с эффектом (по индексу или по имени), то от нее получите и "pass".

ID3DX11EffectTechnique *g_EffectTechnique; // No need to be Release()-d.
g_EffectTechnique = g_Effect->GetTechniqueByIndex(0);
assert(g_EffectTechnique && g_EffectTechnique->IsValid());
ID3DX11EffectPass *g_EffectPass; // No need to be Release()-d.
g_EffectPass = g_EffectTechnique->GetPassByIndex(0);
assert(g_EffectPass && g_EffectPass->IsValid());

Теперь у вас есть этот объект, и вы можете применить настройки этого пасса к deviceContext во время рендеринга:

g_EffectPass->Apply(0, g_Ctx);
g_Ctx->Draw(3, 0);

Но все-таки одна проблема остается. В DirectX 11 вам нужно передать указатель на байт-код  вместе со скомпилированным шейдером при создании input layout - шаг, который вы, вероятно, не можете пропустить. К счастью, есть способ получить доступ к этому указателю. Он хранится внутри загруженного эффекта. Вам просто нужно пройти через два дескриптора, как здесь:

D3DX11_PASS_SHADER_DESC effectVsDesc;
g_EffectPass->GetVertexShaderDesc(&effectVsDesc);
D3DX11_EFFECT_SHADER_DESC effectVsDesc2;
effectVsDesc.pShaderVariable->GetShaderDesc(effectVsDesc.ShaderIndex, &effectVsDesc2);
const void *vsCodePtr = effectVsDesc2.pBytecode;
unsigned vsCodeLen = effectVsDesc2.BytecodeLength;
ID3D11InputLayout *g_InputLayout;
D3D11_INPUT_ELEMENT_DESC inputDesc[] = { /* ... */ };
hr = g_Dev->CreateInputLayout( inputDesc, _countof(inputDesc), vsCodePtr, vsCodeLen, &g_InputLayout);

К счастью, дела обстоят так, что effect framework не добавляет больше функциональности чем HLSL шейдеры поддерживающиеся в D3D11, поэтому вы можете его не использовать. Определение этих методов и passess не столь важно, в конце концов ...

пятница, 27 апреля 2012 г.

SSAO - Простой и практический подход.

Глобальное освещение (GI) — термин, используемый в компьютерной графике для вычисления освещения, вызванного неким взаимодействием между ближайшими поверхностями. Довольно часто термин GI используют только при окрашивании поверхности обьектов лучом отраженным от обьектов окружающей среды. Прямое освещение непосредственно от источника света – легко вычисляется в режиме реального времени на современном оборудовании, но мы не можем сказать то же самое о GI потому, что нам нужно обработать информацию о ближайших поверхностях для каждой точки в сцене, а управлять этим процессом еще довольно сложно. Однако есть определенные методы вычислений GI, которыми управлять не так сложно. Когда свет падает на сцену,или отражается от поверхности, то в сцене могут иметь место некоторые точки, которые имеют меньше шансов получить порцию света: уголки, трещиты между объектами, складки, и др. Это приводит к появлению тех областей, которые в результате будут темнее, чем окружающие их обьекты.

Предпосылки

Оригинальная реализация Crytek базируется на  буфере глубины и работает примерно следующим образом: для каждого пикселя в буфере глубины опрашиваются несколько точек в 3D пространстве вокруг него, проецируются обратно в пространство экрана для сравнения глубин этих пикселей с глубиной  текущего пикселя, с целью понять - выбранные пиксели находится впереди (не загажденный) или за (загражденный) текущим пикселем.
Буфер заграждений создается из усредненных значений дистанций между выборками. Однако у этого метода есть некоторые проблемы (такие, как самозатенение, появление хало), о них я расскажу позднее.

Алгоритм, который я описываю здесь делает все расчеты в 2D, и не использует проекцию. Он использует попиксельную позицию и буфер нормалей, так что если ваш движок использует модель отложенного освещения, то считайте, что пол дела вы уже сделали. Если вы не можете восстановить позицию из буфера глубины, вы можете хранить позицию в буфере позиций с точностью числа с плавающей точкой. Я рекомендую второй способ, так как я не буду обсуждать способы восстановления позиции из буфера глубины в этом посте. В любом случае, для оставшейся части статьи предпологается, что буфер нормалей и буфер позиций у вас уже есть.
Все, что мы сделаем в этой статье - это получим буферы нормалей и позиций, и сгенерируем однокомпонентный попиксельный буффер заграждений. Вы сможете использовать эту информацию как угодно. Обычно она вычитается из окружающего освещения в сцене, но можно также использовать этот буфер в более замысловатых или странных применениях, например если захотите получить не фотореалистичные результаты.


Алгоритм

Имея любой пиксель в сцене, можно вычислить его загражденность, если рассматривать
все соседние пикселы, как маленькие сферы и складывать их совместное влияние на затенение.
Для понятности мы будем работать с точками вместо сфер: occluders - это точки без ориентации, а occludee(пиксель, который принемает затенение) будет иметь пару. Вклад в затенение каждого окклюдера зависит от двух факторов:

Дистанция "d" до окклюдера
Угол между нормалью "N" и векторм между occluder и occludee "V".

С учетом этих двух факторов выводится простая формула для вычисления затенения:

 Occlusion = max( 0.0, dot( N, V) ) * ( 1.0 / ( 1.0 + d ) )

Первое выражение max( 0.0, dot( N,V ) ), основывается на интуитивном предположении, что точки над вершиной вносят больший вклад в затенение, чем точки находящиеся левее или правее. Второе выражение  ( 1.0 / ( 1.0 + d ) )  смягчает  линейность эффекта зафисимую от расстояния. Можно использовать квадратичное затухания или воспользоваться любой другой функцией, это просто дело вкуса. Алгоритм очень прост: выбираем несколько соседей вокруг текущего пиксела и накапливаем их вклад в затенение с использованием формулы выше.

Чтобы выбрать окклюдеры, я использую 4 выборки (<1,0>, <-1,0>, <0,1>, <0,-1>) которые повернуты на 45 ° и 90 ° и отражаются в последствии случайным образом используя текстуру случайных нормалей.

Это код шейдеров HLSL для эффекта, который должен применяться к полноэкранному квадрату:

sampler g_buffer_norm; 
sampler g_buffer_pos; 
sampler g_random; 
float random_size; 
float g_sample_rad; 
float g_intensity; 
float g_scale; 
float g_bias; 
 
struct PS_INPUT 
     float2 uv : TEXCOORD0; 
}; 
 
struct PS_OUTPUT 
     float4 color : COLOR0; 
}; 
 
float3 getPosition(in float2 uv) 
    return tex2D(g_buffer_pos,uv).xyz; 
 
float3 getNormal(in float2 uv) 
    return normalize(tex2D(g_buffer_norm, uv).xyz * 2.0f - 1.0f); 
 
float2 getRandom(in float2 uv) 
     return normalize(tex2D(g_random, g_screen_size * uv / random_size).xy * 2.0f - 1.0f); 
 
float doAmbientOcclusion(in float2 tcoord,in float2 uv, in float3 p, in float3 cnorm) 
   float3 diff = getPosition(tcoord + uv) - p; 
   const float3 v = normalize(diff); 
   const float d = length(diff)*g_scale; 
   return max(0.0,dot(cnorm,v)-g_bias)*(1.0/(1.0+d))*g_intensity; 
 
PS_OUTPUT main(PS_INPUT i) 
   PS_OUTPUT o = (PS_OUTPUT)0; 
 
   o.color.rgb = 1.0f; 
   const float2 vec[4] = {float2(1,0),float2(-1,0), 
                                     float2(0,1),float2(0,-1)}; 
 
float3 p = getPosition(i.uv); 
float3 n = getNormal(i.uv); 
float2 rand = getRandom(i.uv); 
 
float ao = 0.0f; 
float rad = g_sample_rad/p.z; 
 
//**SSAO Calculation**// 
int iterations = 4; 
for (int j = 0; j < iterations; ++j) 
  float2 coord1 = reflect(vec[j],rand)*rad; 
  float2 coord2 = float2(coord1.x*0.707 - coord1.y*0.707, 
                          coord1.x*0.707 + coord1.y*0.707); 
   
  ao += doAmbientOcclusion(i.uv,coord1*0.25, p, n); 
  ao += doAmbientOcclusion(i.uv,coord2*0.5, p, n); 
  ao += doAmbientOcclusion(i.uv,coord1*0.75, p, n); 
  ao += doAmbientOcclusion(i.uv,coord2, p, n); 
ao/=(float)iterations*4.0; 
//**END**// 
 
//Do stuff here with your occlusion value “ao”: modulate ambient lighting, write it to a buffer for later //use, etc. 
return o; 
}

Эта техника очень напоминает технику описанную в "Hardware Accelerated Ambient Occlusion Techniques on GPUs". Основные различия в структуре выборки и функции AO. Он также может рассматриваться как Image-Space версия  "Hardware Accelerated Ambient Occlusion Techniques on GPUs". Некоторые пояснения по коду:

Радиус делится на p.z, для изменения масштаба в зависимости от расстояния до камеры. Если пропустить этот раздел, все точки на экране будут использовать же радиус выборки, и результат не будет учитывать перспективу.
В цикле for,
coord1 это первоначальная выборка координат на 90 °.
coord2 является те же координаты, повернутые на 45 °.

Результаты



Как видите, код не сложный и не большой. Результаты не самозатеняются и хало почти нет.
Это две основные проблемы всех алгоритмов SSAO, которые используют только буфер глубины в качестве исходных данных, вы можете увидеть их в этих изображениях:

Self-occlusion появляется, потому что традиционные алгоритмы формирования выборок внутри области вокруг каждого пикселя, поэтому на плоских поверхностях которые не «occluded», по меньшей мере, половина из выборок помечены как «occluded». Из за этого получается сероватый цвет для общей окклюзии. Белое хало вокруг объектов возникает, потому что в этих областях self-occlusion не выполняется. Таким образом избавление от self-occlusion на самом деле хорошо помогает избавиться от хало. При перемещении камеры результат окклюзии изменяется, если следовать этому методу. Если вы склоняетесь к качеству вместо скорости, можно использовать два или несколько проходов алгоритма (повторяющиеся цикл for в коде) с различными радиусами, один для захвата более глобальных AO, а другие для детализации небольших трещин.


Продолжаем

Я описал упрощенную реализацию SSAO, которая очень хорошо подходит для игр.
Однако ее легко улучшить если принимать во внимание скрытые от камеры поверхности, для получения более высокого качества. Это потребует три буфера: две позиции/глубина буферов (front/back faces) и один буфер нормалей. Но вы можете сделать это только с двумя буферами: хранить глубину front/back faces в красном и зеленом каналах буфера, соответственно, а затем восстановить позицию каждого из них. Таким образом у вас будет один буфер для позиций и второй буфер для нормалей. Таковы результаты при использовании 16 выборок для каждой позиции буфера:


left: front faces occlusion, right: back faces occlusion
Что бы применить это, нужно просто добавить вызова функции doAmbientOcclusion()
Это дополнительный код, который необходимо добавить:
внутри цикла for, добавьте эти вызовы:
ao += doAmbientOcclusionBack(i.uv,coord1*(0.25+0.125), p, n); 
ao += doAmbientOcclusionBack(i.uv,coord2*(0.5+0.125), p, n); 
ao += doAmbientOcclusionBack(i.uv,coord1*(0.75+0.125), p, n); 
ao += doAmbientOcclusionBack(i.uv,coord2*1.125, p, n);

Добавьте эти две функции шейдер: 

 float3 getPositionBack(in float2 uv) 

    return tex2D(g_buffer_posb,uv).xyz; 

float doAmbientOcclusionBack(in float2 tcoord,in float2 uv, in float3 p, in float3 cnorm) 

    float3 diff = getPositionBack(tcoord + uv) - p; 
   const float3 v = normalize(diff); 
   const float d = length(diff)*g_scale; 
   return max(0.0,dot(cnorm,v)-g_bias)*(1.0/(1.0+d)); 
}

Добавьте семлер с именем «g_buffer_posb», содержащий позицию backFaces (для его создания вам нужно рисовать сцену с передних граней).  Еще небольшое изменение, которое может помочь, на этот раз улучшить скорость, является добавление простой системы LOD (уровень детализации) в наш шейдер. Измените фиксированное количества итераций в соответствии с:

 int iterations = lerp(6.0,2.0,p.z/g_far_clip);

Переменная «g_far_clip» - это расстояние до дальней плоскости отсечения, которое должно быть передано в шейдер. Теперь количество итераций, применяемое к каждой точке зависит от расстояния до камеры. Таким образом для дальних точек выполнется меньше выборок, это позволяет повысить производительность без заметного ухучшения качества. Я не использовал это при измерении производительности (см. ниже), но все-же:




Так-же полезно рассмотреть, как этот метод сравнивается с трассировкой лучей AO. Цель этого сравнения заключается в том, чтобы увидеть, будет ли этот метод приравниваться к реальному AO при использовании достаточного количества выборок.


Left: the SSAO presented here, 48 samples per pixel (32 for front faces and 16 for back faces), no blur. Right: Ray traced AO in Mental Ray. 32 samples, spread = 2.0, maxdistance = 1.0; falloff = 1.0.

И совет на последок: не планируйте подключить шейдер в свой конвейер и автоматически получить реалистичный результат. Несмотря на эту реализацию, что-бы получить соотношение хорошей производительности и качества SSAO, вы должны настроить его как можно тщательнее для своих нужд и добиться максимальной производительности.
Добавление или удаление выборок и размытия, изменяя интенсивность и т.д. Вам следует также понять, подходит ли такой алгоритм SSAO для вас. Если у вас есть много динамических объектов в сцене, то возможно нет необходимости в SSAO вообще, может быть использование LightMap-ов достаточно для ваших целей, так- как они могут обеспечить лучшее качество для статических сцен. Я надеюсь, что вы извлекли пользу от этого метода. Весь код, представленный в этой статье доступен по mit-license.
Оригинал статьи можно найти здесь a-simple-and-practical-approach-to-ssao




четверг, 26 апреля 2012 г.

DBF TO ANY

DBF TO ANY - это удобная программа-конвертер данных формата (.dbf)  в данные MSSQL, PostgreSQL,  Oracle и  MySQL. Продукт ориентирован на профессионалов работающих с базами данных.




вторник, 24 апреля 2012 г.

[PARADIGMA] - Engine

Это графический движок, который сравнительно недавно был запущен в разработку. Его особенностью является то, что он использует Microsoft XNA. Имеется две системы рендерринга, Forward и Deffered. Так-же имеется собственный редактор уровней, набор вспомагательных утилит и конверторов, которые будут полезны любому игроделу.


Landscape + instancing + normal + specular


Поддержка бльшого числа источников света (Deferred Shading)


Редактор уровней


пятница, 20 апреля 2012 г.

SGS - Synthetic Engine.

SGS Project.


С недавних пор началась разработка проекта SGS. Рабочее название проекта "Synthetic Engine". Вообще SGS - это аббревиатура расшифровующаяся как Synthetic Graphics System, или же Synthetic Game Studio. В основе программного комплекса лежит Microsoft DirectX11 API. В силу того, что проект создавался одним человеком, да еще на чистом энтузиазме, процесс разработки продвигается не очень быстро, однако кое - какие результаты уже получены. В этой статье я буду стараться периодически освещать состояние проекта, делиться получеными результатами. Приглашаются так-же все желающие поучавствовать в обсуждении вопросов связяных с творческим процессом создания графического движка.  И так, на сегодняшний день уже реализовано не мало. Архитектура программы изначально затачивается для реализации в дальнейшем трех систем рендерринга, Forward, Deffered, Raytracing. Физика - Nvidia PhysX библиотеки. В качестве неба было принято решение пока использовать SkyBox, ввиду его относительной простоты и понятности.


Реализована система материалов, позволяющая создавать базовые материалы такие как Lambert:

Blinn + Phong:


Normal + Specular + Masked


Metall


Metall + Normal



Self Shadowed Parallax



Рендерринг текста и Terrain generator.


Недавно были добавлены Parallel Split Shadow Maps, основаные на алгоритме описаном разработчиками компьютерной игры Battlefield3.



Так-же была разработана система постпроцессов. Пока реализовано следующее:

Bloom:


DOF:



SSAO:




Хотя SSAO пока еще требует хорошей доработки, это планируется в ближайшее время.
Так-же планируется написать свой редактор уровней, но он пока еще находится в зародыше.

 
 

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

визуализация тангенциального пространства:


Bounding Sphere & Bounding Box




Axis:



Cubemapper

Synthetic Cubemapper

Это абсолютно бесплатная программа, которая позволяет создавать кубические текстуры из шести (или менее) 2D текстур, и сохранять их в dds формате. Такие текстуры могут использоваться для создания неба в компьютерных играх, а так же для всевозможных "environment" эффектов, таких как отражение/преломление света и т.д.


Программа требует OS Windows7 и установленный XNA framework 3.1

Скачать бесплатно ...

четверг, 19 апреля 2012 г.

PDF Book Printer

PDF Book Printer  (beta)
Эта бесплатная утилита, двумя кликами мыши, позволяет распечатывать электронные книги (.pdf), из расчета - четыре страницы на один стандартный лист формата А4. Страницы книги группируются в буклеты по 10 листов (40 страниц) для дальнейшего скрепления между собой. Готовые буклеты пронумерованые и приготовленные для печати сохраняются в папке с программой, и могут быть переданы третьим лицам для самостоятельной печати или хранения. Для печати одного буклета вам потребуется положить в лоток принтера 10 листов, а после того как они отпечатаются, перевернуть всю пачку и положить ее снова в лоток вашего принтера и напечатать вторую сторону буклета. После второй процедуры печати, листы лягут в нужном порядке и вам останется только скрепить их посередине скрепками. Буклет готов. После этого можно приступать к печати следующего буклета. Если в распечатываемой книге число страниц не кратно 40, то оставшиеся страницы упаковываются в буклет с названием "Appendix", и на экран выводится сообщение о том, сколько листов потребуется положить в принтер для печати заключительного буклета.





вторник, 17 апреля 2012 г.

Практическое применение XNA. Часть 16.

Skybox

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




Шейдер для скайбокса:

TextureCube shaderTexture;
sampler SampleType;
cbuffer MatrixBuffer
{
   matrix worldMatrix;
   matrix viewMatrix;
   matrix projectionMatrix;
};

struct VS_INPUT
{
  float4 position : POSITION;
  float2 texcoord : TEXCOORD0;
};

struct PS_INPUT
{
  float4 position : SV_POSITION;
  float3 texcoord : TEXCOORD0;
};

PS_INPUT VS(VS_INPUT input)
{
  PS_INPUT output = (PS_INPUT)0;
   
  output.position = mul(mul(mul(input.position, worldMatrix), viewMatrix), projectionMatrix);
  output.texcoord = input.position;
  return output;
}

float4 PS(PS_INPUT input) : SV_TARGET
{
  return shaderTexture.Sample(SampleType, input.texcoord);
}

В результате у нас получится подобная картинка:

Вопросы?