Tutorial+5+-+3D+Transformation

//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.



Summary
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.

Source
The source files, libraries etc. may be downloaded from [|here].

=Transformation=

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
Translation refers to moving or displacing for a certain distance in space.

Rotation
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
Scaling refers to enlarging or shrinking the size of vector components along axis directions.

Multiple Transformations
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.

Creating the Orbit
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:

code format="bb4w" PROC_MatrixRotation(mWorld1, 0, t, 0) code

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:

code format="bb4w" 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 code 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:

code format="bb4w" t = TIME / 100 code

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:

code format="bb4w" 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 code

The Depth Buffer
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:

code format="bb4w" 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 code

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:

code format="bb4w" SYS ID3D11DeviceContext.OMSetRenderTargets%, pImmediateContext%, \ \  1, ^pRenderTargetView%, pDepthStencilView% code

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):

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