tutorial_205_20-_203d_20transformation

*by Richard Russell, August 2015*

**Note that the code in this tutorial requires Windows 8.1 or Windows 10**

This tutorial is closely based on Microsoft's Direct 3D 11 Tutorial 5 but with the code translated from C++ to *BBC BASIC for Windows*. You should refer to the original for a detailed explanation of the code.

In the previous tutorial, we rendered a cube from model space to the screen. In this tutorial, we will extend the concept of transformations and demonstrate simple animation that can be achieved with these transformations.

The outcome of this tutorial will be an object that orbits around another. It would be useful to demonstrate the transformations and how they can be combined to achieve the desired effect. Future tutorials will be building on this foundation as we introduce new concepts.

The source files, libraries etc. may be downloaded from here.

In 3D graphics, transformation is often used to operate on vertices and vectors. It is also used to convert them in one space to another. Transformation is performed by way of multiplication with a matrix. There are typically three types of primitive transformation that can be performed on vertices: translation (where it lies in space relative to the origin), rotation (its direction in relation to the x, y, z frame), and scaling (its distance from origin).

Translation refers to moving or displacing for a certain distance in space.

Rotation refers to rotating vertices about an axis going through the origin. Three such axes are the X, Y, and Z axes in the space.

Scaling refers to enlarging or shrinking the size of vector components along axis directions.

To apply multiple transformations to a vector, we can simply multiply the vector by the first transformation matrix, then multiply the resulting vector by the second transformation matrix, and so on. Because vector and matrix multiplication is associative, we can also multiply all of the matrices first, then multiply the vector by the product matrix and obtain an identical result.

In this tutorial, we will be transforming two cubes. The first one will rotate in place, while the second one will rotate around the first, while spinning on its own axis. Each cube will have its own world transformation matrix associated with it, and this matrix will be reapplied to it in every frame rendered.

The first cube will be spinning in place and act as the center for the orbit. The cube has a rotation along the Y axis applied to the associated world matrix. This is done by calling the MatrixRotation procedure shown in the following code. The cube is rotated by a set amount each frame. Since the cubes are suppose to continuously rotate, the value which the rotation matrix is based on gets incremented with every frame:

PROC_MatrixRotation(mWorld1(), 0, t, 0)

The second cube will be orbiting around the first one. To demonstrate multiple transformations, a scaling factor, and its own axis spin will be added. First the cube will be scaled down to 30% size, and then it will be rotated along its spin axis (the Z axis in this case). To simulate the orbit, it will get translated away from the origin, and then rotated along the Y axis. The desired effect can be achieved by using four separate matrices (mScale, mSpin, mTranslate, mOrbit), then multiplied together:

PROC_MatrixRotation(mSpin(), 0, 0, -t) PROC_MatrixRotation(mOrbit(), 0, -2*t, 0) PROC_MatrixScaling(mScale(), 0.3, 0.3, 0.3) PROC_MatrixTranslation(mTranslate(), -4, 0, 0) mWorld2() = mScale() . mSpin() mWorld2() = mWorld2() . mTranslate() mWorld2() = mWorld2() . mOrbit()

An important point to note is that these operations are not commutative. The order in which the transformations are applied matter. Experiment with the order of transformation and observe the results.

Since all the transformation functions will create a new matrix from the parameters, the amount at which they rotate has to be incremented:

t = TIME / 100

Before the rendering calls are made, the constant buffer must be updated for the shaders. Note that the world matrix is unique to each cube, and thus, changes for every object that gets passed into it:

