Applying an offset during runtime, MAKE THE SQUARES MOVE

Started by
20 comments, last by JWColeman 5 years, 7 months ago

 

I have been googling up and down translation in DirectX. I've seen plenty of tutorials on how to accomplish translation using matrices and I have accomplished the task before in OpenGL. However, I started using DirectX and I am using a vertex buffer and pushing it to a vertex shader for processing.

I'm trying to figure out, where in the process, and what objects come into play, when dynamically translating -specific- sets of vertices.

For instance, if I have two squares, and I push my vertices for my squares into my vertex buffer, after this is accomplished, where do I apply my offsets? Is it in the shader? If so, how to pass my offsets to the shader so that it knows which vertices to apply which offset?

Or, do I recreate my vertex buffer every time translation occurs (could be many times in a frame from user input or whatnot, this doesn't seem right to me).

Does anyone have some good material on simple 2d/3d translation during runtime with vertex buffers, or some quick tips on how you might accomplish such a thing.

 

So, I set up my renderable objects:


Game::Game(HWND hWnd)
{
	Renderable_Object * square1 = new Renderable_Object(-50, -50, 0, 0);
	Renderable_Object * square2 = new Renderable_Object(-0, -0, 50, 50);
	Game_Objects.push_back(square1);
	Game_Objects.push_back(square2);
	renderer = new Renderer(hWnd, Game_Objects);
}

 

In my renderer, I set up my vertex buffer, along with my other directx junk:


void Renderer::InitGraphics(std::vector<Renderable_Object*> Game_Objects)
{
	for (int i = 0; i < Game_Objects.size(); i++)
	{
		for (int j = 0; j < Game_Objects[i]->getVertices().size(); j++)
		{
			OurVertices.push_back(Game_Objects[i]->getVertices()[j]);
		}
	}

	createVertexBuffer();
	createConstantBuffer();
}

 

Then, after all is said and done, I draw the stuff, see below for my commented question:


void Renderer::Update()
{
	// clear the back buffer to a deep blue
	devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

	// Here is where I imagine I will have to do alterations and translation on my vertices based on input
	// my problem is not how to do this, like, the math part, applying an offset and junk like that...
	// my problem is what do I change right here, in order to make my squares move?
	// should I rebind my vertex buffer after applying some kind of offset?
	// like, another createVertexBuffer();?
	// or, is there some way to tell the shader to apply the offset for a specific group of vertices?
	// help please!

	// select which vertex buffer to display
	UINT stride = sizeof(Vertex);
	UINT offset = 0;
	devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);

	// select which primtive type we are using
	devcon->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// draw the vertex buffer to the back buffer
	devcon->Draw(12, 0);

	// switch the back buffer and the front buffer
	swapchain->Present(0, 0);
}

 

    // Here is where I imagine I will have to do alterations and translation on my vertices based on  input
    // my problem is not how to do this, like, the math part, applying an offset and junk like that...
    // my problem is what do I change right here, in order to make my squares move?
    // should I rebind my vertex buffer after applying some kind of offset?
    // like, another createVertexBuffer();?
    // or, is there some way to tell the shader to apply the offset for a specific group of vertices?
    // help please!

Thanks for your help in advance!

 

Advertisement

You could add a constant buffer in your vertex shader with a transform matrix. Something like this :


cbuffer TransformBuffer
{
	float4x4 Transform;
};

 

And still in the vertex shader, you multiply the intput vertex position with the transform matrix.

Now all you have to do is to update that constant buffer before the draw call with the transformation that you want (a translation in your case).

https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-resources-buffers-constant-how-to

 

If you only have squares you might want to use instancing to provide a matrix per square, or even just rewrite the vertex buffer cause a square is such an extremely simple mesh with a tiny amount of vertices to rewrite.

14 hours ago, ChuckNovice said:

You could add a constant buffer in your vertex shader with a transform matrix. Something like this :



cbuffer TransformBuffer
{
	float4x4 Transform;
};

 

And still in the vertex shader, you multiply the intput vertex position with the transform matrix.

Now all you have to do is to update that constant buffer before the draw call with the transformation that you want (a translation in your case).

https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-resources-buffers-constant-how-to

 

This I think I will try, I wasn't sure if constant buffers were the way to accomplish this or not. I've already managed creating a constant buffer for my ortho projection matrix transformations.


void Renderer::createConstantBuffer(void)
{
	ConstantBuffer cbuffer;
	D3DXMatrixOrthoLH(&cbuffer.projection, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1);

	// Fill in a buffer description.
	D3D11_BUFFER_DESC cbDesc;
	cbDesc.ByteWidth = sizeof(ConstantBuffer);
	cbDesc.Usage = D3D11_USAGE_DYNAMIC;
	cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	cbDesc.MiscFlags = 0;
	cbDesc.StructureByteStride = 0;


	// Fill in the subresource data.
	D3D11_SUBRESOURCE_DATA InitData;
	InitData.pSysMem = &cbuffer;
	InitData.SysMemPitch = 0;
	InitData.SysMemSlicePitch = 0;

	dev->CreateBuffer(&cbDesc, &InitData, &pCBuffer);
	devcon->VSSetConstantBuffers(0, 1, &pCBuffer);
}

