ФЭНДОМ


ВведениеПравить

Для урока нам нужен фреймворк первой версии. В предыдущем уроке вы сможете скачать нужный фреймворк и правильно настроенный проект.

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

Обновляем фреймворкПравить

Откройте фреймворк. Сначала обновим версию:). Откройте macros.h, найдите макрос _VERSION_FRAMEWORK и измените число на два:

#define _VERSION_FRAMEWORK 2

Теперь откройте Render.h и перепишите код вот так (изменения выделены жирным курсивом):

#pragma once

namespace D3D11Framework
{
//------------------------------------------------------------------

	class Render
	{
	public:
		Render();
		virtual ~Render();
		
		bool CreateDevice(HWND hwnd);
		void BeginFrame();
		void EndFrame();
		void Shutdown();

		virtual bool Init(HWND hwnd) = 0;
		virtual bool Draw() = 0;
		virtual void Close() = 0;

	protected:
		D3D_DRIVER_TYPE m_driverType;
		D3D_FEATURE_LEVEL m_featureLevel;
		ID3D11Device *m_pd3dDevice;
		ID3D11DeviceContext *m_pImmediateContext;
		IDXGISwapChain *m_pSwapChain;
		ID3D11RenderTargetView *m_pRenderTargetView;
	};

//------------------------------------------------------------------
}

Метод CreateDevice() будет производить инициализацию DirectX. В нем будет тот код, который мы писали в прошлом уроке. Как вы видите, он не виртуальный.

Методы BeginFrame() и EndFrame() - будут отвечать за рисование. Первый, пока что очищает экран, а второй вызывает смену swap chain буферов. Наше же рисование из метода Draw() происходит между ними.

Метод Shutdown() очищает ресурсы.

Мы не будем пользоваться этими 4 методами. Их будет вызывать наш фреймворк.

Наши три виртуальных метода сохранили свое предназначение.

Я думаю, вы помните из прошлого урока предназначение членов класса (а если нет, вернитесь к нему).

Теперь откройте Render.cpp и напишите:

#include "stdafx.h"
#include "Render.h"
#include "macros.h"

namespace D3D11Framework
{
//------------------------------------------------------------------
	
	Render::Render()
	{
		m_driverType = D3D_DRIVER_TYPE_NULL;
		m_featureLevel = D3D_FEATURE_LEVEL_11_0;
		m_pd3dDevice = nullptr;
		m_pImmediateContext = nullptr;
		m_pSwapChain = nullptr;
		m_pRenderTargetView = nullptr;
	}

	Render::~Render()
	{
	}

	bool Render::CreateDevice(HWND hwnd)
	{
		HRESULT hr = S_OK;

		RECT rc;
		GetClientRect( hwnd, &rc );
		UINT width = rc.right - rc.left;
		UINT height = rc.bottom - rc.top;

		UINT createDeviceFlags = 0;
	#ifdef _DEBUG
		createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
	#endif

		D3D_DRIVER_TYPE driverTypes[] =
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_WARP,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		UINT numDriverTypes = ARRAYSIZE( driverTypes );

		D3D_FEATURE_LEVEL featureLevels[] =
		{
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
		};
		UINT numFeatureLevels = ARRAYSIZE( featureLevels );

		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory( &sd, sizeof( sd ) );
		sd.BufferCount = 1;
		sd.BufferDesc.Width = width;
		sd.BufferDesc.Height = height;
		sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.OutputWindow = hwnd;
		sd.SampleDesc.Count = 1;
		sd.SampleDesc.Quality = 0;
		sd.Windowed = TRUE;

		for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
		{
			m_driverType = driverTypes[driverTypeIndex];
			hr = D3D11CreateDeviceAndSwapChain( NULL, m_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &sd, &m_pSwapChain, &m_pd3dDevice, &m_featureLevel, &m_pImmediateContext );
			if( SUCCEEDED( hr ) )
				break;
		}
		if( FAILED( hr ) )
			return false;

		ID3D11Texture2D* pBackBuffer = NULL;
		hr = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
		if( FAILED( hr ) )
			return false;

		hr = m_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView );
		_RELEASE(pBackBuffer);
		if( FAILED( hr ) )
			return false;

