3
// Copyright (C) 2006-2024, the Celestia Development Team
4
// Original version by Chris Laurel <claurel@gmail.com>
6
// This program is free software; you can redistribute it and/or
7
// modify it under the terms of the GNU General Public License
8
// as published by the Free Software Foundation; either version 2
9
// of the License, or (at your option) any later version.
11
#include "ringrenderer.h"
19
#include <celcompat/numbers.h>
20
#include <celengine/body.h>
21
#include <celengine/lightenv.h>
22
#include <celengine/multitexture.h>
23
#include <celengine/render.h>
24
#include <celengine/renderinfo.h>
25
#include <celengine/shadermanager.h>
26
#include <celmath/mathlib.h>
28
namespace celestia::render
34
constexpr float SegmentSizeThreshold = 30.0f;
35
constexpr std::uint32_t BaseSectionCount = UINT32_C(180);
39
std::array<float, 3> pos;
40
std::array<unsigned short, 2> tex;
44
createShaderProperties(const LightingState& ls,
45
const Texture* ringsTex,
48
// Set up the shader properties for ring rendering
49
ShaderProperties shadprop;
50
shadprop.lightModel = LightingModel::RingIllumModel;
51
shadprop.nLights = static_cast<std::uint16_t>(std::min(ls.nLights, MaxShaderLights));
55
// Set one shadow (the planet's) per light
56
for (unsigned int li = 0; li < ls.nLights; li++)
57
shadprop.setEclipseShadowCountForLight(li, 1);
60
if (ringsTex != nullptr)
61
shadprop.texUsage = TexUsage::DiffuseTexture;
67
setUpShadowParameters(CelestiaGLProgram* prog,
68
const LightingState& ls,
69
float planetOblateness)
71
for (unsigned int li = 0; li < ls.nLights; li++)
73
const DirectionalLight& light = ls.lights[li];
75
// Compute the projection vectors based on the sun direction.
76
// I'm being a little careless here--if the sun direction lies
77
// along the y-axis, this will fail. It's unlikely that a
78
// planet would ever orbit underneath its sun (an orbital
79
// inclination of 90 degrees), but this should be made
80
// more robust anyway.
81
Eigen::Vector3f axis = Eigen::Vector3f::UnitY().cross(light.direction_obj);
82
float cosAngle = Eigen::Vector3f::UnitY().dot(light.direction_obj);
86
if (planetOblateness != 0.0f)
88
// For oblate planets, the size of the shadow volume will vary
89
// based on the light direction.
91
// A vertical slice of the planet is an ellipse
92
float a = 1.0f; // semimajor axis
93
float b = a * (1.0f - planetOblateness); // semiminor axis
94
float ecc2 = 1.0f - (b * b) / (a * a); // square of eccentricity
96
// Calculate the radius of the ellipse at the incident angle of the
97
// light on the ring plane + 90 degrees.
98
float r = a * std::sqrt((1.0f - ecc2) /
99
(1.0f - ecc2 * math::square(cosAngle)));
104
// The s axis is perpendicular to the shadow axis in the plane of the
105
// of the rings, and the t axis completes the orthonormal basis.
106
Eigen::Vector3f sAxis = axis * 0.5f;
107
Eigen::Vector3f tAxis = (axis.cross(light.direction_obj)) * 0.5f * tScale;
108
Eigen::Vector4f texGenS;
109
texGenS.head(3) = sAxis;
111
Eigen::Vector4f texGenT;
112
texGenT.head(3) = tAxis;
115
// r0 and r1 determine the size of the planet's shadow and penumbra
117
// A more accurate ring shadow calculation would set r1 / r0
118
// to the ratio of the apparent sizes of the planet and sun as seen
119
// from the rings. Even more realism could be attained by letting
120
// this ratio vary across the rings, though it may not make enough
121
// of a visual difference to be worth the extra effort.
124
float bias = 1.0f / (1.0f - r1 / r0);
126
prog->shadows[li][0].texGenS = texGenS;
127
prog->shadows[li][0].texGenT = texGenT;
128
prog->shadows[li][0].maxDepth = 1.0f;
129
prog->shadows[li][0].falloff = bias / r0;
133
} // end unnamed namespace
135
RingRenderer::RingRenderer(Renderer& _renderer) : renderer(_renderer)
137
// Initialize section scales
138
std::uint32_t nSections = BaseSectionCount;
139
for (unsigned int i = 0; i < RingRenderer::nLODs - 1; ++i)
141
sectionScales[i] = static_cast<float>(std::tan(celestia::numbers::pi / static_cast<double>(nSections)));
146
// Render a planetary ring system
148
RingRenderer::renderRings(RingSystem& rings,
149
const RenderInfo& ri,
150
const LightingState& ls,
152
float planetOblateness,
154
float segmentSizeInPixels,
158
float inner = rings.innerRadius / planetRadius;
159
float outer = rings.outerRadius / planetRadius;
160
Texture* ringsTex = rings.texture.find(renderer.getResolution());
162
ShaderProperties shadprop = createShaderProperties(ls, ringsTex, renderShadow);
164
// Get a shader for the current rendering configuration
165
auto* prog = renderer.getShaderManager().getShader(shadprop);
170
prog->setMVPMatrices(*m.projection, *m.modelview);
172
prog->eyePosition = ls.eyePos_obj;
173
prog->ambientColor = ri.ambientColor.toVector3();
174
prog->setLightParameters(ls, ri.color, ri.specularColor, Color::Black);
176
prog->ringRadius = inner;
177
prog->ringWidth = outer - inner;
179
setUpShadowParameters(prog, ls, planetOblateness);
181
if (ringsTex != nullptr)
184
// Determine level of detail
185
std::uint32_t nSections = BaseSectionCount;
186
unsigned int level = 0;
187
for (level = 0U; level < nLODs - 1U; ++level)
189
if (float s = segmentSizeInPixels * sectionScales[level]; s < SegmentSizeThreshold)
195
Renderer::PipelineState ps;
197
ps.blendFunc = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA};
199
ps.depthMask = inside;
200
renderer.setPipelineState(ps);
202
renderLOD(level, nSections);
206
RingRenderer::initializeLOD(unsigned int level, std::uint32_t nSections)
208
std::vector<RingVertex> ringCoord;
209
ringCoord.reserve(2 * nSections);
211
constexpr float angle = 2.0f * celestia::numbers::pi_v<float>;
212
for (std::uint32_t i = 0; i <= nSections; i++)
214
float theta = angle * static_cast<float>(i) / static_cast<float>(nSections);
216
math::sincos(theta, s, c);
221
vertex.pos[1] = 0.0f;
225
ringCoord.push_back(vertex);
230
ringCoord.push_back(vertex);
233
const auto& bo = buffers[level].emplace(gl::Buffer::TargetHint::Array, ringCoord);
234
auto& vo = vertexObjects[level].emplace(gl::VertexObject::Primitive::TriangleStrip);
235
vo.setCount(static_cast<int>((nSections + 1) * 2))
237
CelestiaGLProgram::TextureCoord0AttributeIndex,
239
gl::VertexObject::DataType::UnsignedShort,
242
offsetof(RingVertex, tex))
244
CelestiaGLProgram::VertexCoordAttributeIndex,
246
gl::VertexObject::DataType::Float,
249
offsetof(RingVertex, pos));
254
RingRenderer::renderLOD(unsigned int level, std::uint32_t nSections)
256
if (!vertexObjects[level].has_value())
257
initializeLOD(level, nSections);
258
glDisable(GL_CULL_FACE);
259
vertexObjects[level]->draw();
260
glEnable(GL_CULL_FACE);
263
} // end namespace celestia::render