I am using the following code to create orthographic matrix:
Matrix4D Matrix4D::fromOrtho(double left, double right, double bottom, double top, double nearZ, double farZ)
{
double ral = right + left;
double rsl = right - left;
double tab = top + bottom;
double tsb = top - bottom;
double fan = farZ + nearZ;
double fsn = farZ - nearZ;
return Matrix4D ( 2.0f / rsl, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / tsb, 0.0f, 0.0f,
0.0f, 0.0f, -2.0f / fsn, 0.0f,
-ral / rsl, -tab / tsb, -fan / fsn, 1.0f);
}
and use the following parameters:
double widthToHeightRatio = screenWidth / screenHeight;
Matrix4D::fromOrtho(-10, 10, -7, 7 ,0.1, 5000);
The left, right, bottom and top parameters are actually calculates as a function of the camera eye and center coordinates, but this is an example for result parameters.
The same matrix works well with OpenGL but not working with Metal. When the matrix is a perspective matrix, everything works well also in Metal.
What might be the problem?
Both perspective and ortho projection matrices from GL are invalid in Metal, because the z range is different. Some matrices MAY still work because your z-clip range is overly-deep in OpenGL so it happens to be deep enough to let fragments through in Metal as well, but this is a bad thing to bank on.
From the Metal Programming Guide, p. 51 “Working with Viewport and Pixel Space Coordinates”:
Metal defines its Normalized Device Coordinate (NDC) system as a
2x2x1 cube with its center at (0, 0, 0.5). The left and bottom for x
and y, respectively, of the NDC system are specified as -1. The right
and top for x and y, respectively, of the NDC system are specified as
+1.
This is different from OpenGL, which has its z go from -1 to 1, in a 2x2x2 cube.
See this blog post for more details: http://blog.athenstean.com/post/135771439196/from-opengl-to-metal-the-projection-matrix
Update — User da1 found an alternate blog post, the above is currently down: http://metashapes.com/blog/opengl-metal-projection-matrix-problem
from AAPLTransfomations.mm this works for me (metal sample project)
simd::float4x4 AAPL::ortho2d(const float& left,
const float& right,
const float& bottom,
const float& top,
const float& near,
const float& far)
{
float sLength = 1.0f / (right - left);
float sHeight = 1.0f / (top - bottom);
float sDepth = 1.0f / (far - near);
simd::float4 P;
simd::float4 Q;
simd::float4 R;
simd::float4 S;
P.x = 2.0f * sLength;
P.y = 0.0f;
P.z = 0.0f;
P.w = 0.0f;
Q.x = 0.0f;
Q.y = 2.0f * sHeight;
Q.z = 0.0f;
Q.w = 0.0f;
R.x = 0.0f;
R.y = 0.0f;
R.z = sDepth;
R.w = 0.0f;
S.x = 0.0f;
S.y = 0.0f;
S.z = -near * sDepth;
S.w = 1.0f;
return simd::float4x4(P, Q, R, S);
} // ortho2d
and implemented by passing to your shader in the
constant_buffer[i].modelview_ortho_matrix = ortho2d(-2.0f, 2.0f, -2.0f, 2.0f, 0, 2); //_projectionMatrix * modelViewMatrix;
and then perhaps in your vertex shader
float4 in_position = float4(float3(vertex_array[vid].position), 1.0);
out.position = constants.modelview_ortho_matrix * in_position;
Related
Sorry for auto translation.
The part that's stuck right now is shadow mapping.
The position of the player is exactly 2000, 0, 2000; there is a light source that is the origin of the shadow mapping camera directly over the sky (it is a directory light).
Question 1
You need a shadow map to apply shadow mapping, don't you?
The shadow map isn't working right now, but let's move on.
As long as the viewport of the shadow mapping camera is in the cover space itself, the render is not working properly on the shadow map.
Even if it's initialized to 1.0f, it's all supposed to be outside the shadows.
Although the current shadow map camera position is 2000, 100, 2000, and the focus position is 0, 0, 0.
It's not a red square area that's supposed to be determined to be outside the shadow.
The green square area is determined to be out of the shadow.
For your information, there's nothing blocking the light in the viewport. The shadow you see in the screenshot is just outside the viewport, so it's a shadow that comes from a 0.0f judgment on the shadow map.
Question 2
This is the fundamental problem. No render on shadow map in shadow pass.
Once this is done, I'll find the first question somehow, but the render itself doesn't work, so there's no shadow of the object -> the cause is unknown
Source indicates that the ShadowShader class is rendering a shadow map (ShadowPassRender)
What affects this is the degree of view-project matrix created at the time of the light source (the Update ShaderVariables portion of the Shadow Shader class).
I'm most suspicious of this one, but the process of making it is not different from the example, so I don't know where it's wrong.
I'm using the light itself as a blin pong, and I'm gonna take this as an example and fix it for the project.
code that generate View-Projection matrix for shadow mapping
void CShadowShader::UpdateShaderVariables(ID3D12GraphicsCommandList* pd3dCommandList, XMFLOAT3 xmf3TargetPos)
{
XMFLOAT3 TargetPos = {950, 0, 950};
XMMATRIX lightView = XMMatrixLookAtLH(XMLoadFloat3(&m_pLight->GetPosition()), XMLoadFloat3(&TargetPos), XMLoadFloat3(&m_pLight->GetUp()));
// Transform bounding sphere to light space.
XMFLOAT3 xmf3CenterLS;
XMStoreFloat3(&xmf3CenterLS, XMVector3TransformCoord(XMLoadFloat3(&TargetPos), lightView));
// Ortho frustum in light space encloses scene.
float l = xmf3CenterLS.x - 3000;
float b = xmf3CenterLS.y - 3000;
float n = xmf3CenterLS.z - 3000;
float r = xmf3CenterLS.x + 3000;
float t = xmf3CenterLS.y + 3000;
float f = xmf3CenterLS.z + 3000;
XMMATRIX lightProj = XMMatrixOrthographicOffCenterLH(l, r, b, t, n, f);
// Transform NDC space [-1,+1]^2 to texture space [0,1]^2
XMMATRIX T(
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
XMMATRIX S = lightView * lightProj * T;
XMFLOAT4X4 m_xmf4x4ShadowTransform;
XMStoreFloat4x4(&m_xmf4x4ShadowTransform, S);
CB_SHADOW cbShadow{ m_xmf4x4ShadowTransform, m_pLight->GetPosition() };
m_ubShadowCB->CopyData(0, cbShadow);
pd3dCommandList->SetGraphicsRootConstantBufferView(3, m_ubShadowCB->Resource()->GetGPUVirtualAddress());
}
make PSO for shadow pass
void CShadowShader::CreateShader(ID3D12Device* pd3dDevice, ID3D12RootSignature* pd3dGraphicsRootSignature)
{
m_ubShadowCB = new UploadBuffer<CB_SHADOW>(pd3dDevice, 1, true);
ID3DBlob* pd3dVertexShaderBlob = NULL, * pd3dPixelShaderBlob = NULL;
D3D12_GRAPHICS_PIPELINE_STATE_DESC d3dPipelineStateDesc;
::ZeroMemory(&d3dPipelineStateDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
d3dPipelineStateDesc.pRootSignature = pd3dGraphicsRootSignature;
d3dPipelineStateDesc.VS = CreateVertexShader(&pd3dVertexShaderBlob);
d3dPipelineStateDesc.PS = CreatePixelShader(&pd3dPixelShaderBlob);
d3dPipelineStateDesc.RasterizerState = CreateRasterizerState();
d3dPipelineStateDesc.RasterizerState.DepthBias = 10000.0f;
d3dPipelineStateDesc.RasterizerState.DepthBiasClamp = 0.0f;
d3dPipelineStateDesc.RasterizerState.SlopeScaledDepthBias = 1.0f;
d3dPipelineStateDesc.BlendState = CreateBlendState();
d3dPipelineStateDesc.DepthStencilState = CreateDepthStencilState();
d3dPipelineStateDesc.InputLayout = CreateInputLayout();
d3dPipelineStateDesc.SampleMask = UINT_MAX;
d3dPipelineStateDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
d3dPipelineStateDesc.NumRenderTargets = 0;
d3dPipelineStateDesc.RTVFormats[0] = DXGI_FORMAT_UNKNOWN;
d3dPipelineStateDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
d3dPipelineStateDesc.SampleDesc.Count = 1;
d3dPipelineStateDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
auto tmp = pd3dDevice->CreateGraphicsPipelineState(&d3dPipelineStateDesc, __uuidof(ID3D12PipelineState), (void**)&m_pd3dPipelineState);
if (pd3dVertexShaderBlob)
pd3dVertexShaderBlob->Release();
if (pd3dPixelShaderBlob)
pd3dPixelShaderBlob->Release();
if (d3dPipelineStateDesc.InputLayout.pInputElementDescs)
delete[] d3dPipelineStateDesc.InputLayout.pInputElementDescs;
}
Shader for Shadow pass
#include "Common.hlsli"
struct VertexIn
{
float3 PosL : POSITION;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut) 0.0f;
MATERIAL matData = material;
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), gmtxWorld);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gmtxShadowTransform);
return vout;
}
// This is only used for alpha cut out geometry, so that shadows
// show up correctly. Geometry that does not need to sample a
// texture can use a NULL pixel shader for depth pass.
void PS(VertexOut pin)
{
// Fetch the material data.
MATERIAL matData = material;
float4 diffuseAlbedo = matData.DiffuseAlbedo;
}
Default.hlsl for render pass (there's few korean comments. not important)
#include "Common.hlsli"
//정점 셰이더의 입력을 위한 구조체를 선언한다.
struct VS_DEFAULT_INPUT
{
float3 position : POSITION;
float3 normal : NORMAL;
};
//정점 셰이더의 출력(픽셀 셰이더의 입력)을 위한 구조체를 선언한다.
struct VS_DEFAULT_OUTPUT
{
float4 position : SV_POSITION;
float4 position_shadow : POSITION0;
float3 position_w : POSITION1;
float3 normal : NORMAL;
};
VS_DEFAULT_OUTPUT VS_Default(VS_DEFAULT_INPUT input)
{
VS_DEFAULT_OUTPUT output;
output.position = mul(mul(float4(input.position, 1.0f), gmtxWorld), gmtxViewProj);
output.position_w = mul(float4(input.position, 1.0f), gmtxWorld).xyz;
output.normal = normalize(mul(float4(input.normal, 0.0f), gmtxWorld).xyz);
output.position_shadow = mul(float4(output.position_w, 1.0f), gmtxShadowTransform);
return (output);
}
float4 PS_Default(VS_DEFAULT_OUTPUT input) : SV_TARGET
{
float4 cColor = float4(0.0f, 0.0f, 0.0f, 0.0f);
cColor += material.AmbientLight * material.DiffuseAlbedo;
float3 toEyeW = normalize(cameraPos - input.position_w);
float3 shadowFactor = float3(1.0f, 1.0f, 1.0f);
shadowFactor[0] = CalcShadowFactor(input.position_shadow);
for (int i = 0; i < nLights; i++)
{
cColor += ComputeLighting(light[i], input.position_w, input.normal, toEyeW, shadowFactor[0]);
}
// Add in specular reflections.
float3 r = reflect(-toEyeW, input.normal);
float4 reflectionColor = { 1.0f, 1.0f, 1.0f, 0.0f };
float3 fresnelFactor = SchlickFresnel(material.FresnelR0, input.normal, r);
cColor.rgb += material.Shininess * fresnelFactor * reflectionColor.rgb;
// Common convention to take alpha from diffuse albedo.
cColor.a = material.DiffuseAlbedo.a;
return (cColor);
}
GitHub Link: https://github.com/kcjsend2/3DGP-BulletPhysics
Bullet physical engine is included, so bullet engine will need to be received and connected to the project to build.
See Chapter 20 Shadow Mapping in Frank Luna's Introduction to 3d game programming with directx 12 for examples.
The framework is independent, so it's very different from the example.
Bullet physics engine is included, so bullet engine will need to be received and connected to the project to build.
I fixed it. just because of hlsl shader and direct x uses different type of matrix.
hlsl shader uses column major, and direct x uses row major matrix.
and I also calculate wrong with matrix multipication order.
worng one is first codes of the question
...and this is fixed code:
XMVECTOR lightPos = XMLoadFloat3(&m_pLight->GetPosition());
XMVECTOR TargetPos = XMLoadFloat3(&xmf3TargetPos);
XMVECTOR lightUp = XMLoadFloat3(&m_pLight->GetUp());
XMMATRIX lightView = XMMatrixLookAtLH(lightPos, TargetPos, lightUp);
/*XMVECTOR lightLook = Vector3::Normalize(lightPos - TargetPos);*/
// Transform bounding sphere to light space.
XMFLOAT3 xmf3CenterLS;
XMStoreFloat3(&xmf3CenterLS, XMVector3TransformCoord(XMLoadFloat3(&xmf3TargetPos), lightView));
// Ortho frustum in light space encloses scene.
float l = xmf3CenterLS.x - 800;
float b = xmf3CenterLS.y - 800;
float n = xmf3CenterLS.z - 800;
float r = xmf3CenterLS.x + 800;
float t = xmf3CenterLS.y + 800;
float f = xmf3CenterLS.z + 800;
XMMATRIX lightProj = XMMatrixOrthographicOffCenterLH(l, r, b, t, n, f);
// Transform NDC space [-1,+1]^2 to texture space [0,1]^2
XMMATRIX T(
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
XMMATRIX S = lightView * lightProj;
XMFLOAT4X4 xmf4x4LightViewProj;
XMStoreFloat4x4(&xmf4x4LightViewProj, XMMatrixTranspose(S));
S = S * T;
XMFLOAT4X4 xmf4x4ShadowTransform;
XMStoreFloat4x4(&xmf4x4ShadowTransform, XMMatrixTranspose(S));
CB_SHADOW cbShadow{ xmf4x4ShadowTransform, xmf4x4LightViewProj, m_pLight->GetPosition() };
m_ubShadowCB->CopyData(0, cbShadow);
pd3dCommandList->SetGraphicsRootConstantBufferView(3, m_ubShadowCB->Resource()->GetGPUVirtualAddress());
Exactly the same issue I was facing when I was not transposing the matrix I was sending to shader, then I transposed it and it worked. :)
I want to draw a filled arc like this:
CCDrawNode only contain methods to draw a circle, a polygon or a line – but no arc.
To be clear, I'm wanting to be able to generate the arc at runtime, with an arbitrary radius and arc angle.
My Question:
Is there a way to have Cocos2d draw an arc, or would I have to do it myself with OpenGL?
I guess you can use the CCProgressNode, and by setting the sprite to an orange circle, you can draw an arc by setting the progress property.
Now, whether you can set the sprite as a vectorized circle that you can just scale, I am not really sure.
Personally, I'd suggest you add the drawing arc code to CCDrawNode, and submit a PR.
tested using cocos2dx v3.8, codes from DrawNode::drawSolidCircle
DrawNode* Pie::drawPie(const Vec2& center, float radius, float startAngle, float endAngle, unsigned int segments, float scaleX, float scaleY, const Color4F &color){
segments++;
auto draw = DrawNode::create();
const float coef = (endAngle - startAngle) / segments;
Vec2 *vertices = new (std::nothrow) Vec2[segments];
if (!vertices)
return nullptr;
for (unsigned int i = 0; i < segments - 1; i++)
{
float rads = i*coef;
GLfloat j = radius * cosf(rads + startAngle) * scaleX + center.x;
GLfloat k = radius * sinf(rads + startAngle) * scaleY + center.y;
vertices[i].x = j;
vertices[i].y = k;
}
vertices[segments - 1].x = center.x;
vertices[segments - 1].y = center.y;
draw->drawSolidPoly(vertices, segments, color);
CC_SAFE_DELETE_ARRAY(vertices);
return draw;
}
call it like
auto pie = Pie::drawPie(_visibleSize / 2, _visibleSize.width / 4, 0, 1, 999, 1, 1, Color4F::BLUE);
I have a triangle created in DirectX11. I now want to play around with viewport and world matrices to help my understanding of them, so Id like to simply rotate the triangle around the Z axis. My code for attempting to do that is below.
void Render(void)
{
if (d3dContext_ == 0)
return;
XMMATRIX view = XMMatrixIdentity();
XMMATRIX projection = XMMatrixOrthographicOffCenterLH(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f); .
XMMATRIX vpMatrix_ = XMMatrixMultiply(view, projection);
XMMATRIX translation = XMMatrixTranslation(0.0f, 0.0f, 0.0f);
XMMATRIX rotationZ = XMMatrixRotationZ(30.0f);
XMMATRIX TriangleWorld = translation * rotationZ;
XMMATRIX mvp = TriangleWorld*vpMatrix_;
mvp = XMMatrixTranspose(mvp);
float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
d3dContext_->ClearRenderTargetView(backBufferTarget_, clearColor);
unsigned int stride = sizeof(VertexPos);
unsigned int offset = 0;
d3dContext_->IASetInputLayout(inputLayout_);
d3dContext_->IASetVertexBuffers(0, 1, &vertexBuffer_, &stride, &offset);
d3dContext_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
d3dContext_->VSSetShader(solidColorVS_, 0, 0);
d3dContext_->PSSetShader(solidColorPS_, 0, 0);
d3dContext_->UpdateSubresource(mvpCB_, 0, 0, &mvp, 0, 0);
d3dContext_->VSSetConstantBuffers(0, 1, &mvpCB_);
d3dContext_->Draw(3, 0);
swapChain_->Present(0, 0);
}
It just displays the standard triangle, its as if it does not take notice of the mvp.
My desired effect is the rotation as controlled by XMMATRIX rotationZ = XMMatrixRotationZ(30);.
Thanks
XMMatrixRotationZ takes a radian as parameter, not degrees (see MSDN Description ).
To get degrees from radians, you have to multiply by M_PI / 180.0f
XMMATRIX rotationZ = XMMatrixRotationZ(30 * M_PI / 180.0);
As far as i know from OpenGl you must increase the XMMatrixRotationZ-value for an animated rotation a little bit per tick, because otherwise you only draw it once in the specific angle.
So (if you haven't) create a loop for your render function and increase the angle-value per round
Hope i could help
Help me please with ray picking
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(35.0f), aspect, 0.1f, 1000.0f);
GLKMatrix4 modelViewMatrix = _mainmodelViewMatrix;
// some transformations
_mainmodelViewMatrix = modelViewMatrix;
_modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
_normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
_modelViewProjectionMatrix and _normalMatrix put to shader
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m);
and in touch end
GLKVector4 normalisedVector = GLKVector4Make((2 * position.x / self.view.bounds.size.width - 1),
(2 * (self.view.bounds.size.height-position.y) / self.view.bounds.size.height - 1) , //1 - 2 * position.y / self.view.bounds.size.height,
-1,
1);
GLKMatrix4 inversedMatrix = GLKMatrix4Invert(_modelViewProjectionMatrix, nil);
GLKVector4 near_point = GLKMatrix4MultiplyVector4(inversedMatrix, normalisedVector);
How I can get far point? And my near_point is correct or not?
Thanks!
it looks like you have
GLKVector4 normalisedVector = GLKVector4Make((2 * position.x / self.view.bounds.size.width - 1),
(2 * (self.view.bounds.size.height-position.y) / self.view.bounds.size.height - 1) ,
-1, 1);
(phew) to calculate the normalized device coordinates of the near point.
To get the far point, just swap the -1 z coordinate for a 1:
GLKVector4 normalisedFarVector = GLKVector4Make((2 * position.x / self.view.bounds.size.width - 1),
(2 * (self.view.bounds.size.height-position.y) / self.view.bounds.size.height - 1) ,
1, 1);
And apply the same inverse transform to that. That should do the trick.
Background: Under normal circumstances, the final coordinates received by the GL for turning a fragment into a pixel are what are called normalised device coordinates. These lie within a cube whose corners are at (-1,-1,-1_ and (1,1,1). So the center of the screen is (0,0,z), the top left corner is (-1,1,z) and so on. The coordinates are transformed so that a point lying on the near plane will have a z coordinate of 1, and one lying just on the far plane will have a z coordinate of -1. These are the numbers that are used for depth testing, if you have it turned on.
So, as you might guess, when you want to convert a screen location back to a point in 3D space, you actually have a number of points to choose from - a line, in fact, stretching from the near plane to the far plane. In normalised device coordinates, this is the line stretching from z=-1 to z=1. So the process goes like this:
convert the x and y coordinates into normalised device coordinates x' and y'
For each of z' = 1 and z' = -1:
convert the coordinates to normalised device coordinates (see here for the formula)
apply the inverse of the projection matrix
apply the inverse of the model/view matrix (as it is before any per-object transformations)
The results are the two coordinates of your line in 3D space.
We can draw line from near_point to far_point.
GLKVector4 normalisedVector = GLKVector4Make((2 * position.x / self.view.bounds.size.width - 1),
(2 * (self.view.bounds.size.height-position.y) / self.view.bounds.size.height - 1),
-1,
1);
GLKMatrix4 inversedMatrix = GLKMatrix4Invert(_modelViewProjectionMatrix, nil);
GLKVector4 near_point = GLKMatrix4MultiplyVector4(inversedMatrix, normalisedVector);
near_point.v[3] = 1.0/near_point.v[3];
near_point = GLKVector4Make(near_point.v[0]*near_point.v[3], near_point.v[1]*near_point.v[3], near_point.v[2]*near_point.v[3], 1);
normalisedVector.z = 1.0;
GLKVector4 far_point = GLKMatrix4MultiplyVector4(inversedMatrix, normalisedVector);
far_point.v[3] = 1.0/far_point.v[3];
far_point = GLKVector4Make(far_point.v[0]*far_point.v[3], far_point.v[1]*far_point.v[3], far_point.v[2]*far_point.v[3], 1);
About 2 days ago I decided to write code to explicitly calculate the Model-View-Projection ("MVP") matrix to understand how it worked. Since then I've had nothing but trouble, seemingly because of the projection matrix I'm using.
Working with an iPhone display, I create a screen centered square described by these 4 corner vertices:
const CGFloat cy = screenHeight/2.0f;
const CGFloat z = -1.0f;
const CGFloat dim = 50.0f;
vxData[0] = cx-dim;
vxData[1] = cy-dim;
vxData[2] = z;
vxData[3] = cx-dim;
vxData[4] = cy+dim;
vxData[5] = z;
vxData[6] = cx+dim;
vxData[7] = cy+dim;
vxData[8] = z;
vxData[9] = cx+dim;
vxData[10] = cy-dim;
vxData[11] = z;
Since I am using OGLES 2.0 I pass the MVP as a uniform to my vertex shader, then simply apply the transformation to the current vertex position:
uniform mat4 mvp;
attribute vec3 vpos;
void main()
{
gl_Position = mvp * vec4(vpos, 1.0);
}
For now I have simplified my MVP to just be the P matrix. There are two projection matrices listed in the code shown below. The first is the standard perspective projection matrix, and the second is an explicit-value projection matrix I found online.
CGRect screenBounds = [[UIScreen mainScreen] bounds];
const CGFloat screenWidth = screenBounds.size.width;
const CGFloat screenHeight = screenBounds.size.height;
const GLfloat n = 0.01f;
const GLfloat f = 100.0f;
const GLfloat fov = 60.0f * 2.0f * M_PI / 360.0f;
const GLfloat a = screenWidth/screenHeight;
const GLfloat d = 1.0f / tanf(fov/2.0f);
// Standard perspective projection.
GLKMatrix4 projectionMx = GLKMatrix4Make(d/a, 0.0f, 0.0f, 0.0f,
0.0f, d, 0.0f, 0.0f,
0.0f, 0.0f, (n+f)/(n-f), -1.0f,
0.0f, 0.0f, (2*n*f)/(n-f), 0.0f);
// The one I found online.
GLKMatrix4 projectionMx = GLKMatrix4Make(2.0f/screenWidth,0.0f,0.0f,0.0f,
0.0f,2.0f/-screenHeight,0.0f,0.0f,
0.0f,0.0f,1.0f,0.0f,
-1.0f,1.0f,0.0f,1.0f);
When using the explicit value matrix, the square renders exactly as desired in the centre of the screen with correct dimension. When using the perspective projection matrix, nothing is displayed on-screen. I've done printouts of the position values generated for screen centre (screenWidth/2, screenHeight/2, 0) by the perspective projection matrix and they're enormous. The explicit value matrix correctly produces zero.
I think the explicit value matrix is an orthographic projection matrix - is that right? My frustration is that I can't work out why my perspective projection matrix fails to work.
I'd be tremendously grateful if someone could help me with this problem. Many thanks.
UPDATE For Christian Rau:
#define Zn 0.0f
#define Zf 100.0f
#define PRIMITIVE_Z 1.0f
//...
CGRect screenBounds = [[UIScreen mainScreen] bounds];
const CGFloat screenWidth = screenBounds.size.width;
const CGFloat screenHeight = screenBounds.size.height;
//...
glUseProgram(program);
//...
glViewport(0.0f, 0.0f, screenBounds.size.width, screenBounds.size.height);
//...
const CGFloat cx = screenWidth/2.0f;
const CGFloat cy = screenHeight/2.0f;
const CGFloat z = PRIMITIVE_Z;
const CGFloat dim = 50.0f;
vxData[0] = cx-dim;
vxData[1] = cy-dim;
vxData[2] = z;
vxData[3] = cx-dim;
vxData[4] = cy+dim;
vxData[5] = z;
vxData[6] = cx+dim;
vxData[7] = cy+dim;
vxData[8] = z;
vxData[9] = cx+dim;
vxData[10] = cy-dim;
vxData[11] = z;
//...
const GLfloat n = Zn;
const GLfloat f = Zf;
const GLfloat fov = 60.0f * 2.0f * M_PI / 360.0f;
const GLfloat a = screenWidth/screenHeight;
const GLfloat d = 1.0f / tanf(fov/2.0f);
GLKMatrix4 projectionMx = GLKMatrix4Make(d/a, 0.0f, 0.0f, 0.0f,
0.0f, d, 0.0f, 0.0f,
0.0f, 0.0f, (n+f)/(n-f), -1.0f,
0.0f, 0.0f, (2*n*f)/(n-f), 0.0f);
//...
// ** Here is the matrix you recommended, Christian:
GLKMatrix4 ts = GLKMatrix4Make(2.0f/screenWidth, 0.0f, 0.0f, -1.0f,
0.0f, 2.0f/screenHeight, 0.0f, -1.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
GLKMatrix4 mvp = GLKMatrix4Multiply(projectionMx, ts);
UPDATE 2
The new MVP code:
GLKMatrix4 ts = GLKMatrix4Make(2.0f/screenWidth, 0.0f, 0.0f, -1.0f,
0.0f, 2.0f/-screenHeight, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
// Using Apple perspective, view matrix generators
// (I can solve bugs in my own implementation later..!)
GLKMatrix4 _p = GLKMatrix4MakePerspective(60.0f * 2.0f * M_PI / 360.0f,
screenWidth / screenHeight,
Zn, Zf);
GLKMatrix4 _mv = GLKMatrix4MakeLookAt(0.0f, 0.0f, 1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 1.0f, 0.0f);
GLKMatrix4 _mvp = GLKMatrix4Multiply(_p, _mv);
GLKMatrix4 mvp = GLKMatrix4Multiply(_mvp, ts);
Still nothing visible at the screen centre, and the transformed x,y coordinates of the screen centre are not zero.
UPDATE 3
Using the transpose of ts instead in the above code works! But the square no longer appears square; it appears to now have aspect ratio screenHeight/screenWidth i.e. it has a longer dimension parallel to the (short) screen width, and a shorter dimension parallel to the (long) screen height.
I'd very much like to know (a) why the transpose is required and whether it is a valid fix, (b) how to correctly rectify the non-square dimension, and (c) how this additional matrix transpose(ts) that we use fits into the transformation chain of Viewport * Projection * View * Model * Point .
For (c): I understand what the matrix does, i.e. the explanation by Christian Rau as to how we transform to range [-1, 1]. But is it correct to include this additional work as a separate transformation matrix, or should some part of our MVP chain be doing this work instead?
Sincere thanks go to Christian Rau for his valuable contribution thus far.
UPDATE 4
My question about "how ts fits in" is silly isn't it - the whole point is the matrix is only needed because I'm choosing to use screen coordinates for my vertices; if I were to use coordinates in world space from the start then this work wouldn't be needed!
Thanks Christian for all your help, it's been invaluable :) Problem solved.
The reason for this is, that your first projection matrix doesn't account for the scaling and translation part of the transformation, whereas the second matrix does it.
So, since your modelview matrix is identity, the first projection matrix assumes the models' coordinates to ly somewhere in [-1,1], whereas the second matrix already contains the scaling and translation part (look at the screenWidth/Height values in there) and therefore assumes the coordinates to ly in [0,screenWidth] x [0,screenHeight].
So you have to right-multiply your projection matrix by a matrix that first scales [0,screenWidth] down to [0,2] and [0,screenHeight] down to [0,2] and then translates [0,2] into [-1,1] (using w for screenWidth and h for screenHeight):
[ 2/w 0 0 -1 ]
[ 0 2/h 0 -1 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
which will result in the matrix
[ 2*d/h 0 0 -d/a ]
[ 0 2*d/h 0 -d ]
[ 0 0 (n+f)/(n-f) 2*n*f/(n-f) ]
[ 0 0 -1 0 ]
So you see that your second matrix corresponds to a fov of 90 degrees, an aspect ratio of 1:1 and a near-far range of [-1,1]. Additionally it also inverts the y-axis, so that the origin is in the upper-left, which results in the second row being negated:
[ 0 -2*d/h 0 d ]
But as an end comment, I suggest you to not configure the projection matrix to account for all this. Instead your projection matrix should look like the first one and you should let the modelview matrix manage any translation or scaling of your world. It is not by accident, that the transformation pipeline was seperated into modelview and projection matrix and you should keep this separation also when using shaders. You can of course still multiply both matrices together on the CPU and upload a single MVP matrix to the shader.
And in general you don't really use a screen-based coordinate system when working with a 3-dimensional world. You would only want to do this if you are drawing 2d graphics (like GUI elements or HUDs) and in this case you would use a more simple orthographic projection matrix, anyway, that is nothing more than the above mentioned scale-translate matrix without all the perspective complexity.
EDIT: To your 3rd update:
(a) The transpose is required because I guess your GLKMatrix4Make function accepts its parameters in column-major format and you put the matrix in row-wise.
(b) I made a little mistake. You should change the screenWidth in the ts matrix into screenHeight (or maybe the other way around, not sure). We actually need a uniform scale, because the aspect ratio is already taken care of by the projection matrix.
(c) It is not easy to classify this matrix into the usual MVP pipeline. This is because it is not really common. Let's look at the two common cases of rendering:
3D: When you have a 3-dimensional world it is not really common to define it's coordinates in screen-based units, because there is not et a mapping from 3d-scene to 2d-screen and using a coordinate system where units equal pixels just doesn't make sense. In this setup you most likely would classify it as part of the modelview matrix for transforming the world into another unit system. But in this case you would need real 3d transformations and not just such a half-baked 2d solution.
2D: When rendering a 2d-scene (like a GUI or a HUD or just some text), you sometimes really want a screen-based coordinate system. But in this case you most likely would use an orthographic projection (without any perspective). Such an orthographic matrix is actually nothing more than this ts matrix (with some additional scale-translate for z, based on the near-far range). So in this case the matrix belongs to, or actually is, the projection matrix. Just look at how the good old glOrtho function constructs its matrix and you'll see its nothing more than ts.