效果如图。
源代码参见: https://github.com/wysaid/SOFT_3D
预编译可执行文件: https://raw.githubusercontent.com/wysaid/SOFT_3D/master/demo-SceneRoaming.exe
跟前面的茶壶repo放到一起, 为提示demo更新, 重新post本章。
本demo对于新手来说, 要完全看懂或理解有较大难度,所以如果看不懂也没关系。 有任何问题可以在下方留言。如果毫无头绪的话建议先看看别的。
demo模拟了一般的FPS游戏的操作, 使用w,a,s,d或者方向键上下左右都可以控制自己移动, 空格键可以跳起, 使用鼠标按住不放拖动可以旋转视角, 滑动滚轮可以将镜头切远或切近。
demo的代码非常简短, 为了便于观看写的比较稀疏, 看起来比较长, 抛去大量空行实际上只有两百余行。
希望对你有所帮助。
作者: wysaid
部分代码参考以及解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
/* * cgeScene.h * * Created on: 2014-10-27 * Author: wysaid * Mail: admin@wysaid.org */ #ifndef _CGE_SCENE_H_ #define _CGE_SCENE_H_ #include "cgeMat.h" #include <cassert> namespace CGE { #ifndef CGE_MIN template<typename Type> inline Type CGE_MIN(Type a, Type b) { return a < b ? a : b; } #endif #ifndef CGE_MAX template<typename Type> inline Type CGE_MAX(Type a, Type b) { return a > b ? a : b; } #endif #ifndef CGE_MID template<typename Type> inline Type CGE_MID(Type n, Type vMin, Type vMax) { if (n < vMin) n = vMin; else if (n > vMax) n = vMax; return n; } #endif #ifndef CGE_MIX template<typename OpType, typename MixType> inline auto CGE_MIX(OpType a, OpType b, MixType value) -> decltype(a - a * value + b * value) { return a - a * value + b * value; } #endif //辅助类, 提供场景漫游公共方法以及参数 class SceneInterfaceHelper { protected: SceneInterfaceHelper() : m_eye(0.0f, 0.0f), m_lookDir(0.0f, 1000.0f), m_eyeZ(0.0f), m_lookZ(0.0f), m_dirLen(1000.0f), m_fovyRad(M_PI / 4.0f), m_zNear(1.0f), m_zFar(1000.0f), m_upDirRot(0.0f) {} public: inline Mat4& modelViewMatrix() { return m_modelViewMatrix; } inline Mat4& projectionMatrix() { return m_projectionMatrix; } inline Vec2f& eyeXY() { return m_eye; } inline float& eyeZ() { return m_eyeZ; } inline const Vec2f& lookDirXY() const { return m_lookDir; } inline float& lookDirZ() { return m_lookZ; } inline float& fovyRad() { return m_fovyRad; } inline float& zNear() { return m_zNear; } inline float& zFar() { return m_zFar; } static inline float maxLookUp() { return M_PI / 2.4f; } inline void updatePerspective() { m_projectionMatrix = Mat4::makePerspective(m_fovyRad, m_aspect, m_zNear, m_zFar); } inline void setEye(const Vec3f& v) { m_eye[0] = v[0]; m_eye[1] = v[1]; m_eyeZ = v[2]; } inline void lookIn(float rad) { m_fovyRad = CGE_MID<float>(m_fovyRad + rad, M_PI / 100.0f, M_PI / 2.0f); updatePerspective(); } inline void lookInTo(float rad) { m_fovyRad = CGE_MID<float>(rad, M_PI / 100.0f, M_PI / 2.0f); updatePerspective(); } inline void setViewport(float w, float h) { m_aspect = w / h; } inline void setAspect(float aspect) { m_aspect = aspect; } inline void rotateByLookDir(float r) { m_upDirRot = r; } protected: Mat4 m_modelViewMatrix, m_projectionMatrix; Vec2f m_eye, m_lookDir; float m_eyeZ, m_lookZ; float m_dirLen; //视景体的视野角度(弧度), 透视投影近裁剪面, 透视投影远裁剪面 float m_fovyRad, m_zNear, m_zFar; float m_aspect; float m_upDirRot; }; // SceneInterface 提供场景漫游辅助接口(非必须) // // 特别注意, SceneInterface 默认使用 xOy 平面作为漫游地面, z正方向为向上方向 // SceneInterface 仅提供相关矩阵和向量, 不提供任何渲染接口。 class SceneInterface : public SceneInterfaceHelper { public: SceneInterface(float w = 1.0f, float h = 1.0f) : SceneInterfaceHelper() { m_aspect = w / h; updatePerspective(); updateView(); } inline void updateView() { Vec2f center(m_eye + m_lookDir); float len = m_lookDir.length(); float tmp = -m_lookZ / len; Vec2f dirBack(m_lookDir * tmp); Vec3f upDir(dirBack[0], dirBack[1], len); if(m_upDirRot != 0.0f) //不需要精确浮点数与0的比较, 这里只是判断是否被置为0 upDir = Mat3::makeRotation(m_upDirRot, m_lookDir[0], m_lookDir[1], m_lookZ) * upDir; m_modelViewMatrix = Mat4::makeLookAt(m_eye[0], m_eye[1], m_eyeZ, center[0], center[1], m_eyeZ + m_lookZ, upDir[0], upDir[1], upDir[2]); } inline void setEyeXY(const Vec2f& v) { m_eye = v; } inline void setEyeZ(float z) { m_eyeZ = z; } //注意, 这里设置的是观察方向, 不可为0 inline void setLookdirXY(const Vec2f& v) { assert(v[0] || v[1]); m_lookDir = v * (m_dirLen / v.length()); } inline void setLookdirZ(float z) { const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID<float>(z, -lookUpMax, lookUpMax); } //同上, 场景漫游中, 不允许朝"正"上方观看(仰头无法形成平角 inline void setLookdir(const Vec3f& v) { assert(v[0] || v[1]); m_lookDir[0] = v[0]; m_lookDir[1] = v[1]; m_lookDir.normalize(); m_lookDir *= m_dirLen; const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID<float>(v[2], -lookUpMax, lookUpMax); } //转动视角, rad(弧度) > 0 时 为向右方向 inline void turn(float rad) { m_lookDir = Mat2::makeRotation(rad) * m_lookDir; } //转动视角, rad(弧度) > 0 时 为右边。 inline void turnTo(float rad) { m_lookDir = Vec2f(sinf(rad), cosf(rad)) * m_dirLen; } //向上观察, motion计算关系 //向上观察弧度计算公式为: arctan(tan("当前向上弧度") + motion) - "当前向上弧度" inline void lookUp(float motion) { const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID(m_lookZ + motion * m_dirLen, -lookUpMax, lookUpMax); } //向上观察到(弧度), 直接仰视到所看弧度 //范围[-PI/2.4, PI/2.4], 约正负75度角 inline void lookUpTo(float rad) { m_lookZ = tanf(CGE_MID<float>(rad, -M_PI / 2.4f, M_PI / 2.4f)) * m_dirLen; } inline void goForward(float motion) { m_eye += m_lookDir * (motion / m_dirLen); } inline void goBack(float motion) { m_eye -= m_lookDir * (motion / m_dirLen); } inline void goLeft(float motion) { float m = motion / m_dirLen; m_eye[0] -= m_lookDir[1] * m; m_eye[1] += m_lookDir[0] * m; } inline void goRight(float motion) { float m = motion / m_dirLen; m_eye[0] += m_lookDir[1] * m; m_eye[1] -= m_lookDir[0] * m; } }; // 使用笛卡尔坐标系来漫游, 使用 xOz 平面作为漫游地面, y正方向为向上方向, 其他部分与SceneInterface相同 class SceneInterfaceDescartes : public SceneInterfaceHelper { public: SceneInterfaceDescartes(float w = 1.0f, float h = 1.0f) : SceneInterfaceHelper() { m_aspect = w / h; updatePerspective(); updateView(); } inline void updateView() { Vec2f center(m_lookDir + Vec2f(m_eye[0], m_eyeZ)); float len = m_lookDir.length(); float tmp = -m_lookZ / len; Vec2f dirBack(m_lookDir * tmp); Vec3f upDir(dirBack[0], len, dirBack[1]); if(m_upDirRot != 0.0f) //不需要精确浮点数与0的比较, 这里只是判断是否被置为0 upDir = Mat3::makeRotation(m_upDirRot, m_lookDir[0], m_lookZ, m_lookDir[1]) * upDir; m_modelViewMatrix = Mat4::makeLookAt(m_eye[0], m_eye[1], m_eyeZ, center[0], m_eye[1] + m_lookZ, center[1], upDir[0], upDir[1], upDir[2]); } inline void setEyeXZ(const Vec2f& v) { m_eye[0] = v[0]; m_eyeZ = v[1]; } inline void setEyeY(float y) { m_eye[1] = y; } //注意, 这里设置的是观察方向, 不可为0 inline void setLookdirXZ(const Vec2f& v) { assert(v[0] || v[1]); m_lookDir = v * (m_dirLen / v.length()); } inline void setLookdirY(float z) { const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID<float>(z, -lookUpMax, lookUpMax); } //同上, 场景漫游中, 不允许朝"正"上方观看(仰头无法形成平角 inline void setLookdir(const Vec3f& v) { assert(v[0] || v[2]); m_lookDir[0] = v[0]; m_lookDir[1] = v[2]; m_lookDir.normalize(); m_lookDir *= m_dirLen; const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID<float>(v[1], -lookUpMax, lookUpMax); } //转动视角, rad(弧度) > 0 时 为向右方向 inline void turn(float rad) { m_lookDir = Mat2::makeRotation(rad) * m_lookDir; } //转动视角, rad(弧度) > 0 时 为右边。 inline void turnTo(float rad) { m_lookDir = Vec2f(sinf(rad), cosf(rad)) * m_dirLen; } //向上观察, motion计算关系 //向上观察弧度计算公式为: arctan(tan("当前向上弧度") + motion) - "当前向上弧度" inline void lookUp(float motion) { const float lookUpMax = m_dirLen * 3.732f; // tan(PI / 75); m_lookZ = CGE_MID(m_lookZ + motion * m_dirLen, -lookUpMax, lookUpMax); } //向上观察到(弧度), 直接仰视到所看弧度 //范围[-PI/2.4, PI/2.4], 约正负75度角 inline void lookUpTo(float rad) { m_lookZ = tanf(CGE_MID<float>(rad, -M_PI / 2.4f, M_PI / 2.4f)) * m_dirLen; } inline void goForward(float motion) { m_eye += m_lookDir * (motion / m_dirLen); } inline void goBack(float motion) { m_eye -= m_lookDir * (motion / m_dirLen); } inline void goLeft(float motion) { float m = motion / m_dirLen; m_eye[0] -= m_lookDir[1] * m; m_eye[1] += m_lookDir[0] * m; } inline void goRight(float motion) { float m = motion / m_dirLen; m_eye[0] += m_lookDir[1] * m; m_eye[1] -= m_lookDir[0] * m; } }; } #endif |
近期评论