/*******************************************************************************
* Copyright 2014 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.sun;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Matrix;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.AbstractLayer;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.OGLStackHandler;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.media.opengl.GL2;
import au.gov.ga.earthsci.worldwind.common.layers.atmosphere.Atmosphere;
import au.gov.ga.earthsci.worldwind.common.render.FrameBufferStack;
import au.gov.ga.earthsci.worldwind.common.sun.SunPositionService;
import au.gov.ga.earthsci.worldwind.common.view.delegate.IDelegateView;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;
import com.jogamp.opengl.util.texture.awt.AWTTextureIO;
/**
* Layer that renders a sun, with lens flare, glow, halo, and dirty lens
* effects.
* <p/>
* Based on the code by Michal Belanec <a
* href="http://www.belanecbn.sk/3dtutorials/index.php?id=7">here</a>.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class SunLayer extends AbstractLayer
{
protected final float sunSize = 20;
protected boolean inited = false;
protected boolean creationFailed = false;
protected Dimension size;
protected Dimension stageTextureSize;
protected Texture circleTexture;
protected Texture dirtTexture;
protected final int[] depthTexture = new int[1];
protected final int[] stageTextures = new int[3];
protected final int[] fbo = new int[1];
protected final SunDepthTestShader sunDepthTestShader = new SunDepthTestShader();
protected final SunRaysLensFlareHaloShader sunRaysLensFlareHaloShader = new SunRaysLensFlareHaloShader();
protected final BlurHorizontalShader blurHorizontalShader = new BlurHorizontalShader();
protected final BlurVerticalShader blurVerticalShader = new BlurVerticalShader();
@Override
protected void doRender(DrawContext dc)
{
if (!inited)
{
init(dc);
inited = true;
}
if (creationFailed)
{
return;
}
resize(dc);
renderSun(dc);
}
protected void init(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2();
//load the textures
try
{
BufferedImage circleImage = ImageIO.read(SunLayer.class.getResourceAsStream("circle.png")); //$NON-NLS-1$
TextureData td = AWTTextureIO.newTextureData(gl.getGLProfile(), circleImage, true);
circleTexture = TextureIO.newTexture(td);
BufferedImage dirtImage = ImageIO.read(SunLayer.class.getResourceAsStream("dirtylens.jpg")); //$NON-NLS-1$
td = AWTTextureIO.newTextureData(gl.getGLProfile(), dirtImage, false);
dirtTexture = TextureIO.newTexture(td);
}
catch (IOException e)
{
e.printStackTrace();
}
//generate textures and a frame buffer object
gl.glGenTextures(depthTexture.length, depthTexture, 0);
gl.glGenTextures(stageTextures.length, stageTextures, 0);
gl.glGenFramebuffers(fbo.length, fbo, 0);
//compile the shaders
creationFailed = !(sunDepthTestShader.create(gl) &&
sunRaysLensFlareHaloShader.create(gl) &&
blurHorizontalShader.create(gl) && blurVerticalShader.create(gl));
}
protected void resize(DrawContext dc)
{
View view = dc.getView();
Rectangle viewport = view.getViewport();
Dimension size = viewport.getSize();
if (size.equals(this.size))
{
return;
}
this.size = size;
GL2 gl = dc.getGL().getGL2();
//generate a depth texture for copying the current scene's depth buffer to
gl.glBindTexture(GL2.GL_TEXTURE_2D, depthTexture[0]);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_DEPTH_COMPONENT24, size.width, size.height, 0,
GL2.GL_DEPTH_COMPONENT, GL2.GL_FLOAT, null);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
//generate the sun staging textures at half-res
stageTextureSize = new Dimension(size.width / 2, size.height / 2);
for (int i = 0; i < stageTextures.length; i++)
{
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[i]);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA8, stageTextureSize.width, stageTextureSize.height, 0,
GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, null);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
}
}
protected void renderSun(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2();
//calculate view rotation
Vec4 up = dc.getView().getUpVector();
Vec4 f = dc.getView().getForwardVector();
Vec4 s = f.cross3(up);
Vec4 u = s.cross3(f);
Matrix viewRotation = new Matrix(
s.x, s.y, s.z, 0.0,
u.x, u.y, u.z, 0.0,
-f.x, -f.y, -f.z, 0.0,
0.0, 0.0, 0.0, 1.0);
//compute modelview matrix by combining view rotation and light direction rotation
Vec4 lightDirection = SunPositionService.INSTANCE.getDirection(dc.getView());
Angle lightAngle = Vec4.UNIT_Z.angleBetween3(lightDirection);
Vec4 lightAxis = Vec4.UNIT_Z.cross3(lightDirection);
Matrix lightRotation = Matrix.fromAxisAngle(lightAngle, lightAxis);
Matrix modelview = viewRotation.multiply(lightRotation);
//compute the view's projection matrix
IDelegateView view = (IDelegateView) dc.getView();
Matrix projection = view.computeProjection(0.1, 1.5);
//project the sun's direction onto the view plane using gluProject
Rectangle viewport = view.getViewport();
double[] modelviewArray = new double[16];
double[] projectionArray = new double[16];
modelview.toArray(modelviewArray, 0, false);
projection.toArray(projectionArray, 0, false);
int[] viewportArray = new int[] { 0, 0, viewport.width, viewport.height };
double[] result = new double[3];
dc.getGLU().gluProject(0.0, 0.0, 1.0, modelviewArray, 0, projectionArray, 0, viewportArray, 0, result, 0);
//calculate sun screen position in screen coordinates
double projectedSunPosX = result[0] / viewport.width;
double projectedSunPosY = result[1] / viewport.height;
double sunWidth = 0.5 * sunSize / viewport.width;
double sunHeight = 0.5 * sunSize / viewport.height;
double x1 = projectedSunPosX - sunWidth;
double x2 = projectedSunPosX + sunWidth;
double y1 = projectedSunPosY - sunHeight;
double y2 = projectedSunPosY + sunHeight;
if (!(0 <= x2 && x1 <= 1 && 0 <= y2 && y1 <= 1 && 0 <= result[2] && result[2] <= 1))
{
//sun not within viewport, so don't render
return;
}
double directionScale = dc.getGlobe().getRadius() * dc.getGlobe().getRadius();
Color sunColor = Atmosphere.getSpaceObjectColor(dc, lightDirection.multiply3(directionScale));
OGLStackHandler ogsh = new OGLStackHandler();
try
{
//switch to ortho mode
ogsh.pushModelviewIdentity(gl);
ogsh.pushProjectionIdentity(gl);
ogsh.pushAttrib(gl, GL2.GL_TEXTURE_BIT | GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT
| GL2.GL_VIEWPORT_BIT);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glOrthof(0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
//copy depth buffer to texture
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, depthTexture[0]);
gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, viewport.x, viewport.y, size.width, size.height);
//render the sun circle texture
gl.glViewport(0, 0, stageTextureSize.width, stageTextureSize.height);
FrameBufferStack.push(gl, fbo[0]);
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[0], 0);
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_TEXTURE_2D, 0, 0);
gl.glClear(GL2.GL_COLOR_BUFFER_BIT); //clear the frame buffer
gl.glEnable(GL2.GL_BLEND);
gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glColor3d(sunColor.getRed() / 255.0, sunColor.getGreen() / 255.0, sunColor.getBlue() / 255.0);
circleTexture.bind(gl);
gl.glBegin(GL2.GL_QUADS);
{
gl.glTexCoord2f(0, 0);
gl.glVertex3d(x1, y1, -1);
gl.glTexCoord2f(1, 0);
gl.glVertex3d(x2, y1, -1);
gl.glTexCoord2f(1, 1);
gl.glVertex3d(x2, y2, -1);
gl.glTexCoord2f(0, 1);
gl.glVertex3d(x1, y2, -1);
}
gl.glEnd();
gl.glColor3d(1.0, 1.0, 1.0);
//test if sun sphere is behind scene geometry
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[1], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[0]);
gl.glActiveTexture(GL2.GL_TEXTURE1);
gl.glBindTexture(GL2.GL_TEXTURE_2D, depthTexture[0]);
sunDepthTestShader.use(gl);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
sunDepthTestShader.unuse(gl);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
//blur sun sphere horizontally (low)
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[0], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[1]);
blurHorizontalShader.use(gl, 1, 1.0f / stageTextureSize.width);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
blurHorizontalShader.unuse(gl);
//blur sun sphere vertically (low)
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[2], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[0]);
blurVerticalShader.use(gl, 1, 1.0f / stageTextureSize.height);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
blurVerticalShader.unuse(gl);
//blur sun sphere horizontally (high)
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[0], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[1]);
blurHorizontalShader.use(gl, 10, 1.0f / stageTextureSize.width);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
blurHorizontalShader.unuse(gl);
//blur sun sphere vertically (high)
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[1], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[0]);
blurVerticalShader.use(gl, 10, 1.0f / stageTextureSize.height);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
blurVerticalShader.unuse(gl);
//blur sun sphere radially and calculate lens flare and halo and apply dirt texture
gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D,
stageTextures[0], 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[2]);
gl.glActiveTexture(GL2.GL_TEXTURE1);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[1]);
gl.glActiveTexture(GL2.GL_TEXTURE2);
gl.glBindTexture(GL2.GL_TEXTURE_2D, dirtTexture.getTextureObject(gl));
sunRaysLensFlareHaloShader.use(gl, (float) projectedSunPosX, (float) projectedSunPosY);
gl.glBegin(GL2.GL_QUADS);
{
gl.glVertex2f(0.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
sunRaysLensFlareHaloShader.unuse(gl);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
gl.glActiveTexture(GL2.GL_TEXTURE1);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
//unbind frame buffer and reset viewport
FrameBufferStack.pop(gl);
gl.glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
//render the final composited sun texture to the screen
gl.glDepthMask(false);
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glBindTexture(GL2.GL_TEXTURE_2D, stageTextures[0]);
gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE_MINUS_SRC_COLOR);
gl.glEnable(GL2.GL_BLEND);
gl.glColor3f(1.0f, 1.0f, 1.0f);
gl.glBegin(GL2.GL_QUADS);
{
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex2f(0.0f, 0.0f);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex2f(1.0f, 0.0f);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex2f(1.0f, 1.0f);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex2f(0.0f, 1.0f);
}
gl.glEnd();
gl.glDisable(GL2.GL_BLEND);
gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
gl.glDisable(GL2.GL_TEXTURE_2D);
gl.glDepthMask(true);
}
finally
{
ogsh.pop(gl);
}
}
@Override
public String toString()
{
return "Sun";
}
}