C3 | Chimera 3volved


Engine C3, a total revamp of Chimera, is almost completed (assuming that an engine can be defined as completed …).
C3 contains a lot of advanced features: it is completely shader-based and contains many postprocessing effects such as filmgrain, depth-of-field etc. but probably the best thing is that it runs on Windows (core 330), macOS (core 320 due to implementation limitations) and iOS (ES2 + ES3) without changing a client line.
A big thank you Igor Dykhta for the POM shader base.

Here is a simple video demonstrating Parallax Occlusion Mapping, Cube Mapping, Normal mapping, Per-Pixel Lighting using 30,000 polygons at 30 FPS on a rusty old iPad2 (not “air” 2, just 2 and that’s it).

 

C3 test

Link al video.

 

Parallax Occlusion Mapping GLSL shader

/*
most of credits to Igor Dykhta
lighting and vertex rewrited by GIO
*/

[vertex]

#version 150
precision highp float;

in vec4 v_position;
in vec3 v_normal;
in vec2 v_texCoord;
in vec3 v_tangent;

// uniforms
uniform mat4 u_model_mat;
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;
uniform mat3 u_normal_mat;
uniform vec3 u_light_position;
uniform vec3 u_camera_position;

// data for fragment shader
out vec2 o_texcoords;
out vec3 o_toLightInTangentSpace;
out vec3 o_toCameraInTangentSpace;

void main(void)
{
o_texcoords = v_texCoord;

vec3 n = normalize (u_normal_mat * v_normal);
vec3 t = normalize (u_normal_mat * v_tangent);
vec3 b = cross (n, t);

mat3 tbnMatrix = mat3(t.x, b.x, n.x,
t.y, b.y, n.y,
t.z, b.z, n.z);

mat4 modelViewMatrix = u_view_mat * u_model_mat;
mat4 mvpMatrix = u_proj_mat * modelViewMatrix;
vec4 worldPosition = vec4(v_position.x, v_position.y, v_position.z, 1.0);

gl_Position = mvpMatrix * worldPosition;
worldPosition = modelViewMatrix * worldPosition;

o_toCameraInTangentSpace = -(worldPosition.xyz / worldPosition.w);
o_toCameraInTangentSpace = normalize(tbnMatrix * o_toCameraInTangentSpace);

o_toLightInTangentSpace = (u_light_position – worldPosition.xyz); //u_light_position = position * viewmatrix
o_toLightInTangentSpace = normalize(tbnMatrix * o_toLightInTangentSpace);
}

[fragment]

#version 150
precision highp float;

// data from vertex shader
in vec2 o_texcoords;
in vec3 o_toLightInTangentSpace;
in vec3 o_toCameraInTangentSpace;

// textures
uniform sampler2D u_diffuseTexture;
uniform sampler2D u_heightTexture;
uniform sampler2D u_normalTexture;
uniform float useGloss;
uniform sampler2D u_GlossTexture;

// scale for size of Parallax Mapping effect
uniform float u_parallaxScale; // ~0.1

uniform vec4 lightDiffuse;
uniform vec4 matDiffuse;
uniform vec4 lightAmbient;
uniform vec4 matAmbient;
uniform vec4 lightSpecular;
uniform vec4 matSpecular;
uniform float matShininess;
uniform float lightAttenuation;

out vec4 fragColor;

//——————————————————————–
// PARALLAX OCCLUSION (POM)
//——————————————————————–

vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)
{

float u_adaptiveScale = 0.5; //declarations for tests
float u_minNumberOfLayers = 40.0;
float u_maxNumberOfLayers = 200.0;

// multiplier for offsets of texture coordinates, determines amount of paralax
float parallaxScale = u_parallaxScale * (1.0 – u_adaptiveScale);
float parallaxAdaptive = u_parallaxScale * pow(abs(dot(vec3(0, 0, 1), V)), 0.5) * u_adaptiveScale;

// number of layer to check. Depends on view angle
float numLayers = floor(mix(u_maxNumberOfLayers, u_minNumberOfLayers, abs(dot(vec3(0.0, 0.0, 1.0), V))));

// height of current layer
float currentLayerHeight = 0.0;
// height of each layer
float layerHeight = 1.0 / numLayers;
// change of texture coordinates with each layer
vec2 texStep = (parallaxScale ) * V.xy / V.z / numLayers;

// initial texcoords
vec2 currentTextureCoords = T;
// get first height from height map
float heightFromTexture = 1.0 – texture(u_heightTexture, currentTextureCoords).r;

// while ray is above surface
while (heightFromTexture > currentLayerHeight)
{
// update height with height of next level
currentLayerHeight += layerHeight;
// offset texture coordinates for sampling
currentTextureCoords -= texStep;
// get new height from height map
heightFromTexture = 1.0 – texture(u_heightTexture, currentTextureCoords).r;
}

//////////////////////////////////////////////////////////////////////////////////

// previous texture coordinates
vec2 prevTexCoords = currentTextureCoords + texStep;

// heights
float nextHeight = heightFromTexture – currentLayerHeight;
float previousHeight = texture(u_heightTexture, prevTexCoords).r – currentLayerHeight + layerHeight;

// interpolation weight
float weight = nextHeight / (nextHeight – previousHeight);

// interpolate to get texture coordinates
vec2 interpolatedTexCoords = prevTexCoords * weight + currentTextureCoords * (1.0 – weight);

// output height of last fragment
parallaxHeight = currentLayerHeight + previousHeight * weight + nextHeight * (1.0 – weight);

// save final texture coordinates offset
return interpolatedTexCoords;

}

