Введение[]
В этом уроке мы научимся использовать текстуру. О текстурах вы можете почитать здесь или более полно здесь (но на английском). Кратко, текстурирование - это проекция двухмерного изображения на трехмерную геометрию.
Текстура, как я сказал выше, это двухмерное изображение (на самом деле есть еще и одномерные и трехмерные текстуры, но мы их не рассмариваем), то есть имеет две оси u и v. Ось U расположена горизонтально изображения, а ось V - вертикально. При этом точка 0 является началом длины изображения, а точка 1 - концом длины изображения. Таким образом точка расположенная в середине изображения имеет (0.5;0.5) координату.
При этом если мы заданим не 1, а к примеру 2 по любой из оси или сразу по обоим, то мы спроецируем не одну а уже две текстуры на каждой из осей.
Для урока нам нужна любая текстура (то есть любое изображение с кратным размером, к примеру 256 на 256 или 512 на 512). Вы можете взять вот эту (сохраните ее, и используйте) или свою собственную.
Пишем код[]
Создайте проект, подключите фреймворк третьей версии. Файл main.cpp
#include "MyRender.h" int main() { Framework framework; MyRender *render = new MyRender(); FrameworkDesc desc; desc.render = render; framework.Init(desc); framework.Run(); framework.Close(); return 0; }
Файл MyRender.h
#pragma once #include "D3D11_Framework.h" #include <xnamath.h> using namespace D3D11Framework; class MyRender : public Render { public: MyRender(); bool Init(HWND hwnd); bool Draw(); void Close(); void* operator new(size_t i) { return _aligned_malloc(i,16); } void operator delete(void* p) { _aligned_free(p); } private: ID3D11Buffer *m_pVertexBuffer; ID3D11InputLayout *m_pVertexLayout; ID3D11VertexShader *m_pVertexShader; ID3D11PixelShader *m_pPixelShader; ID3D11Buffer *m_pIndexBuffer; ID3D11Buffer *m_pConstantBuffer; ID3D11ShaderResourceView *m_pTextureRV; ID3D11SamplerState *m_pSamplerLinear; XMMATRIX m_World1; XMMATRIX m_World2; XMMATRIX m_View; XMMATRIX m_Projection; float m_rot; };
Мы добавили два новых члена m_pTextureRV, который будет хранить текстуру в памяти и m_pSamplerLinear который описывает представление текстуры - фильтрацию, MIP и адрессацию. Текстура, как вы заметили, является шейдер-ресурсом.
MyRender.cpp:
#include "MyRender.h" struct SimpleVertex { XMFLOAT3 Pos; XMFLOAT2 Tex; }; struct ConstantBuffer { XMMATRIX WVP; }; MyRender::MyRender() { m_pVertexShader = nullptr; m_pPixelShader = nullptr; m_pVertexLayout = nullptr; m_pVertexBuffer = nullptr; m_pIndexBuffer = nullptr; m_pConstantBuffer = nullptr; m_pTextureRV = nullptr; m_pSamplerLinear = nullptr; m_rot = 0.01; } 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 ) ) { _RELEASE(pVSBlob); return false; }
Здесь все как и раньше. Только константный буфер упрощен, в шейдер мы будем передавать сразу готовую матрицу (помните, я ранее писал что чем меньше действий в шейдере тем лучше?).
D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, 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 ); 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 ) ) return false; SimpleVertex vertices[] = { { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT2( 0.0f, 0.0f ) }, { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT2( 1.0f, 0.0f ) }, { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) }, { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) }, };
В начале ничего нового, дальше описываем вершины. Как вы помните, длина текстуры от 0 до 1. Если вы впишите вместо 1, двойки, то на куб будет натянута не одна текстура а 4:) Позже попробуйте. Если написать 0.5 - то только половина текстуры.
D3D11_BUFFER_DESC bd; ZeroMemory( &bd, sizeof(bd) ); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof( SimpleVertex ) * 24; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA InitData; ZeroMemory( &InitData, sizeof(InitData) ); InitData.pSysMem = vertices; hr = m_pd3dDevice->CreateBuffer(&bd, &InitData, &m_pVertexBuffer); if( FAILED( hr ) ) return false; UINT stride = sizeof( SimpleVertex ); UINT offset = 0; m_pImmediateContext->IASetVertexBuffers( 0, 1, &m_pVertexBuffer, &stride, &offset ); WORD indices[] = { 3,1,0, 2,1,3, 6,4,5, 7,4,6, 11,9,8, 10,9,11, 14,12,13, 15,12,14, 19,17,16, 18,17,19, 22,20,21, 23,20,22 }; bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof( WORD ) * 36; bd.BindFlags = D3D11_BIND_INDEX_BUFFER; bd.CPUAccessFlags = 0; InitData.pSysMem = indices; hr = m_pd3dDevice->CreateBuffer( &bd, &InitData, &m_pIndexBuffer ); if( FAILED( hr ) ) return false; m_pImmediateContext->IASetIndexBuffer( m_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 ); m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBuffer); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; hr = m_pd3dDevice->CreateBuffer(&bd, NULL, &m_pConstantBuffer); if( FAILED( hr ) ) return false;
И опять ничего нового:)
hr = D3DX11CreateShaderResourceViewFromFile( m_pd3dDevice, L"texture.png", NULL, NULL, &m_pTextureRV, NULL ); if( FAILED( hr ) ) return false; D3D11_SAMPLER_DESC sampDesc; ZeroMemory( &sampDesc, sizeof(sampDesc) ); sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; sampDesc.MinLOD = 0; sampDesc.MaxLOD = D3D11_FLOAT32_MAX; hr = m_pd3dDevice->CreateSamplerState( &sampDesc, &m_pSamplerLinear ); if( FAILED( hr ) ) return false;
А вот теперь, кое-что интересное. Сначала мы используя функцию D3DX11CreateShaderResourceViewFromFile() из библиотеки d3dx11.lib грузим из файла с именем "texture.png" нашу текстуру и присваиваем ее нашему объекту m_pTextureRV.
Затем мы должны создать sample state. Сначала заполняем структуру D3D11_SAMPLER_DESC, выбирая нужную фильтрацию, адресацию и MIP. Затем через метод CreateSamplerState() мы ее создаем. Sample state описывает представление нашей текстуры.
m_World1 = XMMatrixIdentity(); m_World2 = XMMatrixIdentity(); XMVECTOR Eye = XMVectorSet( 0.0f, 3.0f, -8.0f, 0.0f ); XMVECTOR At = XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f ); XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f ); m_View = XMMatrixLookAtLH( Eye, At, Up ); m_Projection = XMMatrixPerspectiveFovLH( 0.4f*3.14f, (float)640/480, 0.1f, 1000.0f); return true; }
Завершаем инициализацию настройкой матриц.
bool MyRender::Draw() { m_rot += .0005f; if(m_rot > 6.26f) m_rot = 0.0f; XMVECTOR rotaxis = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); XMMATRIX Rotation = XMMatrixRotationAxis( rotaxis, m_rot); XMMATRIX Translation = XMMatrixTranslation( 0.0f, 0.0f, 4.0f ); m_World1 = Translation * Rotation; Rotation = XMMatrixRotationAxis( rotaxis, -m_rot); XMMATRIX Scale = XMMatrixScaling( 1.3f, 1.3f, 1.3f ); m_World2 = Rotation * Scale; XMMATRIX WVP = m_World1 * m_View * m_Projection; ConstantBuffer cb; cb.WVP = XMMatrixTranspose(WVP); m_pImmediateContext->UpdateSubresource( m_pConstantBuffer, 0, NULL, &cb, 0, 0 ); m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 ); m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pConstantBuffer ); m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 ); m_pImmediateContext->PSSetShaderResources( 0, 1, &m_pTextureRV ); m_pImmediateContext->PSSetSamplers( 0, 1, &m_pSamplerLinear ); m_pImmediateContext->DrawIndexed( 36, 0, 0 ); WVP = m_World2 * m_View * m_Projection; cb.WVP = XMMatrixTranspose(WVP); m_pImmediateContext->UpdateSubresource( m_pConstantBuffer, 0, NULL, &cb, 0, 0 ); m_pImmediateContext->DrawIndexed( 36, 0, 0 ); return true; }
Здесь мы будем рисовать два куба, один вращается в центре, а другой вокруг первого. На обоих из них натянута наша текстура. Первые три строчки нужны чтобы сделать анимацию вращения. Затем мы задаем мировую матрицу первого и вторго кубов. После этого создаем описание константного буфера и передаем в шейдер уже подсчитанную матрицу первого куба. Потом подключаем шейдеры. Дальше нас интересуют вот эти две строчки:
m_pImmediateContext->PSSetShaderResources( 0, 1, &m_pTextureRV ); m_pImmediateContext->PSSetSamplers( 0, 1, &m_pSamplerLinear );
В первой мы подключаем шейдерный ресурс с нашей текстурой, а в следующей устаналвиваем Sampler state описывающий вывод нашей текстуры.
Дальше я думаю код понятен. Завершаем:
void MyRender::Close() { _RELEASE(m_pConstantBuffer); _RELEASE(m_pVertexBuffer); _RELEASE(m_pIndexBuffer); _RELEASE(m_pVertexLayout); _RELEASE(m_pVertexShader); _RELEASE(m_pPixelShader); _RELEASE(m_pTextureRV); _RELEASE(m_pSamplerLinear); }
Теперь пишем шейдер shader.fx:
cbuffer cbPerObject { float4x4 WVP; }; Texture2D ObjTexture; SamplerState ObjSamplerState; struct VS_OUTPUT { float4 Pos : SV_POSITION; float2 TexCoord : TEXCOORD; }; VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD) { VS_OUTPUT output; output.Pos = mul(inPos, WVP); output.TexCoord = inTexCoord; return output; } float4 PS(VS_OUTPUT input) : SV_TARGET { return ObjTexture.Sample( ObjSamplerState, input.TexCoord ); }
Здесь мы в пиксельном шейдере возвращаем пиксели текстуры.
На этом урок закончен