		m_pImmediateContext->OMSetRenderTargets( 1, &m_pRenderTargetView, NULL );

		D3D11_VIEWPORT vp;
		vp.Width = (FLOAT)width;
		vp.Height = (FLOAT)height;
		vp.MinDepth = 0.0f;
		vp.MaxDepth = 1.0f;
		vp.TopLeftX = 0;
		vp.TopLeftY = 0;
		m_pImmediateContext->RSSetViewports( 1, &vp );

		return Init(hwnd);
	}
	void Render::BeginFrame()
	{
		float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
		m_pImmediateContext->ClearRenderTargetView( m_pRenderTargetView, ClearColor );
	}

	void Render::EndFrame()
	{
		m_pSwapChain->Present( 0, 0 );
	}

	void Render::Shutdown()
	{
		Close();

		if( m_pImmediateContext ) 
			m_pImmediateContext->ClearState();

		_RELEASE(m_pRenderTargetView);
		_RELEASE(m_pSwapChain);
		_RELEASE(m_pImmediateContext);
		_RELEASE(m_pd3dDevice);
	}

//------------------------------------------------------------------
}

Здесь все тоже самое что и в предыдущем уроке.

И последнее изменение, откройте Framework.cpp. В методе Framework::Init() замените строчку:

	if (!m_render->Init(m_wnd->GetHWND()))

на строчку:

	if (!m_render->CreateDevice(m_wnd->GetHWND()))

В методе Framework::m_frame():

	if (!m_render->Draw())
		return false;

на:

	m_render->BeginFrame();
	if (!m_render->Draw())
		return false;
	m_render->EndFrame();

И в методе Framework::Close():

	_CLOSE(m_render);

на:

	m_render->Shutdown();
	_DELETE(m_render);

Примечание: я так сделал, чтобы вы могли дальше писать очистку в Close().

Собственно, все. На этом обновление фреймворка завершено. Сейчас протестируем и начнем писать наш код.

Тестируем измененияПравить

Создавайте main.cpp и пишите:

#include "MyRender.h"

int main()
{
	Framework framework;

	MyRender *render = new MyRender();

	framework.SetRender(render);
	framework.Init();

	framework.Run();

	framework.Close();

	return 0;
}

Создайте MyRender.h:

#pragma once

#include "D3D11_Framework.h"

using namespace D3D11Framework;

class MyRender : public Render
{
public:
	MyRender();
	bool Init(HWND hwnd);
	bool Draw();
	void Close();

private:
	HRESULT m_compileshaderfromfile( WCHAR* FileName, LPCSTR EntryPoint, LPCSTR ShaderModel, ID3DBlob** ppBlobOut );
	
	ID3D11Buffer *m_pVertexBuffer;
	ID3D11InputLayout *m_pVertexLayout;	
	ID3D11VertexShader *m_pVertexShader;
	ID3D11PixelShader *m_pPixelShader;
};

MyRender.cpp:

#include "MyRender.h"
#include <xnamath.h>
#include <d3dcompiler.h>

struct SimpleVertex
{
	XMFLOAT3 Pos;
};

MyRender::MyRender()
{
	m_pVertexShader = nullptr;
	m_pPixelShader = nullptr;
	m_pVertexLayout = nullptr;
	m_pVertexBuffer = nullptr;
}

HRESULT MyRender::m_compileshaderfromfile(WCHAR* FileName, LPCSTR EntryPoint, LPCSTR ShaderModel, ID3DBlob** ppBlobOut)
{
	return S_OK;
}

bool MyRender::Init(HWND hwnd)
{
	return true;
}

bool MyRender::Draw()
{
	return true;
}

