I am trying to recreate something similar to the Github Globe. I’m wondering what the best approach would be to replicate this atmospheric glow effect:
I’d try having two spheres, one for the Earth and the other for the atmosphere.
Then you need to write a custom fragment shader.
Calculate the dot product of the normal and vector that goes from the camera to the current point. The dot product is equal to 0 when the vectors are orthogonal ( perpendicular), so the closer it gets to 0 the closer your point is to the edge of the sphere.
Then you use that to determine the colour of the point.
Thanks @Arthur, really appreciate the response. I was able to copy over that example into a new codepen. This is close to producing the effect I want but the custom shader does not reflect light as I would expect. The atmospheric glow is currently uniform around the sphere. It does not change based on the directional light source coming from the top left.
Ideally it would look more like the example below, where the glow is stronger on the left due to the light source and practically non-existent on the lower right where there is no light.
All lights in the scene can be passed in as uniforms if you enable the lights on your shader material. Then you can get the lights’ position, etc. I would recommend diving into how the shader is implemented for the physical material to learn how to use these.
I modified your shader a bit to illustrate how the light’s position can be taken into account. Here I just hardcoded the position of the light (which might be good enough)
The variable dotNL is a value between 0 and 1 where 1 means that the varying normal of the fragment is directly facing the light source. By multiplying this with the intensity, it will only glow where the normals are facing the light.
Thanks @adamringhede for the code snipped. It helped me to get a grasp on basics of atmospheric shaders and was a basis for a working example in my case.
It works like a charm as long as the camera is pointing towards the center of the sphere.
If you rotate the camera with respect to the current camera position (i.e. not around the sphere), the intensity distribution rotates along - which is something we don’t want.
I modified your example adding an additional parameter: ‘camPosToVertexDir’ which is the vector from the camera position to the current vertex position.
var vertexShader = [
'varying float intensity ;',
'uniform vec3 lightSourcePos;',
'uniform vec3 camPos;',
// M_model: transform from model (local) coordinates to world coordinates
// M_view: tramsform from world to camera coordinates (looking from -z to 0,0,0)
// M_proj: transform to clip space; distant obj will appear smaller, obj out of boundary will be clipped
// pos_view = M_project x M_view x M_model
'void main() {',
'vec3 vNormal = normalize( normalMatrix * normal );',
'vec4 viewLightPos = modelViewMatrix * vec4(lightSourcePos, 1.0);', // pos of light source
'vec4 viewCamPos = viewMatrix * vec4(camPos, 1.0);',
'vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);',
'vec3 camPosToVertexDir = normalize(viewCamPos.xyz - vViewPosition4.xyz);',
'vec3 lightDir = normalize(viewLightPos.xyz - vViewPosition4.xyz) ;',
'float lightsourceIntensity = clamp(dot(lightDir, vNormal) + 1.0, 0.0, 1.0);', //lightsource facing surface has higher intensity
'intensity = pow( 0.7 - dot(vNormal, camPosToVertexDir), 12.0 ) * lightsourceIntensity;',//intensity is highest at faces orthogonal to cam pos-vertex direction
'gl_Position = projectionMatrix * vViewPosition4;',
'vec3 vPosition = gl_Position.xyz;',
'}'
].join('\n')
var fragmentShader: [
'varying float intensity ;',
'void main() {',
'vec3 glow = vec3(0.3, 0.6, 1.0) * intensity*0.3;',
'gl_FragColor = vec4( glow, 1.0 ) ;',
'}'
].join('\n')