941 lines
32 KiB
C++
941 lines
32 KiB
C++
// renderglsl.cpp
|
|
//
|
|
// Functions for rendering objects using dynamically generated GLSL shaders.
|
|
//
|
|
// Copyright (C) 2006-2020, the Celestia Development Team
|
|
// Original version by Chris Laurel <claurel@gmail.com>
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
|
|
#include <celmath/geomutil.h>
|
|
#include <celmath/mathlib.h>
|
|
#include <celmodel/material.h>
|
|
#include <celutil/color.h>
|
|
#include "body.h"
|
|
#include "framebuffer.h"
|
|
#include "geometry.h"
|
|
#include "glsupport.h"
|
|
#include "lodspheremesh.h"
|
|
#include "rendcontext.h"
|
|
#include "render.h"
|
|
#include "renderglsl.h"
|
|
#include "renderinfo.h"
|
|
#include "shadermanager.h"
|
|
#include "texture.h"
|
|
#include "vecgl.h"
|
|
|
|
using namespace celestia;
|
|
|
|
|
|
namespace
|
|
{
|
|
|
|
// Calculate the matrix used to render the model from the
|
|
// perspective of the light.
|
|
Eigen::Matrix4f directionalLightMatrix(const Eigen::Vector3f& lightDirection)
|
|
{
|
|
const Eigen::Vector3f &viewDir = lightDirection;
|
|
Eigen::Vector3f upDir = viewDir.unitOrthogonal();
|
|
Eigen::Vector3f rightDir = upDir.cross(viewDir);
|
|
Eigen::Matrix4f m = Eigen::Matrix4f::Identity();
|
|
|
|
m.row(0).head(3) = rightDir;
|
|
m.row(1).head(3) = upDir;
|
|
m.row(2).head(3) = viewDir;
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
/*! Render a mesh object
|
|
* Parameters:
|
|
* tsec : animation clock time in seconds
|
|
*/
|
|
void renderGeometryShadow_GLSL(Geometry* geometry,
|
|
FramebufferObject* shadowFbo,
|
|
const LightingState& ls,
|
|
int lightIndex,
|
|
double tsec,
|
|
Renderer* renderer,
|
|
Eigen::Matrix4f *lightMatrix)
|
|
{
|
|
auto *prog = renderer->getShaderManager().getShader("depth");
|
|
if (prog == nullptr)
|
|
return;
|
|
|
|
GLint oldFboId;
|
|
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFboId);
|
|
shadowFbo->bind();
|
|
glViewport(0, 0, shadowFbo->width(), shadowFbo->height());
|
|
|
|
// Write only to the depth buffer
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
renderer->enableDepthMask();
|
|
renderer->enableDepthTest();
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
// Render backfaces only in order to reduce self-shadowing artifacts
|
|
glCullFace(GL_FRONT);
|
|
|
|
Shadow_RenderContext rc(renderer);
|
|
|
|
prog->use();
|
|
|
|
// Enable poligon offset to decrease "shadow acne"
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(.001f, .001f);
|
|
|
|
Eigen::Matrix4f projMat = celmath::Ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
|
|
Eigen::Matrix4f modelViewMat = directionalLightMatrix(ls.lights[lightIndex].direction_obj);
|
|
*lightMatrix = projMat * modelViewMat;
|
|
prog->setMVPMatrices(projMat, modelViewMat);
|
|
geometry->render(rc, tsec);
|
|
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
// Re-enable the color buffer
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
glCullFace(GL_BACK);
|
|
shadowFbo->unbind(oldFboId);
|
|
}
|
|
|
|
} // end unnamed namespace
|
|
|
|
|
|
// Render a planet sphere with GLSL shaders
|
|
void renderEllipsoid_GLSL(const RenderInfo& ri,
|
|
const LightingState& ls,
|
|
Atmosphere* atmosphere,
|
|
float cloudTexOffset,
|
|
const Eigen::Vector3f& semiAxes,
|
|
unsigned int textureRes,
|
|
std::uint64_t renderFlags,
|
|
const Eigen::Quaternionf& planetOrientation,
|
|
const celmath::Frustum& frustum,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
float radius = semiAxes.maxCoeff();
|
|
|
|
Texture* textures[MAX_SPHERE_MESH_TEXTURES] =
|
|
{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
|
|
unsigned int nTextures = 0;
|
|
|
|
ShaderProperties shadprop;
|
|
shadprop.nLights = std::min(ls.nLights, MaxShaderLights);
|
|
|
|
// Set up the textures used by this object
|
|
if (ri.baseTex != nullptr)
|
|
{
|
|
shadprop.texUsage = ShaderProperties::DiffuseTexture;
|
|
textures[nTextures++] = ri.baseTex;
|
|
}
|
|
|
|
if (ri.bumpTex != nullptr)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::NormalTexture;
|
|
textures[nTextures++] = ri.bumpTex;
|
|
if (ri.bumpTex->getFormatOptions() & Texture::DXT5NormalMap)
|
|
shadprop.texUsage |= ShaderProperties::CompressedNormalTexture;
|
|
}
|
|
|
|
if (ri.specularColor != Color::Black)
|
|
{
|
|
shadprop.lightModel = ShaderProperties::PerPixelSpecularModel;
|
|
if (ri.glossTex == nullptr)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::SpecularInDiffuseAlpha;
|
|
}
|
|
else
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::SpecularTexture;
|
|
textures[nTextures++] = ri.glossTex;
|
|
}
|
|
}
|
|
if (ri.lunarLambert != 0.0f)
|
|
{
|
|
shadprop.lightModel |= ShaderProperties::LunarLambertModel;
|
|
}
|
|
|
|
if (ri.nightTex != nullptr)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::NightTexture;
|
|
textures[nTextures++] = ri.nightTex;
|
|
}
|
|
|
|
if (ri.overlayTex != nullptr)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::OverlayTexture;
|
|
textures[nTextures++] = ri.overlayTex;
|
|
}
|
|
|
|
if (atmosphere != nullptr)
|
|
{
|
|
if ((renderFlags & Renderer::ShowAtmospheres) != 0)
|
|
{
|
|
// Only use new atmosphere code in OpenGL 2.0 path when new style parameters are defined.
|
|
// ... but don't show atmospheres when there are no light sources.
|
|
if (atmosphere->mieScaleHeight > 0.0f && shadprop.nLights > 0)
|
|
shadprop.texUsage |= ShaderProperties::Scattering;
|
|
}
|
|
|
|
if ((renderFlags & Renderer::ShowCloudMaps) != 0 &&
|
|
(renderFlags & Renderer::ShowCloudShadows) != 0)
|
|
{
|
|
Texture* cloudTex = nullptr;
|
|
if (atmosphere->cloudTexture.tex[textureRes] != InvalidResource)
|
|
cloudTex = atmosphere->cloudTexture.find(textureRes);
|
|
|
|
// The current implementation of cloud shadows is not compatible
|
|
// with virtual or split textures.
|
|
bool allowCloudShadows = true;
|
|
for (unsigned int i = 0; i < nTextures; i++)
|
|
{
|
|
if (textures[i] != nullptr &&
|
|
(textures[i]->getLODCount() > 1 ||
|
|
textures[i]->getUTileCount(0) > 1 ||
|
|
textures[i]->getVTileCount(0) > 1))
|
|
{
|
|
allowCloudShadows = false;
|
|
}
|
|
}
|
|
|
|
// Split cloud shadows can't cast shadows
|
|
if (cloudTex != nullptr)
|
|
{
|
|
if (cloudTex->getLODCount() > 1 ||
|
|
cloudTex->getUTileCount(0) > 1 ||
|
|
cloudTex->getVTileCount(0) > 1)
|
|
{
|
|
allowCloudShadows = false;
|
|
}
|
|
}
|
|
|
|
if (cloudTex != nullptr && allowCloudShadows && atmosphere->cloudShadowDepth > 0.0f)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::CloudShadowTexture;
|
|
textures[nTextures++] = cloudTex;
|
|
glActiveTexture(GL_TEXTURE0 + nTextures);
|
|
cloudTex->bind();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
for (unsigned int lightIndex = 0; lightIndex < ls.nLights; lightIndex++)
|
|
{
|
|
if (ls.lights[lightIndex].castsShadows)
|
|
{
|
|
shadprop.setCloudShadowForLight(lightIndex, true);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the shadow information.
|
|
// Track the total number of shadows; if there are too many, we'll have
|
|
// to fall back to multipass.
|
|
unsigned int totalShadows = 0;
|
|
|
|
for (unsigned int li = 0; li < ls.nLights; li++)
|
|
{
|
|
if (ls.shadows[li] && !ls.shadows[li]->empty())
|
|
{
|
|
unsigned int nShadows = static_cast<unsigned int>(std::min(static_cast<std::size_t>(MaxShaderEclipseShadows),
|
|
ls.shadows[li]->size()));
|
|
shadprop.setEclipseShadowCountForLight(li, nShadows);
|
|
totalShadows += nShadows;
|
|
}
|
|
}
|
|
|
|
if (ls.shadowingRingSystem)
|
|
{
|
|
Texture* ringsTex = ls.shadowingRingSystem->texture.find(textureRes);
|
|
if (ringsTex != nullptr)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + nTextures);
|
|
ringsTex->bind();
|
|
nTextures++;
|
|
|
|
#ifdef GL_ES
|
|
if (gl::OES_texture_border_clamp)
|
|
{
|
|
#endif
|
|
// Tweak the texture--set clamp to border and a border color with
|
|
// a zero alpha.
|
|
float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
#ifndef GL_ES
|
|
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
|
#else
|
|
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR_OES, bc);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_OES);
|
|
#endif
|
|
|
|
#ifdef GL_ES
|
|
}
|
|
#endif
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
shadprop.texUsage |= ShaderProperties::RingShadowTexture;
|
|
|
|
for (unsigned int lightIndex = 0; lightIndex < ls.nLights; lightIndex++)
|
|
{
|
|
if (ls.lights[lightIndex].castsShadows &&
|
|
ls.shadowingRingSystem == ls.ringShadows[lightIndex].ringSystem)
|
|
{
|
|
shadprop.setRingShadowForLight(lightIndex, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Get a shader for the current rendering configuration
|
|
CelestiaGLProgram* prog = renderer->getShaderManager().getShader(shadprop);
|
|
if (prog == nullptr)
|
|
return;
|
|
|
|
prog->use();
|
|
prog->setMVPMatrices(*m.projection, *m.modelview);
|
|
|
|
#ifdef USE_HDR
|
|
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black, ri.nightLightScale);
|
|
#else
|
|
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black);
|
|
#endif
|
|
|
|
prog->eyePosition = ls.eyePos_obj;
|
|
prog->shininess = ri.specularPower;
|
|
if ((shadprop.lightModel & ShaderProperties::LunarLambertModel) != 0)
|
|
prog->lunarLambert = ri.lunarLambert;
|
|
|
|
if ((shadprop.texUsage & ShaderProperties::RingShadowTexture) != 0)
|
|
{
|
|
float ringWidth = ls.shadowingRingSystem->outerRadius - ls.shadowingRingSystem->innerRadius;
|
|
prog->ringRadius = ls.shadowingRingSystem->innerRadius / radius;
|
|
prog->ringWidth = radius / ringWidth;
|
|
prog->ringPlane = Eigen::Hyperplane<float, 3>(ls.ringPlaneNormal, ls.ringCenter / radius).coeffs();
|
|
prog->ringCenter = ls.ringCenter / radius;
|
|
for (unsigned int lightIndex = 0; lightIndex < ls.nLights; ++lightIndex)
|
|
{
|
|
if (shadprop.hasRingShadowForLight(lightIndex))
|
|
{
|
|
prog->ringShadowLOD[lightIndex] = ls.ringShadows[lightIndex].texLod;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (atmosphere != nullptr)
|
|
{
|
|
if ((shadprop.texUsage & ShaderProperties::CloudShadowTexture) != 0)
|
|
{
|
|
prog->shadowTextureOffset = cloudTexOffset;
|
|
prog->cloudHeight = 1.0f + atmosphere->cloudHeight / radius;
|
|
}
|
|
|
|
if (shadprop.hasScattering())
|
|
{
|
|
prog->setAtmosphereParameters(*atmosphere, radius, radius);
|
|
}
|
|
}
|
|
|
|
if (shadprop.hasEclipseShadows())
|
|
prog->setEclipseShadowParameters(ls, semiAxes, planetOrientation);
|
|
|
|
unsigned int attributes = LODSphereMesh::Normals;
|
|
if (ri.bumpTex != nullptr)
|
|
attributes |= LODSphereMesh::Tangents;
|
|
g_lodSphere->render(attributes,
|
|
frustum, ri.pixWidth,
|
|
textures[0], textures[1], textures[2], textures[3]);
|
|
}
|
|
|
|
|
|
#undef DEPTH_BUFFER_DEBUG
|
|
|
|
/*! Render a mesh object
|
|
* Parameters:
|
|
* tsec : animation clock time in seconds
|
|
*/
|
|
void renderGeometry_GLSL(Geometry* geometry,
|
|
const RenderInfo& ri,
|
|
ResourceHandle texOverride,
|
|
const LightingState& ls,
|
|
const Atmosphere* atmosphere,
|
|
float geometryScale,
|
|
std::uint64_t renderFlags,
|
|
const Eigen::Quaternionf& planetOrientation,
|
|
double tsec,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
auto *shadowBuffer = renderer->getShadowFBO(0);
|
|
Eigen::Matrix4f lightMatrix(Eigen::Matrix4f::Identity());
|
|
|
|
if (shadowBuffer != nullptr && shadowBuffer->isValid())
|
|
{
|
|
std::array<int, 4> viewport;
|
|
renderer->getViewport(viewport);
|
|
|
|
float range[2];
|
|
glGetFloatv(GL_DEPTH_RANGE, range);
|
|
glDepthRange(0.0f, 1.0f);
|
|
|
|
#ifdef DEPTH_STATE_DEBUG
|
|
float bias, bits, clear, range[2], scale;
|
|
glGetFloatv(GL_DEPTH_BIAS, &bias);
|
|
glGetFloatv(GL_DEPTH_BITS, &bits);
|
|
glGetFloatv(GL_DEPTH_CLEAR_VALUE, &clear);
|
|
glGetFloatv(GL_DEPTH_RANGE, range);
|
|
glGetFloatv(GL_DEPTH_SCALE, &scale);
|
|
fmt::printf("bias: %f bits: %f clear: %f range: %f - %f, scale:%f\n", bias, bits, clear, range[0], range[1], scale);
|
|
#endif
|
|
|
|
renderGeometryShadow_GLSL(geometry, shadowBuffer, ls, 0,
|
|
tsec, renderer, &lightMatrix);
|
|
renderer->setViewport(viewport);
|
|
#ifdef DEPTH_BUFFER_DEBUG
|
|
renderer->disableDepthTest();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadMatrixf(Ortho2D(0.0f, (float)viewport[2], 0.0f, (float)viewport[3]).data());
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glUseProgram(0);
|
|
glColor4f(1, 1, 1, 1);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, shadowBuffer->depthTexture());
|
|
#if GL_ONLY_SHADOWS
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
|
#endif
|
|
|
|
glBegin(GL_QUADS);
|
|
float side = 300.0f;
|
|
glTexCoord2f(0.0f, 0.0f);
|
|
glVertex2f(0.0f, 0.0f);
|
|
glTexCoord2f(1.0f, 0.0f);
|
|
glVertex2f(side, 0.0f);
|
|
glTexCoord2f(1.0f, 1.0f);
|
|
glVertex2f(side, side);
|
|
glTexCoord2f(0.0f, 1.0f);
|
|
glVertex2f(0.0f, side);
|
|
glEnd();
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glDisable(GL_TEXTURE_2D);
|
|
renderer->enableDepthTest();
|
|
#endif
|
|
glDepthRange(range[0], range[1]);
|
|
}
|
|
|
|
GLSL_RenderContext rc(renderer, ls, geometryScale, planetOrientation);
|
|
rc.setModelViewMatrix(m.modelview);
|
|
rc.setProjectionMatrix(m.projection);
|
|
|
|
if ((renderFlags & Renderer::ShowAtmospheres) != 0)
|
|
{
|
|
rc.setAtmosphere(atmosphere);
|
|
}
|
|
|
|
if (shadowBuffer != nullptr && shadowBuffer->isValid())
|
|
{
|
|
rc.setShadowMap(shadowBuffer->depthTexture(), shadowBuffer->width(), &lightMatrix);
|
|
}
|
|
|
|
rc.setCameraOrientation(ri.orientation);
|
|
rc.setPointScale(ri.pointScale);
|
|
|
|
// Handle extended material attributes (per model only, not per submesh)
|
|
rc.setLunarLambert(ri.lunarLambert);
|
|
|
|
// Handle material override; a texture specified in an ssc file will
|
|
// override all materials specified in the geometry file.
|
|
if (texOverride != InvalidResource)
|
|
{
|
|
cmod::Material m;
|
|
m.diffuse = cmod::Color(ri.color);
|
|
m.specular = cmod::Color(ri.specularColor);
|
|
m.specularPower = ri.specularPower;
|
|
|
|
m.setMap(cmod::TextureSemantic::DiffuseMap, texOverride);
|
|
rc.setMaterial(&m);
|
|
rc.lock();
|
|
geometry->render(rc, tsec);
|
|
}
|
|
else
|
|
{
|
|
geometry->render(rc, tsec);
|
|
}
|
|
}
|
|
|
|
|
|
/*! Render a mesh object without lighting.
|
|
* Parameters:
|
|
* tsec : animation clock time in seconds
|
|
*/
|
|
void renderGeometry_GLSL_Unlit(Geometry* geometry,
|
|
const RenderInfo& ri,
|
|
ResourceHandle texOverride,
|
|
float geometryScale,
|
|
std::uint64_t /* renderFlags */,
|
|
const Eigen::Quaternionf& /* planetOrientation */,
|
|
double tsec,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
GLSLUnlit_RenderContext rc(renderer, geometryScale);
|
|
|
|
rc.setModelViewMatrix(m.modelview);
|
|
rc.setProjectionMatrix(m.projection);
|
|
rc.setPointScale(ri.pointScale);
|
|
|
|
// Handle material override; a texture specified in an ssc file will
|
|
// override all materials specified in the model file.
|
|
if (texOverride != InvalidResource)
|
|
{
|
|
cmod::Material m;
|
|
m.diffuse = cmod::Color(ri.color);
|
|
m.specular = cmod::Color(ri.specularColor);
|
|
m.specularPower = ri.specularPower;
|
|
|
|
m.setMap(cmod::TextureSemantic::DiffuseMap, texOverride);
|
|
rc.setMaterial(&m);
|
|
rc.lock();
|
|
geometry->render(rc, tsec);
|
|
}
|
|
else
|
|
{
|
|
geometry->render(rc, tsec);
|
|
}
|
|
}
|
|
|
|
|
|
// Render the cloud sphere for a world a cloud layer defined
|
|
void renderClouds_GLSL(const RenderInfo& ri,
|
|
const LightingState& ls,
|
|
Atmosphere* atmosphere,
|
|
Texture* cloudTex,
|
|
Texture* cloudNormalMap,
|
|
float texOffset,
|
|
const Eigen::Vector3f& semiAxes,
|
|
unsigned int /*textureRes*/,
|
|
std::uint64_t renderFlags,
|
|
const Eigen::Quaternionf& planetOrientation,
|
|
const celmath::Frustum& frustum,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
float radius = semiAxes.maxCoeff();
|
|
|
|
Texture* textures[MAX_SPHERE_MESH_TEXTURES] =
|
|
{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
|
|
unsigned int nTextures = 0;
|
|
|
|
ShaderProperties shadprop;
|
|
shadprop.nLights = ls.nLights;
|
|
|
|
// Set up the textures used by this object
|
|
if (cloudTex != nullptr)
|
|
{
|
|
shadprop.texUsage = ShaderProperties::DiffuseTexture;
|
|
textures[nTextures++] = cloudTex;
|
|
}
|
|
|
|
if (cloudNormalMap != nullptr)
|
|
{
|
|
shadprop.texUsage |= ShaderProperties::NormalTexture;
|
|
textures[nTextures++] = cloudNormalMap;
|
|
if (cloudNormalMap->getFormatOptions() & Texture::DXT5NormalMap)
|
|
shadprop.texUsage |= ShaderProperties::CompressedNormalTexture;
|
|
}
|
|
|
|
#if 0
|
|
if (rings != nullptr && (renderFlags & Renderer::ShowRingShadows) != 0)
|
|
{
|
|
Texture* ringsTex = rings->texture.find(textureRes);
|
|
if (ringsTex != nullptr)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + nTextures);
|
|
ringsTex->bind();
|
|
nTextures++;
|
|
|
|
// Tweak the texture--set clamp to border and a border color with
|
|
// a zero alpha.
|
|
float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
|
|
GL_CLAMP_TO_BORDER);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
shadprop.texUsage |= ShaderProperties::RingShadowTexture;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (atmosphere != nullptr)
|
|
{
|
|
if ((renderFlags & Renderer::ShowAtmospheres) != 0)
|
|
{
|
|
// Only use new atmosphere code in OpenGL 2.0 path when new style parameters are defined.
|
|
// ... but don't show atmospheres when there are no light sources.
|
|
if (atmosphere->mieScaleHeight > 0.0f && shadprop.nLights > 0)
|
|
shadprop.texUsage |= ShaderProperties::Scattering;
|
|
}
|
|
}
|
|
|
|
// Set the shadow information.
|
|
// Track the total number of shadows; if there are too many, we'll have
|
|
// to fall back to multipass.
|
|
unsigned int totalShadows = 0;
|
|
for (unsigned int li = 0; li < ls.nLights; li++)
|
|
{
|
|
if (ls.shadows[li] && !ls.shadows[li]->empty())
|
|
{
|
|
unsigned int nShadows = static_cast<unsigned int>(std::min(static_cast<std::size_t>(MaxShaderEclipseShadows),
|
|
ls.shadows[li]->size()));
|
|
shadprop.setEclipseShadowCountForLight(li, nShadows);
|
|
totalShadows += nShadows;
|
|
}
|
|
}
|
|
|
|
// Get a shader for the current rendering configuration
|
|
CelestiaGLProgram* prog = renderer->getShaderManager().getShader(shadprop);
|
|
if (prog == nullptr)
|
|
return;
|
|
|
|
prog->use();
|
|
prog->setMVPMatrices(*m.projection, *m.modelview);
|
|
|
|
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black);
|
|
prog->eyePosition = ls.eyePos_obj;
|
|
prog->ambientColor = ri.ambientColor.toVector3();
|
|
prog->textureOffset = texOffset;
|
|
|
|
if (atmosphere != nullptr)
|
|
{
|
|
float cloudRadius = radius + atmosphere->cloudHeight;
|
|
|
|
if (shadprop.hasScattering())
|
|
{
|
|
prog->setAtmosphereParameters(*atmosphere, radius, cloudRadius);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (shadprop.texUsage & ShaderProperties::RingShadowTexture)
|
|
{
|
|
float ringWidth = rings->outerRadius - rings->innerRadius;
|
|
prog->ringRadius = rings->innerRadius / cloudRadius;
|
|
prog->ringWidth = 1.0f / (ringWidth / cloudRadius);
|
|
}
|
|
#endif
|
|
|
|
if (shadprop.shadowCounts != 0)
|
|
prog->setEclipseShadowParameters(ls, semiAxes, planetOrientation);
|
|
|
|
unsigned int attributes = LODSphereMesh::Normals;
|
|
if (cloudNormalMap != nullptr)
|
|
attributes |= LODSphereMesh::Tangents;
|
|
g_lodSphere->render(attributes,
|
|
frustum, ri.pixWidth,
|
|
textures[0], textures[1], textures[2], textures[3]);
|
|
|
|
prog->textureOffset = 0.0f;
|
|
}
|
|
|
|
|
|
// Render the sky sphere for a world with an atmosphere
|
|
void
|
|
renderAtmosphere_GLSL(const RenderInfo& ri,
|
|
const LightingState& ls,
|
|
Atmosphere* atmosphere,
|
|
float radius,
|
|
const Eigen::Quaternionf& /*planetOrientation*/,
|
|
const celmath::Frustum& frustum,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
// Currently, we just skip rendering an atmosphere when there are no
|
|
// light sources, even though the atmosphere would still the light
|
|
// of planets and stars behind it.
|
|
if (ls.nLights == 0)
|
|
return;
|
|
|
|
ShaderProperties shadprop;
|
|
shadprop.nLights = ls.nLights;
|
|
|
|
shadprop.texUsage |= ShaderProperties::Scattering;
|
|
shadprop.lightModel = ShaderProperties::AtmosphereModel;
|
|
|
|
// Get a shader for the current rendering configuration
|
|
CelestiaGLProgram* prog = renderer->getShaderManager().getShader(shadprop);
|
|
if (prog == nullptr)
|
|
return;
|
|
|
|
prog->use();
|
|
|
|
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black);
|
|
prog->ambientColor = Eigen::Vector3f::Zero();
|
|
|
|
float atmosphereRadius = radius + -atmosphere->mieScaleHeight * std::log(AtmosphereExtinctionThreshold);
|
|
float atmScale = atmosphereRadius / radius;
|
|
|
|
prog->eyePosition = ls.eyePos_obj / atmScale;
|
|
prog->setAtmosphereParameters(*atmosphere, radius, atmosphereRadius);
|
|
|
|
#if 0
|
|
// Currently eclipse shadows are ignored when rendering atmospheres
|
|
if (shadprop.shadowCounts != 0)
|
|
prog->setEclipseShadowParameters(ls, radius, planetOrientation);
|
|
#endif
|
|
|
|
prog->setMVPMatrices(*m.projection, (*m.modelview) * vecgl::scale(atmScale));
|
|
|
|
glFrontFace(GL_CW);
|
|
renderer->enableBlending();
|
|
renderer->disableDepthMask();
|
|
renderer->setBlendingFactors(GL_ONE, GL_SRC_ALPHA);
|
|
|
|
g_lodSphere->render(LODSphereMesh::Normals,
|
|
frustum,
|
|
ri.pixWidth,
|
|
nullptr);
|
|
|
|
renderer->disableBlending();
|
|
renderer->enableDepthMask();
|
|
glFrontFace(GL_CCW);
|
|
}
|
|
|
|
static void renderRingSystem(GLuint *vboId,
|
|
float innerRadius,
|
|
float outerRadius,
|
|
unsigned int nSections = 180)
|
|
{
|
|
struct RingVertex
|
|
{
|
|
GLfloat pos[3];
|
|
GLshort tex[2];
|
|
};
|
|
|
|
constexpr const float angle = 2.0f * static_cast<float>(PI);
|
|
|
|
if (*vboId == 0)
|
|
{
|
|
struct RingVertex vertex;
|
|
std::vector<struct RingVertex> ringCoord;
|
|
ringCoord.reserve(2 * nSections);
|
|
for (unsigned i = 0; i <= nSections; i++)
|
|
{
|
|
float t = static_cast<float>(i) / static_cast<float>(nSections);
|
|
float theta = t * angle;
|
|
float s = std::sin(theta);
|
|
float c = std::cos(theta);
|
|
|
|
// inner point
|
|
vertex.pos[0] = c * innerRadius;
|
|
vertex.pos[1] = 0.0f;
|
|
vertex.pos[2] = s * innerRadius;
|
|
vertex.tex[0] = 0;
|
|
vertex.tex[1] = (i & 1) ^ 1; // even?(i) ? 0 : 1;
|
|
ringCoord.push_back(vertex);
|
|
|
|
// outer point
|
|
vertex.pos[0] = c * outerRadius;
|
|
// vertex.pos[1] = 0.0f;
|
|
vertex.pos[2] = s * outerRadius;
|
|
vertex.tex[0] = 1;
|
|
// vertex.tex[1] = (i & 1) ^ 1;
|
|
ringCoord.push_back(vertex);
|
|
}
|
|
|
|
glGenBuffers(1, vboId);
|
|
glBindBuffer(GL_ARRAY_BUFFER, *vboId);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
ringCoord.size() * sizeof(struct RingVertex),
|
|
ringCoord.data(),
|
|
GL_STATIC_DRAW);
|
|
}
|
|
else
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, *vboId);
|
|
}
|
|
glEnableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex);
|
|
glVertexAttribPointer(CelestiaGLProgram::TextureCoord0AttributeIndex,
|
|
2, GL_SHORT, GL_FALSE,
|
|
sizeof(struct RingVertex),
|
|
reinterpret_cast<GLvoid*>(offsetof(struct RingVertex, tex)));
|
|
|
|
glEnableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex);
|
|
glVertexAttribPointer(CelestiaGLProgram::VertexCoordAttributeIndex,
|
|
3, GL_FLOAT, GL_FALSE,
|
|
sizeof(struct RingVertex), 0);
|
|
|
|
// Celestia uses glCullFace(GL_BACK) by default so we just skip it here
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, (nSections+1)*2);
|
|
glCullFace(GL_FRONT);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, (nSections+1)*2);
|
|
glCullFace(GL_BACK);
|
|
|
|
glDisableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex);
|
|
glDisableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
|
|
class GLRingRenderData : public RingRenderData
|
|
{
|
|
public:
|
|
~GLRingRenderData() override
|
|
{
|
|
glDeleteBuffers(vboId.size(), vboId.data());
|
|
vboId.fill(0);
|
|
}
|
|
|
|
std::array<GLuint, 4> vboId {{ 0, 0, 0, 0 }};
|
|
};
|
|
|
|
// Render a planetary ring system
|
|
void renderRings_GLSL(RingSystem& rings,
|
|
RenderInfo& ri,
|
|
const LightingState& ls,
|
|
float planetRadius,
|
|
float planetOblateness,
|
|
unsigned int textureResolution,
|
|
bool renderShadow,
|
|
float segmentSizeInPixels,
|
|
const Matrices &m,
|
|
Renderer* renderer)
|
|
{
|
|
float inner = rings.innerRadius / planetRadius;
|
|
float outer = rings.outerRadius / planetRadius;
|
|
Texture* ringsTex = rings.texture.find(textureResolution);
|
|
|
|
ShaderProperties shadprop;
|
|
// Set up the shader properties for ring rendering
|
|
{
|
|
shadprop.lightModel = ShaderProperties::RingIllumModel;
|
|
shadprop.nLights = std::min(ls.nLights, MaxShaderLights);
|
|
|
|
if (renderShadow)
|
|
{
|
|
// Set one shadow (the planet's) per light
|
|
for (unsigned int li = 0; li < ls.nLights; li++)
|
|
shadprop.setEclipseShadowCountForLight(li, 1);
|
|
}
|
|
|
|
if (ringsTex != nullptr)
|
|
shadprop.texUsage = ShaderProperties::DiffuseTexture;
|
|
}
|
|
|
|
|
|
// Get a shader for the current rendering configuration
|
|
CelestiaGLProgram* prog = renderer->getShaderManager().getShader(shadprop);
|
|
if (prog == nullptr)
|
|
return;
|
|
|
|
prog->use();
|
|
prog->setMVPMatrices(*m.projection, *m.modelview);
|
|
|
|
prog->eyePosition = ls.eyePos_obj;
|
|
prog->ambientColor = ri.ambientColor.toVector3();
|
|
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black);
|
|
|
|
for (unsigned int li = 0; li < ls.nLights; li++)
|
|
{
|
|
const DirectionalLight& light = ls.lights[li];
|
|
|
|
// Compute the projection vectors based on the sun direction.
|
|
// I'm being a little careless here--if the sun direction lies
|
|
// along the y-axis, this will fail. It's unlikely that a
|
|
// planet would ever orbit underneath its sun (an orbital
|
|
// inclination of 90 degrees), but this should be made
|
|
// more robust anyway.
|
|
Eigen::Vector3f axis = Eigen::Vector3f::UnitY().cross(light.direction_obj);
|
|
float cosAngle = Eigen::Vector3f::UnitY().dot(light.direction_obj);
|
|
axis.normalize();
|
|
|
|
float tScale = 1.0f;
|
|
if (planetOblateness != 0.0f)
|
|
{
|
|
// For oblate planets, the size of the shadow volume will vary
|
|
// based on the light direction.
|
|
|
|
// A vertical slice of the planet is an ellipse
|
|
float a = 1.0f; // semimajor axis
|
|
float b = a * (1.0f - planetOblateness); // semiminor axis
|
|
float ecc2 = 1.0f - (b * b) / (a * a); // square of eccentricity
|
|
|
|
// Calculate the radius of the ellipse at the incident angle of the
|
|
// light on the ring plane + 90 degrees.
|
|
float r = a * std::sqrt((1.0f - ecc2) /
|
|
(1.0f - ecc2 * celmath::square(cosAngle)));
|
|
|
|
tScale *= a / r;
|
|
}
|
|
|
|
// The s axis is perpendicular to the shadow axis in the plane of the
|
|
// of the rings, and the t axis completes the orthonormal basis.
|
|
Eigen::Vector3f sAxis = axis * 0.5f;
|
|
Eigen::Vector3f tAxis = (axis.cross(light.direction_obj)) * 0.5f * tScale;
|
|
Eigen::Vector4f texGenS;
|
|
texGenS.head(3) = sAxis;
|
|
texGenS[3] = 0.5f;
|
|
Eigen::Vector4f texGenT;
|
|
texGenT.head(3) = tAxis;
|
|
texGenT[3] = 0.5f;
|
|
|
|
// r0 and r1 determine the size of the planet's shadow and penumbra
|
|
// on the rings.
|
|
// TODO: A more accurate ring shadow calculation would set r1 / r0
|
|
// to the ratio of the apparent sizes of the planet and sun as seen
|
|
// from the rings. Even more realism could be attained by letting
|
|
// this ratio vary across the rings, though it may not make enough
|
|
// of a visual difference to be worth the extra effort.
|
|
float r0 = 0.24f;
|
|
float r1 = 0.25f;
|
|
float bias = 1.0f / (1.0f - r1 / r0);
|
|
|
|
prog->shadows[li][0].texGenS = texGenS;
|
|
prog->shadows[li][0].texGenT = texGenT;
|
|
prog->shadows[li][0].maxDepth = 1.0f;
|
|
prog->shadows[li][0].falloff = bias / r0;
|
|
}
|
|
|
|
renderer->enableBlending();
|
|
renderer->setBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
if (ringsTex != nullptr)
|
|
ringsTex->bind();
|
|
|
|
if (rings.renderData == nullptr)
|
|
rings.renderData = std::make_shared<GLRingRenderData>();
|
|
auto data = reinterpret_cast<GLRingRenderData*>(rings.renderData.get());
|
|
|
|
unsigned nSections = 180;
|
|
std::size_t i = 0;
|
|
for (i = 0; i < data->vboId.size() - 1; i++)
|
|
{
|
|
float s = segmentSizeInPixels * tan(PI / nSections);
|
|
if (s < 30.0f) // TODO: make configurable
|
|
break;
|
|
nSections <<= 1;
|
|
}
|
|
renderRingSystem(&data->vboId[i], inner, outer, nSections);
|
|
|
|
renderer->setBlendingFactors(GL_SRC_ALPHA, GL_ONE);
|
|
}
|