I am developing a stellar system viewer and I have problems writing shaders to render light casted by the star on the planets (or moons or whatever are the celestial bodies involved, but I'll often say planets for convenience).
Currently planets are only colored spheres, so either the pixel of the planet is on the sunny side and it's the color of the planet, either it's black (no ambiant light involved, very straightforward).
Why do I have to write shaders to do that you say ? Here's the trick : the distances involved in the rendering of a stellar system are huge, so to see every planet from a distance in an engine like Irrlicht I have to do two things :
1)Force camera to be on (0, 0, 0) but compute its "position" anyway
2)Use camera's fake "position" to scale every planet (and the star) down and make them closer to (0,0,0) so that their viewing angle stays constant and they seem far away even if they are in front of your eyes as tiny balls.
That way I can render a stellar system to scale without any visible precision problem.
Now as I have to scale down the star and put it closer to the (0,0,0) too, angles between planets, star and camera are totally messed up within Irrlicht. I just can't put a LightSceneNode at the position of the star (or if I could in a way, please let me know, but I'll need shaders for atmospheres anyway so I'd better get used to it now). That's why I am writing a shader that could take a fake star position on the fly for every planet (we are scaling the fake star down by the same amount than the planet, so that angles are preserved).
Now that I put the context, here is my implementation that doesn't work (it's inspired from the Irrlicht Tutorial 10 about shaders) :
Code: Select all
void FarCelestialBodyShader::OnSetConstants(
irr::video::IMaterialRendererServices* services, irr::s32 userData)
{
(void) userData;
irr::video::IVideoDriver* driver = services->getVideoDriver();
//I should point out at this moment that I have no idea what I'm doing with this matrix
//in OpenGL I'd use a ModelView matrix but it seems to work even worse in Irrlicht so I kept this inverse model matrix as it provides the closest result to what I need but I don't know why it would work
//(it's used in the tutorial, that's why I use it)
core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
invWorld.makeInverse();
services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);
core::matrix4 worldViewProj;
worldViewProj = driver->getTransform(video::ETS_PROJECTION);
worldViewProj *= driver->getTransform(video::ETS_VIEW);
worldViewProj *= driver->getTransform(video::ETS_WORLD);
services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(),
16);
//parameters and currentParameter are my way to specify which body we are rendering, currentParameter is an int set in OnSetMaterial()
//parameters is a vector containing the shader parameters for each planet which are a structure containing Vector3 bodyCenter and Vector3 centralBodyCenter (which are respectively the planet center position in irrlicht and the "fake star" position in Irrlicht coordinates also, see further how they are computed, the error could come out of that)
core::vector3df bodyCenter = toIrrlicht(parameters[currentParameter].bodyCenter);
core::vector3df centralBodyCenter = toIrrlicht(parameters[currentParameter].centralBodyCenter);
services->setVertexShaderConstant("bodyCenter", reinterpret_cast<f32*>(&bodyCenter), 3);
services->setVertexShaderConstant("centralBodyCenter", reinterpret_cast<f32*>(¢ralBodyCenter), 3);
}
void FarCelestialBodyShader::OnSetMaterial(irr::video::SMaterial const& material)
{
currentParameter = material.MaterialTypeParam2;
}
Code: Select all
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform vec3 bodyCenter;
uniform vec3 centralBodyCenter;
varying vec3 normal;
varying vec3 centralRelativePosToBody;
void main(void)
{
gl_Position = mWorldViewProj * gl_Vertex;
gl_FrontColor = gl_BackColor = gl_Color;
//why mInvWorld ?
normal = mInvWorld*vec4(gl_Normal, 0);
normal = normalize(normal);
centralRelativePosToBody = centralBodyCenter - bodyCenter;
}
Code: Select all
varying vec3 normal;
varying vec3 centralRelativePosToBody;
void main (void)
{
if(dot(centralRelativePosToBody, normal) < 0)
gl_FragColor = vec4(0, 0, 0, 0);
else
gl_FragColor = gl_Color;
}
Code: Select all
//update mesh data for a given universal time and a given camera real world position (not the Irrlicht camera position : remember, Irrlicht camera position is (0,0,0))
void CelestialBodyDrawer::updateMesh(UniversalTime uT, Vector3 const& cameraPos)
{
//Relative position of camera towards the CelestialBody,
//drawnBody is an object that compute physics, so no Irrlicht involved there
Vector3 camRelPos(drawnBody->getAbsolutePositionAtUT(uT) - cameraPos);
double camDist(camRelPos.length());
//scale at which we will reduce our planet size and distance to camera
//centerPosition is the desired Irrlicht distance to camera, it's dynamically determined in another method so that we can ensure a "z-order" between planets
double scale(centerPosition / camDist);
//Irrlicht camera being on (0,0,0), the scale transform is linear in Irrlicht coordinates from the camera relative position
mesh->setPosition(toIrrlicht(scale*camRelPos));
//ensures that we always see the celestial body no matter how far it is (in real world we can see planets very far away)
if((scale*drawnBody->getParameters().radius)/centerPosition < 0.0007)
scale = 0.0007*centerPosition/drawnBody->getParameters().radius;
//apply scale on the mesh, which original size is the true planet size (very big, but Irrlicht doesn't mind as it is always scales down)
//the mesh is only a SphereSceneNode generated by SceneManager::addSphereSceneNode() by the way
mesh->setScale(toIrrlicht(Vector3(scale, scale, scale)));
//Now we get the parameters pointer from the shader for this specific celestial body
FarCelestialBodyShader::ShaderParameters* sParams = FarCelestialBodyShader::getInstance()->getMyParameters(shaderParametersId);
//Body center is the mesh new position
Vector3 bodyCenter(scale*camRelPos);
//Star is always at (0,0,0) in my physics coordinate, so camRelPos is -1*cameraPos for it
Vector3 centralBodyCenter(-1*scale*cameraPos);
//Send parameters to the shader for next rendering
sParams->bodyCenter = bodyCenter;
sParams->centralBodyCenter = centralBodyCenter;
}
As you can see, here is a red planet with its grey moon. On the first pic, we can see the sun behind the planet and yet it is fully lit (and its back is black). Well at this time you could think I can just make it colorful were it's black and vice versa and tt wouldn't be a problem if it wasn't for the second pic : the shadow of the planet turns the wrong side (if we consider it to be the "lit" side as it's shown on the first pic) as I turn the camera around it. (As you can see the sun went right and the shadow went left). Keep in mind the star is way further than the planet is, so it couldn't possibly appear half-lit. In fact, the result we should see is an inversion of lit side/dark side and a central symmetry of the rendered planet.
So here you have pretty much everything, please tell me Anything that seems weird or wrong to you and ask for further explanations if you need it to help me.
Thank you for your help !