Rendering - Space Place

Video link: Rendering

Languages: C++, GLSL (Custom Engine). Grade: 92%.

About

For this module, I put together a space scene and planet surface scene using OpenGL. I implemented a variety of graphical techniques using the knowledge gained from tutorials and assistance from lecturers. Some of the basic graphical techniques that were covered within lecture material, which you can see within this project, include:


GitHub repo at: https://github.com/JunkyX1122/GraphicsCourse.
Majority of code that isn’t tutorial code can be found in the ‘Blank Project’ and ‘Shaders’ folders.

The Cooler Parts

Despite this project being my first time propperly working with the graphics pipeline and lower-level rendering, I already had many ideas as to how to enhance the scene. I really like visual flare. This includes:

Camera Work

The part of the project I am proud of the most. I introduced movement smoothening by interpolating the camera’s current position and its destination. I also applied this same logic to the yaw, pitch and roll of the camera.

    float dampenCam = 0.01f;
    float dampenRot = 0.01f;
    position = position * (1.0f - dampenCam) + positionSetter * dampenCam;
    pitch = pitch * (1.0f - dampenRot) + rotationsSetter.x * dampenRot;
    yaw = yaw * (1.0f - dampenRot) + rotationsSetter.y * dampenRot;
    roll = roll * (1.0f - dampenRot) + rotationsSetter.z * dampenRot;

Afterwards, I created a basic keyframing system to interpolate the target destination of the camera from one state to another.

    void Renderer::UpdateCameraMovementPlanet(float dt)
    {
        ...
        Vector3 pos1 = cameraPositions_Planet[currentKeyFrame];
        Vector3 pos2 = cameraPositions_Planet[nextKeyFrame];
        Vector3 posTrans = Vector3(
            naive_lerp(pos1.x, pos2.x, cameraTimer),
            naive_lerp(pos1.y, pos2.y, cameraTimer),
            naive_lerp(pos1.z, pos2.z, cameraTimer)
        );
        camera->SetPositionSetter(posTrans);
        ...
    }
    void Renderer::AddCameraKeyFrame(Vector3 pos, Vector3 rot, float timeToReach)
    {
        cameraPositions_Planet.push_back(pos);
        cameraRotations_Planet.push_back(rot);
        cameraKeyTimes_Planet.push_back(timeToReach);
    }

And with that I am able to achive a cinematic fly-through of the scene.


As seen in the video at the start of this page, this whole process allows me to sync the movement up to music.

Scrolling Spherical Texture

With the code provided by the tutorials, it was simple to create a scrolling texture on a quad. However, this logic was not applicable to the planets in my scene due to their spherical shape. To get a texture to scroll longitudinally and latitudinally, I had to create a sepperate fragment shader for the planets where the UV coordinates were transformed to be in a longitude-latitude.

    ...
    vec2 longitudeLatitude = 
        vec2(
            (atan(IN.texCoord.y, IN.texCoord.x) / 3.1415926 + 1.0) * 0.5,
            (asin(IN.texCoord.z) / 3.1415926 + 0.5)
            );

    vec2 texCoordsTransformed = (longitudeLatitude * transformValues.xy) // Scaling
                                + transformValues.zw; // Scrolling
    vec4 diffuse = texture(diffuseTex, longitudeLatitude);
    vec4 diffuse2 = texture(diffuseTex2, texCoordsTransformed);
    vec3 bumpNormal = texture(bumpTex, longitudeLatitude).rgb;
    ...

Cloud scrolling sped up for demonstration purposes.

Bloom Post Processing

The concept of post processing was introduced within the tutorials, but its extent was to simply just blur screen. With this, you can put together a form of “bloom” by adding the blurred image’s colours to the base the base image, but I was not satisfied with this alone.
First, I created a shader grabs the areas of the image above a certain lightness threshold.

        fragColor = vec4(0, 0, 0, 1);

        vec4 diffuse = texture2D(sceneTex, IN.texCoord);
        float brightness = dot(diffuse.rgb, vec3(0.9126, 0.7152, 0.0722));

        if(brightness > 1.0)
        {
            diffuse = vec4(diffuse.rgb, 1.0);
        }
        else
        {
            diffuse = vec4(0.0, 0.0, 0.0, 1.0);
        }

        fragColor =  diffuse;

Then, I applied a blur shader to that output. I found that using gausian blur was rather limiting as adjusting the intensity and the range of the blurring prooved difficult. Instead, I used single-direction blur with a curve that I can change the paremeters for during runtime and applied it to the output twice. Once horizontally and once vertically.

#version 330 core

uniform sampler2D sceneTex;

uniform int isVertical;
uniform int bloomRange;
uniform float bloomIntensity;
in Vertex
{
	vec2 texCoord;
} IN;

out vec4 fragColor;

void main(void)
{
	fragColor = vec4(0, 0, 0, 1);
	vec2 delta = vec2(0, 0);

	if(isVertical == 1)
	{
		delta = dFdy(IN.texCoord);
	}
	else
	{
		delta = dFdx(IN.texCoord);
	}
	float z = 0;
        int ranger = bloomRange;
	float startCurve = 8.0f;
	for(int i = 0; i < ranger; i++)
	{
		vec2 offset = delta * (z - (ranger-1)/2);
		vec4 tmp = texture2D(sceneTex, IN.texCoord.xy + offset);
		
		float curve = 1.0/(pow(2.0, 
		abs(

		-startCurve + (startCurve*2)/(ranger-1) * z
		
		)
		));
		fragColor += tmp * curve * bloomIntensity;
		z+=1.0;
	}
}

All that was left was too add the final result to the base image.


Shader Off


Shader On

Pixelize Post Processing

For the transition between the ground scene and the space scene, I put together a simple pixelisation shader.

    vec2 newUV = floor(IN.texCoord.xy * screenSize / pixelSize) 
                 / screenSize * pixelSize;
    if(floor(pixelSize)<=1)
    {
        newUV = IN.texCoord.xy;
    }
    vec4 tmp = texture2D(sceneTex, newUV);

    fragColor = tmp;
    fragColor = fragColor * colourCorrection;