This is what my shader looks like:


cbuffer ConstantBuffer : register(b0)
{
	matrix projection;
}


struct VOut
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
};

VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
	VOut output;

	output.position = mul(position, projection);
	output.color = color;

	return output;
}

 

So, first of all, for best practice, should I use the same cbuffer I use for my world projection? I should be able to create another without any trouble. 

Second of all, in order to update an already created constant buffer, which functions do I need to call?

I'm guessing that I need to update the memory in the buffer like this:

InitData.pSysMem = &cbuffer;

Do I need to do anything else?

 

Thanks very much for your reply!

 

9 hours ago, wintertime said:

If you only have squares you might want to use instancing to provide a matrix per square, or even just rewrite the vertex buffer cause a square is such an extremely simple mesh with a tiny amount of vertices to rewrite.

Hi wintertime, what you said may well be true in my specific scenario, but I don't want to draw two squares for the rest of my life, I hope to build rapidly on small principles I have established thus far.

4 hours ago, JWColeman said:

So, first of all, for best practice, should I use the same cbuffer I use for my world projection? I should be able to create another without any trouble. 

Second of all, in order to update an already created constant buffer, which functions do I need to call?

I'm guessing that I need to update the memory in the buffer like this:

InitData.pSysMem = &cbuffer;

Do I need to do anything else?

Well as a general rule for constant buffers it's preferable to group them based on update frequency. For example your projection matrix/view matrix along with few other future parameters may be updated once per frame. The world matrix however may be updated once per object to draw. So don't hesitate to split those.

Refer to this page to know how to update the data : https://bell0bytes.eu/shader-data/ there's a section called "Updating Constant Buffers" that show you how to use UpdateSubresource.

 

What an excellent tutorial, I couldn't find anything with such clarity, simplicity, and down to the point of making it do practical things. 

 

Thanks so much! I'll provide an update when I get some sucess!

I'd also like to suggest using a template for your constant buffers as you'll end up having multiple cbuffers whether due to multiple shaders, different update intervals, etc.

Note: The following code is for DirectX 11.

I don't remember where I had found this resource from, but I use something like this


#ifndef ConstantBuffer_h__
#define ConstantBuffer_h__
#include <d3d11.h>
#include <wrl/client.h>

template<class T>
class ConstantBuffer
{
private:
	ConstantBuffer(const ConstantBuffer<T>& rhs);

private:

	Microsoft::WRL::ComPtr<ID3D11Buffer> buffer;
	bool mInitialized;

public:
	ConstantBuffer() {}

	T Data;

	ID3D11Buffer* Buffer()const
	{
		return buffer.Get();
	}

	HRESULT Initialize(Microsoft::WRL::ComPtr<ID3D11Device> &device)
	{
		HRESULT hr = S_OK;
		D3D11_BUFFER_DESC desc;
		desc.Usage = D3D11_USAGE_DYNAMIC;
		desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		desc.MiscFlags = 0;
		desc.ByteWidth = static_cast<UINT>(sizeof(T) + (16 - (sizeof(T) % 16)));
		desc.StructureByteStride = 0;

		hr = device->CreateBuffer(&desc, 0, buffer.GetAddressOf());
		mInitialized = true;
		return hr;
	}