void MyRender::Close()
{
	_RELEASE(m_pVertexBuffer);
	_RELEASE(m_pVertexLayout);
	_RELEASE(m_pVertexShader);
	_RELEASE(m_pPixelShader);
}

В этом примере мы не будем пользоваться обработкой ввода. Скомпилируйте и проверьте на работоспособность. Не пугайтесь непонятного кода, далее я объясню его значение.

Выводим треугольникПравить

Почему треугольник?Править

Почему именно треугольник? Дело в том что вся графика в любой компьютерной игре состоит из треугольников, так как ими можно представить любую форму. Вы не верите?:) Вот посмотрите на мамонта из всемирно известного Skyrim:

DirectXSteptoStepLesson4 1

Снизу я выделил синим цветом один треугольник, чтобы вам было легче сориентироваться. Вот такой вон он - трехмерный мир игр - миллионы треугольников. Кроме треугольника можно еще рисовать точки и линии, но мы в ближайшее время не будем их рассматривать.

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

Треугольник как элементПравить

Каждый треугольник описан тремя точками, которые также называют вершинами (vertex - постарайтесь запомнить это слово). То есть набор из трех вершин в уникальных позициях, определяет один уникальный треугольник. GPU для рисования треугольника мы должны передать позиции этих вершин. К примеру, чтобы вывести треугольник в двухмерном пространстве, как на рисунке ниже, мы должны передать позиции трех вершин ( (0,0) (0,1) и (1,0) ) в GPU, а оно уже само нарисует нам из этих вершин треугольник (конечно при условии что мы ранее сообщили GPU что нужно рисовать треугольник).

DirectXSteptoStepLesson4 2

Кроме позиции у вершины могут быть другие характеристики - нормаль, один или несколько цветов, координаты текстуры и так далее. В данном примере мы будем разобрать только с позицией. Для описания одной вершины мы будем использовать структуру SimpleVertex:

struct SimpleVertex
{
	XMFLOAT3 Pos;
};

XMFLOAT3 - это структура из заголовка DirectX - xnamath.h. Она состоит из трех вещественных чисел типа float. О системе координат мы более подробно поговорим в одном из следующих уроков, а сейчас просто вспомните из уроков геометрии что в трехмерном пространстве есть три позиции - x, y, z.

Вас наверное удивило слово xna в заголовке? Не обращайте внимание, этот заголовок вполне универсален и не требует никаких XNA :). В DirectX 11.1 XnaMath переименован в DirectX Math. Подробнее об этом заголовке мы также поговорим в следующем уроке, когда будем разбирать компьютерную математику.

В Direct3D 11 вершины хранятся в специальном буфере - буфере вершин (vertex buffer). Мы должны создать буфер вершин и затем заполнить его нашими вершинами. Буфер вершин описывается интерфейсом ID3D11Buffer и у нас он будет называться m_pVertexBuffer (см. приватные члены в MyRender.h)

Данный буфер должен быть достаточно большим чтобы вместить наши вершины. Мы знаем что наш буфер должен вместить три вершины, но сколько байт занимает каждая из вершин? Чтобы ответить на этот вопрос, мы должны понять формат вершин (vertex layout).

Входной формат (input layout)Править

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

В нашем примере структура SimpleVertex предназначена для хранения информации о вершине в памяти нашего приложения. Однако, когда мы из буфера вершин отправим ее в GPU, мы на самом деле отправим только кусок памяти. GPU же должно из этого куска памяти извлечь нужные свойства. Это достигается за счет использования входного формата (input layout). В Direct3D 11, входной формат - это объект Direct3D, который описывает структуру в понятном для GPU формате. Входной формат описан интерфейсом ID3D11InputLayout и мы создадим экземпляр с именем m_pVertexLayout.

Мы разобрали два члена из четырех. Оставшиеся два: m_pVertexShader и m_pPixelShader - используются для работы с шейдерами что мы сейчас и рассмотрим.