REM Update variables for first cube: PROC_MatrixTranspose(ConstantBuffer{}, ConstantBuffer.mWorld{}, mWorld1()) SYS ID3D11DeviceContext.UpdateSubresource%, pImmediateContext%, pConstantBuffer%, \ \ 0, NULL, ConstantBuffer{}, 0, 0 REM Render first cube: SYS ID3D11DeviceContext.VSSetShader%, pImmediateContext%, pVertexShader%, NULL, 0 SYS ID3D11DeviceContext.VSSetConstantBuffers%, pImmediateContext%, 0, 1, \ \ ^pConstantBuffer% SYS ID3D11DeviceContext.PSSetShader%, pImmediateContext%, pPixelShader%, NULL, 0 SYS ID3D11DeviceContext.DrawIndexed%, pImmediateContext%, 36, 0, 0 REM Update variables for second cube: PROC_MatrixTranspose(ConstantBuffer{}, ConstantBuffer.mWorld{}, mWorld2()) SYS ID3D11DeviceContext.UpdateSubresource%, pImmediateContext%, pConstantBuffer%, \ \ 0, NULL, ConstantBuffer{}, 0, 0 REM Render second cube: SYS ID3D11DeviceContext.VSSetConstantBuffers%, pImmediateContext%, 0, 1, \ \ ^pConstantBuffer% SYS ID3D11DeviceContext.DrawIndexed%, pImmediateContext%, 36, 0, 0

There is one other important addition to this tutorial, and that is the depth buffer. Without it, the smaller orbiting cube would still be drawn on top of the larger centre cube when it went around the back of the latter. The depth buffer allows Direct3D to keep track of the depth of every pixel drawn to the screen. The following code in the sample creates a depth buffer (a DepthStencil texture). It also creates a DepthStencilView of the depth buffer so that Direct3D 11 knows to use it as a Depth Stencil texture:

REM Create depth stencil texture: DIM descDepth{} = D3D11_TEXTURE2D_DESC{} descDepth.Width% = Width% descDepth.Height% = Height% descDepth.MipLevels% = 1 descDepth.ArraySize% = 1 descDepth.Format% = DXGI_FORMAT_D24_UNORM_S8_UINT descDepth.SampleDesc.Count% = 1 descDepth.SampleDesc.Quality% = 0 descDepth.Usage% = D3D11_USAGE_DEFAULT descDepth.BindFlags% = D3D11_BIND_DEPTH_STENCIL descDepth.CPUAccessFlags% = 0 descDepth.MiscFlags% = 0 SYS ID3D11Device.CreateTexture2D%, pd3dDevice%, descDepth{}, NULL, \ \ ^pDepthStencil% TO hr% IF hr% <> 0 OR pDepthStencil% = 0 ERROR "ID3D11Device::CreateTexture2D failed: "+STR$~hr% REM Create the depth stencil view: DIM descDSV{} = D3D11_DEPTH_STENCIL_VIEW_DESC_2D{} descDSV.Format% = descDepth.Format% descDSV.ViewDimension% = D3D11_DSV_DIMENSION_TEXTURE2D descDSV.Texture2D.MipSlice% = 0 SYS ID3D11Device.CreateDepthStencilView%, pd3dDevice%, pDepthStencil%, descDSV{}, \ \ ^pDepthStencilView% TO hr% IF hr% <> 0 OR pDepthStencilView% = 0 THEN ERROR "ID3D11Device::CreateDepthStencilView failed: "+STR$~hr% ENDIF

In order to use this newly created depth stencil buffer, it must be bound to the device. This is done by passing the depth stencil view to the third parameter of the OMSetRenderTargets function:

SYS ID3D11DeviceContext.OMSetRenderTargets%, pImmediateContext%, \ \ 1, ^pRenderTargetView%, pDepthStencilView%

As with the render target, we must also clear the depth buffer before rendering. This ensures that depth values from previous frames do not incorrectly discard pixels in the current frame. In the code below the tutorial is actually setting the depth buffer to be the maximum amount (1.0):

REM Clear the depth buffer to 1.0 (max depth): SYS ID3D11DeviceContext.ClearDepthStencilView%, pImmediateContext%, \ \ pDepthStencilView%, D3D11_CLEAR_DEPTH, FN_f4(1), 0

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies

tutorial_205_20-_203d_20transformation.txt · Last modified: 2018/04/13 23:39 by richardrussell

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International