{"id":4447,"date":"2016-10-03T09:12:36","date_gmt":"2016-10-03T15:12:36","guid":{"rendered":"http:\/\/www.realtimerendering.com\/blog\/?p=4447"},"modified":"2016-10-03T09:14:00","modified_gmt":"2016-10-03T15:14:00","slug":"webgl-2-new-features","status":"publish","type":"post","link":"https:\/\/www.realtimerendering.com\/blog\/webgl-2-new-features\/","title":{"rendered":"WebGL 2: New Features"},"content":{"rendered":"<p><em>by <a href=\"https:\/\/www.linkedin.com\/in\/shuai-shao-3718818b\">Shuai Shao<br \/>\ng<\/a>ithub repo for article <a href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md\">here<\/a><\/em><\/p>\n<p><a href=\"http:\/\/www.realtimerendering.com\/blog\/webgl-2-basics\/\">Last time<\/a> we showed how to deal with issues porting a WebGL 1 engine to WebGL 2. In this article, we will talk about what new features come with WebGL 2 and what cool things can we do with them.<\/p>\n<h1><a id=\"user-content-new-features\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#new-features\"><\/a>New features<\/h1>\n<h2><a id=\"user-content-multisampled-renderbuffers\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#multisampled-renderbuffers\"><\/a>Multisampled Renderbuffers<\/h2>\n<p>Previously, if we wanted antialiasing we would either have to render it to the default backbuffer or perform our own post-process AA (such as FXAA or <a href=\"http:\/\/threejs.org\/examples\/#webgl_postprocessing_smaa\">SMAA<\/a>) on content rendered to a texture.<\/p>\n<p>Now, with Multisampled Renderbuffers, we can use the general rendering pipeline in WebGL to provide multisampled antialiasing (MSAA):<\/p>\n<blockquote><p>pre-z pass &#8211;&gt; rendering pass to FBO &#8211;&gt; postprocessing pass &#8211;&gt; render to window<\/p><\/blockquote>\n<p><code>renderbufferStorageMultisample<\/code> is the relevant function here.<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> colorRenderbuffer <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createRenderbuffer<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindRenderbuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RENDERBUFFER<\/span>, colorRenderbuffer);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">renderbufferStorageMultisample<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RENDERBUFFER<\/span>, <span class=\"pl-c1\">4<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA8<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">x<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">y<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, framebuffers[<span class=\"pl-c1\">FRAMEBUFFER<\/span>.<span class=\"pl-c1\">RENDERBUFFER<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">framebufferRenderbuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">COLOR_ATTACHMENT0<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RENDERBUFFER<\/span>, colorRenderbuffer);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, <span class=\"pl-c1\">null<\/span>);<\/pre>\n<\/div>\n<p>Pay attention to the fact that the multisample renderbuffers cannot be directly bound to textures, but they can be resolved to single-sample textures using the <code>blitFramebuffer<\/code> call. This is a new feature in WebGL 2 as well, and is used like this:<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> texture <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createTexture<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, texture);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAG_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">NEAREST<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MIN_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">NEAREST<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texImage2D<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">x<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">y<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>, <span class=\"pl-c1\">null<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-c1\">null<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, framebuffers[<span class=\"pl-c1\">FRAMEBUFFER<\/span>.<span class=\"pl-c1\">COLORBUFFER<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">framebufferTexture2D<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">COLOR_ATTACHMENT0<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, texture, <span class=\"pl-c1\">0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FRAMEBUFFER<\/span>, <span class=\"pl-c1\">null<\/span>);\r\n\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n\r\n<span class=\"pl-c\">\/\/ After drawing to the multisampled renderbuffers<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">READ_FRAMEBUFFER<\/span>, framebuffers[<span class=\"pl-c1\">FRAMEBUFFER<\/span>.<span class=\"pl-c1\">RENDERBUFFER<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindFramebuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">DRAW_FRAMEBUFFER<\/span>, framebuffers[<span class=\"pl-c1\">FRAMEBUFFER<\/span>.<span class=\"pl-c1\">COLORBUFFER<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">clearBufferfv<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">COLOR<\/span>, <span class=\"pl-c1\">0<\/span>, [<span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">1.0<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">blitFramebuffer<\/span>(\r\n    <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">x<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">y<\/span>,\r\n    <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">x<\/span>, <span class=\"pl-c1\">FRAMEBUFFER_SIZE<\/span>.<span class=\"pl-c1\">y<\/span>,\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">COLOR_BUFFER_BIT<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">NEAREST<\/span>\r\n);<\/pre>\n<\/div>\n<h2><a id=\"user-content-3d-texture\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#3d-texture\"><\/a>3D Texture<\/h2>\n<p>The first thing that comes to mind with 3D textures is volumetric effects, such as fire, smoke, light rays, realistic fog, etc. Now we can bring these features into our WebGL engine. In addition, 3D textures can be used to store medical data such as MRI and CT scans, and are useful when implementing cross-sectioning. 3D textures can also improve performance by using them to cache light for real-time global illumination.<\/p>\n<p>WebGL 2 support for 3D textures is as good as that for 2D textures. We have fast access speed and native tri-linear interpolation.<\/p>\n<p>The code for setting up a 3D texture usually has a 2D texture counterpart.<\/p>\n<table>\n<thead>\n<tr>\n<th>Texture 2D<\/th>\n<th>Texture 3D<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>texImage2D<\/code><\/td>\n<td><code>texImage3D<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>texSubImage2D<\/code><\/td>\n<td><code>texSubImage3D<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>copyTexImage2D<\/code><\/td>\n<td><code>copyTexImage3D<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>compressedTexImage2D<\/code><\/td>\n<td><code>compressedTexImage3D<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>compressedTexSubImage2D<\/code><\/td>\n<td><code>compressedTexSubImage3D<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>texStorage2D<\/code><\/td>\n<td><code>texStorage3D<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>There are certain elements that do not match exactly. For example, since we have one more dimension, we will have <code>depth<\/code>,\u00a0<code>zoffset<\/code>, and <code>TEXTURE_WRAP_T<\/code> for 3D textures. Also, the internal format and type combinations are not 100% matched.<\/p>\n<p>The sampler used in shaders is <code>sampler3D<\/code> instead of <code>sampler2D<\/code>.<\/p>\n<p>Here&#8217;s an example setup:<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> texture <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createTexture<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">activeTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>, texture);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_BASE_LEVEL<\/span>, <span class=\"pl-c1\">0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAX_LEVEL<\/span>, <span class=\"pl-c1\">Math<\/span>.<span class=\"pl-c1\">log2<\/span>(<span class=\"pl-c1\">SIZE<\/span>));\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MIN_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">LINEAR_MIPMAP_LINEAR<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameteri<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAG_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">LINEAR<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texImage3D<\/span>(\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>,  <span class=\"pl-c\">\/\/ target<\/span>\r\n    <span class=\"pl-c1\">0<\/span>,              <span class=\"pl-c\">\/\/ level<\/span>\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">R8<\/span>,        <span class=\"pl-c\">\/\/ internalformat<\/span>\r\n    <span class=\"pl-c1\">SIZE<\/span>,           <span class=\"pl-c\">\/\/ width<\/span>\r\n    <span class=\"pl-c1\">SIZE<\/span>,           <span class=\"pl-c\">\/\/ height<\/span>\r\n    <span class=\"pl-c1\">SIZE<\/span>,           <span class=\"pl-c\">\/\/ depth<\/span>\r\n    <span class=\"pl-c1\">0<\/span>,              <span class=\"pl-c\">\/\/ border<\/span>\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RED<\/span>,         <span class=\"pl-c\">\/\/ format<\/span>\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>,       <span class=\"pl-c\">\/\/ type<\/span>\r\n    data            <span class=\"pl-c\">\/\/ pixel<\/span>\r\n    );\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">generateMipmap<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_3D<\/span>);<\/pre>\n<\/div>\n<p>Last but not the least, the 2D Texture Array concept is available with the 3D Texture feature. That is, multiple 2D textures can be stored in an array that can be accessed. It has its own sampler: <code>sampler2DArray<\/code>, but it shares the <code>texImage3D<\/code> GL functions. Here&#8217;s an example call:<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texImage3D<\/span>(\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D_ARRAY<\/span>,\r\n    <span class=\"pl-c1\">0<\/span>,\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>,\r\n    <span class=\"pl-c1\">IMAGE_SIZE<\/span>.<span class=\"pl-c1\">width<\/span>,\r\n    <span class=\"pl-c1\">IMAGE_SIZE<\/span>.<span class=\"pl-c1\">height<\/span>,\r\n    <span class=\"pl-c1\">NUM_IMAGES<\/span>,\r\n    <span class=\"pl-c1\">0<\/span>,\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>,\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>,\r\n    pixels\r\n);<\/pre>\n<\/div>\n<h2><a id=\"user-content-uniform-buffer\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#uniform-buffer\"><\/a>Uniform Buffer<\/h2>\n<p>Setting uniforms for shaders is often a considerable amount of the time spent by an engine. Take the <a href=\"http:\/\/cesiumjs.org\/Cesium\/Build\/Apps\/CesiumViewer\/index.html\">Cesium Globe<\/a> as an example. For regular draw calls, <code>uniform4fv<\/code> is within the top 5 GL functions taking the most execution time. Also, the sum of all <code>uniform[i]fv<\/code> and <code>uniformMatrix[i]fv<\/code> calls is nearly 2.5% of all execution time. That&#8217;s quite a large percentage. We always have to call them to update uniform values each frame. What&#8217;s more, it can be annoying that we have to make duplicated uniform calls for one same uniform object shared by several shaders.<\/p>\n<p>Now the Uniform buffer object may bring us a boost in performance by allowing us to store blocks of uniforms in buffers stored on the GPU, just like vertex\/index buffers.\u00a0This can make switching between sets of uniforms faster. Additionally, uniform buffers can be shared by multiple programs at the same time.<\/p>\n<p>Those are quite a few benefits. But, with so many improvements, the setup routine is about to change a lot. We will have a basic setup example first, and then look at something that might need your attention.<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> uniformPerSceneLocation <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">getUniformBlockIndex<\/span>(program, <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>PerScene<span class=\"pl-pds\">'<\/span><\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">uniformBlockBinding<\/span>(program, uniformPerSceneLocation, <span class=\"pl-c1\">2<\/span>);\r\n<span class=\"pl-c\">\/\/...<\/span>\r\n<span class=\"pl-k\">var<\/span> material <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">Float32Array<\/span>([\r\n    <span class=\"pl-c1\">0.1<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.5<\/span>, <span class=\"pl-c1\">0.0<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.5<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">128.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>\r\n]);\r\n<span class=\"pl-k\">var<\/span> uniformPerSceneBuffer <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createBuffer<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNIFORM_BUFFER<\/span>, uniformPerSceneBuffer);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bufferData<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNIFORM_BUFFER<\/span>, material, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">STATIC_DRAW<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNIFORM_BUFFER<\/span>, <span class=\"pl-c1\">null<\/span>);\r\n<span class=\"pl-c\">\/\/...<\/span>\r\n<span class=\"pl-c\">\/\/ Render<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBufferBase<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNIFORM_BUFFER<\/span>, <span class=\"pl-c1\">2<\/span>, uniformPerSceneBuffer);<\/pre>\n<\/div>\n<p>The first thing that may confuse you is the layout standard (we will focus on std140 here). You can always find the details in<a href=\"https:\/\/www.khronos.org\/registry\/gles\/specs\/3.0\/es_spec_3.0.0.pdf\">OpenGL ES 3.00 Spec<\/a> Page 68.<\/p>\n<p>One thing that I really want you to notice is:<\/p>\n<blockquote><p>when the data member is a three-component vector with components consuming N basic machine units, the base alignment is 4N<\/p><\/blockquote>\n<p>And:<\/p>\n<blockquote><p>If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its members, and rounded up to the base alignment of a vec4.<\/p><\/blockquote>\n<p>Here&#8217;s an example:<\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-k\">struct<\/span> Material\r\n{\r\n    <span class=\"pl-k\">vec3<\/span> ambient;\r\n    <span class=\"pl-k\">vec3<\/span> diffuse;\r\n    <span class=\"pl-k\">vec3<\/span> specular;\r\n    <span class=\"pl-k\">float<\/span> shininess;\r\n};\r\n\r\n<span class=\"pl-k\">uniform<\/span> PerScene\r\n{\r\n    Material material;\r\n} u_perScene;<\/pre>\n<\/div>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> material <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">Float32Array<\/span>([\r\n    <span class=\"pl-c1\">0.1<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.5<\/span>, <span class=\"pl-c1\">0.0<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.5<\/span>,  <span class=\"pl-c1\">0.0<\/span>,\r\n    <span class=\"pl-c1\">128.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>, <span class=\"pl-c1\">0.0<\/span>\r\n]);<\/pre>\n<\/div>\n<p>Here we have <code>vec3<\/code>, so our data should add a <code>0<\/code> for each <code>vec3<\/code> for alignment. Also, since we use a struct to wrap our data, it should be rounded to a multiple of <code>vec4<\/code>. That&#8217;s why we have 3 extra zeroes after the <code>float shininess<\/code>.<\/p>\n<p>Another concern is about updating the uniform block. There are several different approaches that can get us there. However, their performance can vary. It&#8217;s pretty tricky to make the most of our uniform block.<\/p>\n<p>Here are some detailed discussions on <a href=\"http:\/\/stackoverflow.com\/questions\/38841124\/updating-uniform-buffer-data-in-webgl-2.\">Stack Overflow<\/a> and <a href=\"http:\/\/www.gamedev.net\/topic\/655969-speed-gluniform-vs-uniform-buffer-objects\/\">gamedev.net<\/a>.<\/p>\n<p>But, basically, we can use <code>gl.bufferSubData<\/code> to copy the updated typedArray into the uniform buffers.<\/p>\n<h2><a id=\"user-content-sync-objects\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#sync-objects\"><\/a>Sync Objects<\/h2>\n<p>Sync objects can be used to synchronize execution between the GL server and the client, which gives you more control over GPU by letting you set a fence to inform the GPU to wait until a set of GL operations have finished. Sync objects are more efficient than <code>gl.finish<\/code>.<\/p>\n<p>We can get more accurate benchmarks with sync objects. In addition, applications such as image manipulation, where data of each frame comes from the CPU, will benefit from this degree of control.<\/p>\n<h2><a id=\"user-content-query-objects\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#query-objects\"><\/a>Query Objects<\/h2>\n<p>This operation is very useful when we want to do occlusion testing. We can know how many geometries are actually drawn by performing a <code>gl.ANY_SAMPLES_PASSED<\/code> query around a set of draw calls. We can use these queries and so get rid of specialized picking method code.<\/p>\n<p>Keep in mind that these queries are asynchronous. A query&#8217;s result is never available in the same frame that the query is issued. This is different from OpenGL ES 3 where query result may be available in the same frame. It&#8217;s an application portability concern.<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">beginQuery<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ANY_SAMPLES_PASSED<\/span>, query);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">drawArraysInstanced<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TRIANGLES<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">3<\/span>, <span class=\"pl-c1\">2<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">endQuery<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ANY_SAMPLES_PASSED<\/span>);\r\n<span class=\"pl-c\">\/\/...<\/span>\r\n(<span class=\"pl-k\">function<\/span> <span class=\"pl-en\">tick<\/span>() {\r\n    <span class=\"pl-k\">if<\/span> (<span class=\"pl-k\">!<\/span><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">getQueryParameter<\/span>(query, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">QUERY_RESULT_AVAILABLE<\/span>)) {\r\n        <span class=\"pl-c\">\/\/ A query's result is never available in the same frame<\/span>\r\n        <span class=\"pl-c\">\/\/ the query was issued.  Try in the next frame.<\/span>\r\n        <span class=\"pl-en\">requestAnimationFrame<\/span>(tick);\r\n        <span class=\"pl-k\">return<\/span>;\r\n    }\r\n\r\n    <span class=\"pl-k\">var<\/span> samplesPassed <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">getQueryParameter<\/span>(query, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">QUERY_RESULT<\/span>);\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">deleteQuery<\/span>(query);\r\n})();<\/pre>\n<\/div>\n<h2><a id=\"user-content-sampler-objects\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#sampler-objects\"><\/a>Sampler Objects<\/h2>\n<p>In WebGL 1 texture image data and sampling information (which tells GPU how to read the image data) are both stored in texture objects. It can be annoying when we want to read from the same texture twice but with a different method (say, linear filtering vs nearest filtering) because we need to have two texture objects. With sampler objects, we can separate these two concepts. We can have one texture object and two different sampler objects. This will result in a change in how our engine organize textures.<\/p>\n<p>Here&#8217;s an example:<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> samplerA <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createSampler<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerA, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MIN_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">NEAREST_MIPMAP_NEAREST<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerA, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAG_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">NEAREST<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerA, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_WRAP_S<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">CLAMP_TO_EDGE<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerA, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_WRAP_T<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">CLAMP_TO_EDGE<\/span>);\r\n\r\n<span class=\"pl-k\">var<\/span> samplerB <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createSampler<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerB, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MIN_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">LINEAR_MIPMAP_LINEAR<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerB, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAG_FILTER<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">LINEAR<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerB, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_WRAP_S<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">MIRRORED_REPEAT<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">samplerParameteri<\/span>(samplerB, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_WRAP_T<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">MIRRORED_REPEAT<\/span>);\r\n\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">activeTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, texture);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindSampler<\/span>(<span class=\"pl-c1\">0<\/span>, samplerA);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">activeTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE1<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTexture<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, texture);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindSampler<\/span>(<span class=\"pl-c1\">1<\/span>, samplerB);<\/pre>\n<\/div>\n<h2><a id=\"user-content-transform-feedback\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#transform-feedback\"><\/a>Transform Feedback<\/h2>\n<p>Transform feedback allows the output of the vertex shader to be captured in a buffer object. This is useful for particle systems and simulation that perform on the GPU without any CPU intervention.<\/p>\n<p>In WebGL 1, when we want to implement such feature, usually a texture storing the states of particles is inevitable. Two textures, to be precise, storing states from previous frame and current frame, and ping-pong between them.<\/p>\n<p>Here&#8217;s an example of the WebGL 1 approach (from <a href=\"https:\/\/github.com\/toji\/webgl2-particles-2\">toji&#8217;s WebGL Particles take 2<\/a>).<\/p>\n<p>In the first-pass fragment shader, do the simulation, and store the position results in a texture.<\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-c\">\/\/ First pass - Fragment Shader<\/span>\r\n<span class=\"pl-k\">uniform<\/span> <span class=\"pl-k\">sampler2D<\/span> tPositions;\r\n<span class=\"pl-k\">varying<\/span> <span class=\"pl-k\">vec2<\/span> vUv;\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n<span class=\"pl-k\">vec4<\/span> runSimulation(<span class=\"pl-k\">vec4<\/span> pos) {\r\n    <span class=\"pl-c\">\/\/ simulation<\/span>\r\n    <span class=\"pl-c\">\/\/ ...<\/span>\r\n    <span class=\"pl-k\">return<\/span> pos;\r\n}\r\n\r\n<span class=\"pl-k\">void<\/span> main() {\r\n    <span class=\"pl-k\">vec4<\/span> pos <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">texture2D<\/span>( tPositions, vUv );\r\n    <span class=\"pl-c\">\/\/...<\/span>\r\n    pos <span class=\"pl-k\">=<\/span> runSimulation(pos);\r\n\r\n    <span class=\"pl-c\">\/\/ Write new position out<\/span>\r\n    <span class=\"pl-c1\">gl_FragColor<\/span> <span class=\"pl-k\">=<\/span> pos;\r\n}<\/pre>\n<\/div>\n<p>And then we use this position texture as an input for our second pass vertex shader.<\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-c\">\/\/ Second pass - Vertex Shader<\/span>\r\n<span class=\"pl-k\">attribute<\/span> <span class=\"pl-k\">vec3<\/span> position;\r\n<span class=\"pl-k\">uniform<\/span> <span class=\"pl-k\">float<\/span> pointSize;\r\n<span class=\"pl-k\">uniform<\/span> <span class=\"pl-k\">sampler2D<\/span> map;\r\n<span class=\"pl-k\">varying<\/span> <span class=\"pl-k\">vec2<\/span> vUv;\r\n\r\n<span class=\"pl-c\">\/\/...<\/span>\r\n\r\n<span class=\"pl-k\">void<\/span> main() {\r\n    vUv <span class=\"pl-k\">=<\/span> position.xy <span class=\"pl-k\">+<\/span> <span class=\"pl-k\">vec2<\/span>( <span class=\"pl-c1\">0.5<\/span> <span class=\"pl-k\">\/<\/span> width, <span class=\"pl-c1\">0.5<\/span> <span class=\"pl-k\">\/<\/span> height );\r\n    <span class=\"pl-k\">vec3<\/span> color <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">texture2D<\/span>( map, vUv ).rgb;\r\n    <span class=\"pl-c1\">gl_PointSize<\/span> <span class=\"pl-k\">=<\/span> pointSize;\r\n    <span class=\"pl-c1\">gl_Position<\/span> <span class=\"pl-k\">=<\/span> projectionMatrix <span class=\"pl-k\">*<\/span> modelViewMatrix <span class=\"pl-k\">*<\/span> <span class=\"pl-k\">vec4<\/span>( color, <span class=\"pl-c1\">1.0<\/span>);\r\n}<\/pre>\n<\/div>\n<p>What follows\u00a0is how we do it in WebGL 2. With Transform feedback, we can discard the fragment shader in step 1, as well as the texture. We write the output (position) of the vertex shader in step 1 to the vertex attribute array input of step 2. (In practice, you still need a placeholder trivial fragment shader for the first step to correctly compile the program.)<\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-c\">\/\/ First pass - Vertex Shader<\/span>\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n<span class=\"pl-k\">out<\/span> <span class=\"pl-k\">vec3<\/span> v_position;\r\n<span class=\"pl-k\">void<\/span> main() {\r\n    <span class=\"pl-c\">\/\/ ...<\/span>\r\n    v_position <span class=\"pl-k\">=<\/span> u_projMatrix <span class=\"pl-k\">*<\/span> u_modelViewMatrix <span class=\"pl-k\">*<\/span> <span class=\"pl-k\">vec4<\/span>(a_position, <span class=\"pl-c1\">1.0<\/span>);\r\n}<\/pre>\n<\/div>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-c\">\/\/ Second pass - Vertex Shader<\/span>\r\n<span class=\"pl-k\">in<\/span> <span class=\"pl-k\">vec3<\/span> a_position;\r\n<span class=\"pl-k\">void<\/span> main() {\r\n    <span class=\"pl-c1\">gl_Position<\/span> <span class=\"pl-k\">=<\/span> projectionMatrix <span class=\"pl-k\">*<\/span> modelViewMatrix <span class=\"pl-k\">*<\/span> <span class=\"pl-k\">vec4<\/span>( a_position, <span class=\"pl-c1\">1.0<\/span> );\r\n    <span class=\"pl-c\">\/\/ ...<\/span>\r\n}<\/pre>\n<\/div>\n<p>And here&#8217;s how we bind the buffers (from <a href=\"https:\/\/github.com\/WebGLSamples\/WebGL2Samples\">WebGL2SamplesPack<\/a>):<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> transformFeedback <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">createTransformFeedback<\/span>();\r\n<span class=\"pl-k\">var<\/span> varyings <span class=\"pl-k\">=<\/span> [<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>v_position<span class=\"pl-pds\">'<\/span><\/span>, <span class=\"pl-c\">\/*...*\/<\/span>];\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">transformFeedbackVaryings<\/span>(programTransform, varyings, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">SEPARATE_ATTRIBS<\/span>);\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, particleVBOs[i][<span class=\"pl-smi\">Particle<\/span>.<span class=\"pl-c1\">POSITION<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bufferData<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, particlePositions, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">STREAM_COPY<\/span>);\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindTransformFeedback<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TRANSFORM_FEEDBACK<\/span>, transformFeedback);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBufferBase<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TRANSFORM_FEEDBACK_BUFFER<\/span>, <span class=\"pl-c1\">0<\/span>, particleVBOs[(currentSourceIdx <span class=\"pl-k\">+<\/span> <span class=\"pl-c1\">1<\/span>) <span class=\"pl-k\">%<\/span> <span class=\"pl-c1\">2<\/span>][<span class=\"pl-smi\">Particle<\/span>.<span class=\"pl-c1\">POSITION<\/span>]);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">enable<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RASTERIZER_DISCARD<\/span>);   <span class=\"pl-c\">\/\/ we are not drawing<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">beginTransformFeedback<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">POINTS<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">drawArrays<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">POINTS<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">NUM_PARTICLES<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">endTransformFeedback<\/span>();\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">disable<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RASTERIZER_DISCARD<\/span>);\r\n\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBufferBase<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TRANSFORM_FEEDBACK_BUFFER<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">null<\/span>);<\/pre>\n<\/div>\n<h2><a id=\"user-content-a-set-of-texture-new-features\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#a-set-of-texture-new-features\"><\/a>A set of new texture features:<\/h2>\n<p>Here is a list of the new texture features in WebGL 2.<\/p>\n<ul>\n<ul>\n<li>sRGB textures<br \/>\nAllow the application to perform gamma-correct rendering.<\/li>\n<\/ul>\n<\/ul>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texImage2D<\/span>(\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>,\r\n    <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c\">\/\/ Level of details<\/span>\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">SRGB8<\/span>, <span class=\"pl-c\">\/\/ Format<\/span>\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB<\/span>,\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>, <span class=\"pl-c\">\/\/ Size of each channel<\/span>\r\n    image\r\n);<\/pre>\n<\/div>\n<p>The sRGB texture will be automatically converted to linear space when being fetched in the shader. For physically-based rendering and other operations we normally want to deal with colors in a linear space, not in display space.<\/p>\n<ul>\n<li>Vertex texture for:\n<ul>\n<li>terrain<\/li>\n<li>water<\/li>\n<li>skeleton animation<\/li>\n<\/ul>\n<\/li>\n<li>Texture LOD<\/li>\n<\/ul>\n<p>The texture LOD parameter is used to determine which mipmap to fetch from; it can now be clamped. The base and maximum mipmap level can both be set as clamps. This allows mipmap streaming, i.e., loading only the mipmap levels currently needed. This is very useful for a WebGL environment, where textures are downloaded via a network.<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameterf<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MIN_LOD<\/span>, <span class=\"pl-c1\">0.0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">texParameterf<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_MAX_LOD<\/span>, <span class=\"pl-c1\">10.0<\/span>);<\/pre>\n<\/div>\n<ul>\n<li>ETC2\/EAC texture compression<\/li>\n<\/ul>\n<p>A mandatory supported feature, compressed textures have obvious transmission time savings.<\/p>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">compressedTexImage2D<\/span>(\r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">TEXTURE_2D<\/span>, \r\n    <span class=\"pl-c1\">0<\/span>, \r\n    <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">COMPRESSED_RGBA8_ETC2_EAC<\/span>, \r\n    <span class=\"pl-c1\">IMAGE_SIZE<\/span>.<span class=\"pl-c1\">width<\/span>, \r\n    <span class=\"pl-c1\">IMAGE_SIZE<\/span>.<span class=\"pl-c1\">height<\/span>, \r\n    <span class=\"pl-c1\">0<\/span>, \r\n    pixels\r\n);<\/pre>\n<\/div>\n<ul>\n<li>Integer textures<\/li>\n<li>Non-Power-of-Two Texture\n<ul>\n<li>texturing video<\/li>\n<li>2D Sprite<\/li>\n<\/ul>\n<\/li>\n<li>Floating point textures\n<ul>\n<li>half-float: High dynamic range imaging<\/li>\n<li>full-float: Variance shadow maps soft shadow<\/li>\n<li>a feature coming together with <strong>floating point textures<\/strong> is <strong>floating point renderbuffer<\/strong> (also with multisample support).<\/li>\n<\/ul>\n<\/li>\n<li>Seamless cube map<\/li>\n<\/ul>\n<p>Cube map is already available in WebGL 1. What&#8217;s new in WebGL 2 is that the cube map is seamless (and is always seamless, unlike in OpenGL where you can set it). With this feature we are free from using hacks to get rid of the artifacts near the borders.<\/p>\n<ul>\n<li>A set of additional texture formats<\/li>\n<\/ul>\n<div class=\"highlight highlight-source-js\">\n<pre>textureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGB<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGB8<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB8<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGB16F<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB16F<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">HALF_FLOAT<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGBA32F<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA32F<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FLOAT<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">R16F<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">R16F<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RED<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">HALF_FLOAT<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RG16F<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RG16F<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RG<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">HALF_FLOAT<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGBA<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGB8UI<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB8UI<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGB_INTEGER<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>\r\n};\r\n\r\ntextureFormats[<span class=\"pl-smi\">TextureTypes<\/span>.<span class=\"pl-c1\">RGBA8UI<\/span>] <span class=\"pl-k\">=<\/span> {\r\n    internalFormat<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA8UI<\/span>,\r\n    format<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">RGBA_INTEGER<\/span>,\r\n    type<span class=\"pl-k\">:<\/span> <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">UNSIGNED_BYTE<\/span>\r\n};<\/pre>\n<\/div>\n<h2><a id=\"user-content-new-glsl-300-es-shader\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#new-glsl-300-es-shader\"><\/a>New GLSL 3.00 ES Shader<\/h2>\n<p>And here comes our new shader: GLSL 3.00 ES! This new version brings in a bunch of new features that are not in GLSL 1.00. But the grammar changed at some point, so it can be quite painful converting over at the start.<\/p>\n<p>Note that a shader in GLSL 1.00 is still fully supported in a WebGL 2 context. It&#8217;s only the GLSL 3.00 ES grammar that doesn&#8217;t have backwards compatibility with GLSL 1.00. Only when a <code>#version 300 es<\/code> tag is added at the top of the shaders will the GLSL 3.00 ES version be turned on.<\/p>\n<p>We will quickly list here a bunch of new features and new built-in functions in GLSL 3.00 ES.<\/p>\n<ul>\n<li>Layout qualifiers<\/li>\n<\/ul>\n<p>Vertex shader inputs can now be declared with layout qualifiers to explicitly bind the location in the shader source without requiring making <code>gl.getAttribLocation<\/code> calls. These are declared like this:<\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre><span class=\"pl-k\">#version<\/span> <span class=\"pl-c1\">300<\/span> es\r\n<span class=\"pl-k\">#define<\/span> POSITION_LOCATION <span class=\"pl-c1\">0<\/span>\r\n<span class=\"pl-k\">#define<\/span> TEXCOORD_LOCATION <span class=\"pl-c1\">4<\/span>\r\n<span class=\"pl-c\">\/\/ ...<\/span>\r\n<span class=\"pl-k\">layout<\/span>(location <span class=\"pl-k\">=<\/span> POSITION_LOCATION) <span class=\"pl-k\">in<\/span> <span class=\"pl-k\">vec2<\/span> position;\r\n<span class=\"pl-k\">layout<\/span>(location <span class=\"pl-k\">=<\/span> TEXCOORD_LOCATION) <span class=\"pl-k\">in<\/span> <span class=\"pl-k\">vec2<\/span> texcoord;<\/pre>\n<\/div>\n<div class=\"highlight highlight-source-js\">\n<pre><span class=\"pl-k\">var<\/span> vertexPosLocation <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">0<\/span>; <span class=\"pl-c\">\/\/ set with GLSL layout qualifier<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">enableVertexAttribArray<\/span>(vertexPosLocation);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, vertexPosBuffer);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">vertexAttribPointer<\/span>(vertexPosLocation, <span class=\"pl-c1\">2<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FLOAT<\/span>, <span class=\"pl-c1\">false<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, <span class=\"pl-c1\">null<\/span>);\r\n\r\n<span class=\"pl-k\">var<\/span> vertexTexLocation <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">4<\/span>; <span class=\"pl-c\">\/\/ set with GLSL layout qualifier<\/span>\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">enableVertexAttribArray<\/span>(vertexTexLocation);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, vertexTexBuffer);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">vertexAttribPointer<\/span>(vertexTexLocation, <span class=\"pl-c1\">2<\/span>, <span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">FLOAT<\/span>, <span class=\"pl-c1\">false<\/span>, <span class=\"pl-c1\">0<\/span>, <span class=\"pl-c1\">0<\/span>);\r\n<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-en\">bindBuffer<\/span>(<span class=\"pl-smi\">gl<\/span>.<span class=\"pl-c1\">ARRAY_BUFFER<\/span>, <span class=\"pl-c1\">null<\/span>);<\/pre>\n<\/div>\n<p>The same applies to fragment shader outputs. Layout qualifiers can also be used to control the memory layout for uniform blocks.<\/p>\n<ul>\n<li>Non-square matrix<\/li>\n<\/ul>\n<p>Quite straightforward. One use case is replacing a 4&#215;4 affine matrix where the last row is (0, 0, 0, 1) with a 4&#215;3 matrix.<\/p>\n<ul>\n<li>Full integer support<\/li>\n<\/ul>\n<p>Built-in functions can now take an integer as an input variable.<\/p>\n<ul>\n<li>Flat\/smooth interpolators<\/li>\n<\/ul>\n<p>We can now explicitly declare <code>flat<\/code> interpolators to have flat shading.<\/p>\n<ul>\n<li>Centroid sampling<\/li>\n<\/ul>\n<p>This is used to avoid rendering artifacts when multisampling. Read <a href=\"https:\/\/www.opengl.org\/pipeline\/article\/vol003_6\/\">this article<\/a> for more details. Here is a <a href=\"http:\/\/webglsamples.org\/WebGL2Samples\/#glsl_centroid\">WebGL 2 Sample of centroid sampling<\/a>.<\/p>\n<ul>\n<li>New built-in functions<\/li>\n<\/ul>\n<p>Some very handy functions such as <code>textureOffset<\/code>, <code>texelFetch<\/code>, <code>dFdx<\/code>, <code>textureGrad<\/code>, <code>textureLOD<\/code>, etc. You can always find the complete lists in the\u00a0<a href=\"https:\/\/www.khronos.org\/registry\/gles\/specs\/3.0\/GLSL_ES_Specification_3.00.4.pdf\">GLSL 3.00 ES Spec<\/a><\/p>\n<ul>\n<li><code>gl_InstanceID<\/code> and <code>gl_VertexID<\/code><\/li>\n<\/ul>\n<p>These allow identification of instances and vertices within the shader.<\/p>\n<ul>\n<li>Other function name changes<\/li>\n<\/ul>\n<p>For example, <code>texture2D(sampler2D sampler, vec2 coord)<\/code>, <code>textureCube(samplerCube sampler, vec3 coord)<\/code>, (and <code>texture3D<\/code> if there is any in our old version) are now replaced with a set of overrided functions of <code>texture<\/code><\/p>\n<div class=\"highlight highlight-source-glsl\">\n<pre>gvec4 texture (gsampler2D <span class=\"pl-k\">sampler<\/span>, <span class=\"pl-k\">vec2<\/span> P [, <span class=\"pl-k\">float<\/span> bias] )\r\ngvec4 texture (gsampler3D <span class=\"pl-k\">sampler<\/span>, <span class=\"pl-k\">vec3<\/span> P [, <span class=\"pl-k\">float<\/span> bias] )\r\ngvec4 texture (gsamplerCube <span class=\"pl-k\">sampler<\/span>, <span class=\"pl-k\">vec3<\/span> P [, <span class=\"pl-k\">float<\/span> bias] )<\/pre>\n<\/div>\n<h1><a id=\"user-content-credits\" class=\"anchor\" href=\"https:\/\/github.com\/shrekshao\/MoveWebGL1EngineToWebGL2\/blob\/master\/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md#credits\"><\/a>Credits<\/h1>\n<ul>\n<li>Brandon Jones <a href=\"http:\/\/blog.tojicode.com\/2013\/09\/whats-coming-in-webgl-20.html\">http:\/\/blog.tojicode.com\/2013\/09\/whats-coming-in-webgl-20.html<\/a><\/li>\n<li>Hongwei Li <a href=\"https:\/\/zhuanlan.zhihu.com\/p\/19957067?refer=webgl\">https:\/\/zhuanlan.zhihu.com\/p\/19957067?refer=webgl<\/a><\/li>\n<li>WebGL 2 Spec <a href=\"https:\/\/www.khronos.org\/registry\/webgl\/specs\/latest\/2.0\/\">https:\/\/www.khronos.org\/registry\/webgl\/specs\/latest\/2.0\/<\/a><\/li>\n<li>OpenGL ES 3 Spec <a href=\"https:\/\/www.khronos.org\/registry\/gles\/specs\/3.0\/es_spec_3.0.0.pdf\">https:\/\/www.khronos.org\/registry\/gles\/specs\/3.0\/es_spec_3.0.0.pdf<\/a><\/li>\n<li>OpenGL ES 3 Programming Guide <a href=\"http:\/\/1.droppdf.com\/files\/v4voM\/addison-wesley-opengl-es-3-0-programming-guide-2nd-2014.pdf\">http:\/\/1.droppdf.com\/files\/v4voM\/addison-wesley-opengl-es-3-0-programming-guide-2nd-2014.pdf<\/a><\/li>\n<li><a href=\"http:\/\/gamedev.stackexchange.com\/questions\/9668\/what-are-3d-textures\">http:\/\/gamedev.stackexchange.com\/questions\/9668\/what-are-3d-textures<\/a><\/li>\n<li><a href=\"http:\/\/www.gamedev.net\/topic\/655969-speed-gluniform-vs-uniform-buffer-objects\/\">http:\/\/www.gamedev.net\/topic\/655969-speed-gluniform-vs-uniform-buffer-objects\/<\/a><\/li>\n<li>Sijie Tian <a href=\"https:\/\/hacks.mozilla.org\/2014\/01\/webgl-deferred-shading\/\">https:\/\/hacks.mozilla.org\/2014\/01\/webgl-deferred-shading\/<\/a><\/li>\n<li>Dong Dong <a href=\"https:\/\/www.zhihu.com\/question\/49327688\/answer\/115691345?from=profile_answer_card\">https:\/\/www.zhihu.com\/question\/49327688\/answer\/115691345?from=profile_answer_card<\/a><\/li>\n<li>Cesium <a href=\"https:\/\/github.com\/AnalyticalGraphicsInc\/cesium\">https:\/\/github.com\/AnalyticalGraphicsInc\/cesium<\/a><\/li>\n<li>WebGL Stats <a href=\"http:\/\/webglstats.com\/\">http:\/\/webglstats.com\/<\/a><\/li>\n<li>WebGL 2 for Siggraph Asia 2015 <a href=\"https:\/\/docs.google.com\/presentation\/d\/1Orx0GB0cQcYhHkYsaEcoo5js3c5-pv7ahPniIRIzzfg\/edit#slide=id.gd1fc5cab2_0_8\">https:\/\/docs.google.com\/presentation\/d\/1Orx0GB0cQcYhHkYsaEcoo5js3c5-pv7ahPniIRIzzfg\/edit#slide=id.gd1fc5cab2_0_8<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>by Shuai Shao github repo for article here Last time we showed how to deal with issues porting a WebGL 1 engine to WebGL 2. In this article, we will talk about what new features come with WebGL 2 and what cool things can we do with them. New features Multisampled Renderbuffers Previously, if we [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4447","post","type-post","status-publish","format-standard","hentry","category-misc"],"_links":{"self":[{"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/posts\/4447","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/comments?post=4447"}],"version-history":[{"count":3,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/posts\/4447\/revisions"}],"predecessor-version":[{"id":4450,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/posts\/4447\/revisions\/4450"}],"wp:attachment":[{"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/media?parent=4447"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/categories?post=4447"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.realtimerendering.com\/blog\/wp-json\/wp\/v2\/tags?post=4447"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}