#include "PlanetObject.h" #include #include #include "render/OpenGlExtensions.h" #include "Environment.h" #include "StoneObject.h" namespace ZL { #ifdef EMSCRIPTEN using std::min; using std::max; #endif extern const char* CONST_ZIP_FILE; Matrix3f GetRotationForTriangle(const Triangle& tri) { Vector3f vA = tri.data[0]; Vector3f vB = tri.data[1]; Vector3f vC = tri.data[2]; // 1. Вычисляем ось X (горизонталь). Vector3f x_axis = (vC - vB).normalized(); // 2. Вычисляем нормаль (ось Z). // Порядок cross product (AB x AC) определит "лицевую" сторону. Vector3f edge1 = vB - vA; Vector3f edge2 = vC - vA; Vector3f z_axis = edge1.cross(edge2).normalized(); // 3. Вычисляем ось Y (вертикаль). // В ортонормированном базисе Y всегда перпендикулярна Z и X. Vector3f y_axis = z_axis.cross(x_axis).normalized(); // 4. Формируем прямую матрицу поворота (Rotation/World Matrix). Matrix3f rot; // Столбец 0: Ось X rot.data()[0] = x_axis.data()[0]; rot.data()[3] = x_axis.data()[1]; rot.data()[6] = x_axis.data()[2]; // Столбец 1: Ось Y rot.data()[1] = y_axis.data()[0]; rot.data()[4] = y_axis.data()[1]; rot.data()[7] = y_axis.data()[2]; // Столбец 2: Ось Z rot.data()[2] = z_axis.data()[0]; rot.data()[5] = z_axis.data()[1]; rot.data()[8] = z_axis.data()[2]; return rot; } PlanetObject::PlanetObject() { } void PlanetObject::init() { // 1. Инициализируем данные (генерация мешей) planetData.init(); // 2. Забираем данные для VBO // Берем максимальный LOD для начальной отрисовки int lodIndex = planetData.getMaxLodIndex(); planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; //planetRenderStruct.data.PositionData.resize(9); planetRenderStruct.RefreshVBO(); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand2.png", CONST_ZIP_FILE)); stoneTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); #ifndef SIMPLIFIED // Атмосфера planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; if (planetAtmosphereRenderStruct.data.PositionData.size() > 0) { planetAtmosphereRenderStruct.RefreshVBO(); } #endif planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); stonesToRender = planetStones.inflate(planetStones.allInstances.size()); stoneToBake = planetStones.inflateOne(0, 0.75); } void PlanetObject::update(float deltaTimeMs) { float movementThreshold = 1.0f; if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { return; } lastUpdatePos = Environment::shipPosition; auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); // Сортировка важна для сравнения векторов std::sort(newIndices.begin(), newIndices.end()); if (newIndices != triangleIndicesToDraw) { triangleIndicesToDraw = std::move(newIndices); } } void PlanetObject::bakeStoneTexture(Renderer& renderer) { glViewport(0, 0, 512, 512); glClearColor(0,0,0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); static const std::string defaultShaderName2 = "planetBake"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // 1. Получаем матрицу вращения (оси в столбцах) Matrix3f mr = GetRotationForTriangle(tr); // 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты // Используем MultMatrixVector(Matrix, Vector). // Если ваша функция считает V * M, то передайте Inverse(mr). Vector3f rA = mr * tr.data[0]; Vector3f rB = mr * tr.data[1]; Vector3f rC = mr * tr.data[2]; // 3. Вычисляем реальные границы треугольника после поворота float minX = min(rA(0), min(rB(0), rC(0))); float maxX = max(rA(0), max(rB(0), rC(0))); float minY = min(rA(1), min(rB(1), rC(1))); float maxY = max(rA(1), max(rB(1), rC(1))); // Находим центр и размеры float width = maxX - minX; float height = maxY - minY; float centerX = (minX + maxX) * 0.5f; float centerY = (minY + maxY) * 0.5f; //width = width * 0.995; //height = height * 0.995; renderer.PushProjectionMatrix( centerX - width*0.5, centerX + width * 0.5, centerY - height * 0.5, centerY + height * 0.5, 150, 200000); renderer.PushMatrix(); renderer.LoadIdentity(); // Сдвигаем камеру по Z renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 }); // Применяем вращение renderer.RotateMatrix(mr); // Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения) Vector3f planeNormal = mr.col(2); renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data()); renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data()); renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation glActiveTexture(GL_TEXTURE0); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Отсекаем задние грани if (stoneToBake.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); renderer.DrawVertexRenderStruct(stoneToBake); CheckGlError(); } glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::draw(Renderer& renderer) { #ifndef SIMPLIFIED { if (stoneMapFB == nullptr) { //stoneMapFB = std::make_unique(512, 512, true); stoneMapFB = std::make_unique(512, 512); } stoneMapFB->Bind(); bakeStoneTexture(renderer); stoneMapFB->Unbind(); stoneMapFB->GenerateMipmaps(); } #endif //bakeStoneTexture(renderer); glViewport(0, 0, Environment::width, Environment::height); //-------------------------- drawPlanet(renderer); drawStones(renderer); #ifndef SIMPLIFIED drawAtmosphere(renderer); #endif } void PlanetObject::drawPlanet(Renderer& renderer) { static const std::string defaultShaderName = "planetLand"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray("vTangent"); renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; // 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("BakedTexture", 1); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании // Позиция камеры (корабля) в мире renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); //renderer.RenderUniform1f("uHeightScale", 0.08f); renderer.RenderUniform1f("uHeightScale", 0.0f); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); // Направление на солнце в мировом пространстве Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); // Направление от центра планеты к игроку для расчета дня/ночи Vector3f playerDirWorld = Environment::shipPosition.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); // Тот же фактор освещенности игрока float playerLightFactor = playerDirWorld.dot(-sunDirWorld); playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); #ifndef SIMPLIFIED glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); #endif glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray("vTangent"); renderer.DisableVertexAttribArray("vBinormal"); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::drawStones(Renderer& renderer) { static const std::string defaultShaderName2 = "planetStone"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; // 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); // PlanetObject.cpp, метод drawStones Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); Vector3f playerDirWorld = Environment::shipPosition.normalized(); float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); for (int i : triangleIndicesToDraw) //for (int i = 0; i < stonesToRender.size(); i++) { if (stonesToRender[i].data.PositionData.size() > 0) { renderer.DrawVertexRenderStruct(stonesToRender[i]); } } CheckGlError(); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); glClear(GL_DEPTH_BUFFER_BIT); } void PlanetObject::drawAtmosphere(Renderer& renderer) { static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string vPositionName = "vPosition"; static const std::string vNormalName = "vNormal"; //glClear(GL_DEPTH_BUFFER_BIT); glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); float currentZNear = zRange.first; float currentZFar = zRange.second; if (currentZNear < 200) { currentZNear = 200; currentZFar = currentZNear * 100; } // 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); Vector3f color = { 0.0, 0.5, 1.0 }; renderer.RenderUniform3fv("uColor", color.data()); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); // В начале drawAtmosphere или как uniform float time = SDL_GetTicks() / 1000.0f; renderer.RenderUniform1f("uTime", time); // В методе PlanetObject::drawAtmosphere Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Получаем текущую матрицу вида (ModelView без трансляции объекта) // В вашем движке это Environment::inverseShipMatrix Matrix3f viewMatrix2 = Environment::inverseShipMatrix; // Трансформируем вектор света в пространство вида Vector3f viewLightDir = viewMatrix2 * worldLightDir; // Передаем инвертированный вектор (направление НА источник) Vector3f lightToSource = -viewLightDir.normalized(); renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); // В методе Game::drawCubemap renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f); // Направление света в мировом пространстве для освещения облаков //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) Vector3f playerDirWorld = Environment::shipPosition.normalized(); Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Насколько игрок на свету float playerLightFactor = playerDirWorld.dot(-sunDirWorld); playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог // 2. ОБЯЗАТЕЛЬНО передаем в шейдер renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) renderer.RenderUniform3fv("uSkyColor", color.data()); //Vector3f playerDirWorld = Environment::shipPosition.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { return planetData.distanceToPlanetSurfaceFast(viewerPosition); } } // namespace ZL