You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
cocos_lib/cocos/scene/Camera.cpp

486 lines
17 KiB

/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "scene/Camera.h"
#include "core/Root.h"
#include "core/platform/Debug.h"
#include "core/scene-graph/Node.h"
#include "math/MathUtil.h"
#include "renderer/gfx-base/GFXDevice.h"
#include "renderer/pipeline/Define.h"
#if CC_USE_GEOMETRY_RENDERER
#include "renderer/pipeline/GeometryRenderer.h"
#endif
#include "application/ApplicationManager.h"
#include "platform/interfaces/modules/IXRInterface.h"
namespace cc {
namespace scene {
namespace {
ccstd::array<Mat4, 4> correctionMatrices;
void assignMat4(Mat4 &mat4, float m0, float m1, float m2, float m3, float m4, float m5) {
mat4.m[0] = m0;
mat4.m[1] = m1;
mat4.m[2] = m2;
mat4.m[3] = m3;
mat4.m[4] = m4;
mat4.m[5] = m5;
}
constexpr ccstd::array<ccstd::array<float, 4>, 4> PRE_TRANSFORMS = {{
{{1, 0, 0, 1}}, // SurfaceTransform.IDENTITY
{{0, 1, -1, 0}}, // SurfaceTransform.ROTATE_90
{{-1, 0, 0, -1}}, // SurfaceTransform.ROTATE_180
{{0, -1, 1, 0}} // SurfaceTransform.ROTATE_270
}};
} // namespace
const ccstd::vector<float> Camera::FSTOPS{1.8F, 2.0F, 2.2F, 2.5F, 2.8F, 3.2F, 3.5F, 4.0F, 4.5F, 5.0F, 5.6F, 6.3F, 7.1F, 8.0F, 9.0F, 10.0F, 11.0F, 13.0F, 14.0F, 16.0F, 18.0F, 20.0F, 22.0F};
const ccstd::vector<float> Camera::SHUTTERS{1.0F, 1.0F / 2.0F, 1.0F / 4.0F, 1.0F / 8.0F, 1.0F / 15.0F, 1.0F / 30.0F, 1.0F / 60.0F, 1.0F / 125.0F,
1.0F / 250.0F, 1.0F / 500.0F, 1.0F / 1000.0F, 1.0F / 2000.0F, 1.0F / 4000.0F};
const ccstd::vector<float> Camera::ISOS{100.0F, 200.0F, 400.0F, 800.0F};
Camera::Camera(gfx::Device *device)
: _device(device) {
_apertureValue = Camera::FSTOPS.at(static_cast<int>(_aperture));
_shutterValue = Camera::SHUTTERS.at(static_cast<int>(_shutter));
_isoValue = Camera::ISOS[static_cast<int>(_iso)];
_aspect = _screenScale = 1.F;
_frustum = ccnew geometry::Frustum();
_frustum->addRef();
_frustum->setAccurate(true);
if (correctionMatrices.empty()) {
float ySign = _device->getCapabilities().clipSpaceSignY;
assignMat4(correctionMatrices[static_cast<int>(gfx::SurfaceTransform::IDENTITY)], 1.F, 0, 0, 0, 0, ySign);
assignMat4(correctionMatrices[static_cast<int>(gfx::SurfaceTransform::ROTATE_90)], 0, 1.F, 0, 0, -ySign, 0);
assignMat4(correctionMatrices[static_cast<int>(gfx::SurfaceTransform::ROTATE_180)], -1, 0, 0, 0, 0, -ySign);
assignMat4(correctionMatrices[static_cast<int>(gfx::SurfaceTransform::ROTATE_270)], 0, -1, 0, 0, ySign, 0);
}
_xr = CC_GET_XR_INTERFACE();
}
Camera::~Camera() = default;
bool Camera::initialize(const ICameraInfo &info) {
_usage = info.usage;
_trackingType = info.trackingType;
_cameraType = info.cameraType;
_node = info.node;
_width = 1.F;
_height = 1.F;
_clearFlag = gfx::ClearFlagBit::NONE;
_clearDepth = 1.0F;
_visibility = pipeline::CAMERA_DEFAULT_MASK;
_name = info.name;
_proj = info.projection;
_priority = info.priority;
_aspect = _screenScale = 1.F;
updateExposure();
changeTargetWindow(info.window);
return true;
}
void Camera::destroy() {
detachFromScene();
if (_window) {
_window->detachCamera(this);
_window = nullptr;
}
_name.clear();
#if CC_USE_GEOMETRY_RENDERER
CC_SAFE_DESTROY_NULL(_geometryRenderer);
#endif
CC_SAFE_RELEASE_NULL(_frustum);
}
void Camera::attachToScene(RenderScene *scene) {
_enabled = true;
_scene = scene;
}
void Camera::detachFromScene() {
_enabled = false;
_scene = nullptr;
}
void Camera::resize(uint32_t width, uint32_t height) {
if (!_window) {
return;
}
_width = width;
_height = height;
updateAspect();
}
void Camera::setFixedSize(uint32_t width, uint32_t height) {
_width = width;
_height = height;
updateAspect(false);
_isWindowSize = false;
}
// Editor specific gizmo camera logic
void Camera::syncCameraEditor(const Camera *camera) {
#if CC_EDITOR
_position = camera->_position;
_forward = camera->_forward;
_matView = camera->_matView;
_matProj = camera->_matProj;
_matProjInv = camera->_matProjInv;
_matViewProj = camera->_matViewProj;
#endif
}
void Camera::update(bool forceUpdate /*false*/) {
if (!_node) {
return;
}
bool viewProjDirty = false;
// view matrix
if (_node->getChangedFlags() || forceUpdate) {
_matView = _node->getWorldMatrix().getInversed();
_forward.set(-_matView.m[2], -_matView.m[6], -_matView.m[10]);
Mat4 scaleMat{};
scaleMat.scale(_node->getWorldScale());
// remove scale
Mat4::multiply(scaleMat, _matView, &_matView);
_position.set(_node->getWorldPosition());
viewProjDirty = true;
}
// projection matrix
auto *swapchain = _window->getSwapchain();
const auto &orientation = swapchain ? swapchain->getSurfaceTransform() : gfx::SurfaceTransform::IDENTITY;
if (swapchain) {
_systemWindowId = swapchain->getWindowId();
}
if (_isProjDirty || _curTransform != orientation) {
_curTransform = orientation;
const float projectionSignY = _device->getCapabilities().clipSpaceSignY;
// Only for rendertexture processing
if (_proj == CameraProjection::PERSPECTIVE) {
Mat4::createPerspective(_fov, _aspect, _nearClip, _farClip,
_fovAxis == CameraFOVAxis::VERTICAL, _device->getCapabilities().clipSpaceMinZ, projectionSignY, static_cast<int>(orientation), &_matProj);
} else {
const float x = _orthoHeight * _aspect;
const float y = _orthoHeight;
Mat4::createOrthographicOffCenter(-x, x, -y, y, _nearClip, _farClip,
_device->getCapabilities().clipSpaceMinZ, projectionSignY,
static_cast<int>(orientation), &_matProj);
}
_matProjInv = _matProj.getInversed();
viewProjDirty = true;
_isProjDirty = false;
}
if (_xr) {
xr::XREye wndXREye = _xr->getXREyeByRenderWindow(_window);
if (wndXREye != xr::XREye::NONE && _xr->getXRConfig(xr::XRConfigKey::SESSION_RUNNING).getBool()) {
// xr flow
if (_proj == CameraProjection::PERSPECTIVE) {
const auto &projFloat = _xr->getXRViewProjectionData(static_cast<uint32_t>(wndXREye), _nearClip, _farClip);
std::memcpy(_matProj.m, projFloat.data(), sizeof(float) * 16);
} else {
const ccstd::array<float, 4> &preTransform = PRE_TRANSFORMS[static_cast<int>(orientation)];
_xr->adaptOrthographicMatrix(this, preTransform, _matProj, _matView);
}
_matProjInv = _matProj.getInversed();
viewProjDirty = true;
}
}
// view-projection
if (viewProjDirty) {
Mat4::multiply(_matProj, _matView, &_matViewProj);
_matViewProjInv = _matViewProj.getInversed();
_frustum->update(_matViewProj, _matViewProjInv);
}
}
void Camera::changeTargetWindow(RenderWindow *window) {
if (_window) {
_window->detachCamera(this);
}
RenderWindow *win = window ? window : Root::getInstance()->getMainWindow();
if (win) {
win->attachCamera(this);
_window = win;
// window size is pre-rotated
auto *swapchain = win->getSwapchain();
const auto orientation = swapchain ? swapchain->getSurfaceTransform() : gfx::SurfaceTransform::IDENTITY;
if (static_cast<int32_t>(orientation) % 2) {
resize(win->getHeight(), win->getWidth());
} else {
resize(win->getWidth(), win->getHeight());
}
if (swapchain) {
_systemWindowId = swapchain->getWindowId();
}
}
}
void Camera::initGeometryRenderer() {
#if CC_USE_GEOMETRY_RENDERER
if (!_geometryRenderer) {
_geometryRenderer = ccnew pipeline::GeometryRenderer();
_geometryRenderer->activate(_device);
}
#endif
}
void Camera::detachCamera() {
if (_window) {
_window->detachCamera(this);
}
}
geometry::Ray Camera::screenPointToRay(float x, float y) {
CC_ASSERT_NOT_NULL(_node);
const float cx = _orientedViewport.x * static_cast<float>(_width);
const float cy = _orientedViewport.y * static_cast<float>(_height);
const float cw = _orientedViewport.z * static_cast<float>(_width);
const float ch = _orientedViewport.w * static_cast<float>(_height);
const bool isProj = _proj == CameraProjection::PERSPECTIVE;
const float ySign = _device->getCapabilities().clipSpaceSignY;
const ccstd::array<float, 4> &preTransform = PRE_TRANSFORMS[static_cast<int>(_curTransform)];
Vec3 tmpVec3{
(x - cx) / cw * 2 - 1.F,
(y - cy) / ch * 2 - 1.F,
isProj ? 1.F : -1.F};
float tmpX = tmpVec3.x;
tmpVec3.x = tmpX * preTransform[0] + tmpVec3.y * preTransform[2] * ySign;
tmpVec3.y = tmpX * preTransform[1] + tmpVec3.y * preTransform[3] * ySign;
geometry::Ray out;
if (isProj) {
tmpVec3.transformMat4(tmpVec3, _matViewProjInv);
} else {
out.o.transformMat4(tmpVec3, _matViewProjInv);
}
if (isProj) {
// camera origin
geometry::Ray::fromPoints(&out, _node->getWorldPosition(), tmpVec3);
} else {
out.d.set(0, 0, -1.F);
out.d.transformQuat(_node->getWorldRotation());
}
return out;
}
Vec3 Camera::screenToWorld(const Vec3 &screenPos) {
const float cx = _orientedViewport.x * static_cast<float>(_width);
const float cy = _orientedViewport.y * static_cast<float>(_height);
const float cw = _orientedViewport.z * static_cast<float>(_width);
const float ch = _orientedViewport.w * static_cast<float>(_height);
const float ySign = _device->getCapabilities().clipSpaceSignY;
const ccstd::array<float, 4> &preTransform = PRE_TRANSFORMS[static_cast<int>(_curTransform)];
Vec3 out;
if (_proj == CameraProjection::PERSPECTIVE) {
// calculate screen pos in far clip plane
out.set(
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
1.0F);
// transform to world
float tmpX = out.x;
out.x = tmpX * preTransform[0] + out.y * preTransform[2] * ySign;
out.y = tmpX * preTransform[1] + out.y * preTransform[3] * ySign;
out.transformMat4(out, _matViewProjInv);
// lerp to depth z
Vec3 tmpVec3;
if (_node) {
tmpVec3.set(_node->getWorldPosition());
}
out = tmpVec3.lerp(out, MathUtil::lerp(_nearClip / _farClip, 1, screenPos.z));
} else {
out.set(
(screenPos.x - cx) / cw * 2 - 1,
(screenPos.y - cy) / ch * 2 - 1,
screenPos.z * 2 - 1);
// transform to world
float tmpX = out.x;
out.x = tmpX * preTransform[0] + out.y * preTransform[2] * ySign;
out.y = tmpX * preTransform[1] + out.y * preTransform[3] * ySign;
out.transformMat4(out, _matViewProjInv);
}
return out;
}
Vec3 Camera::worldToScreen(const Vec3 &worldPos) {
const float ySign = _device->getCapabilities().clipSpaceSignY;
const ccstd::array<float, 4> &preTransform = PRE_TRANSFORMS[static_cast<int>(_curTransform)];
Vec3 out;
Vec3::transformMat4(worldPos, _matViewProj, &out);
float tmpX = out.x;
out.x = tmpX * preTransform[0] + out.y * preTransform[2] * ySign;
out.y = tmpX * preTransform[1] + out.y * preTransform[3] * ySign;
const float cx = _orientedViewport.x * static_cast<float>(_width);
const float cy = _orientedViewport.y * static_cast<float>(_height);
const float cw = _orientedViewport.z * static_cast<float>(_width);
const float ch = _orientedViewport.w * static_cast<float>(_height);
out.x = cx + (out.x + 1) * 0.5F * cw;
out.y = cy + (out.y + 1) * 0.5F * ch;
out.z = out.z * 0.5F + 0.5F;
return out;
}
Mat4 Camera::worldMatrixToScreen(const Mat4 &worldMatrix, uint32_t width, uint32_t height) {
Mat4 out;
Mat4::multiply(_matViewProj, worldMatrix, &out);
Mat4::multiply(correctionMatrices[static_cast<int>(_curTransform)], out, &out);
const float halfWidth = static_cast<float>(width) / 2;
const float halfHeight = static_cast<float>(height) / 2;
Mat4 tmpMat4(Mat4::IDENTITY);
tmpMat4.translate(halfWidth, halfHeight, 0);
tmpMat4.scale(halfWidth, halfHeight, 1);
out.multiply(tmpMat4);
return out;
}
/**
* @en Calculate and set oblique view frustum projection matrix.
* @zh 计算并设置斜视锥体投影矩阵
* @param clipPlane clip plane in camera space
*/
void Camera::calculateObliqueMat(const Vec4 &viewSpacePlane) {
float clipSpaceMinZ = _device->getCapabilities().clipSpaceMinZ;
Vec4 far{math::sgn(viewSpacePlane.x), math::sgn(viewSpacePlane.y), 1.F, 0.F};
_matProjInv.transformVector(&far);
const Vec4 m4 = {_matProj.m[3], _matProj.m[7], clipSpaceMinZ, _matProj.m[15]};
const float scale = 2.F / Vec4::dot(viewSpacePlane, far);
const Vec4 newViewSpaceNearPlane = viewSpacePlane * scale;
const Vec4 m3 = newViewSpaceNearPlane - m4;
_matProj.m[2] = m3.x;
_matProj.m[6] = m3.y;
_matProj.m[10] = m3.z;
_matProj.m[14] = m3.w;
}
float Camera::getClipSpaceMinz() const {
return _device->getCapabilities().clipSpaceMinZ;
}
void Camera::setNode(Node *val) { _node = val; }
void Camera::setExposure(float ev100) {
_exposure = 0.833333F / std::pow(2.0F, ev100);
}
void Camera::updateExposure() {
const float ev100 = std::log2((_apertureValue * _apertureValue) / _shutterValue * 100.F / _isoValue);
setExposure(ev100);
}
void Camera::updateAspect(bool oriented) {
_aspect = (static_cast<float>(getWindow()->getWidth()) * _viewport.z) / (static_cast<float>(getWindow()->getHeight()) * _viewport.w);
// window size/viewport is pre-rotated, but aspect should be oriented to acquire the correct projection
if (oriented) {
auto *swapchain = getWindow()->getSwapchain();
const auto orientation = swapchain ? swapchain->getSurfaceTransform() : gfx::SurfaceTransform::IDENTITY;
if (static_cast<int32_t>(orientation) % 2) _aspect = 1 / _aspect;
}
_isProjDirty = true;
}
void Camera::setViewport(const Rect &val) {
debug::warnID(8302);
setViewportInOrientedSpace(val);
}
void Camera::setViewportInOrientedSpace(const Rect &val) {
const auto x = val.x;
const auto width = val.width;
const auto height = val.height;
const auto y = _device->getCapabilities().screenSpaceSignY < 0 ? 1.F - val.y - height : val.y;
auto *swapchain = getWindow()->getSwapchain();
const auto orientation = swapchain ? swapchain->getSurfaceTransform() : gfx::SurfaceTransform::IDENTITY;
switch (orientation) {
case gfx::SurfaceTransform::ROTATE_90:
_viewport.x = 1 - y - height;
_viewport.y = x;
_viewport.z = height;
_viewport.w = width;
break;
case gfx::SurfaceTransform::ROTATE_180:
_viewport.x = 1 - x - width;
_viewport.y = 1 - y - height;
_viewport.z = width;
_viewport.w = height;
break;
case gfx::SurfaceTransform::ROTATE_270:
_viewport.x = y;
_viewport.y = 1 - x - width;
_viewport.z = height;
_viewport.w = width;
break;
case gfx::SurfaceTransform::IDENTITY:
_viewport.x = x;
_viewport.y = y;
_viewport.z = width;
_viewport.w = height;
break;
default:
break;
}
_orientedViewport.x = x;
_orientedViewport.y = y;
_orientedViewport.z = width;
_orientedViewport.w = height;
resize(_width, _height);
}
} // namespace scene
} // namespace cc