Gouraud-like Shader Problem

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Post Reply
Dexter9313
Posts: 3
Joined: Tue May 08, 2012 2:18 pm

Gouraud-like Shader Problem

Post by Dexter9313 »

Hello everyone !

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*>(&centralBodyCenter), 3);
}
 
void FarCelestialBodyShader::OnSetMaterial(irr::video::SMaterial const& material)
{
    currentParameter = material.MaterialTypeParam2;
}
 
Here is the vertex shader (GLSL) :

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;
}
And here is the fragment shader (GLSL) :

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;
}
Finally, here is the way I compute parameters[currentParameter].bodyCenter and parameters[currentParameter].centralBodyCenter :

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;
 
}
 
The result of this code is the following :
Image
Image


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 !
Dexter9313
Posts: 3
Joined: Tue May 08, 2012 2:18 pm

Re: Gouraud-like Shader Problem

Post by Dexter9313 »

Oddly enough, I spent two full days on this problem and a few minutes after explaining it on this forum I "solved" it.
In fact, the result we should see is an inversion of lit side/dark side and a central symmetry of the rendered planet.
So naively right after writing this I tried a color inversion and a central symmetry.

Here is what I changed in the vertex shader :

Code: Select all

gl_Position = mWorldViewProj * gl_Vertex;
became

Code: Select all

vec4 vertex = gl_Vertex;
vertex.x *= -1;
vertex.y *= -1;
vertex.z *= -1;
gl_Position = mWorldViewProj * vertex; 
And here is what changed in the fragment shader :

Code: Select all

if(dot(centralRelativePosToBody, normal) < 0)
became

Code: Select all

if(dot(centralRelativePosToBody, normal) > 0)
As my result is now correct, I am not satisfied by this hack so could someone explain to me what is going on and how I should do it properly please ?
mongoose7
Posts: 1227
Joined: Wed Apr 06, 2011 12:13 pm

Re: Gouraud-like Shader Problem

Post by mongoose7 »

centralRelativePosToBody is in object coordinates but the normal is in world coordinates. Also, the normal should be transformed by the transpose of the (world) matrix, which is the same if the matrix is a pure rotation (ignoring the translation part). But if this is true you can simply use the world matrix itself. (The problem is really scaling differently in different directions, and shearing.)
Dexter9313
Posts: 3
Joined: Tue May 08, 2012 2:18 pm

Re: Gouraud-like Shader Problem

Post by Dexter9313 »

mongoose7 wrote:centralRelativePosToBody is in object coordinates but the normal is in world coordinates. Also, the normal should be transformed by the transpose of the (world) matrix, which is the same if the matrix is a pure rotation (ignoring the translation part). But if this is true you can simply use the world matrix itself. (The problem is really scaling differently in different directions, and shearing.)
Thanks for answering mongoose7 !

So "world coordinates" are not Irrlicht object coordinates ? When I use IMeshSceneNode::setPosition(), I am using what you are calling object coordinates, that's right ? (Because centralRelativePosToBody is composed of two vectors that are related to Irrlicht absolute coordinates, bodyCenter and centralBodyCenter which are defined as the center of the bodies once they've been scaled down and repositioned for display then "passed" to setPosition() (centralBodyCenter isn't really drawn but it's the same coordinate system as bodyCenter anyway).)

Do you have mathematical explanations and/or documentation that contains the reasons I should use the transpose of the world matrix for normals please ? Because I don't get it. (I could just use it but I prefer understanding it before and the way I see it I should only use the world matrix "as is" everytime but I must be missing something.)
mongoose7
Posts: 1227
Joined: Wed Apr 06, 2011 12:13 pm

Re: Gouraud-like Shader Problem

Post by mongoose7 »

I don't know your situation. But if you parent NodeA to NodeB, then the coordinates you give to NodeA are relative to NodeB, so they are object coordinates but not world coordinates.

I am no longer sure about the transpose. Maybe it is the transpose of the inverse. Anyway, for pure rotations or uniform scaling, you can use the world matrix.

(You cannot use the inverse or the transpose of the world matrix, I think, because a matrix rotating the world must rotate the normals in the same way. Rotate the world 90 deg around the Y axis and the normals have to be rotated the same way, and certainly not 90 deg the other way. And the transpose of a rotation is the inverse. But if you consider scaling only the Z coordinate by 2, say, then the normals have to be scaled in the Z coordinate by 0.5. In the plane, the normal to y=x is y=-x. Scale by doubling the X coordinate and the line becomes y=0.5*x and the normal y=-2x. The scaling matrix is [(2 0) (0 1)], the inverse [(.5 0) (0 1)] and the transposed inverse [(.5 0) (0 1)]. So the transposed inverse works for scaling, and for rotations (as the transposed inverse is the original transformation) as well. You should be able to solve the general case with a bit of algebra.)
Post Reply