{"id":1551,"date":"2015-04-22T16:36:24","date_gmt":"2015-04-22T15:36:24","guid":{"rendered":"http:\/\/www.giodalnegro.com\/?p=1551"},"modified":"2022-04-23T09:29:22","modified_gmt":"2022-04-23T08:29:22","slug":"c3-chimera-3volved","status":"publish","type":"post","link":"https:\/\/www.giodalnegro.com\/?p=1551","title":{"rendered":"C3 | Chimera 3volved"},"content":{"rendered":"<p>Engine C3, a total revamp of Chimera, is almost completed (assuming that an engine can be defined as completed &#8230;).<br \/>\nC3 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.<br \/>\nA big thank you Igor Dykhta for the POM shader base.<\/p>\n<p>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 &#8220;air&#8221; 2, just 2 and that&#8217;s it).<\/p>\n<p>&nbsp;<\/p>\n<p><strong>C3 test<\/strong><br \/>\n<div id=\"kgvid_kgvid_0_wrapper\" class=\"kgvid_wrapper\">\n\t\t\t<div id=\"video_kgvid_0_div\" class=\"fitvidsignore kgvid_videodiv\" data-id=\"kgvid_0\" data-kgvid_video_vars=\"{&quot;id&quot;:&quot;kgvid_0&quot;,&quot;attachment_id&quot;:0,&quot;player_type&quot;:&quot;Video.js v8&quot;,&quot;width&quot;:&quot;640&quot;,&quot;height&quot;:&quot;360&quot;,&quot;fullwidth&quot;:&quot;true&quot;,&quot;fixed_aspect&quot;:&quot;false&quot;,&quot;countable&quot;:false,&quot;count_views&quot;:&quot;quarters&quot;,&quot;start&quot;:&quot;&quot;,&quot;autoplay&quot;:&quot;false&quot;,&quot;pauseothervideos&quot;:&quot;false&quot;,&quot;set_volume&quot;:1,&quot;muted&quot;:&quot;false&quot;,&quot;meta&quot;:false,&quot;endofvideooverlay&quot;:false,&quot;resize&quot;:&quot;true&quot;,&quot;auto_res&quot;:&quot;automatic&quot;,&quot;pixel_ratio&quot;:&quot;true&quot;,&quot;right_click&quot;:&quot;on&quot;,&quot;playback_rate&quot;:&quot;false&quot;,&quot;title&quot;:&quot;c3.mov&quot;,&quot;skip_buttons&quot;:[],&quot;nativecontrolsfortouch&quot;:&quot;false&quot;,&quot;locale&quot;:&quot;it&quot;,&quot;enable_resolutions_plugin&quot;:false}\" itemprop=\"video\" itemscope itemtype=\"https:\/\/schema.org\/VideoObject\"><meta itemprop=\"embedUrl\" content=\"http:\/\/www.giodalnegro.com\/movies\/c3.mov\"><meta itemprop=\"contentUrl\" content=\"http:\/\/www.giodalnegro.com\/movies\/c3.mov\"><meta itemprop=\"name\" content=\"c3.mov\"><meta itemprop=\"description\" content=\"Video\"><meta itemprop=\"uploadDate\" content=\"2015-04-22T16:36:24+01:00\">\n\t\t\t\t<video id=\"video_kgvid_0\" playsinline controls preload=\"metadata\" width=\"640\" height=\"360\" class=\"fitvidsignore video-js kg-video-js-skin\">\n\t\t\t\t\t<source src=\"http:\/\/www.giodalnegro.com\/movies\/c3.mov?id=0\" type=\"video\/mp4\" data-res=\"Full\">\n\t\t\t\t<\/video>\n\t\t\t<\/div>\n\t\t<\/div><\/p>\n<p>Link al <a href=\"http:\/\/www.giodalnegro.com\/movies\/c3.mov\">video<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<p>Parallax Occlusion Mapping GLSL shader<\/p>\n<p>\/*<br \/>\nmost of credits to Igor Dykhta<br \/>\nlighting and vertex rewrited by GIO<br \/>\n*\/<\/p>\n<p>[vertex]<\/p>\n<p>#version 150<br \/>\nprecision highp float;<\/p>\n<p>in vec4 v_position;<br \/>\nin vec3 v_normal;<br \/>\nin vec2 v_texCoord;<br \/>\nin vec3 v_tangent;<\/p>\n<p>\/\/ uniforms<br \/>\nuniform mat4 u_model_mat;<br \/>\nuniform mat4 u_view_mat;<br \/>\nuniform mat4 u_proj_mat;<br \/>\nuniform mat3 u_normal_mat;<br \/>\nuniform vec3 u_light_position;<br \/>\nuniform vec3 u_camera_position;<\/p>\n<p>\/\/ data for fragment shader<br \/>\nout vec2 o_texcoords;<br \/>\nout vec3 o_toLightInTangentSpace;<br \/>\nout vec3 o_toCameraInTangentSpace;<\/p>\n<p>void main(void)<br \/>\n{<br \/>\no_texcoords = v_texCoord;<\/p>\n<p>vec3 n = normalize (u_normal_mat * v_normal);<br \/>\nvec3 t = normalize (u_normal_mat * v_tangent);<br \/>\nvec3 b = cross (n, t);<\/p>\n<p>mat3 tbnMatrix = mat3(t.x, b.x, n.x,<br \/>\nt.y, b.y, n.y,<br \/>\nt.z, b.z, n.z);<\/p>\n<p>mat4 modelViewMatrix = u_view_mat * u_model_mat;<br \/>\nmat4 mvpMatrix = u_proj_mat * modelViewMatrix;<br \/>\nvec4 worldPosition = vec4(v_position.x, v_position.y, v_position.z, 1.0);<\/p>\n<p>gl_Position = mvpMatrix * worldPosition;<br \/>\nworldPosition = modelViewMatrix * worldPosition;<\/p>\n<p>o_toCameraInTangentSpace = -(worldPosition.xyz \/ worldPosition.w);<br \/>\no_toCameraInTangentSpace = normalize(tbnMatrix * o_toCameraInTangentSpace);<\/p>\n<p>o_toLightInTangentSpace = (u_light_position &#8211; worldPosition.xyz); \/\/u_light_position = position * viewmatrix<br \/>\no_toLightInTangentSpace = normalize(tbnMatrix * o_toLightInTangentSpace);<br \/>\n}<\/p>\n<p>[fragment]<\/p>\n<p>#version 150<br \/>\nprecision highp float;<\/p>\n<p>\/\/ data from vertex shader<br \/>\nin vec2 o_texcoords;<br \/>\nin vec3 o_toLightInTangentSpace;<br \/>\nin vec3 o_toCameraInTangentSpace;<\/p>\n<p>\/\/ textures<br \/>\nuniform sampler2D u_diffuseTexture;<br \/>\nuniform sampler2D u_heightTexture;<br \/>\nuniform sampler2D u_normalTexture;<br \/>\nuniform float useGloss;<br \/>\nuniform sampler2D u_GlossTexture;<\/p>\n<p>\/\/ scale for size of Parallax Mapping effect<br \/>\nuniform float u_parallaxScale; \/\/ ~0.1<\/p>\n<p>uniform vec4 lightDiffuse;<br \/>\nuniform vec4 matDiffuse;<br \/>\nuniform vec4 lightAmbient;<br \/>\nuniform vec4 matAmbient;<br \/>\nuniform vec4 lightSpecular;<br \/>\nuniform vec4 matSpecular;<br \/>\nuniform float matShininess;<br \/>\nuniform float lightAttenuation;<\/p>\n<p>out vec4 fragColor;<\/p>\n<p>\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\/\/ PARALLAX OCCLUSION (POM)<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/p>\n<p>vec2 parallaxMapping(in vec3 V, in vec2 T, out float parallaxHeight)<br \/>\n{<\/p>\n<p>float u_adaptiveScale = 0.5; \/\/declarations for tests<br \/>\nfloat u_minNumberOfLayers = 40.0;<br \/>\nfloat u_maxNumberOfLayers = 200.0;<\/p>\n<p>\/\/ multiplier for offsets of texture coordinates, determines amount of paralax<br \/>\nfloat parallaxScale = u_parallaxScale * (1.0 &#8211; u_adaptiveScale);<br \/>\nfloat parallaxAdaptive = u_parallaxScale * pow(abs(dot(vec3(0, 0, 1), V)), 0.5) * u_adaptiveScale;<\/p>\n<p>\/\/ number of layer to check. Depends on view angle<br \/>\nfloat numLayers = floor(mix(u_maxNumberOfLayers, u_minNumberOfLayers, abs(dot(vec3(0.0, 0.0, 1.0), V))));<\/p>\n<p>\/\/ height of current layer<br \/>\nfloat currentLayerHeight = 0.0;<br \/>\n\/\/ height of each layer<br \/>\nfloat layerHeight = 1.0 \/ numLayers;<br \/>\n\/\/ change of texture coordinates with each layer<br \/>\nvec2 texStep = (parallaxScale ) * V.xy \/ V.z \/ numLayers;<\/p>\n<p>\/\/ initial texcoords<br \/>\nvec2 currentTextureCoords = T;<br \/>\n\/\/ get first height from height map<br \/>\nfloat heightFromTexture = 1.0 &#8211; texture(u_heightTexture, currentTextureCoords).r;<\/p>\n<p>\/\/ while ray is above surface<br \/>\nwhile (heightFromTexture &gt; currentLayerHeight)<br \/>\n{<br \/>\n\/\/ update height with height of next level<br \/>\ncurrentLayerHeight += layerHeight;<br \/>\n\/\/ offset texture coordinates for sampling<br \/>\ncurrentTextureCoords -= texStep;<br \/>\n\/\/ get new height from height map<br \/>\nheightFromTexture = 1.0 &#8211; texture(u_heightTexture, currentTextureCoords).r;<br \/>\n}<\/p>\n<p>\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/p>\n<p>\/\/ previous texture coordinates<br \/>\nvec2 prevTexCoords = currentTextureCoords + texStep;<\/p>\n<p>\/\/ heights<br \/>\nfloat nextHeight = heightFromTexture &#8211; currentLayerHeight;<br \/>\nfloat previousHeight = texture(u_heightTexture, prevTexCoords).r &#8211; currentLayerHeight + layerHeight;<\/p>\n<p>\/\/ interpolation weight<br \/>\nfloat weight = nextHeight \/ (nextHeight &#8211; previousHeight);<\/p>\n<p>\/\/ interpolate to get texture coordinates<br \/>\nvec2 interpolatedTexCoords = prevTexCoords * weight + currentTextureCoords * (1.0 &#8211; weight);<\/p>\n<p>\/\/ output height of last fragment<br \/>\nparallaxHeight = currentLayerHeight + previousHeight * weight + nextHeight * (1.0 &#8211; weight);<\/p>\n<p>\/\/ save final texture coordinates offset<br \/>\nreturn interpolatedTexCoords;<\/p>\n<p>}<\/p>\n<p>\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\/\/ Implements self-shadowing technique &#8211; hard or soft shadows Returns shadow factor<br \/>\nfloat parallaxSoftShadowMultiplier(in vec3 L, in vec2 initialTexCoord, in float initialHeight)<br \/>\n{<\/p>\n<p>float shadowMultiplier = 1.0;<br \/>\nconst float minLayers = 15.0;<br \/>\nconst float maxLayers = 30.0;<\/p>\n<p>\/\/ calculate lighting only for surface oriented to the light source<br \/>\nif(dot(vec3(0.0, 0.0, 1.0), L) &gt; 0.0)<br \/>\n{<\/p>\n<p>\/\/ calculate initial parameters<br \/>\nfloat numSamplesUnderSurface = 0.0;<br \/>\nshadowMultiplier = 0.0;<br \/>\nfloat numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), L)));<br \/>\nfloat layerHeight = initialHeight \/ numLayers;<br \/>\nvec2 texStep = u_parallaxScale * L.xy \/ L.z \/ numLayers;<\/p>\n<p>\/\/ current parameters<br \/>\nfloat currentLayerHeight = initialHeight &#8211; layerHeight;<br \/>\nvec2 currentTextureCoords = initialTexCoord + texStep;<br \/>\nfloat heightFromTexture = texture(u_heightTexture, currentTextureCoords).r;<br \/>\nfloat stepIndex = 1.0; \/\/era int<\/p>\n<p>\/\/ while point is below depth 0.0 )<br \/>\nwhile (currentLayerHeight &gt; 0.0)<br \/>\n{<br \/>\n\/\/ if point is under the surface<br \/>\nif (heightFromTexture &lt; currentLayerHeight)<br \/>\n{<br \/>\n\/\/ calculate partial shadowing factor<br \/>\nnumSamplesUnderSurface += 1.0;<br \/>\nfloat newShadowMultiplier = (currentLayerHeight &#8211; heightFromTexture) *<br \/>\n(1.0 &#8211; stepIndex \/ numLayers);<br \/>\nshadowMultiplier = max(shadowMultiplier, newShadowMultiplier);<br \/>\n}<\/p>\n<p>\/\/ offset to the next layer<br \/>\nstepIndex += 1.0;<br \/>\ncurrentLayerHeight -= layerHeight;<br \/>\ncurrentTextureCoords += texStep;<br \/>\nheightFromTexture = 1.0 &#8211; texture(u_heightTexture, currentTextureCoords).r;<br \/>\n}<\/p>\n<p>\/\/ Shadowing factor should be 1 if there were no points under the surface<br \/>\nif (numSamplesUnderSurface &lt; 1.0)<br \/>\nshadowMultiplier = 1.0;<br \/>\nelse<br \/>\nshadowMultiplier = 1.0 &#8211; shadowMultiplier;<br \/>\n}<br \/>\nreturn shadowMultiplier;<\/p>\n<p>}<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\/\/ Calculates lighting by Blinn-Phong model and Normal Mapping &#8211; heavily modded by Gio<br \/>\nvec4 normalMappingLighting(in vec2 T, in vec3 L, in vec3 V, float shadowMultiplier)<br \/>\n{<\/p>\n<p>vec3 N = normalize(texture(u_normalTexture, T).xyz * 2.0 &#8211; 1.0);<br \/>\nvec4 D = texture(u_diffuseTexture, T);<\/p>\n<p>\/\/attenuation<br \/>\nfloat dist = length(L);<br \/>\nfloat coeff = dist * lightAttenuation;<br \/>\nvec4 attenuation = vec4(coeff,coeff,coeff,1.0);<\/p>\n<p>\/\/ambient<br \/>\nvec4 ambient = (lightAmbient + matAmbient) * attenuation;<br \/>\n\/\/ diffuse lighting<br \/>\nfloat diffPower = clamp(dot(N, L), 0, 1);<br \/>\nvec4 diffuse = (diffPower * (lightDiffuse + matDiffuse)) * attenuation;<br \/>\n\/\/ specular lighting<br \/>\nfloat specPower = 0;<br \/>\nvec4 specular = vec4(0,0,0,1);<br \/>\nif(dot(N, L) &gt; 0.0)<br \/>\n{<br \/>\nvec3 R = reflect(-L, N);<br \/>\nspecPower = pow(max(0.0,dot(R,V)), matShininess);<br \/>\nspecular = (specPower * matSpecular) * attenuation;<\/p>\n<p>if (useGloss &gt; 0.0)<br \/>\n{<br \/>\nfloat opacity = texture(u_GlossTexture, o_texcoords.st).r;<br \/>\nspecular = specular * opacity;<br \/>\n}<br \/>\n}<\/p>\n<p>vec4 resColor;<br \/>\nresColor = D * (ambient + (diffuse + specular) * pow(shadowMultiplier, 4));<br \/>\nreturn resColor;<br \/>\n}<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nvoid main(void)<br \/>\n{<br \/>\n\/\/ normalize vectors after vertex shader<br \/>\nvec3 V = normalize(o_toCameraInTangentSpace);<br \/>\nvec3 L = normalize(o_toLightInTangentSpace);<\/p>\n<p>\/\/ get new texture coordinates from Parallax Mapping<br \/>\nfloat parallaxHeight = 0;<br \/>\nvec2 T = parallaxMapping(V, o_texcoords, parallaxHeight);<\/p>\n<p>\/\/ get self-shadowing factor for elements of parallax<br \/>\nfloat shadowMultiplier = parallaxSoftShadowMultiplier(L, T, parallaxHeight &#8211; 0.05);<\/p>\n<p>\/\/ calculate lighting<br \/>\nfragColor = normalMappingLighting(T, L, V, shadowMultiplier);<br \/>\n}<br \/>\n\/\/&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Engine C3, a total revamp of Chimera, is almost completed (assuming that an engine can be defined as completed &#8230;). 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1552,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[7,201,1,14,8],"tags":[163,37,165,166,167,164],"class_list":{"0":"post-1551","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-ios","8":"category-macos","9":"category-news","10":"category-osx","11":"category-windows","12":"tag-c3","13":"tag-chimera","14":"tag-normal-mapping","15":"tag-opengl-es-2","16":"tag-opengl-es-3","17":"tag-parallax-occlusion-ios","18":"czr-hentry"},"_links":{"self":[{"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/posts\/1551","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1551"}],"version-history":[{"count":28,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/posts\/1551\/revisions"}],"predecessor-version":[{"id":2345,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/posts\/1551\/revisions\/2345"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=\/wp\/v2\/media\/1552"}],"wp:attachment":[{"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1551"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1551"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.giodalnegro.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1551"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}