Программируемый графический конвейер (Programmable graphics pipeline)Править

Графический конвейер - это набор шагов которые проходят наши данные, чтобы попасть на экран. Он состоит из 10 стадий:

  1. Input Assembler (IA) Stage
  2. Vertex Shader (VS) Stage
  3. Hull Shader (HS) Stage
  4. Tessellator Shader (TS) Stage
  5. Domain Shader (DS) Stage
  6. Geometry Shader (GS) Stage
  7. Stream Output (SO) Stage
  8. Rasterizer (RS) Stage
  9. Pixel Shader (PS) Stage
  10. Output Merger (OM) Stage

D3d11pipeline


Input Assembler (IA) Stage - Отвечает за передачу данных (треугольники, линии и точки) в конвейер.

Vertex Shader (VS) Stage - Вершинный шейдер, обрабатывающий вершины, обычно выполняя такие операции как трансформации, скининг и освещение. Вершинный шейдер получает одну входную вершину.

Hull Shader (HS) Stage

Tessellator Shader (TS) Stage

Domain Shader (DS) Stage

Geometry Shader (GS) Stage - геометрический шейдер обрабатывает примитивы. Она получает один примитив (три вершины, если треугольник, две - для линии, одна вершина, если точка).

Stream Output (SO) Stage - передает данные о примитивах в стадию растеризации.

Rasterizer (RS) Stage - готовит полученную геометрию для передачи в пиксельный шейдер

Pixel Shader (PS) Stage - пиксельный шейдер обрабатывает полученные данные о геометрии как пиксели, позволяя к примеру определять цвет.

Output Merger (OM) Stage - Объединение различных выходных данных (значение пиксельного шейдера, информация о глубине и трафарете) с содержимым цели рендера (render target) и буфером глубины/трафарета, чтобы выполнить заключительный этап конвейера.

Три нерассмотренных стадии предназначены для работы с тесселяцией и мы их рассмотрим в следующих уроках.

ШейдерыПравить

Шейдеры - это короткие программы выполняемые GPU и написанные на специальном языке HLSL. Они выполняются в соответствующую стадию графического конвейера. Шейдеры получают данные с предыдущей стадии конвейера, выполняют определенные операции и передают результат в следующую стадию. В данном уроке мы рассмотрим только два типа шейдеров - вершинный (vertex shader), который выполняется во второй стадии конвейера и пиксельный (pixel shader), выполняемый в 9 стадии.

Вершинный шейдер получает вершину на входе и производит над ней операции. Вершинный шейдер выполняется для каждой из вершин отправленных в GPU буфером вершин. Пиксельный шейдер получает пиксель на входе.

Для работы с шейдерами мы создали m_pVertexShader и m_pPixelShader. m_pVertexShader - это экземпляр интерфейса ID3D11VertexShader и отвечает за вершинный шейдер. m_pPixelShader - это экземпляр интерфейса ID3D11PixelShader и отвечает за пиксельный шейдер.

Как было сказано ранее, шейдеры пишутся на специальном языке HLSL и мы должны "загрузить" их в наше приложение. Для этого и будет служить приватный метод m_compileshaderfromfile(). В нем мы будем создавать шейдеры из файлов с кодом HLSL. Давайте напишем этот метод. Откройте MyRender.cpp, найдите этот метод и напишите:

HRESULT MyRender::m_compileshaderfromfile(WCHAR* FileName, LPCSTR EntryPoint, LPCSTR ShaderModel, ID3DBlob** ppBlobOut )
{
	HRESULT hr = S_OK;

	hr = D3DX11CompileFromFile(FileName, NULL, NULL, 
		EntryPoint, ShaderModel, 0, 
		0, NULL, ppBlobOut, NULL, NULL);

	return hr;
}

Здесь мы создаем объекты шейдеров из файлов с кодом используя функцию D3DX11CompileFromFile().