//——————————————————————–
// Implements self-shadowing technique – hard or soft shadows Returns shadow factor
float parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord, in float initialHeight)
{

float shadowMultiplier = 1.0;
const float minLayers = 15.0;
const float maxLayers = 30.0;

// calculate lighting only for surface oriented to the light source
if(dot(vec3(0.0, 0.0, 1.0), L) > 0.0)
{

// calculate initial parameters
float numSamplesUnderSurface = 0.0;
shadowMultiplier = 0.0;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), L)));
float layerHeight = initialHeight / numLayers;
vec2 texStep = u_parallaxScale * L.xy / L.z / numLayers;

// current parameters
float currentLayerHeight = initialHeight – layerHeight;
vec2 currentTextureCoords = initialTexCoord + texStep;
float heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;
float stepIndex = 1.0; //era int

// while point is below depth 0.0 )
while (currentLayerHeight > 0.0)
{
// if point is under the surface
if (heightFromTexture < currentLayerHeight)
{
// calculate partial shadowing factor
numSamplesUnderSurface += 1.0;
float newShadowMultiplier = (currentLayerHeight – heightFromTexture) *
(1.0 – stepIndex / numLayers);
shadowMultiplier = max(shadowMultiplier, newShadowMultiplier);
}

// offset to the next layer
stepIndex += 1.0;
currentLayerHeight -= layerHeight;
currentTextureCoords += texStep;
heightFromTexture = 1.0 – texture(u_heightTexture, currentTextureCoords).r;
}

// Shadowing factor should be 1 if there were no points under the surface
if (numSamplesUnderSurface < 1.0)
shadowMultiplier = 1.0;
else
shadowMultiplier = 1.0 – shadowMultiplier;
}
return shadowMultiplier;

}
//——————————————————————–
// Calculates lighting by Blinn-Phong model and Normal Mapping – heavily modded by Gio
vec4 normalMappingLighting(in vec2 T, in vec3 L, in vec3 V, float shadowMultiplier)
{

vec3 N = normalize(texture(u_normalTexture, T).xyz * 2.0 – 1.0);
vec4 D = texture(u_diffuseTexture, T);

//attenuation
float dist = length(L);
float coeff = dist * lightAttenuation;
vec4 attenuation = vec4(coeff,coeff,coeff,1.0);

//ambient
vec4 ambient = (lightAmbient + matAmbient) * attenuation;
// diffuse lighting
float diffPower = clamp(dot(N, L), 0, 1);
vec4 diffuse = (diffPower * (lightDiffuse + matDiffuse)) * attenuation;
// specular lighting
float specPower = 0;
vec4 specular = vec4(0,0,0,1);
if(dot(N, L) > 0.0)
{
vec3 R = reflect(-L, N);
specPower = pow(max(0.0,dot(R,V)), matShininess);
specular = (specPower * matSpecular) * attenuation;

if (useGloss > 0.0)
{
float opacity = texture(u_GlossTexture, o_texcoords.st).r;
specular = specular * opacity;
}
}

vec4 resColor;
resColor = D * (ambient + (diffuse + specular) * pow(shadowMultiplier, 4));
return resColor;
}
//——————————————————————–
void main(void)
{
// normalize vectors after vertex shader
vec3 V = normalize(o_toCameraInTangentSpace);
vec3 L = normalize(o_toLightInTangentSpace);

// get new texture coordinates from Parallax Mapping
float parallaxHeight = 0;
vec2 T = parallaxMapping(V, o_texcoords, parallaxHeight);

// get self-shadowing factor for elements of parallax
float shadowMultiplier = parallaxSoftShadowMultiplier(L, T, parallaxHeight – 0.05);

// calculate lighting
fragColor = normalMappingLighting(T, L, V, shadowMultiplier);
}
//——————————————————————–