	void ApplyChanges(Microsoft::WRL::ComPtr<ID3D11DeviceContext> &dc)
	{
		D3D11_MAPPED_SUBRESOURCE mappedResource;
		HRESULT hr = dc->Map(buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
		CopyMemory(mappedResource.pData, &Data, sizeof(T));
		dc->Unmap(buffer.Get(), 0);
	}
};

#endif // ConstantBuffer_h__

Then I could define a structure for my cbuffer layout like this (In this example this is a constant buffer for my vertex shader for my 2d UI elements


struct CB_VS_UI
{
	XMFLOAT4 position;
};

In my graphics class, I am declaring the constant buffer of this type with


ConstantBuffer<CB_VS_UI> cb_vs_ui;

To initialize this constant buffer, I am doing


//create constant buffer for ui elements
HRESULT hr = this->cb_vs_ui.Initialize(this->d3d11Device); //<--Pass in the ComPtr<ID3D11Device> 
if (hr != S_OK)
{
	LogError("Failed Initialize Constant Buffer for UI Elements Vertex Shader. Error Code: " + std::to_string(hr)); 
	return false;
}

Then, before the Draw call, I am updating the constant buffer with this (note d3d11DeviceContext is type ComPtr<ID3D11DeviceContext> )


//constant buffer for ui vertex shader
this->cb_vs_ui.Data.position.x = xPosition;
this->cb_vs_ui.Data.position.y = yPosition;
this->cb_vs_ui.Data.position.z = 0;
this->cb_vs_ui.Data.position.w = 0;
auto vsbuffer = this->cb_vs_ui.Buffer();
this->d3d11DeviceContext->VSSetConstantBuffers(0, 1, &vsbuffer); //set the constant buffer for the vertex shader
this->cb_vs_ui.ApplyChanges(this->d3d11DeviceContext);

 

For reference, here is the basic vertex shader I am using for this example.


cbuffer cbPerUI : register(b0)
{
    float4 uipos;
};

struct UI_VS_OUTPUT    //output structure for skymap vertex shader
{
    float4 Pos : SV_POSITION;
    float2 texCoord : TEXCOORD;
};

UI_VS_OUTPUT main(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD)
{
    UI_VS_OUTPUT output;
    output.Pos = inPos.xyzz + uipos;
    output.texCoord = inTexCoord;
    return output;
}

 

 

Sorry for wall of text, just trying to put some info out there that might be helpful.

 

Hopefully I didn't forget to include anything.

Hi all, attempting to do this as simplistically as possible at first.

 

I created my buffer in my shader:

 


cbuffer PositionConstantBuffer : register (b1)
{
	float3 xyz;
}

 

It is my second constant buffer, so I set it to b1, seemed to make sense so I went for it..

 

I created my struct in my c++ code:

 


	struct PositionConstantBuffer
	{
		float x, y, z;
	};

 

I set up my shader to perform the translation (according to the example you provided, as best I could):


VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
	VOut output;

	float4 newposition = { xyz.x, xyz.y, xyz.z, 1.0f };

	output.position += newposition;

	output.position = mul(position, projection); // Apply ortho projection matrix

	output.color = color; // Apply color from vertex input

	return output;
}

 

 

Now I am attempting to set up the cbuffer in my code, I made a really basic function to wrap all the lines:


void Renderer::createTranslationBuffer(void)
{
	PositionConstantBuffer cbuffer;
	cbuffer.x = 300;
	cbuffer.y = 0.0;
	cbuffer.z = 0.0;

	// Fill in a buffer description.
	D3D11_BUFFER_DESC cbDesc;
	cbDesc.ByteWidth = 16;
	cbDesc.Usage = D3D11_USAGE_DYNAMIC;
	cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	cbDesc.MiscFlags = 0;
	cbDesc.StructureByteStride = 0;


	// Fill in the subresource data.
	D3D11_SUBRESOURCE_DATA InitData;
	InitData.pSysMem = &cbuffer;
	InitData.SysMemPitch = 0;
	InitData.SysMemSlicePitch = 0;

	dev->CreateBuffer(&cbDesc, &InitData, &pPositionConstantBuffer);
	devcon->VSSetConstantBuffers(1, 1, &pPositionConstantBuffer); // this did not come easily
}

Note, I sat for ages trying to figure out why my square wasn't displaying on my screen. Finally I checked closely the parameters of VSSetConstantBuffers and saw start slot, thought this might have had something to do with it and I changed it from 0 to 1, viola, my square came back. I was able to do a screen cap and see that my two constant buffers loaded!

image.thumb.png.ea482d21a080535095f2450403abedd1.png

 

Now, I'm gonna try playing with this to make my square move from left to right...

 

I have one big problem, changing the x value in my C++ code to 300 doesn't seem to move this thing an inch. :(

 

 

Hah! I figured it out, I'll beat you yet mathematics!


VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
	VOut output;

	float4 newposition = { xyz.x, xyz.y, xyz.z, 1.0f };
	
	newposition = mul(newposition, projection);
	output.position = mul(position, projection); // Apply ortho projection matrix
	output.position += newposition;
	output.color = color; // Apply color from vertex input

	return output;
}

image.png.55c18058766def8603d7e74a6f7471d6.png

If you want to make your life even easier you could deal with a matrix instead of a float3. A matrix allow you to perform any combination of translation, rotation and scaling.

Which involves changing this :


cbuffer PositionConstantBuffer : register (b1)
{
	float3 xyz;
}

to this :


cbuffer PositionConstantBuffer : register (b1)
{
	float4x4 world;
}

 

And adjusting the shader a little bit to something like this :


VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
	VOut output;
	output.position = mul(position, world);
        //output.position = mul(output.position, view); // you'll very soon want a view matrix there
	output.position = mul(output.position, projection); // Apply ortho projection matrix
	output.color = color; // Apply color from vertex input
	return output;
}

 

I'm assuming that you are already using a math library that provides common matrix operations such as Translate/Rotate/Scale.

This topic is closed to new replies.

Advertisement