Теперь начнем работать с методом MyRender::Init(). Найдите его и напишите:

bool MyRender::Init(HWND hwnd)
{
	HRESULT hr = S_OK;

	// Компиляция вершинного шейдера
	ID3DBlob* pVSBlob = NULL;
	hr = m_compileshaderfromfile( L"shader.fx", "VS", "vs_4_0", &pVSBlob );
	if( FAILED( hr ) )
	{
		Log::Get()->Err("Невозможно скомпилировать файл shader.fx. Пожалуйста, запустите данную программу из папки, содержащей этот файл");
		return false;
	}

	// Создание вершинного шейдера
	hr = m_pd3dDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &m_pVertexShader );
	if( FAILED( hr ) )
	{	
		Log::Get()->Err("Не удалось создать вершинный шейдер");
		_RELEASE(pVSBlob);
		return false;
	}

Интерфейс ID3DBlob используется для хранения произвольных данных. То есть в нем может хранится что угодно, но обычно он используется для хранения буферов.

Сначала мы компилируем наш шейдер и помещаем полученные данные в pVSBlob. Компиляция выполняется через наш метод m_compileshaderfromfile(). Первый параметр - это файл нашего кода. Мы его напишем позже. VS - это название функции с которой начнется выполнение нашего кода. Как любая программа начинается с main (или WinMain), так и наш шейдер должен начинаться с такой функции, и вторым параметром мы указываем ее имя. Третий параметр указывает модель шейдера (то есть тип и версию). Ну и последний - это указатель по которому мы поместим скомпилированные данные.

После компиляции кода шейдера мы должны его создать, за что и отвечает метод CreateVertexShader().

Теперь напишите ниже:

	// определение входного формата
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
	UINT numElements = ARRAYSIZE( layout );

	// создание входного формата
	hr = m_pd3dDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &m_pVertexLayout );

	_RELEASE(pVSBlob);
	if( FAILED( hr ) )
		return false;

	// установка входного формата
	m_pImmediateContext->IASetInputLayout( m_pVertexLayout );

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

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

Семантический индекс добавляется к семантическому имени. У вершины может быть несколько характеристик аналогичного свойства. Например, у нее может быть два набора текстур или 2 цвета. Вместо того чтобы использовать имена с числами, такие как "COLOR0" и "COLOR1", мы просто обоим задаем одно семантическое имя "COLOR" и разные семантические индексы - 0 и 1.

Формат типа данных данной характеристики. DXGI_FORMAT_R32G32B32_FLOAT - означает что у нас есть три 32 битных числа типа float (ведь позиция определяется тремя числами - x,y,z).

Следующий параметр нас пока не интересует.

Далее, вершина хранится в буфере вершин в виде простого куска памяти. В пятом параметре мы указываем на область память в которой начинается данные характеристики. Так как позиция у нас первая, мы указываем 0.

Сам же входной формат создается через метод CreateInputLayout(). После того как мы его создали, мы должны подключить его к нашему контексту.

Далее пишите код:

	// Компиляция пиксельного шейдера
	ID3DBlob* pPSBlob = NULL;
	hr = m_compileshaderfromfile( L"shader.fx", "PS", "ps_4_0", &pPSBlob );
	if( FAILED( hr ) )
	{
		Log::Get()->Err("Невозможно скомпилировать файл shader.fx. Пожалуйста, запустите данную программу из папки, содержащей этот файл");
		return false;
	}

	// Создание пиксельного шейдера
	hr = m_pd3dDevice->CreatePixelShader( pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &m_pPixelShader );
	_RELEASE(pPSBlob);
	if( FAILED( hr ) )
	{	
		Log::Get()->Err("Не удалось создать пиксельный шейдер");
		_RELEASE(pPSBlob);
		return false;
	}

Мы сделали тоже самое что и ранее с вершинным шейдером - скомпилировали и создали.

