1 /*
2  * Stellarium
3  * Copyright (C) 2003 Fabien Chereau
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #include "Atmosphere.hpp"
21 #include "StelUtils.hpp"
22 #include "StelApp.hpp"
23 #include "StelProjector.hpp"
24 #include "StelToneReproducer.hpp"
25 #include "StelCore.hpp"
26 #include "StelPainter.hpp"
27 #include "StelFileMgr.hpp"
28 #include "Dithering.hpp"
29 
30 #include <QFile>
31 #include <QDebug>
32 #include <QSettings>
33 #include <QOpenGLShaderProgram>
34 
35 
Atmosphere(void)36 Atmosphere::Atmosphere(void)
37 	: viewport(0,0,0,0)
38 	, skyResolutionY(44)
39 	, skyResolutionX(44)
40 	, posGrid(Q_NULLPTR)
41 	, posGridBuffer(QOpenGLBuffer::VertexBuffer)
42 	, indicesBuffer(QOpenGLBuffer::IndexBuffer)
43 	, colorGrid(Q_NULLPTR)
44 	, colorGridBuffer(QOpenGLBuffer::VertexBuffer)
45 	, averageLuminance(0.f)
46 	, overrideAverageLuminance(false)
47 	, eclipseFactor(1.f)
48 	, lightPollutionLuminance(0)
49 {
50 	setFadeDuration(1.5f);
51 
52 	QOpenGLShader vShader(QOpenGLShader::Vertex);
53 	{
54 		QFile vert(":/shaders/atmosphere.vert");
55 		if(!vert.open(QFile::ReadOnly))
56 			qFatal("Failed to open atmosphere vertex shader source");
57 		QFile toneRepro(":/shaders/xyYToRGB.glsl");
58 		if(!toneRepro.open(QFile::ReadOnly))
59 			qFatal("Failed to open ToneReproducer shader source");
60 		if (!vShader.compileSourceCode(vert.readAll()+toneRepro.readAll()))
61 			qFatal("Error while compiling atmosphere vertex shader: %s", vShader.log().toLatin1().constData());
62 	}
63 	if (!vShader.log().isEmpty())
64 	{
65 		qWarning() << "Warnings while compiling atmosphere vertex shader: " << vShader.log();
66 	}
67 	QOpenGLShader fShader(QOpenGLShader::Fragment);
68 	if (!fShader.compileSourceCode(
69 					makeDitheringShader()+
70 					"varying mediump vec3 resultSkyColor;\n"
71 					"void main()\n"
72 					"{\n"
73 					 "   gl_FragColor = vec4(dither(resultSkyColor), 1.);\n"
74 					 "}"))
75 	{
76 		qFatal("Error while compiling atmosphere fragment shader: %s", fShader.log().toLatin1().constData());
77 	}
78 	if (!fShader.log().isEmpty())
79 	{
80 		qWarning() << "Warnings while compiling atmosphere fragment shader: " << vShader.log();
81 	}
82 	atmoShaderProgram = new QOpenGLShaderProgram();
83 	atmoShaderProgram->addShader(&vShader);
84 	atmoShaderProgram->addShader(&fShader);
85 	StelPainter::linkProg(atmoShaderProgram, "atmosphere");
86 
87 	atmoShaderProgram->bind();
88 	shaderAttribLocations.bayerPattern = atmoShaderProgram->uniformLocation("bayerPattern");
89 	shaderAttribLocations.rgbMaxValue = atmoShaderProgram->uniformLocation("rgbMaxValue");
90 	shaderAttribLocations.alphaWaOverAlphaDa = atmoShaderProgram->uniformLocation("alphaWaOverAlphaDa");
91 	shaderAttribLocations.oneOverGamma = atmoShaderProgram->uniformLocation("oneOverGamma");
92 	shaderAttribLocations.term2TimesOneOverMaxdLpOneOverGamma = atmoShaderProgram->uniformLocation("term2TimesOneOverMaxdLpOneOverGamma");
93 	shaderAttribLocations.brightnessScale = atmoShaderProgram->uniformLocation("brightnessScale");
94 	shaderAttribLocations.sunPos = atmoShaderProgram->uniformLocation("sunPos");
95 	shaderAttribLocations.term_x = atmoShaderProgram->uniformLocation("term_x");
96 	shaderAttribLocations.Ax = atmoShaderProgram->uniformLocation("Ax");
97 	shaderAttribLocations.Bx = atmoShaderProgram->uniformLocation("Bx");
98 	shaderAttribLocations.Cx = atmoShaderProgram->uniformLocation("Cx");
99 	shaderAttribLocations.Dx = atmoShaderProgram->uniformLocation("Dx");
100 	shaderAttribLocations.Ex = atmoShaderProgram->uniformLocation("Ex");
101 	shaderAttribLocations.term_y = atmoShaderProgram->uniformLocation("term_y");
102 	shaderAttribLocations.Ay = atmoShaderProgram->uniformLocation("Ay");
103 	shaderAttribLocations.By = atmoShaderProgram->uniformLocation("By");
104 	shaderAttribLocations.Cy = atmoShaderProgram->uniformLocation("Cy");
105 	shaderAttribLocations.Dy = atmoShaderProgram->uniformLocation("Dy");
106 	shaderAttribLocations.Ey = atmoShaderProgram->uniformLocation("Ey");
107 	shaderAttribLocations.projectionMatrix = atmoShaderProgram->uniformLocation("projectionMatrix");
108 	shaderAttribLocations.skyVertex = atmoShaderProgram->attributeLocation("skyVertex");
109 	shaderAttribLocations.skyColor = atmoShaderProgram->attributeLocation("skyColor");
110 	atmoShaderProgram->release();
111 }
112 
~Atmosphere(void)113 Atmosphere::~Atmosphere(void)
114 {
115 	delete [] posGrid;
116 	posGrid = Q_NULLPTR;
117 	delete[] colorGrid;
118 	colorGrid = Q_NULLPTR;
119 	delete atmoShaderProgram;
120 	atmoShaderProgram = Q_NULLPTR;
121 }
122 
computeColor(double JD,Vec3d _sunPos,Vec3d moonPos,float moonPhase,float moonMagnitude,StelCore * core,float latitude,float altitude,float temperature,float relativeHumidity)123 void Atmosphere::computeColor(double JD, Vec3d _sunPos, Vec3d moonPos, float moonPhase, float moonMagnitude,
124 							   StelCore* core, float latitude, float altitude, float temperature, float relativeHumidity)
125 {
126 	const StelProjectorP prj = core->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff);
127 	if (viewport != prj->getViewport())
128 	{
129 		// The viewport changed: update the number of point of the grid
130 		viewport = prj->getViewport();
131 		delete[] colorGrid;
132 		delete [] posGrid;
133 		skyResolutionY = StelApp::getInstance().getSettings()->value("landscape/atmosphereybin", 44).toUInt();
134 		skyResolutionX = static_cast<unsigned int>(floorf(0.5f+skyResolutionY*(0.5f*sqrtf(3.0f))*prj->getViewportWidth()/prj->getViewportHeight()));
135 		posGrid = new Vec2f[static_cast<size_t>((1+skyResolutionX)*(1+skyResolutionY))];
136 		colorGrid = new Vec4f[static_cast<size_t>((1+skyResolutionX)*(1+skyResolutionY))];
137 		float stepX = static_cast<float>(prj->getViewportWidth()) / static_cast<float>(skyResolutionX-0.5f);
138 		float stepY = static_cast<float>(prj->getViewportHeight()) / skyResolutionY;
139 		float viewport_left = prj->getViewportPosX();
140 		float viewport_bottom = prj->getViewportPosY();
141 		for (unsigned int x=0; x<=skyResolutionX; ++x)
142 		{
143 			for(unsigned int y=0; y<=skyResolutionY; ++y)
144 			{
145 				Vec2f &v(posGrid[y*(1+skyResolutionX)+x]);
146 				v[0] = viewport_left + ((x == 0) ? 0.f :
147 						(x == skyResolutionX) ? prj->getViewportWidth() : (x-0.5f*(y&1))*stepX);
148 				v[1] = viewport_bottom+y*stepY;
149 			}
150 		}
151 		posGridBuffer.destroy();
152 		//posGridBuffer = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
153 		Q_ASSERT(posGridBuffer.type()==QOpenGLBuffer::VertexBuffer);
154 		posGridBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
155 		posGridBuffer.create();
156 		posGridBuffer.bind();
157 		posGridBuffer.allocate(posGrid, static_cast<int>((1+skyResolutionX)*(1+skyResolutionY))*8);
158 		posGridBuffer.release();
159 
160 		// Generate the indices used to draw the quads
161 		unsigned short* indices = new unsigned short[static_cast<size_t>((skyResolutionX+1)*skyResolutionY*2)];
162 		int i=0;
163 		for (unsigned int y2=0; y2<skyResolutionY; ++y2)
164 		{
165 			unsigned short g0 = static_cast<unsigned short>(y2*(1+skyResolutionX));
166 			unsigned short g1 = static_cast<unsigned short>((y2+1)*(1+skyResolutionX));
167 			for (unsigned int x2=0; x2<=skyResolutionX; ++x2)
168 			{
169 				indices[i++]=g0++;
170 				indices[i++]=g1++;
171 			}
172 		}
173 		indicesBuffer.destroy();
174 		//indicesBuffer = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
175 		Q_ASSERT(indicesBuffer.type()==QOpenGLBuffer::IndexBuffer);
176 		indicesBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
177 		indicesBuffer.create();
178 		indicesBuffer.bind();
179 		indicesBuffer.allocate(indices, static_cast<int>((skyResolutionX+1)*skyResolutionY*2*2));
180 		indicesBuffer.release();
181 		delete[] indices;
182 		indices=Q_NULLPTR;
183 
184 		colorGridBuffer.destroy();
185 		colorGridBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
186 		colorGridBuffer.create();
187 		colorGridBuffer.bind();
188 		colorGridBuffer.allocate(colorGrid, static_cast<int>((1+skyResolutionX)*(1+skyResolutionY)*4*4));
189 		colorGridBuffer.release();
190 	}
191 
192 	if (qIsNaN(_sunPos.length()))
193 		_sunPos.set(0.,0.,-1.*AU);
194 	if (qIsNaN(moonPos.length()))
195 		moonPos.set(0.,0.,-1.*AU);
196 
197 	// Update the eclipse intensity factor to apply on atmosphere model
198 	// these are for radii
199 	const double sun_angular_size = atan(696000./AU/_sunPos.length());
200 	const double moon_angular_size = atan(1738./AU/moonPos.length());
201 	const double touch_angle = sun_angular_size + moon_angular_size;
202 
203 	// determine luminance falloff during solar eclipses
204 	_sunPos.normalize();
205 	moonPos.normalize();
206 	// Calculate the atmosphere RGB for each point of the grid. We can use abbreviated numbers here.
207 	Vec3f sunPosF=_sunPos.toVec3f();
208 	Vec3f moonPosF=moonPos.toVec3f();
209 
210 	double separation_angle = std::acos(_sunPos.dot(moonPos));  // angle between them
211 	// qDebug("touch at %f\tnow at %f (%f)\n", touch_angle, separation_angle, separation_angle/touch_angle);
212 	// bright stars should be visible at total eclipse
213 	// TODO: correct for atmospheric diffusion
214 	// TODO: use better coverage function (non-linear)
215 	// because of above issues, this algorithm darkens more quickly than reality
216 	// Note: On Earth only, else moon would brighten other planets' atmospheres (LP:1673283)
217 	if ((core->getCurrentLocation().planetName=="Earth") && (separation_angle < touch_angle))
218 	{
219 		double dark_angle = moon_angular_size - sun_angular_size;
220 		double min = 0.0025; // 0.005f; // 0.0001f;  // so bright stars show up at total eclipse
221 		if (dark_angle < 0.)
222 		{
223 			// annular eclipse
224 			double asun = sun_angular_size*sun_angular_size;
225 			min = (asun - moon_angular_size*moon_angular_size)/asun;  // minimum proportion of sun uncovered
226 			dark_angle *= -1;
227 		}
228 
229 		if (separation_angle < dark_angle)
230 			eclipseFactor = static_cast<float>(min);
231 		else
232 			eclipseFactor = static_cast<float>(min + (1.0-min)*(separation_angle-dark_angle)/(touch_angle-dark_angle));
233 	}
234 	else
235 		eclipseFactor = 1.f;
236 	// TODO: compute eclipse factor also for Lunar eclipses! (lp:#1471546)
237 
238 	// No need to calculate if not visible
239 	if (!fader.getInterstate())
240 	{
241 		// GZ 20180114: Why did we add light pollution if atmosphere was not visible?????
242 		// And what is the meaning of 0.001? Approximate contribution of stellar background? Then why is it 0.0001 below???
243 		averageLuminance = 0.001f;
244 		return;
245 	}
246 
247 	sky.setParamsv(sunPosF, 5.f);
248 	skyb.setLocation(latitude * M_PI_180f, altitude, temperature, relativeHumidity);
249 	skyb.setSunMoon(moonPosF[2], sunPosF[2]);
250 
251 	// Calculate the date from the julian day.
252 	int year, month, day;
253 	StelUtils::getDateFromJulianDay(JD, &year, &month, &day);
254 	skyb.setDate(year, month, moonPhase, moonMagnitude);
255 
256 	// Variables used to compute the average sky luminance
257 	float sum_lum = 0.f;
258 
259 	Vec3d point(1., 0., 0.);
260 	float lumi;
261 
262 	// Compute the sky color for every point above the ground
263 	for (unsigned int i=0; i<(1+skyResolutionX)*(1+skyResolutionY); ++i)
264 	{
265 		const Vec2f &v(posGrid[i]);
266 		prj->unProject(static_cast<double>(v[0]),static_cast<double>(v[1]),point);
267 
268 		Q_ASSERT(fabs(point.lengthSquared()-1.0) < 1e-10);
269 
270 		Vec3f pointF=point.toVec3f();
271 		// Use mirroring for sun only
272 		if (pointF[2]<=0.f)
273 		{
274 			pointF[2] *= -1.f;
275 			// The sky below the ground is the symmetric of the one above :
276 			// it looks nice and gives proper values for brightness estimation
277 			// Use the Skybright.cpp 's models for brightness which gives better results.
278 		}
279 		lumi = skyb.getLuminance(moonPosF[0]*pointF[0]+moonPosF[1]*pointF[1]+
280 				moonPosF[2]*pointF[2], sunPosF[0]*pointF[0]+sunPosF[1]*pointF[1]+
281 				sunPosF[2]*pointF[2], pointF[2]);
282 		lumi *= eclipseFactor;
283 		// Add star background luminance
284 		lumi += 0.0001f;
285 		// Multiply by the input scale of the ToneConverter (is not done automatically by the xyYtoRGB method called later)
286 		//lumi*=eye->getInputScale();
287 
288 		// Add the light pollution luminance AFTER the scaling to avoid scaling it because it is the cause
289 		// of the scaling itself
290 		lumi += fader.getInterstate()*lightPollutionLuminance;
291 
292 		// Store for later statistics
293 		sum_lum+=lumi;
294 
295 		// Now need to compute the xy part of the color component
296 		// This is done in the openGL shader
297 		// Store the back projected position + luminance in the input color to the shader
298 		colorGrid[i].set(pointF[0], pointF[1], pointF[2], lumi);
299 	}
300 
301 	colorGridBuffer.bind();
302 	colorGridBuffer.write(0, colorGrid, static_cast<int>((1+skyResolutionX)*(1+skyResolutionY)*4*4));
303 	colorGridBuffer.release();
304 
305 	// Update average luminance
306 	if (!overrideAverageLuminance)
307 		averageLuminance = sum_lum/((1+skyResolutionX)*(1+skyResolutionY));
308 }
309 
310 // override computable luminance. This is for special operations only, e.g. for scripting of brightness-balanced image export.
311 // To return to auto-computed values, set any negative value.
setAverageLuminance(float overrideLum)312 void Atmosphere::setAverageLuminance(float overrideLum)
313 {
314 	if (overrideLum<0.f)
315 	{
316 		overrideAverageLuminance=false;
317 		averageLuminance=0.f;
318 	}
319 	else
320 	{
321 		overrideAverageLuminance=true;
322 		averageLuminance=overrideLum;
323 	}
324 }
325 
326 // Draw the atmosphere using the precalc values stored in tab_sky
draw(StelCore * core)327 void Atmosphere::draw(StelCore* core)
328 {
329 	if (StelApp::getInstance().getVisionModeNight())
330 		return;
331 
332 	StelToneReproducer* eye = core->getToneReproducer();
333 
334 	if (!fader.getInterstate())
335 		return;
336 
337 	StelPainter sPainter(core->getProjection2d());
338 	sPainter.setBlending(true, GL_ONE, GL_ONE);
339 
340 	const float atm_intensity = fader.getInterstate();
341 
342 	GL(atmoShaderProgram->bind());
343 	float a, b, c;
344 	GL(eye->getShadersParams(a, b, c));
345 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.alphaWaOverAlphaDa, a));
346 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.oneOverGamma, b));
347 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.term2TimesOneOverMaxdLpOneOverGamma, c));
348 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.brightnessScale, atm_intensity));
349 	Vec3f sunPos;
350 	float term_x, Ax, Bx, Cx, Dx, Ex, term_y, Ay, By, Cy, Dy, Ey;
351 	sky.getShadersParams(sunPos, term_x, Ax, Bx, Cx, Dx, Ex, term_y, Ay, By, Cy, Dy, Ey);
352 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.sunPos, sunPos[0], sunPos[1], sunPos[2]));
353 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.term_x, term_x));
354 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Ax, Ax));
355 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Bx, Bx));
356 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Cx, Cx));
357 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Dx, Dx));
358 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Ex, Ex));
359 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.term_y, term_y));
360 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Ay, Ay));
361 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.By, By));
362 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Cy, Cy));
363 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Dy, Dy));
364 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.Ey, Ey));
365 	const Mat4f& m = sPainter.getProjector()->getProjectionMatrix();
366 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.projectionMatrix,
367 					      QMatrix4x4(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15])));
368 
369 	const auto rgbMaxValue=calcRGBMaxValue(sPainter.getDitheringMode());
370 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.rgbMaxValue, rgbMaxValue[0], rgbMaxValue[1], rgbMaxValue[2]));
371 	auto& gl=*sPainter.glFuncs();
372 	gl.glActiveTexture(GL_TEXTURE1);
373 	if(!bayerPatternTex)
374 		bayerPatternTex=makeBayerPatternTexture(*sPainter.glFuncs());
375 	gl.glBindTexture(GL_TEXTURE_2D, bayerPatternTex);
376 	GL(atmoShaderProgram->setUniformValue(shaderAttribLocations.bayerPattern, 1));
377 
378 	GL(colorGridBuffer.bind());
379 	GL(atmoShaderProgram->setAttributeBuffer(shaderAttribLocations.skyColor, GL_FLOAT, 0, 4, 0));
380 	GL(colorGridBuffer.release());
381 	GL(atmoShaderProgram->enableAttributeArray(shaderAttribLocations.skyColor));
382 	GL(posGridBuffer.bind());
383 	GL(atmoShaderProgram->setAttributeBuffer(shaderAttribLocations.skyVertex, GL_FLOAT, 0, 2, 0));
384 	GL(posGridBuffer.release());
385 	GL(atmoShaderProgram->enableAttributeArray(shaderAttribLocations.skyVertex));
386 
387 	// And draw everything at once
388 	GL(indicesBuffer.bind());
389 	std::size_t shift=0;
390 	for (unsigned int y=0;y<skyResolutionY;++y)
391 	{
392 		sPainter.glFuncs()->glDrawElements(GL_TRIANGLE_STRIP, static_cast<int>((skyResolutionX+1)*2), GL_UNSIGNED_SHORT, reinterpret_cast<void*>(shift));
393 		shift += static_cast<size_t>((skyResolutionX+1)*2*2);
394 	}
395 	GL(indicesBuffer.release());
396 
397 	GL(atmoShaderProgram->disableAttributeArray(shaderAttribLocations.skyVertex));
398 	GL(atmoShaderProgram->disableAttributeArray(shaderAttribLocations.skyColor));
399 	GL(atmoShaderProgram->release());
400 	// GZ: debug output
401 	//const StelProjectorP prj = core->getProjection(StelCore::FrameEquinoxEqu);
402 	//StelPainter painter(prj);
403 	//painter.setFont(font);
404 	//sPainter.setColor(0.7, 0.7, 0.7);
405 	//sPainter.drawText(83, 120, QString("Atmosphere::getAverageLuminance(): %1" ).arg(getAverageLuminance()));
406 	//qDebug() << atmosphere->getAverageLuminance();
407 }
408