1 /** 2 dlsl.projection 3 4 5 Authors: Peter Particle ( based on gl3n by David Herberth ) 6 License: MIT 7 8 Note: All methods marked with pure are weakly pure since, they all access an instance member. 9 All static methods are strongly pure. 10 */ 11 12 module dlsl.projection; 13 14 import dlsl.matrix; 15 import dlsl.vector; 16 17 import std.math : sqrt, sin, cos, tan, PI; 18 import std.traits : isFloatingPoint; 19 20 21 /// http://www.geeks3d.com/20090729/howto-perspective-projection-matrix-in-opengl/ 22 23 nothrow @nogc: 24 25 private float[ 6 ] cperspective( float fovy, float aspect, float near, float far ) { 26 float top = near * tan( fovy * ( PI / 360.0 )); 27 float bottom = - top; 28 float right = top * aspect; 29 float left = - right; 30 31 return [ left, right, bottom, top, near, far ]; 32 } 33 34 /// Construct a symmetric perspective matrix ( 4x4 and floating - point matrices only ). 35 mat4 glPerspective( float fovy, float aspect, float near, float far ) { 36 float[ 6 ] cdata = cperspective( fovy, aspect, near, far ); 37 return glPerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] ); 38 } 39 40 /// Construct an optionally non-symmetric perspective matrix 41 mat4 glPerspective( float left, float right, float bottom, float top, float near, float far ) 42 in { 43 assert( right - left != 0 ); 44 assert( top - bottom != 0 ); 45 assert( far - near != 0 ); 46 } 47 body { 48 mat4 result; 49 result.clear( 0 ); 50 result.data[ 0 ][ 0 ] = ( 2 * near ) / ( right - left ); 51 result.data[ 2 ][ 0 ] = ( right + left ) / ( right - left ); 52 result.data[ 1 ][ 1 ] = ( 2 * near ) / ( top - bottom ); 53 result.data[ 2 ][ 1 ] = ( top + bottom ) / ( top - bottom ); 54 result.data[ 2 ][ 2 ] = - ( far + near ) / ( far - near ); 55 result.data[ 3 ][ 2 ] = - ( 2 * far * near ) / ( far - near ); 56 result.data[ 2 ][ 3 ] = - 1; 57 58 return result; 59 } 60 61 /// Construct an inverse, symmetric perspective matrix ( 4x4 and floating - point matrices only ). 62 mat4 glInversePerspective( float fovy, float aspect, float near, float far ) { 63 float[ 6 ] cdata = cperspective( fovy, aspect, near, far ); 64 return glInversePerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] ); 65 } 66 67 /// Construct an inverse, optionally non-symmetric perspective matrix 68 mat4 glInversePerspective( float left, float right, float bottom, float top, float near, float far ) 69 in { 70 assert( right - left != 0 ); 71 assert( top - bottom != 0 ); 72 assert( far - near != 0 ); 73 } 74 body { 75 mat4 result; 76 result.clear( 0 ); 77 78 result.data[ 0 ][ 0 ] = ( right - left ) / ( 2 * near ); 79 result.data[ 3 ][ 0 ] = ( right + left ) / ( 2 * near ); 80 result.data[ 1 ][ 1 ] = ( top - bottom ) / ( 2 * near ); 81 result.data[ 3 ][ 1 ] = ( top + bottom ) / ( 2 * near ); 82 result.data[ 3 ][ 2 ] = - 1; 83 result.data[ 2 ][ 3 ] = - ( far - near ) / ( 2 * far * near ); 84 result.data[ 3 ][ 3 ] = ( far + near ) / ( 2 * far * near ); 85 86 return result; 87 } 88 89 // ( 2 ) and ( 3 ) say this one is correct 90 /// Construct an orthographic matrix ( 4x4 and floating - point matrices only ). 91 mat4 glOrthographic( float left, float right, float bottom, float top, float near, float far ) 92 in { 93 assert( right - left != 0 ); 94 assert( top - bottom != 0 ); 95 assert( far - near != 0 ); 96 } 97 body { 98 mat4 result = void; 99 result.clear( 0 ); 100 101 result.data[ 0 ][ 0 ] = 2 / ( right - left ); 102 result.data[ 3 ][ 0 ] = - ( right + left ) / ( right - left ); 103 result.data[ 1 ][ 1 ] = 2 / ( top - bottom ); 104 result.data[ 3 ][ 1 ] = - ( top + bottom ) / ( top - bottom ); 105 result.data[ 2 ][ 2 ] = - 2 / ( far - near ); 106 result.data[ 3 ][ 2 ] = - ( far + near ) / ( far - near ); 107 result.data[ 3 ][ 3 ] = 1; 108 109 return result; 110 } 111 112 // ( 1 ) and ( 2 ) say this one is correct 113 /// Returns an inverse orographic matrix ( 4x4 and floating - point matrices only ). 114 mat4 glInverseOrthographic( float left, float right, float bottom, float top, float near, float far ) { 115 mat4 result; 116 result.clear( 0 ); 117 118 result.data[ 0 ][ 0 ] = ( right - left ) / 2; 119 result.data[ 3 ][ 0 ] = ( right + left ) / 2; 120 result.data[ 1 ][ 1 ] = ( top - bottom ) / 2; 121 result.data[ 3 ][ 1 ] = ( top + bottom ) / 2; 122 result.data[ 2 ][ 2 ] = ( far - near ) / - 2; 123 result.data[ 3 ][ 2 ] = ( far + near ) / 2; 124 result.data[ 3 ][ 3 ] = 1; 125 126 return result; 127 } 128 129 130 unittest { 131 float aspect = 6.0 / 9.0; 132 float[ 6 ] cp = cperspective( 60f, aspect, 1f, 100f ); 133 assert( cp[ 4 ] == 1.0f ); 134 assert( cp[ 5 ] == 100.0f ); 135 assert( cp[ 0 ] == - cp[ 1 ] ); 136 assert(( cp[ 0 ] < - 0.38489f ) && ( cp[ 0 ] > - 0.38491f )); 137 assert( cp[ 2 ] == - cp[ 3 ] ); 138 assert(( cp[ 2 ] < - 0.577349f ) && ( cp[ 2 ] > - 0.577351f )); 139 140 assert( mat4.perspective( 60.0, aspect, 1.0, 100.0 ) == mat4.perspective( cp[ 0 ], cp[ 1 ], cp[ 2 ], cp[ 3 ], cp[ 4 ], cp[ 5 ] )); 141 float[ 4 ][ 4 ] m4p = mat4.perspective( 60.0, aspect, 1.0, 100.0 ).data; 142 assert(( m4p[ 0 ][ 0 ] < 2.598077f ) && ( m4p[ 0 ][ 0 ] > 2.598075f )); 143 assert( m4p[ 0 ][ 2 ] == 0.0f ); 144 assert(( m4p[ 1 ][ 1 ] < 1.732052 ) && ( m4p[ 1 ][ 1 ] > 1.732050 )); 145 assert( m4p[ 1 ][ 2 ] == 0.0f ); 146 assert(( m4p[ 2 ][ 2 ] < - 1.020201 ) && ( m4p[ 2 ][ 2 ] > - 1.020203 )); 147 assert(( m4p[ 3 ][ 2 ] < - 2.020201 ) && ( m4p[ 3 ][ 2 ] > - 2.020203 )); 148 assert(( m4p[ 2 ][ 3 ] < - 0.90000f ) && ( m4p[ 2 ][ 3 ] > - 1.10000f )); 149 150 float[ 4 ][ 4 ] m4pi = mat4.glInversePerspective( 60.0, aspect, 1.0, 100.0 ).data; 151 assert(( m4pi[ 0 ][ 0 ] < 0.384901 ) && ( m4pi[ 0 ][ 0 ] > 0.384899 )); 152 assert( m4pi[ 0 ][ 3 ] == 0.0f ); 153 assert(( m4pi[ 1 ][ 1 ] < 0.577351 ) && ( m4pi[ 1 ][ 1 ] > 0.577349 )); 154 assert( m4pi[ 1 ][ 3 ] == 0.0f ); 155 assert( m4pi[ 3 ][ 2 ] == - 1.0f ); 156 assert(( m4pi[ 2 ][ 3 ] < - 0.494999 ) && ( m4pi[ 2 ][ 3 ] > - 0.495001 )); 157 assert(( m4pi[ 3 ][ 3 ] < 0.505001 ) && ( m4pi[ 3 ][ 3 ] > 0.504999 )); 158 159 // maybe the next tests should be improved 160 float[ 4 ][ 4 ] m4o = mat4.orthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data; 161 assert( m4o == [ 162 [ 1.0f, 0.0f, 0.0f, 0.0f ], 163 [ 0.0f, 1.0f, 0.0f, 0.0f ], 164 [ 0.0f, 0.0f, - 1.0f, 0.0f ], 165 [ 0.0f, 0.0f, 0.0f, 1.0f ] ] ); 166 167 float[ 4 ][ 4 ] m4oi = mat4.inverseOrthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data; 168 assert( m4oi == [ 169 [ 1.0f, 0.0f, 0.0f, 0.0f ], 170 [ 0.0f, 1.0f, 0.0f, 0.0f ], 171 [ 0.0f, 0.0f, - 1.0f, 0.0f ], 172 [ 0.0f, 0.0f, 0.0f, 1.0f ] ] ); 173 } 174 175 176 //////////////////////// 177 // Vulkan Projections // 178 //////////////////////// 179 180 mat4 vkPerspective( float fovy, float aspect, float near, float far ) { 181 mat4 result = void; 182 result.clear( 0 ); 183 184 const float t = 1.0f / tan( 0.5f * fovy * deg2rad ); 185 const float n_f = near - far; 186 /* 187 result[0][0] = t / aspect; 188 result[1][1] = t; 189 result[2][2] = - ( near + far ) / n_f; 190 result[2][3] = 1; 191 result[3][2] = ( 2 * near * far ) / n_f; 192 193 // premultiplying a glProjection with this clip matrix results in expected vkProjection 194 // source: https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ 195 auto clip = mat4( vec4( 1, 0, 0, 0 ), vec4( 0, -1, 0, 0 ), vec4( 0, 0, 0.5, 0 ), vec4( 0, 0, 0.5, 1 )); 196 return clip * result; 197 */ 198 // we can avoid matrix multiply of clip and projection with following variant for the matrix entries 199 result[0][0] = t / aspect; 200 result[1][1] = - t; 201 result[2][2] = 0.5 - 0.5 * ( near + far ) / n_f; 202 result[2][3] = 1; 203 result[3][2] = near * far / n_f; 204 205 return result; 206 }