Следующий, последний кусок кода метода Init():

	// Создание буфера вершин
	SimpleVertex vertices[] =
	{
		XMFLOAT3( 0.0f, 0.5f, 0.5f ),
		XMFLOAT3( 0.5f, -0.5f, 0.5f ),
		XMFLOAT3( -0.5f, -0.5f, 0.5f ),
	};

	D3D11_BUFFER_DESC bd;
	ZeroMemory( &bd, sizeof(bd) );
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof( SimpleVertex ) * 3;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = 0;

	D3D11_SUBRESOURCE_DATA Data;
	ZeroMemory( &Data, sizeof(Data) );
	Data.pSysMem = vertices;

	hr = m_pd3dDevice->CreateBuffer(&bd, &Data, &m_pVertexBuffer);
	if( FAILED( hr ) )
		return false;

	// Установка вершинного буфера
	UINT stride = sizeof( SimpleVertex );
	UINT offset = 0;
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);

	// установка топологии примитива
	m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return true;
}

Как я писал ранее, мы должны вывести треугольник состоящий из трех вершин. Вершину в нашей программе описывает структура SimpleVertex (layout, который мы задавали для входного формата, описывает вершину для видеокарты)

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

ByteWidth - определяет размер нашего буфера, помните, я говорил что буфер должен быть достаточного размера чтобы вместить наши вершины?

BindFlags - указывает что это буфер вершин.

Описав будущий буфер, мы описываем наши данные через D3D11_SUBRESOURCE_DATA. То есть просто присваиваем наш массив вершин к экземпляру этой структуры.

После всего этого мы наконец-то создаем буфер вершин через метод CreateBuffer() и устанавливаем его к нашему контексту через метод IASetVertexBuffers() .

И в самом конце мы указываем топологию примитива, как список треугольников (мы же решили рисовать треугольниками?:) )

Теперь найдите метод MyRender::Draw() и пишите:

bool MyRender::Draw()
{
	m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 );
	m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 );
	m_pImmediateContext->Draw( 3, 0 );

	return true;
}

Собственно, здесь мы подключаем вершинный и пиксельный шейдеры, а затем рисуем три вершины. Кстати, вы наверное заметили что у некоторых методов контекста впереди есть две буквы (VS* и PS*, а также более ранее IA*)? Это сокращения стадий конвейера рендера, хотя я думаю вы и сами догадались.

Пишем шейдерыПравить

Скомпилируйте ваш проект (но не запускайте). Зайдите в папку с exe файлом вашего проекта и создайте простой текстовый файл shader.fx. В нем напишите:

//--------------------------------------------------------------------------------------
// Вершинный шейдер
//--------------------------------------------------------------------------------------
float4 VS( float4 Pos : POSITION ) : SV_POSITION
{
    return Pos;
}

//--------------------------------------------------------------------------------------
// Пикслельный шейдер
//--------------------------------------------------------------------------------------
float4 PS( float4 Pos : SV_POSITION ) : SV_Target
{
    return float4( 1.0f, 1.0f, 0.0f, 1.0f );
}

В вершинном шейдере мы ничего не делаем. То есть получив позицию, мы ее возвращаем без изменений.

В пиксельном шейдере мы возвращаем желтый цвет, поэтому все пиксели будут у нас желтого цвета.

Вот, а теперь можете запускать. Если все было правильно, вы должны получить такое:

DirectX11StepToStep4 1

Теперь,чтобы лучше понять шейдер, вы можете переписать PS вот так:

float4 PS( float4 Pos : SV_POSITION ) : SV_Target
{
    float fLimiter = 500.0f;
    float dist = Pos.x*Pos.x + Pos.y*Pos.y;
    dist = (dist % fLimiter) / fLimiter;
    return float4( dist, 0.0f, dist, 1.0f );
}

(взято отсюда)

Вот так и работают шейдеры :)

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

Исходный кодПравить

СкачатьПравить

Материалы сообщества доступны в соответствии с условиями лицензии CC-BY-SA , если не указано иное.