tag:blogger.com,1999:blog-84366142865579829272024-02-01T19:07:33.330-08:00Oleksandr Popov Live Wallpapers.We create 3D live wallpapers for AndroidOleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.comBlogger55125tag:blogger.com,1999:blog-8436614286557982927.post-45882067683851883642023-10-28T03:47:00.001-07:002023-10-28T03:48:58.743-07:003D Castle Live WallpaperWe're glad to announce the release of new Android live wallpaper app - "3D Castle Live Wallpaper".
<p>
Immerse yourself in a dynamic, enchanting 3D scene featuring a charming and stylized castle nestled within a lush forest. This animated live wallpaper is designed to breathe life into your device, offering a rich and captivating visual experience.
<p>
Step into a whimsical world where a beautifully designed castle stands amidst a serene forest. The cartoon-style graphics exude charm and character, making your screen come alive with vivid colors and engaging details.
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYAkJg5IevC8sveeOhY6oXX2I0fQp67Cg0l4rZnJ-GlYRINzr9TTHFHoY2cqg-ZowWgZfU_SowOnpLKcjwW90zh19sHyYVAi5X7NJgvy0iXm-zZl2Z8poYw4BnCuQ-JeB7c-RYVFdi5-Gq1GCcbOY79-PMd41KHpWdwZNNwQR-juWGSJtMskhJRhM2Qo/s1280/youtube.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="700" data-original-height="720" data-original-width="1280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYAkJg5IevC8sveeOhY6oXX2I0fQp67Cg0l4rZnJ-GlYRINzr9TTHFHoY2cqg-ZowWgZfU_SowOnpLKcjwW90zh19sHyYVAi5X7NJgvy0iXm-zZl2Z8poYw4BnCuQ-JeB7c-RYVFdi5-Gq1GCcbOY79-PMd41KHpWdwZNNwQR-juWGSJtMskhJRhM2Qo/s400/youtube.png"/></a></div>
<p>
Get app on Google Play - <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.cartooncastle3d">https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.cartooncastle3d</a>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-28756634964766566752023-09-22T13:03:00.005-07:002023-09-22T13:05:22.525-07:003D Airport Live WallpaperWe are thrilled to announce the release of our latest Android app, "3D Airport Live Wallpaper"! Immerse yourself in the captivating world of aviation with this dynamic live wallpaper. Transform your Android device into a bustling airport scene, brought to life with stunning 3D graphics. With "3D Airport Live Wallpaper," you can choose from three different plane models to suit your style and preference, adding a personal touch to your device's home screen. Experience the magic of a day-night cycle that seamlessly transitions from the brightness of day to the tranquility of night, creating a truly immersive airport ambiance. Elevate your Android experience with "3D Airport Live Wallpaper" and embark on a journey like never before. Download it now and let your screen take flight!
<a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.airport">Get the app on Google Play!</a>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2UXDWsnu4rLRZXbJuMKki19mFplXBH5KayC7xt5YbI3aO4W-pyv_uAoRlhl5i1x5fhaWVaLsgpHEZ1LOABrjd6caJvEAGxWdBuwRmZadx_uyPlJYvT8qZoFUgRwrv10POEuV2w2fBS2wQ7jMLTXegFdi9gNLVdfsDLjv7Y6WI3YnVXuhoaYu0RaMrRZs/s1280/youtube.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="600" data-original-height="720" data-original-width="1280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2UXDWsnu4rLRZXbJuMKki19mFplXBH5KayC7xt5YbI3aO4W-pyv_uAoRlhl5i1x5fhaWVaLsgpHEZ1LOABrjd6caJvEAGxWdBuwRmZadx_uyPlJYvT8qZoFUgRwrv10POEuV2w2fBS2wQ7jMLTXegFdi9gNLVdfsDLjv7Y6WI3YnVXuhoaYu0RaMrRZs/s600/youtube.png"/></a></div>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-60941563217935342242023-07-22T12:21:00.000-07:002023-07-22T12:21:06.038-07:003D Golden Ganesha Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.ganesha">3D Golden Ganesha Wallpaper</a>. This is a captivating and spiritually enriching live wallpaper app that brings the divine presence of Lord Ganesha to life on the home screen of your device.
</p>
<p>
Allow the divine presence of Lord Ganesha to grace your device's home screen with this mesmerizing live wallpaper. Delve into a world of abundance, wisdom, and spiritual enlightenment as you navigate through your device and experience the blessings of Lord Ganesha. Let this live wallpaper serve as a constant reminder of Ganesha's divine guidance and unwavering support in your daily life.
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/OE12L30LY1M" width="579" youtube-src-id="OE12L30LY1M"></iframe></div><br />
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-14525883922368627072023-07-22T12:18:00.004-07:002023-07-22T12:19:03.722-07:003D Golden Shiva Live Wallpaper<p>
We've released a new <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.shiva">3D Golden Shiva Live Wallpaper</a>. This meticulously crafted live wallpaper unveils a captivating 3D scene featuring two majestic golden statues of Shiva: Lord Shiva and Shiva Nataraja.
</p>
<p>
Let the "3D Golden Shiva Live Wallpaper" grace your device's home screen and become a constant reminder of Lord Shiva's timeless wisdom, cosmic power, and divine grace in your life. Immerse yourself in the transformative energy of Shiva as you embark on a spiritual journey of self-discovery and find solace, inspiration, and enlightenment in the divine embrace of the supreme deity. Surrender to the divine dance of Lord Shiva or seek solace in his serene presence as you explore the depths of spirituality and embrace the transcendent essence of Shiva's divine energy.
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/NQ3mVapMPQE" width="579" youtube-src-id="NQ3mVapMPQE"></iframe></div><br />
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-45031912978488177362023-07-22T12:16:00.001-07:002023-07-22T12:16:23.128-07:003D Lord Krishna Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.krishna">3D Lord Krishna Wallpaper</a>. This live wallpaper is a mesmerizing and spiritually uplifting app that brings the divine presence of Lord Krishna right to the home screen of your device.
</p>
<p>
Allow the divine presence of Lord Krishna to grace your device's home screen with this live wallpaper. Dive into a world of serenity, beauty, and devotion as you navigate through your device and experience the loving embrace of Lord Krishna's divine energy. Let this live wallpaper serve as a constant reminder of the power of love, compassion, and enlightenment, inspiring you in your daily life.
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/idFZKypOHY0" width="579" youtube-src-id="idFZKypOHY0"></iframe></div><br />
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-30265087365318036162022-12-19T11:45:00.004-08:002023-01-02T09:03:54.893-08:00Voxel Airplanes 3D Live Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.voxelairplanes">Voxel Airplanes 3D Wallpaper</a>. This live wallpaper brings a fully 3D scene with cute cartoonish airplanes flying above various landscapes to the home screen of your phone.
</p>
<p>
Scene has a distinct old-skool pixelated look. All objects are deliberately of a low fidelity - ground and clouds have these crunchy, unfiltered textures. Yet they are full of small eye-catching details like occasional stylized anime-like glass reflections and planes moving around ever so slightly, struggling in the strong wind. Make sure to enable a fully randomized mode to see all varieties of planes and terrains!
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/w8imKGLQaGY" width="579" youtube-src-id="w8imKGLQaGY"></iframe></div><br />
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com1tag:blogger.com,1999:blog-8436614286557982927.post-32903336197819611772022-10-26T11:58:00.005-07:002022-10-26T12:02:46.684-07:00Floating Islands 3D Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.floatingislands">Floating Islands 3D Wallpaper</a>. This live wallpaper brings a magical world with mysterious floating islands onto the home screen of your phone.
</p>
<p>
You can contemplate a serene scene with an endless flight through this wondrous place. Huge rocks are suspended mid-air, defying all laws of physics. These ancient giants are veiled by the thick fog, and lush green ferns grow here and there on their cliffs. Occasionally you may encounter flocks of local birds emerging from the dense clouds.</p>
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/FVAWrjMFHmI" width="579" youtube-src-id="FVAWrjMFHmI"></iframe></div><br />
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com1tag:blogger.com,1999:blog-8436614286557982927.post-53813131692531345742022-06-06T13:39:00.006-07:002022-06-06T13:40:35.966-07:00Spring Flowers 3D Live Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.flowers">Spring Flowers 3D Live Wallpaper</a>. With this Android app you can decorate your phone’s screen with a view of blooming spring field.
</p>
<p>
This app brings you to the field of fresh green grass, bright young flowers and sunny spring weather. Butterflies fly over the blooming dandelions, and grass blades wave under the gentle wind. You can notice small details in flower petals, and even ants doing their business on the ground under the vegetation. This live wallpaper can represent different times of day - either bright sunny afternoon, sunset and sunrise lit in warm colors, or starry night. Scene is represented in a true 3D - everything can be viewed from any different angles as the camera slowly moves.
</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="368" src="https://www.youtube.com/embed/Cz1pk10slRA" width="579" youtube-src-id="Cz1pk10slRA"></iframe></div><br /><p><br /></p>
<p>Customization options of Spring Flowers 3D Live Wallpaper:</p>
<ul>
<li>Smooth, detailed graphics and 4K image quality</li>
<li>Different camera settings</li>
<li>Different time of day settings</li>
<li>Customizable color modes</li>
<li>View can rotate interactively with the swipe of screen</li>
<li>Low power and memory consumption</li>
<li>Battery-saving limiting of FPS</li>
</ul>
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.flowers&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-9834933538422788552022-04-06T02:32:00.000-07:002022-04-06T02:32:15.665-07:00Stonehenge 3D Live Wallpaper<p>
Today we've released a <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.stonehenge">Stonehenge 3D Live Wallpaper</a>. With this Android app you can decorate your phone’s screen with a view of a famous English Stonehenge monument.
</p>
<p>
This live wallpaper will bring you to one of the most famous historical landmarks of England - the monument of Stonehenge. The location and its surroundings are designated by UNESCO as a World Heritage Site. With this app you can contemplate this official British cultural icon right on the home screen of your phone. A fully 3D aerial photo scan of the whole monument shows various details of Stonehenge's standing stones from any angle in true 3D.
</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8nPZTFzGtr0YWcDEt8oS08jC3nbdneOwLd-0dg7yodN6o1eXFwneDEN8X225PQ4d_UnntfTQ-iUQ5T88ElQleVycOC_tYThjgt8nV97vAGsqbcu0tYNLrJBObCa4Uf_H4w4uWA42yKnGbSC1MBDHza5quhC_ZjrSm8b5TsJObYZv08kDUXNXOAEOu/s1024/feature.jpg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="600" data-original-height="500" data-original-width="1024" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8nPZTFzGtr0YWcDEt8oS08jC3nbdneOwLd-0dg7yodN6o1eXFwneDEN8X225PQ4d_UnntfTQ-iUQ5T88ElQleVycOC_tYThjgt8nV97vAGsqbcu0tYNLrJBObCa4Uf_H4w4uWA42yKnGbSC1MBDHza5quhC_ZjrSm8b5TsJObYZv08kDUXNXOAEOu/s600/feature.jpg"/></a></div>
<p>Customization options of Stonehenge 3D Live Wallpaper:</p>
<ul>
<li>Smooth graphics and 4K image quality</li>
<li>Different camera settings</li>
<li>Customizable color modes</li>
<li>View rotates interactively with the swipe of screen</li>
<li>Low power and memory consumption</li>
<li>Battery-saving limiting of FPS</li>
</ul>
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.stonehenge&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div>
<p>
Original 3D scans are created by GSXNet and are used by CC BY 4.0 license - <a href="https://skfb.ly/6QXqU">https://skfb.ly/6QXqU</a>
</p>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-46694873181980462512022-04-05T09:46:00.003-07:002022-04-05T09:46:31.784-07:00Versailles 3D Live Wallpaper<p>With this app you can always contemplate the sheer beauty of The Palace of Versailles. Its gorgeous interiors are the classical examples of French Baroque architectural style. App has full 3D scans of four most famous buildings and chambers of the Versailles.</p>
<p>App features the following customization options:</p>
<li>Four different rooms</li><li>Various camera angles</li><li>Camera speed can be configured</li><li>Various color modes</li><li>Fast access to settings using double tap on home screen</li>
<br/>
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="498" src="https://www.youtube.com/embed/RSla_WehSE8" width="680" youtube-src-id="RSla_WehSE8"></iframe></div>
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperversailles&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div>
<br/>
<p>
Original 3D scans are created by The Palace of Versailles and are used by CC BY 4.0 license - <a href="https://en.chateauversailles.fr/">https://en.chateauversailles.fr/<a>
</p>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-40462445681415951592022-04-04T13:50:00.002-07:002022-04-04T13:53:45.356-07:00Lanhoso 3D Live Wallpaper<p>Lanhoso Castle Live Wallpaper brings a fully 3D view of a medieval castle situated on the top of the rock to the home screen of your phone or tablet.</p><p>Both the beauty of natural vista and power of the old stronghold are combined in this castle live wallpaper. This app represents the real location of the Castle of Lanhoso, the landmark and attraction of the Póvoa de Lanhoso region, Portugal. The castle is surrounded by countryside landscape with iconic red rooftops, and sits on top of the mount called Laje Grande which is 385 meters (1,263 ft.) tall. Especially striking is the ancient keep tower placed at the highest point of the mountain.</p><p>FEATURES of live wallpaper:</p><p>• Smooth graphics and 4K HD image quality</p><p>• Different camera settings</p><p>• View rotates interactively with the swipe of screen</p><p>• Low power and memory consumption</p><p>• Battery-saving limiting of FPS</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/a2xRhTerSU4" width="320" youtube-src-id="a2xRhTerSU4"></iframe></div><br /><p><br /></p><p>Get app from <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaper.lanhoso">Google Play here</a>.</p>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com1tag:blogger.com,1999:blog-8436614286557982927.post-69631249460577700472021-04-08T05:39:00.004-07:002021-04-08T05:40:02.268-07:00Hallwyl Museum 3D Live Wallpaper<p>We're proud to announce our newly released app - <i>Hallwyl Museum 3D Live Wallpaper</i>. This wallpaper brings a real 3D scanned interiors of Hallwyl museum from Stockholm. Now you can contemplate gorgeous architecture of 19th century right on the home screen of your phone! </p><span></span><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="320" src="https://www.youtube.com/embed/v4T3mn_r5jU" width="480" youtube-src-id="v4T3mn_r5jU"></iframe></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpapermuseum" rel="nofollow" target="_blank"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div></div>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com3tag:blogger.com,1999:blog-8436614286557982927.post-16315923042206512602021-01-06T02:44:00.005-08:002021-04-08T05:40:18.674-07:00New wallpaper released - Dunes Desert 3D Live Wallpaper<p> We're proud to announce our newly released live wallpaper. This app provides a calming and peaceful scene of sand dunes desert. It is not as empty as you may think, take a look at it!</p><span></span><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/2Mau53AZZjs" width="320" youtube-src-id="2Mau53AZZjs"></iframe></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperdunes" rel="nofollow" target="_blank"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div></div>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com4tag:blogger.com,1999:blog-8436614286557982927.post-34466722701828850342020-09-27T02:50:00.004-07:002020-09-27T02:51:06.064-07:00Launceston City 3D Live Wallpaper<p style="text-align: left;">We're proud to announce our newly released live wallpaper. It has quite a new theme for us - an aerial overview of a city. This app provides a calming and peaceful scene of Tasmanian city Launceston seen from the bird's eye height.</p><span></span><span><a name='more'></a></span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKtQAEnXnprMXHk7acgXzUYgxUvJKosHQd21uILLR7lAHkFyaJvT24A1fWsI9aZnY_PboRcVz-DoWQHAQ6wN14kKM7xRu4qt70s00bsEh_9rkHQWgoZP5OXgCkAonjXyfzKLXDBlX9z_c/s1920/Screenshot_1601199969.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1920" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKtQAEnXnprMXHk7acgXzUYgxUvJKosHQd21uILLR7lAHkFyaJvT24A1fWsI9aZnY_PboRcVz-DoWQHAQ6wN14kKM7xRu4qt70s00bsEh_9rkHQWgoZP5OXgCkAonjXyfzKLXDBlX9z_c/w640-h400/Screenshot_1601199969.jpg" width="640" /></a></div><div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperlaunceston&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1"><img alt="Get it on Google Play" height="155" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="400" /></a></div>
<p>
App is based on real 3D scans of Launceston which are licensed under Creative Commons License - Attribution 3.0 Australia (CC BY 3.0).<br />
© Launceston City Council 2013<br />
Spatial Sciences Department<br />
Contact 03 63233341
</p>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com3tag:blogger.com,1999:blog-8436614286557982927.post-28434045636969544342020-07-13T11:39:00.001-07:002020-07-13T11:39:47.948-07:00Optimization of OpenGL ES vertex data<div>For Android live wallpapers it is very important to be lightweight. To get the best possible performance, smallest memory and power usage we constantly improve our live wallpapers by reducing the size of app resources and using various compressions supported by hardware.</div><div>The latest update of <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha" target="_blank">3D Buddha Live Wallpaper</a> introduced a more compact storing 3D objects to save memory and improve performance. We’ve updated its <a href="https://keaukraine.github.io/webgl-buddha/index.html" target="_blank">WebGL demo</a> counterpart in the same way, and in this article we will describe the process of this optimization.</div><span><a name='more'></a></span><div><h3 style="text-align: left;">Compact data types in OpenGL ES / WebGL</h3><div>Previously in our apps we used only floats to store all per-vertex information - position, normal, colors, etc. These are standard 32-bit IEEE-754 floating-point values which are versatile enough to keep any type of information ranging from vertex coordinates to colors.</div><div>However, not all types of data require precision of 32-bit floats. And OpenGL ES 2.0/WebGL have other less precise but more compact data types to use instead of 32-bit floats.</div><div><br /></div><div>First, OpenGL supports 16 and 8 bit signed and unsigned integers. So how can an integer value substitute a float? There are two options - use integer values in shader as is and cast them to floats, or normalize them. Normalization means that the driver/GPU performs conversion from integer to float value and vertex shader receives ready to use float value. Normalization converts integer values to a range [0, 1] or [-1, 1], depending on whether they are unsigned or signed integers. Precision of normalized value is specified by range of source integer value - the more bits are in source integer, the better is precision.</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDoihuhv-Mzm00oYkFD_zItp6R39bWWfnBEO4NitnxXfL2lF67xbLqdwDnVn6T8SkSJyiHTom25n_fKwuMOsebTdIZuplb1ULTlHwLX6HAx7Zjm0Oy1N0w5KKlV1nTxUGPIPfxsB_Wga4/s949/types.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="469" data-original-width="949" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDoihuhv-Mzm00oYkFD_zItp6R39bWWfnBEO4NitnxXfL2lF67xbLqdwDnVn6T8SkSJyiHTom25n_fKwuMOsebTdIZuplb1ULTlHwLX6HAx7Zjm0Oy1N0w5KKlV1nTxUGPIPfxsB_Wga4/w625-h310/types.png" width="625" /></a></div>So, for example, unsigned byte value 128 will be normalized to 0.5, and signed short -16383 will be normalized to -0.5. You can read more about conversions of normalized integers on this <a href="https://www.blogger.com/#">OpenGL wiki page</a>. To use normalized integers, you must set the normalized parameter of <font face="courier">glVertexAttribPointer</font> to <font face="courier">true</font>, and a shader will receive normalized floats. Typical values stored in unsigned bytes are colors, because there’s no need to have more than 1/256th precision for colors’ components - 3 or 4 unsigned bytes are perfect to store RGB or RGBA colors, respectively. Two shorts can be used to store UV coordinates of a typical 3D model, assuming they are within the [0, 1] range and repeating textures are not used on meshes. They provide enough precision for these needs - for example, unsigned short will provide sub-texel precision even for texture with dimension of 4096 since it’s precision is 1/65536. <div><br />Newer OpenGL ES 3.0 (and WebGL 2 which is based on it) introduces new compact data types:<div><span id="docs-internal-guid-0453369c-7fff-a7b7-bd49-999ff639c756"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div align="left" dir="ltr" style="margin-left: 0pt;"><ul><li><span id="docs-internal-guid-0453369c-7fff-a7b7-bd49-999ff639c756"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div align="left" dir="ltr" style="margin-left: 0pt;">Half floats for vertex data - these are 16-bit IEEE-754 floating point numbers. They use 2 bytes similar to GL_SHORT but their range and precision are not as limited as normalized values.</div></span></span></li><li><div>4-bytes packed format INT_2_10_10_10_REV which contains 4 integer values which can be normalized to floats. Three of these integers have 10 bits precision and one has only 2 bits. This format is described in section 2.9.2 of <a href="https://www.khronos.org/registry/OpenGL/specs/es/3.0/es_spec_3.0.pdf" target="_blank">OpenGL ES 3.0 specs</a>.</div></li></ul></div>On some hardware usage of normalized integer types may not be free and could require a couple of extra GPU cycles to convert values to floats before feeding them into the shader. However memory savings provide more benefits than additional conversion overhead since it is performed per vertex.</span></span></div></div><div><span><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><br /></span></span></div><div><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><h3 style="text-align: left;">Stride size, offsets and paddings</h3><div><div>In our pipeline we use a two-steps approach - first generate and then compress vertex data. First, source OBJ and FBX files are converted into ready-to-use by GPU arrays - vertex indices and interleaved vertex attributes data (strides). The next step is converting float values to more compact data types. This is done with a command-line utility written in JavaScript running on Node.js. You can get it <a href="https://github.com/keaukraine/stride-compressor" target="_blank">from GitHub</a>.</div></div><div><div>To achieve the best cache coherency of reading vertex data it is recommended to create strides of a certain size. However, this depends on a GPU type so there are quite different recommendations regarding optimal total stride size:</div></div><div><div><ul style="text-align: left;"><li><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div><div>According to official <a href="https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html" target="_blank">Apple iOS OpenGL ES documentation</a>, stride size must be a multiple of 4 bytes to achieve best performance and reduce driver overhead. Apparently this is caused by the architecture of Apple chips, and they use Imagination Technologies PowerVR GPUs.</div></div></span></li><li><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div><div>Official <a href="https://cdn.imgtec.com/sdk-documentation/PowerVR.Performance+Recommendations.pdf" target="_blank">PowerVR Performance Recommendations</a> document vaguely states that some hardware may benefit from strides aligned by 16 byte boundaries.</div></div></span></li><li><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div><div>ARM in their <a href="https://developer.arm.com/docs/dui0555/a/optimization-checklist/ensure-your-application-is-not-cpu-bound/align-data" target="_blank">Application Optimization Guide</a> recommends aligning data to 8 bytes for optimal performance on Mali GPUs.</div></div></span></li><li><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><div><div>There are no official recommendations for vertex data alignment for Qualcomm Adreno GPUs.</div></div></span></li></ul></div></div><div>Our tool aligns data by 4 bytes to save more memory (in our applications we don’t use models with an excessive amount of vertices so accessing vertex data is not the bottleneck).</div><div><br /></div><div><div>Next, when you use mixed data types in interleaved vertex data it is necessary for each attribute data to be properly aligned within stride.This is stated in section 2.10.2 of <a href="https://www.khronos.org/registry/OpenGL/specs/es/3.0/es_spec_3.0.pdf" target="_blank">OpenGL ES 3.0 specs</a> - attribute offsets must be a multiple of corresponding data type size. If you don’t fulfill this requirement there are differences in behavior of OpenGL ES on Android and WebGL. OpenGL ES doesn’t produce any errors and the result depends on hardware (and probably drivers) - Adreno GPUs seem to process such malformed data without generating any errors while Mali GPUs fail to draw anything. WebGL implementations, on the other hand, detect misaligned interleaved attributes and you will find either an error or a warning about this in the console.</div></div><div><br /></div><div><div>Chrome gives the following error:</div><div><font face="courier">GL_INVALID_OPERATION: Offset must be a multiple of the passed in datatype.</font></div><div><br /></div><div>Firefox generates this warning:</div><div><font face="courier">WebGL warning: vertexAttribI?Pointer: `stride` and `byteOffset` must satisfy the alignment requirement of `type`.</font></div></div><div><br /></div><div><div>Our tool can add empty padding bytes to properly align any data types.</div><div><br /></div><div>As it was mentioned before, OpenGL ES 3.0 and WebGL 2 support special packed <font face="courier">INT_2_10_10_10_REV</font> structures which contain three 10-bit and one 2-bit signed integers. This data type provides a bit better precision than byte while taking only 1 byte more than 3 separate bytes. Our tool can convert 3 floats to this packed data type. Please note that even if you use only 3 components from this structure you should specify size 4 for <font face="courier">glVertexAttribPointer</font> when using it (in shader you may still use <font face="courier">vec3</font> uniforms, w components will be ignored).</div><div><br /></div><div>Here are three different examples of compressed and aligned strides. Original size of each stride composed of 32-bit floats is 40 bytes (10 floats) - 3 floats for vertex coordinates, 4 for two sets of UV coordinates (diffuse and lightmap), and 3 for normals. Here are examples of the same data compressed in three different ways down to 16 bytes (60% smaller than original) per vertex without visually perceivable quality loss.</div></div><div><br /></div><div><div>Original stride:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrf9AUCx22SNSRYM3vdbWoKBjbCUnCXGwonE-X6nUW9GEPLCPEuRcWS6F4tJuV_VUpGg0Nl2SG0yj7fBKXV8onim-SYZUBhDT3eXQJ0VSkG-Qqj4FotVPTYbusDvHfTZ3UTldX0ZWe7SM/s1028/original-strides.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="111" data-original-width="1028" height="69" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrf9AUCx22SNSRYM3vdbWoKBjbCUnCXGwonE-X6nUW9GEPLCPEuRcWS6F4tJuV_VUpGg0Nl2SG0yj7fBKXV8onim-SYZUBhDT3eXQJ0VSkG-Qqj4FotVPTYbusDvHfTZ3UTldX0ZWe7SM/w625-h69/original-strides.png" width="625" /></a></div><div><br /></div><div><div>Different variants of compressed strides:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZKuK1rKHsCiCZblyad5nvuC_Og8CzLgzqnCBFtruJkEYK21mmUowKdfXhU1KtlmhimZtFxZbl6Pm0Tdyh_IDTqTz76HDEgEoh4nLJqtDqnuaxLyjDIR2pYNQ0zxgqAc8fB0lzqFDDUzA/s1027/compressed-strides.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="207" data-original-width="1027" height="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZKuK1rKHsCiCZblyad5nvuC_Og8CzLgzqnCBFtruJkEYK21mmUowKdfXhU1KtlmhimZtFxZbl6Pm0Tdyh_IDTqTz76HDEgEoh4nLJqtDqnuaxLyjDIR2pYNQ0zxgqAc8fB0lzqFDDUzA/w625-h125/compressed-strides.png" width="625" /></a></div><div><br /></div><div><div>Color codes for data types:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6yOeKy7oOIN-fBZeEwGVainrWeFNnWEElPI5NaEQ6WrtrVuwrguejLu57YKEjAimz30xT2R0_Dp88VuUmae66c519BOIBobh-nWvxte2oGPv3oi5SXhCheCHrBRy4EsfrnSqNhKwFNDM/s1026/color-codes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="59" data-original-width="1026" height="36" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6yOeKy7oOIN-fBZeEwGVainrWeFNnWEElPI5NaEQ6WrtrVuwrguejLu57YKEjAimz30xT2R0_Dp88VuUmae66c519BOIBobh-nWvxte2oGPv3oi5SXhCheCHrBRy4EsfrnSqNhKwFNDM/w625-h36/color-codes.png" width="625" /></a></div><div><br /></div><div><div>In the first case normals don’t require alignment because they use normalized <font face="courier">GL_UNSIGNED_BYTE</font> type. The second case uses all normal values packed into a single <font face="courier">INT_2_10_10_10_REV</font> structure for better precision. Please note that this requires it to be aligned by multiple of 4 boundaries. For this alignment 2 unused padding bytes are added, shifting normals to offset 12. Useful data size of the first case is 13 bytes with 3 padding bytes to align total stride size, and the second case uses 14 bytes with 2 unused bytes for internal alignment. Both of them fit into 16 bytes (a closest multiple of 4) for GPUs to fetch whole strides more efficiently.</div><div>You may want to swap certain attributes to fit data tightly and eliminate the necessity of using internal empty paddings. In general, placing the largest data types first will make it easier to align smaller data types after them. For example, in the third case packed normals are stored at offset 0 and since this doesn't cause misaligned half-floats and bytes after it, there’s no need to add internal padding bytes.</div></div><div><br /></div><div><h3 style="text-align: left;">Size, performance and quality difference</h3></div><div><div>We have compressed vertex data for Buddha statue model by using half floats for positions, unsigned bytes for diffuse and lightmap UV coordinates, and signed bytes for normals. This resulted in reduction of uncompressed (before gzip) strides data size from 47 kB to 18 kB.</div><div>Even though we used the least accurate precision for UV coordinates, it is just enough because in this model we don’t use textures larger than 256x256. And normalized signed bytes are enough for normals. On the image below you can see that test visualization of normals shows no visual difference between various data types, only perceptual diff can spot minuscule difference between certain pixels:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK8m6-NuY3S5f7Yf-HHXljbbOJdSD9nS3mttg5_rUM7RnS35ZwvBGybYL5R-P5JIK1uCbTg5_jBpIkIuoyNfr2fH5QAgJMVOWYDI02Tj2_IBhyphenhyphenTtefKqWLVIXnndUBWPebGJ1vBogQYb4/s826/uncompressed.webp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="597" data-original-width="826" height="451" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK8m6-NuY3S5f7Yf-HHXljbbOJdSD9nS3mttg5_rUM7RnS35ZwvBGybYL5R-P5JIK1uCbTg5_jBpIkIuoyNfr2fH5QAgJMVOWYDI02Tj2_IBhyphenhyphenTtefKqWLVIXnndUBWPebGJ1vBogQYb4/w625-h451/uncompressed.webp" width="625" /></a></div><div><br /></div><div><div>Since accessing vertex data is not a bottleneck here (the scene must contain a really huge amount of vertices for this to be noticeable) this hasn't improved FPS. However, this reduces memory bandwidth which improves power usage, and this is important for live wallpapers.</div><div><br /></div><div>To accurately measure how optimization affected memory usage we used <a href="https://developer.qualcomm.com/software/snapdragon-profiler" target="_blank">Snapdragon Profiler</a> to capture average values for two real time vertex data metrics. On Google Pixel 3 we have the following results:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkJUUIcRKI5yntj3uldR2mxfKNozEo7Aossa58FfpyboeXtD3uknViPF4WDXC__FJmEhYGjkJhHUEEwtUrUcEHokVWLjfjvTMTF08ieeAQRKk2hNAc6UQvFi3m4DzaX6ZXLY_IEQhy-x0/s916/profiler-results.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="163" data-original-width="916" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkJUUIcRKI5yntj3uldR2mxfKNozEo7Aossa58FfpyboeXtD3uknViPF4WDXC__FJmEhYGjkJhHUEEwtUrUcEHokVWLjfjvTMTF08ieeAQRKk2hNAc6UQvFi3m4DzaX6ZXLY_IEQhy-x0/w625-h111/profiler-results.png" width="625" /></a></div><div class="separator" style="clear: both; text-align: left;">This is a significant change which decreases app’s total RAM consumption and also reduces total memory bandwidth. Reduced GPU load allows for smoother system UI drawn over live wallpaper and improves battery usage.</div><div class="separator" style="clear: both; text-align: left;"><h3 style="text-align: left;">Result</h3><div><div>You can get the updated Android live wallpaper from <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha" target="_blank">Google Play</a>, watch the updated <a href="https://keaukraine.github.io/webgl-buddha/index.html" target="_blank">WebGL demo here</a>, and examine its <a href="https://github.com/keaukraine/webgl-buddha" target="_blank">sources here</a>.</div></div><div><br /></div></div><div><br /></div></span></div>Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-92206202361239552532020-07-09T11:22:00.001-07:002020-07-09T11:23:06.983-07:003D Rose live wallpaper is reviewed on Taimienphi.vn<div style="text-align: left;">Our most popular app 3D Rose Live Wallpaper has been reviewed on a well-known Vietnamese tech web site <a data-saferedirecturl="https://www.google.com/url?q=https://taimienphi.vn&source=gmail&ust=1594404649628000&usg=AFQjCNHVO5GyN9D8UTqywWcEC6ickGwBiw" href="https://taimienphi.vn/" target="_blank">https://taimienphi.vn</a>.</div><div style="text-align: left;">Please follow this link to see review and get our app and find other interesting related apps:</div><div style="text-align: center;"><a href="https://taimienphi.vn/download-3d-rose-live-wallpaper-89323" title="download"><img alt="download" src="https://taimienphi.vn/Images/bn/reviewed/tmp2.png" title="awarded 5 Stars at Taimienphi" /></a></div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com1tag:blogger.com,1999:blog-8436614286557982927.post-40325175895230023872020-02-10T11:33:00.000-08:002020-02-10T11:34:40.635-08:00Implementing soft particles in WebGL and OpenGL ESParticles are one of the easiest ways of improving the visual appearance of any scene. When we decided to update visuals of our <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha">3D Buddha Live Wallpaper</a> the most obvious way of filling in the empty space around Buddha statue was to add some smoke/fog particles. And we have achieved quite good looking results by using soft particles. In this article we will describe the implementation of soft particles in pure WebGL / OpenGL ES without any 3rd party library or engine used.<br />
<a name='more'></a>The difference between the old and updated app is even better than we expected. Simple smoke particles significantly improve the scene, making it visually more pleasing and rich. Particles add more detail to scene and improve transition from foreground objects to background:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhazf96MRmuEptF_o5FXhXAYfDzfANjgDdNNEZSfX3qncr25hwwJX-TB-Bdi8p7MCjPpDe58YulLWppnnhjhKwzFpmQHiFRLmJocatFBpa0GADA2pf-b_9DW6f8B40smvfFLlZPzpSptVY/s1600/old-vs-new.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="808" height="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhazf96MRmuEptF_o5FXhXAYfDzfANjgDdNNEZSfX3qncr25hwwJX-TB-Bdi8p7MCjPpDe58YulLWppnnhjhKwzFpmQHiFRLmJocatFBpa0GADA2pf-b_9DW6f8B40smvfFLlZPzpSptVY/s640/old-vs-new.jpg" width="640" /></a></div>
You can see live WebGL demo here: <a href="https://keaukraine.github.io/webgl-buddha/index.html">https://keaukraine.github.io/webgl-buddha/index.html</a>.<br />
<br />
<h2>
Soft Particles</h2>
<div>
So what are soft particles? You may remember that in most older games (Quake 3 and CS 1.6 times) smoke and explosion effects had clearly visible hard edges at intersections of particles with other geometries. All modern games got rid of this by using particles with soft edges around adjacent geometry.</div>
<div>
<br /></div>
<h2>
Rendering</h2>
What is needed to make particle edges soft? First, we need to have a scene depth information for particles shader to detect intersections and soften them. Then we will be able to detect exact places where particles intersect with geometry by comparing depth of scene and particle in fragment shader - intersection is where these depth values are equal. Let’s go through the rendering pipeline step-by-step. Because WebGL is based on OpenGL ES, both Android and web implementations of rendering are the same, the main difference is in loading resources. WebGL implementation is open-sourced and you can get it here - <a href="https://github.com/keaukraine/webgl-buddha">https://github.com/keaukraine/webgl-buddha</a>.<br />
<br />
<h3>
Rendering to depth texture</h3>
To render scene depth, we first need to create off-screen depth and colors textures and assign them to corresponding FBO. This stuff is done in the <span style="font-family: "courier new" , "courier" , monospace;">initOffscreen()</span> method of <a href="https://github.com/keaukraine/webgl-buddha/blob/master/js/app/BuddhaRenderer.js">BuddhaRenderer.js</a>. Actual rendering of depth scene objects is done in <span style="font-family: "courier new" , "courier" , monospace;">drawDepthObjects()</span> which draws a Buddha statue and a floor plane. However, there’s one trick here. Since we don’t need color information but only depth, rendering of color is disabled by <span style="font-family: "courier new" , "courier" , monospace;">gl.colorMask(false, false, false, false)</span> call, and then re-enabled by <span style="font-family: "courier new" , "courier" , monospace;">gl.colorMask(true, true, true, true)</span>. <span style="font-family: "courier new" , "courier" , monospace;">glcolorMask()</span> can toggle rendering of red, green, blue, and alpha components individually so to completely skip writing to the color buffer we set all components to false and then re-enable them by setting them to true. Result depth information of the scene can be viewed by uncommenting the call to <span style="font-family: "courier new" , "courier" , monospace;">drawTestDepth()</span> in <span style="font-family: "courier new" , "courier" , monospace;">drawScene()</span> method. Because depth texture is single-channel, it is treated as red-only so green and blue channels have zero values. Result looks like this if visualized:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVgW1QZHVk8jgtRCD7irzUsE8ovoGZPw3dGAa9MGz2lobSVTc0BwNzJqVZiEmqGdRk2PhGK3mAq80AUipVMp-X6kaf9XPJzhTv-9IuPMmX3eBMgMtpxLpZCQz6mYU5LAZ59unsydayYLU/s1600/depth_300x2000.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="808" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVgW1QZHVk8jgtRCD7irzUsE8ovoGZPw3dGAa9MGz2lobSVTc0BwNzJqVZiEmqGdRk2PhGK3mAq80AUipVMp-X6kaf9XPJzhTv-9IuPMmX3eBMgMtpxLpZCQz6mYU5LAZ59unsydayYLU/s640/depth_300x2000.png" width="640" /></a></div>
<div>
<br />
<div>
<h3>
Rendering particles</h3>
</div>
</div>
Shader used for rendering of soft particles can be found in <a href="https://github.com/keaukraine/webgl-buddha/blob/master/js/app/SoftDiffuseColoredShader.js">SoftDiffuseColoredShader.js</a>. Let’s take a look at how it works.<br />
<br />
The main idea of detecting intersection between particle and scene geometry is by comparing fragment depth with scene depth which is stored in texture. First thing needed to compare depth is linearization of depth values because original values are exponential. This is done using the <span style="font-family: "courier new" , "courier" , monospace;">calc_depth()</span> function. This technique is described here - <a href="https://community.khronos.org/t/soft-blending-do-it-yourself-solved/58190">https://community.khronos.org/t/soft-blending-do-it-yourself-solved/58190</a>. To linearize these values we need <span style="font-family: "courier new" , "courier" , monospace;">vec2 uCameraRange</span> uniform which x and y components have near and far planes. Then shader calculates the linear difference between particle geometry and scene depth - it is stored in a variable. However, if we apply this coefficient to particle color we will get too dim particles - they will linearly fade away from any geometries behind them, and this fade is quite fast. This is how linear depth difference looks when visualized (you can uncomment corresponding line in shader to see it):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGb6RP_ua8sv3up0NNIDdHAhgdiQOvWDicGmO5SrrOH3jonOMhCj4B4D_-D1FdsfO0ForLS6BLji-qjWiqhyXeUzSFmVd858OlwnOWqmxdfLx439lLFLRi-UQLiac13jQFnmL0lxWuo34/s1600/shader-z-diff.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="808" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGb6RP_ua8sv3up0NNIDdHAhgdiQOvWDicGmO5SrrOH3jonOMhCj4B4D_-D1FdsfO0ForLS6BLji-qjWiqhyXeUzSFmVd858OlwnOWqmxdfLx439lLFLRi-UQLiac13jQFnmL0lxWuo34/s640/shader-z-diff.png" width="640" /></a></div>
To make particles more transparent only near the intersection edge (which happens at <i>a=0</i>) we apply GLSL <span style="font-family: "courier new" , "courier" , monospace;">smoothstep()</span> function to it with <span style="font-family: "courier new" , "courier" , monospace;">uTransitionSize</span> coefficient which defines the size of a soft edge. If you want to understand how <span style="font-family: "courier new" , "courier" , monospace;">smoothstep()</span> function works and see some more cool examples on how to use it, you should read this great article - <a href="http://www.fundza.com/rman_shaders/smoothstep/">http://www.fundza.com/rman_shaders/smoothstep/</a>. This final blending coefficient is stored in a variable simply named b. For blending mode used by our particles we simply multiply a particle’s diffuse color by this coefficient, in other implementations it may be applied to the alpha channel. If you uncomment line in shader to visualize this coefficient you will see image similar to this one:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNKKvtlaPNO0boRLjJX6aIHqu8JH6a-cJLTZubglorO-emdLw9y5upUxKUwHX9FTTW9EhWOA55hgG-bhf1ipK_kMaLRMkayETX7cikCWnvz-tPw1cXfMBJC5fAGyy7H1byNZzskIk_iq4/s1600/shader-smoothstep.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="808" height="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNKKvtlaPNO0boRLjJX6aIHqu8JH6a-cJLTZubglorO-emdLw9y5upUxKUwHX9FTTW9EhWOA55hgG-bhf1ipK_kMaLRMkayETX7cikCWnvz-tPw1cXfMBJC5fAGyy7H1byNZzskIk_iq4/s640/shader-smoothstep.png" width="640" /></a></div>
<div>
<div>
Here you can see visual difference between different values of particle softness uniform:</div>
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihBKTIN7FnjeD7bArQFS_xsAbch7CuFnyIo9U1xfG2hZFUTr1N5_lCI-BhLo5xCI90nFkzuDz4suNbrL_L8twkwpVTimbxh8DrvfvhUOD_Hv-NS0nFygG6WQlO_GA2xhqcfXG9FYvkbJE/s1600/softness.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="541" data-original-width="808" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihBKTIN7FnjeD7bArQFS_xsAbch7CuFnyIo9U1xfG2hZFUTr1N5_lCI-BhLo5xCI90nFkzuDz4suNbrL_L8twkwpVTimbxh8DrvfvhUOD_Hv-NS0nFygG6WQlO_GA2xhqcfXG9FYvkbJE/s640/softness.gif" width="640" /></a><br />
<div>
<h3>
Sprite billboard meshes</h3>
</div>
<div>
Small dust particles are rendered as point sprites (rendering using <span style="font-family: "courier new" , "courier" , monospace;">GL_POINTS</span>). This mode is easy to use because it automatically creates a quad shape in the fragment shader. However, they are a bad choice for large smoke particles. First of all, they are frustum-culled by the point center and thus would disappear abruptly on screen edges. Also, quad shape is not very efficient and can add significant overdraw. We decided to use custom particle mesh with optimized shape - with cut corners where texture is completely transparent:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijucIxRfZieaYCM2RhQ4vl4QNguhKLsvNr2oot_-d0S9kP0Ao9Y4O0m_wfDerAKlODsd-Ve7WX5N2x23KOta_xbZkx7d5yF-wSQll_yA4_OiAJmHKnPufFEBgxlFDM62zdDFi8pS1Is_I/s1600/particle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="632" data-original-width="632" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijucIxRfZieaYCM2RhQ4vl4QNguhKLsvNr2oot_-d0S9kP0Ao9Y4O0m_wfDerAKlODsd-Ve7WX5N2x23KOta_xbZkx7d5yF-wSQll_yA4_OiAJmHKnPufFEBgxlFDM62zdDFi8pS1Is_I/s400/particle.png" width="400" /></a></div>
<br />
<br />
These custom quads cannot be rendered in batches with <span style="font-family: "courier new" , "courier" , monospace;">GL_POINTS</span>, each particle is rendered separately. They have to be positioned at any world coordinates, properly scaled but should be always rotated facing towards the camera. This can be achieved by technique described in <a href="https://stackoverflow.com/a/5487981/405681">this answer on StackOverflow</a>. In <a href="https://github.com/keaukraine/webgl-buddha/blob/master/js/app/BuddhaRenderer.js">BuddhaRenderer.js</a> there’s a <span style="font-family: "courier new" , "courier" , monospace;">calculateMVPMatrixForSprite()</span> method which creates MVP matrices for billboard meshes. It performs regular scale and translation of mesh and then uses <span style="font-family: "courier new" , "courier" , monospace;">resetMatrixRotations()</span> to reset rotation of the model-view matrix before it is multiplied with the projection matrix. This results in an MVP matrix which always faces a camera.<br />
<h2>
Result</h2>
You can see the final result here - <a href="https://keaukraine.github.io/webgl-buddha/index.html">https://keaukraine.github.io/webgl-buddha/index.html</a>.<br />
Feel free cloning source code and modifying it to your needs from Github - <a href="https://github.com/keaukraine/webgl-buddha">https://github.com/keaukraine/webgl-buddha</a>.Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-13497243918468972452019-07-31T01:16:00.001-07:002019-07-31T01:18:45.940-07:00Bonsai 3D Live WallpaperWe've recently released a new live wallpaper - <i>Bonsai 3D Live Wallpaper</i>.<br />
<br />
This live wallpaper puts a beautiful bonsai tree to home screen of your device.<br />
You can relax and contemplate bonsai tree and serene Japanese landscape in background, all in glorious 3D right on home screen of your phone.<br />
<br />
This app is completely free and has a lot of different customization options which can be unlocked by watching video ads instead of buying full version.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/THLwXgOg5XY/0.jpg" frameborder="0" height="350" src="https://www.youtube.com/embed/THLwXgOg5XY?feature=player_embedded" width="75%"></iframe></div>
<br />
<div style="text-align: center;">
<a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbonsai" target="_blank"><img border="0" height="124" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPKESR18NKI0N3CviIjaUHTGE8xZWrOWA25NU3khbpQEBPo3y1H4LdNuMfC8L83cvovrawBh-pnh4Qj7VbkgwHO6g_XAU0rErkfxWOw5aPkWS6A1yJYGPBMGmUKHmdZUCjSbSyjMH40kQ/s320/google-play-badge.png" width="320" /></a></div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-11095820599384298152019-01-27T12:22:00.000-08:002019-01-28T05:41:53.176-08:00Creating landing page for Buddha 3D Live WallpaperRight after we've released our latest <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha">3D Buddha Live Wallpaper</a> an idea to make a landing page for this app emerged. Because what is the best way to show features of live wallpaper? To run it right in user's browser, of course. Thanks to WebGL users can preview exactly the same rich, fully 3D graphics and smooth performance as in full-featured Android app right in mobile browser. This experience is way beyond watching screenshots or video of an app on Google Play.<br />
<a name='more'></a><br />
It was quite easy to implement <a href="https://keaukraine.github.io/webgl-buddha/index.html">this landing page</a> because we share the same framework for both web and Android platforms. Actually, web version of an app was used to implement first draft versions of scene even before making an actual Android app. This is because it is way faster and easier to experiment and tweak various stuff in runtime in web app than in its Android sibling.<br />
<div>
<br />
Our custom low-level, lightweight framework used to render scene doesn't process input data and simply loads it to GPU as is. Input data is already converted into binary format so framework loads data into GPU immediately after each geometry resource is downloaded, without converting, parsing or any other processing. Texture data is downloaded in form of PNG images, so it is converted before being passed to GPU but this work is done by browser and WebGL/drivers so overhead is negligible.<br />
It is not a full-featured framework, it obviously lacks a lot of stuff compared to engines like three.js or Pixi.js. But its goal was different - it was meant to be minimalistic and to match Android rendering code as much possible.<br />
<br />
<h3>
Scene composition</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5PIbfkfQi_PRkVaCNZHzl-AU4v5EAa3RmfohqPGggeX_vnkmtrtM74_P66R96wIFCpyCLRHcyjTuuAxsH6kM1SMfaoNqF7SqETYeJhPeU1CotjYQ98vE_O0i_sFCW0lSKCUjBLcH2GoA/s1600/ezgif.com-optimize+%25281%2529.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="360" data-original-width="640" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5PIbfkfQi_PRkVaCNZHzl-AU4v5EAa3RmfohqPGggeX_vnkmtrtM74_P66R96wIFCpyCLRHcyjTuuAxsH6kM1SMfaoNqF7SqETYeJhPeU1CotjYQ98vE_O0i_sFCW0lSKCUjBLcH2GoA/s640/ezgif.com-optimize+%25281%2529.gif" width="640" /></a></div>
<div>
<br /></div>
<div>
Scene is composed of just five objects: table, Buddha statue, sky, light shafts, and dust. They are drawn in certain order with a single draw call per each object (dust particles are drawn in 8 draw calls, more on that later), each using specific shader. This improves performance by minimizing OpenGL state changes. Scene is completely rendered in just 12 draw calls.<br />
<br />
Object are rendered in fixed order. Opaque objects are drawn in <a href="https://en.wikipedia.org/wiki/Painter%27s_algorithm">reverse painter's order</a>, than transparent objects are drawn. This improves performance again by reducing overdraw.<br />
<br /></div>
<h3>
Shaders used in demo</h3>
<div>
Below I describe the GLSL shaders which are used to render scene. You can check out sources of each of them <a href="https://github.com/keaukraine/webgl-buddha/tree/master/js/app">here</a>. Exactly the same shaders are used in Android live wallpaper.</div>
<div>
<br /></div>
<i>DiffuseShader</i>. The simplest shader which simply renders texture on model's triangles according to provided texture coordinates. It is used to render sky.<br />
<br />
<i>SphericalMapLMShader</i>. It is used to render Buddha statue. It uses normal and spherical mapping to visualize shiny metallic surface. This is the same shader used to draw coins in Bitcoins live wallpaper and 3D demo. You can read about it in our <a href="https://androidworks-kea.blogspot.com/2016/10/porting-android-live-wallpaper-to-webgl_4.html">Porting Android live wallpaper to WebGL</a> post.<br />
<br />
<br />
<i>LMTableShader</i>. This shader is used to render table surface with baked shadows from lightmap under statue. Also table surface fades away to blend with background at the edges of round area. It takes two textures - diffuse and lightmap. Lightmap utilizes <a href="http://wiki.polycount.com/wiki/ChannelPacking">channel packing</a> and only red and green channels from lightmap are used. This means that lightmap is essentially monochromatic. Shadow color is a product of red and green channel values. Diffuse color is then multiplied by shadow color. So at the center of texture, where shadow of Buddha forms green spot it is quite dark while yellow color of main area results in lightmap color close to 1.0. Opacity of table is determined by green channel.<br />
The following image explains how lightmap and opacity affect image:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8wCWWhvVpzYZ031QnOnnG3EieO3F_EsDIz69XDZTG5pqHjYkT1JlNhg3ZqvEPJZbwA37dkQjAlMwxGgEWJuE-xYBl2m2wf19YJU5pQkTvYbG9N716U5EQJ6O5Cc9E2KwT8vSYztUqnb4/s1600/channel-packing.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="850" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8wCWWhvVpzYZ031QnOnnG3EieO3F_EsDIz69XDZTG5pqHjYkT1JlNhg3ZqvEPJZbwA37dkQjAlMwxGgEWJuE-xYBl2m2wf19YJU5pQkTvYbG9N716U5EQJ6O5Cc9E2KwT8vSYztUqnb4/s640/channel-packing.jpg" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLFKBKHW9OjnPQZyMpMIX8xItftPNFEkUGkZf6QHzWlavM26SKWSl2Kk72Hu_XdNjar5sc8k4qC52ulF-2fCPf7Mm_46go_ft8BJ3OFdnHLZQ0xWVLTJSZrgh0dR93UHksIbfr-gbQfLw/s1600/table-lm-explained.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLFKBKHW9OjnPQZyMpMIX8xItftPNFEkUGkZf6QHzWlavM26SKWSl2Kk72Hu_XdNjar5sc8k4qC52ulF-2fCPf7Mm_46go_ft8BJ3OFdnHLZQ0xWVLTJSZrgh0dR93UHksIbfr-gbQfLw/s1600/table-lm-explained.png" /></a></div>
<br />
<br />
<i>LightShaftShader</i>. This shader is used to render light shafts with moving effect. It uses rim lighting formula to smoothly show and hide light shafts depending on an angle between triangle surface and camera. Light shafts geometry itself is just a few sheets with normals and texture coordinates.<br />
<br />
<i>PointSpriteScaledColoredShader</i>. This shader is used to efficiently render large amount of point sprites. It scales point sprites according to Z coordinate so the closer sprite is to camera the larger it is. <span style="font-family: "courier new" , "courier" , monospace;">gl.POINTS</span> primitives are used to render each dust cloud model which contain 20 coordinates. So with just 8 draw calls we are able to render an impressively dense cloud of 160 dust particles. Each dust cloud is slowly rotated in random direction and because these objects overlap it is virtually impossible to notice that some particles belong to the same cloud and are orbiting around the same pivot.<br />
<br />
<h3>
Size and Speed Optimizations</h3>
Total size of transferred content is only 434 kBytes gzipped so demo loads all content pretty fast even on mobile connections. So yes, it can fit on a floppy disk. Three times.<br />
<br />
Such small size is achieved by some optimizations. First, this page doesn't use fully-featured 3D engines like three.js, only our own minimal framework. For comparison, JS code of widely used three.js library alone is <a href="https://github.com/mrdoob/three.js/blob/dev/build/three.min.js">135 KB gzipped</a>.<br />
<br />
To make content even smaller, we've compressed PNG images to 8 bits in parts where quality is not crucial. We haven't compressed images where quality degradation by reducing colors is clearly visible, so sky and normal maps textures require full-color 24-bit images.<br />
<br />
<h3>
Result</h3>
You can check out the <a href="https://keaukraine.github.io/webgl-buddha/index.html">web page here</a> and the <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha">Android app here</a> and compare them. And of course feel free checking out <a href="https://github.com/keaukraine/webgl-buddha">source code on GitHub here</a>.</div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-86418683106474114102017-03-02T07:29:00.001-08:002017-10-30T07:13:28.483-07:00WebGL 2 fur simulationWebGL 2 recently became available in latest Firefox and Chrome so it was tempting to try out some of its new features. One of the most important WebGL 2 (and OpenGL ES 3.0, which it is based upon) features is <i>instanced rendering</i>. This feature reduces draw calls overhead by drawing the same geometry multiple times with altered transformations. It was supported in certain implementations of WebGL 1 too but required a certain extension. Most useful for foliage and particles, this technique is also quite often used to simulate fur.<br />
<a name='more'></a><h3>
</h3>
<h3>
</h3>
<h3>
</h3>
<h3>
</h3>
<h3>
<br /></h3>
<h3>
Concept</h3>
There are a lot of articles on fur simulation in OpenGL, but our implementation is roughly based on this <a href="https://www.youtube.com/watch?v=AQqqPFEUEYs">YouTube tutorial</a>. It describes process of creating custom Unity shader, however its step-by-step instructions are really insightful. If you are not familiar with this technique of fur simulation, we recommend to spend 13 minutes and watch this video to understand how it works.<br />
All textures for demo are hand-painted from scratch (by looking at photos of fur) - they are very simple and don’t require any special skills.<br />
<br />
You can try a live demo here - <a href="https://keaukraine.github.io/webgl-fur/">https://keaukraine.github.io/webgl-fur/</a>.<br />
<br />
<h3>
Implementation</h3>
To showcase fur simulation first let’s see result of rendering only 2 additional fur layers with quite large fur thickness (distance between layers). You can clearly see original object without fur, and two transparent fur layers:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_84GeY3Ng2XLa9DIlHFeOsaNELPmOTtlmQ1If3f2lW_PegFRexLurQq4ertNmP-UIHj-wU0x6zZldKm_KL4yZQkT536LwL30WrSsIv0Im_7emXIF9hQJJ7JSIpQZse33Sg61I4LyE1XM/s1600/explanation2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_84GeY3Ng2XLa9DIlHFeOsaNELPmOTtlmQ1If3f2lW_PegFRexLurQq4ertNmP-UIHj-wU0x6zZldKm_KL4yZQkT536LwL30WrSsIv0Im_7emXIF9hQJJ7JSIpQZse33Sg61I4LyE1XM/s320/explanation2.jpg" width="299" /></a></div>
<div style="text-align: left;">
<div>
<br />
If we increase layers count and reduce layer thickness we can get more realistic result. On this image with 6 layers of relatively thin layers you can see that each layer fades away from fully opaque to transparent:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6Y5H3xJaezYWOcGmWcq7y0A_knakWq3X3puKtvwrkac2XIYxIj8bjnVFK-PfZ2oCiOM-5nVWx4QaYCD1EAvksrSxaMhHnufV3lFwFzRjvejO-tRF4QYkzXdkJHppyyAhTxMQ461lM44M/s1600/explanation3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6Y5H3xJaezYWOcGmWcq7y0A_knakWq3X3puKtvwrkac2XIYxIj8bjnVFK-PfZ2oCiOM-5nVWx4QaYCD1EAvksrSxaMhHnufV3lFwFzRjvejO-tRF4QYkzXdkJHppyyAhTxMQ461lM44M/s320/explanation3.jpg" width="299" /></a></div>
<div style="text-align: left;">
<div>
<br />
And finally we can get quite realistic results with 20 very thin layers:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLML9s5wlCjg6jvCke4xKy0qE2cxcUMiWCjHNv3lPngX5OBMEyd7PIuDas7UF3Lz2Zv8WE7cgKUGGhuh_Wk6iCTKMCxt0CdC6RY-78yuKGwRjY7S029JKFlt6ui9BGBKJT0wUvs2oBcDM/s1600/explanation4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLML9s5wlCjg6jvCke4xKy0qE2cxcUMiWCjHNv3lPngX5OBMEyd7PIuDas7UF3Lz2Zv8WE7cgKUGGhuh_Wk6iCTKMCxt0CdC6RY-78yuKGwRjY7S029JKFlt6ui9BGBKJT0wUvs2oBcDM/s320/explanation4.jpg" width="299" /></a></div>
<div>
<div>
<br /></div>
<div>
Our demo has 5 different presets - 4 fur presets and 1 moss. They are rendered with the same shader but with different input.</div>
<div>
Each preset of fur is defined by the following parameters: start and end color of fur for AO simulation, layers count and layer thickness to specify fur length, diffuse and alpha textures, and finally, a wave scale for wind simulation.</div>
<div>
<br /></div>
<div>
First we draw cube with the same diffuse texture used for fur layers. It should be dimmed to the same color as first fur layer for it to blend nicely with fur so it is multiplied by fur start color. We use a really simple shader here which takes fragment color from texture and multiplies it by specified color.</div>
<div>
<br /></div>
<div>
Next, we need to draw fur layers. They are translucent and require correct blending mode to look as intended. Using regular <i>glBlendFunc()</i> blending mode resulted in either too dim fur or too bright results because they affect alpha channel and therefore distort fur colors. On the other side, <i>glBlendFuncSeparate()</i> function specifies separate blending modes for RGB and alpha channels of fragments and it was possible to keep alpha constant for each layer (controllable in shader) while nicely blending fur color. This is blending function used in demo:</div>
<div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE);
</pre>
</div>
<div>
<br /></div>
<div>
This image shows how separate blending compares to other non-separated blending modes:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-oL_OpgvJHhY/WND4sn7fNhI/AAAAAAAALDE/3-OlPLZQQVoP6CEzIUzNgcdhLXHAD8IwQCPcB/s1600/blending-demo.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://3.bp.blogspot.com/-oL_OpgvJHhY/WND4sn7fNhI/AAAAAAAALDE/3-OlPLZQQVoP6CEzIUzNgcdhLXHAD8IwQCPcB/s320/blending-demo.gif" width="228" /></a></div>
<div style="text-align: center;">
<br /></div>
<br />
After correct blending mode is set, we start drawing fur. It is implemented in a single draw call - all instancing is done by GPU and shader, so all further explanations are related to shader. Please note that GLSL 3.0 is different from GLSL 1.0 - you can refer to this tutorial on how to update your old shaders to new version.</div>
<div>
To create fur layers, shader extrudes each vertex in direction of normal (so you can easily adjust fur direction by changing model normals). The higher <i>gl_InstanceID</i> (built-in variable with current instance number) is, the further we should extrude vertices:</div>
<div>
<br /></div>
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">float</span> f <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">float</span>(gl_InstanceID <span style="color: #333333;">+</span> <span style="color: #0000dd; font-weight: bold;">1</span>) <span style="color: #333333;">*</span> layerThickness; <span style="color: #888888;">// calculate final layer offset distance</span>
<span style="color: #008800; font-weight: bold;">vec4</span> vertex <span style="color: #333333;">=</span> rm_Vertex <span style="color: #333333;">+</span> <span style="color: #008800; font-weight: bold;">vec4</span>(rm_Normal, <span style="color: #6600ee; font-weight: bold;">0.0</span>) <span style="color: #333333;">*</span> <span style="color: #008800; font-weight: bold;">vec4</span>(f, f, f, <span style="color: #6600ee; font-weight: bold;">0.0</span>); <span style="color: #888888;">// move vertex in direction of normal</span>
</pre>
</div>
<br /></div>
<div>
For fur to look realistic, it should be dense at the start and thin at the end. This can be done by reducing layer alpha. Also, to simulate ambient occlusion fur should be darker inside and brighter outside. Both these parameters are specified by start and end color of fur. Typically start color is <i>[0.0, 0.0, 0.0, 1.0]</i> and end color is <i>[1.0, 1.0, 1.0, 0.0]</i> so it starts with completely black and ends with source diffuse color, while alpha fades away from fully opaque to transparent.</div>
<div>
First, in vertex shader we calculate layer color coefficient and then interpolate from start to end color, and then simply multiply diffuse color by that value. Finally, alpha value of fragment is multiplied by value from alpha map which determines fur hairs pattern.</div>
<div>
<br /></div>
<div>
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #888888;">// vertex shader</span>
<span style="color: #008800; font-weight: bold;">float</span> layerCoeff <span style="color: #333333;">=</span> <span style="color: #008800; font-weight: bold;">float</span>(gl_InstanceID) <span style="color: #333333;">/</span> layersCount;
vAO <span style="color: #333333;">=</span> mix(colorStart, colorEnd, layerCoeff);
<span style="color: #888888;">// fragment shader</span>
<span style="color: #008800; font-weight: bold;">vec4</span> diffuseColor <span style="color: #333333;">=</span> texture(diffuseMap, vTexCoord0); <span style="color: #888888;">// get diffuse color</span>
<span style="color: #008800; font-weight: bold;">float</span> alphaColor <span style="color: #333333;">=</span> texture(alphaMap, vTexCoord0).r; <span style="color: #888888;">// get alpha from alpha map</span>
fragColor <span style="color: #333333;">=</span> diffuseColor <span style="color: #333333;">*</span> vAO; <span style="color: #888888;">// simulate AO</span>
fragColor.a <span style="color: #333333;">*=</span> alphaColor; <span style="color: #888888;">// apply alpha mask</span>
</pre>
</div>
<div>
<br /></div>
<div>
There can be multiple ways to simulate fur movement on wind. In this demo we move each vertex a little bit depending on time uniform passed to shader. To do this, we have to use some sort of unique “hash” value for vertices with the same coordinates. We cannot rely on built-in <i>gl_VertexID</i> variable because it is actually different for different vertices, even the ones with the same coordinates can have different <i>gl_VertexID</i>. So we calculate some “magic sum” of vertex coordinate and create a sine+cosine wave based on that value. Example of moving vertex according to input of time uniform:</div>
<div>
<br /></div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">const</span> <span style="color: #008800; font-weight: bold;">float</span> PI2 <span style="color: #333333;">=</span> <span style="color: #6600ee; font-weight: bold;">6.2831852</span>; <span style="color: #888888;">// Pi * 2 for sine wave calculation</span>
<span style="color: #008800; font-weight: bold;">const</span> <span style="color: #008800; font-weight: bold;">float</span> RANDOM_COEFF_1 <span style="color: #333333;">=</span> <span style="color: #6600ee; font-weight: bold;">0.1376</span>; <span style="color: #888888;">// just some random float</span>
<span style="color: #008800; font-weight: bold;">float</span> timePi2 <span style="color: #333333;">=</span> time <span style="color: #333333;">*</span> PI2;
vertex.x <span style="color: #333333;">+=</span> sin(timePi2 <span style="color: #333333;">+</span> ((rm_Vertex.x<span style="color: #333333;">+</span>rm_Vertex.y<span style="color: #333333;">+</span>rm_Vertex.z) <span style="color: #333333;">*</span> RANDOM_COEFF_1)) <span style="color: #333333;">*</span> waveScaleFinal;
vertex.y <span style="color: #333333;">+=</span> cos(timePi2 <span style="color: #333333;">+</span> ((rm_Vertex.x<span style="color: #333333;">-</span>rm_Vertex.y<span style="color: #333333;">+</span>rm_Vertex.z) <span style="color: #333333;">*</span> RANDOM_COEFF_2)) <span style="color: #333333;">*</span> waveScaleFinal;
vertex.z <span style="color: #333333;">+=</span> sin(timePi2 <span style="color: #333333;">+</span> ((rm_Vertex.x<span style="color: #333333;">+</span>rm_Vertex.y<span style="color: #333333;">-</span>rm_Vertex.z) <span style="color: #333333;">*</span> RANDOM_COEFF_3)) <span style="color: #333333;">*</span> waveScaleFinal;
</pre>
</div>
<div>
<br /></div>
<h3>
Further Improvements</h3>
While already achieving quite good results, this implementation can be improved by applying directional force (wind) to fur and/or adjustable fur length using per-vertex coefficients. Feel free to <a href="https://github.com/keaukraine/webgl-fur">get sources</a> from GitHub and modify it according to your needs, code uses MIT license.</div>
</div>
</div>
</div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-66536677690474396642016-10-04T03:47:00.000-07:002016-10-05T04:47:29.110-07:00Porting Android live wallpaper to WebGL<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Now WebGL works in almost any browser, including mobile ones so it was tempting to try it out. We have experience in creating Android apps using OpenGL ES 2.0. We have released quite a lot of 3D live wallpapers with rich 3D graphics. They are implemented in Java + OpenGL ES 2.0 without using third-party game engines (such as Unity) or high-level frameworks (such as libGDX). This makes our apps lightweight and well-optimized. And since WebGL is based on OpenGL ES 2.0 process of porting live wallpaper to run in browser is quite straightforward.</span><br />
<a name='more'></a></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 21.333333333333332px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Basic renderer code</span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Similar to original Java code, I’ve implemented </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BaseRenderer </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">and </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BaseShader </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">classes. I decided to use ECMAScript 2015 JS classes because they would improve readability of the code and the only browser not supporting it is IE11. </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BaseRenderer</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> handles WebGL context creation and initialization, viewport resize, etc. Also it has empty stubs for all necessary functions. </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BaseShader</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> has code to compile and use shader. Actual implementation of renderer with loading data and drawing stuff is in </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BitcoinRenderer</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">.</span></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 21.333333333333332px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Loading raw binary data</span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Most WebGL engines and demos load data from either JSON, OBJ or other formats. This approach requires some additional processing of data to create buffers which will be consumed by GPU. In our Java engine we use binary data which is ready to be put to OpenGL buffers, and it is implemented in demo the same way. Luckily, </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">XMLHttpRequest Level 2,</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> which is a part of HTML5 specification, supports loading of binary data in JavaScript. For convenience, I’ve created a simple </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">BinaryDataLoader</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> wrapper for loading binary data using XHR 2.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">FullModel</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> class handles work with meshes. Its </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">load()</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> method loads two buffers for mesh - strides buffer with data about vertices, UVs and other data and indices buffer which defines actual triangles of a mesh. These buffers contain binary data ready to be provided to GPU. This class also has </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">bindBuffers()</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> method to bind buffers before actual </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">glDrawElements() </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">call.</span></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 21.333333333333332px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Using ETC1 compressed textures</span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">To save video memory usage we use various compressed textures. Our Java framework supports ETC1, ETC2, PVRTC and ASTC compressed textures and it uses the one which is most suitable for given Android device. In WebGL port I’ve implemented only ETC1 and uncompressed RGB textures.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In OpenGL ES 2.0 ETC1 is an obligatory part of specification and is supported on all devices with no exceptions. However, in WebGL this compression is optional, and you have to check for presence of </span><a href="https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">WEBGL_compressed_texture_etc1</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> extension. All desktop browsers except IE11 and Edge support ETC1 compression. For Microsoft browsers we simply fall back to uncompressed textures.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">You can check which texture compressions your browser supports using this handy page - </span><a href="http://toji.github.io/texture-tester/" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://toji.github.io/texture-tester/</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">By using ETC1 textures we achieved reduced memory usage and faster loading times. Uncompressed textures have some loading overhead. They have to be decoded from original image format (PNG, JPEG, WebP, GIF, etc) to bitmap (either RGBA or RGB, with or without alpha) and then uploaded to GPU. On the other hand, ETC1 textures are ready to be uploaded to GPU and therefore load significantly faster. Speaking of a memory footprint, 512x512 uncompressed RGB texture uses 768 kb and the same textures uses only 128 kb in ETC1 compressed format. However, ETC1 is not perfect and introduces some noticeable visual artifacts. These artifacts are not visible on diffuse textures and lightmaps but can be clearly seen on normal maps (blocky, distorted normals) and spherical reflection maps (colors are not accurate). So we use both compressed and uncompressed textures depending on requirements to texture quality.</span></div>
<b id="docs-internal-guid-5f0a69c5-8f4c-51bd-a661-c2fbeb95f03b" style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In Android, loading of ETC1 is very simple - you can use </span><a href="https://developer.android.com/reference/android/opengl/ETC1Util.html" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">ETC1Util</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> to load texture stored in PKM format. Since texture compression is not a required part of WebGL, it doesn’t provide any utilities for loading compressed textures from any known file format. I had to create my own utility to handle this.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">PKM is a quite simple file format, it has a 16 bytes header followed by raw data ready to be passed to GPU. You can get more information about its header </span><a href="https://community.arm.com/thread/3968" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">here</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> and </span><a href="https://github.com/Ericsson/ETCPACK" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">here</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">. However, there is one tricky part in retrieving texture dimensions from header in JavaScript. These values are stored as 16 bit big-endian integers. We cannot use </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Int16Array</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> to get these values because there is no way to specify endianness of buffer and we have to read bytes using </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Uint8Array</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> and calculate 16-bit value manually from its high and low bytes.</span></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 21.333333333333332px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Shaders</span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Application makes use of only two GLSL shaders: one for table surface and another one for coins.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Table surface shader is a basic lightmapping shader - it uses two texture samplers: one for diffuse color and another one for lightmap texture. Their values are simply multiplied and that is the final output of the shader.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Coins shader is more complicated, it incorporates following features:</span></div>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Sphere mapping (a.k.a. </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">matcap</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> material);</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Lightmapping with color boosting;</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Normal mapping.</span></div>
</li>
</ol>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Sphere mapping is a variation of reflection mapping technique. It stores information about reflection in a texture which looks like a photo of chrome sphere:</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><img alt="Image result for matcap texture" height="134" src="https://lh6.googleusercontent.com/JvEKK987CmUPVBVai-BQSrFmzG07TaL0GeYn9geOKiRTzk3LdveLNJOalWi-uKVYBetCsbKnQKD-CciBU2lkv9FrYT3ym4UdIxRmiauxoyMGL8t6trQlVvF5FFbQBSZSA3fSuxZF" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="134" /></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Sphere mapping has following pros and cons reflection techniques:</span></div>
<ul style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: disc; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">minimal shader code, great efficiency and performance;</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: disc; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">overhead of only one 2D texture sample;</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: disc; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">reflection texture is simple to understand and manipulate in Photoshop;</span></div>
</li>
</ul>
<ul style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: disc; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">it does not work on flat surfaces;</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: disc; text-decoration: none; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">texture space usage is sub-optimal (corners are not used).</span></div>
</li>
</ul>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The best thing about sphere mapping is the ease of implementation: once you have your screen-space normals calculated, use its (x,y) part as UV coordinates to read reflection and that’s all. Below is an excerpt from our shader:</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: transparent; color: black; font-family: "courier new"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> vec4 sphereColor = texture2D(sphereMap, vec2(vNormal2.x, vNormal2.y));</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">However, sphere mapping has one disadvantage: it is calculated based on screen-space normal only, thus, it would fill large flat polygons with a constant color (as they have constant normals). And coin model consists mostly of two flat surfaces. To overcome this problem, we’ve added normal maps to our coin models and made them as busy as possible: with noise, numbers and text added. This disrupted the flatness of the model enough, and sphere mapping worked just fine.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One more trick was used for lighting coins. Coins use baked lightmap texture as well, but with one trick in shader. Usual multiplication with lightmap texture would result in quite correct, but dull result: coin colors would become </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">only</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> darker in shadowed places. Instead, we </span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: italic; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">multiply</span><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> coins color by itself, using pow() function. The exponent is bigger for darker lightmap areas. This simulates the behavior of pile of shiny coins when light gets ‘trapped’ in tight places and intensifies itself because of multiple reflections from the same metal. These techniques result in in more realistic metallic surface:</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><img alt="result.jpg" height="299" src="https://lh5.googleusercontent.com/CleSsWPScRrz-PtQFGa9bxASdvfBTAH15vyEFOcd81AejDDw--vdXenY9zKfCDbZBL4Z5wDJqQIfjI7rEQFbu5dQEmDBMoL4fxyIQjunpkDzs017uMtRwLiGdEXnh-uI59dExhII" style="-webkit-transform: rotate(0.00rad); border: none; transform: rotate(0.00rad);" width="420" /></span></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 21.333333333333332px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Conclusion</span></h2>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">You can watch final demo on </span><a href="https://keaukraine.github.io/webgl-bitcoin/" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">this page</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> and compare it to original </span><span style="color: black; font-family: "arial"; font-size: 14.6667px; vertical-align: baseline; white-space: pre-wrap;"> </span><a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbitcoins" style="text-decoration: none;"><span style="color: #1155cc; font-family: "arial"; font-size: 14.6667px; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">Bitcoins 3D Live Wallpaper</span></a><span style="color: black; font-family: "arial"; font-size: 14.6667px; vertical-align: baseline; white-space: pre-wrap;">. It works in latest Chrome, Firefox, Safari and MS Edge browsers. You can get sources from GitHub </span><a href="https://github.com/keaukraine/webgl-bitcoin" style="text-decoration: none;"><span style="color: #1155cc; font-family: "arial"; font-size: 14.6667px; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">here</span></a><span style="color: black; font-family: "arial"; font-size: 14.6667px; vertical-align: baseline; white-space: pre-wrap;"> and reuse it in your projects under terms of MIT license.</span></div>
<div>
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 14.666666666666666px; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span></div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-88931524796960930822015-12-29T00:43:00.001-08:002015-12-29T00:47:02.307-08:00Using screenshots and video from our appsWe allow using screenshots and videos of our apps in any kind of Internet media services, such as news articles, blog posts and video reviews, as long as author provides direct links to apps on Google Play.Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com0tag:blogger.com,1999:blog-8436614286557982927.post-86053812936943848152015-02-23T01:09:00.000-08:002015-04-18T09:11:49.926-07:00Developer's notes V. Custom watch faces for Android Wear with OpenGL ES 2.0<h2 dir="ltr" id="docs-internal-guid-5977e1ef-b5a7-1119-d716-6d0437e50ff3" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="color: black; font-family: inherit; font-size: 15px; font-weight: normal; vertical-align: baseline;">As soon as Google announced possibility to create </span><a href="https://developer.android.com/training/wearables/watch-faces/index.html" style="font-family: inherit; line-height: 1.38; text-decoration: none;"><span style="color: #1155cc; font-size: 15px; font-weight: normal; text-decoration: underline; vertical-align: baseline;">custom watch faces</span></a><span style="color: black; font-family: inherit; font-size: 15px; font-weight: normal; vertical-align: baseline;"> with new Android 5.0 for Android Wear, we’ve ordered brand new ASUS ZenWatch to develop watch face (I be<span style="font-family: inherit;">lieve <span style="font-family: inherit;">nothing can beat qu<span style="font-family: inherit;">ality of this device for its price</span></span></span>). We decided not to port any of existing live wallpapers to Android Wear but to create some completely new scene for it. This resulted in creating </span><a href="https://play.google.com/store/apps/details?id=com.axiomworks.watchfacedigital" style="font-family: inherit; line-height: 1.38; text-decoration: none;"><span style="color: #1155cc; font-size: 15px; font-weight: normal; text-decoration: underline; vertical-align: baseline;">Axiom Watch Faces</span></a><span style="color: black; font-family: inherit; font-size: 15px; font-weight: normal; vertical-align: baseline;"> app which consists of 5 digital watch faces, implemented in 3D with OpenGL ES 2.0.</span></h2>
<a name='more'></a><span style="font-family: inherit;"><br /></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Original concept</span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">First, watch face was inspired by “</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Numbers</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">” video by </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">beeple</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> - </span><a href="https://vimeo.com/18876537" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline;">https://vimeo.com/18876537</span></a><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">. It looks pretty cool and doesn’t seem to be very complicated at a first glance. However, we’ve found out that there are some significant t<span style="font-family: inherit;">echnical</span> limitations which prevented us from creating this scene as seen in video. It is not that easy to implement such wireframe numbers which <span style="font-family: inherit;">at the same time</span> correctly occludes itself. In short, it will require too much draw calls to draw such seemingly simple scene<span style="font-family: inherit;">, and quite limited hardware of watch<span style="font-family: inherit;">es may n<span style="font-family: inherit;">ot be capable of <span style="font-family: inherit;">handling tha<span style="font-family: inherit;">t much d<span style="font-family: inherit;">raw calls</span></span></span></span></span></span></span></span>. So we had to develop some other concept which resulted in idea of adding depth to pixel fonts from old computers with low-res graphics.</div>
<span style="font-family: inherit;"><br /></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">OpenGL ES config w/ missing depth component</span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><a href="https://developer.android.com/reference/android/support/wearable/watchface/Gles2WatchFaceService.html" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline;">Gles2WatchFaceService</span></a><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> from Android Wear API doesn’t provide access to all features needed to create a 3D watch face. The major problem we’ve encountered is that it doesn’t allow you to pick a desired OpenGL ES config. In fact, it provides no access to EGL at all. It <span style="font-family: inherit;">is enough </span>to run '<i>Tilt</i>' sample watch face from official examples without any flexibility in mind - that example has no overlapping geometry and thus doesn’t need z-buffer at all. So </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Gles2WatchFaceService</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> chooses EGL config without depth buffer - </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">EGL_DEPTH_SIZE</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> is simply not specified in it, a<span style="font-family: inherit;">nd there's no way to specify own <span style="font-family: inherit;">EGL con<span style="font-family: inherit;">fig too</span></span></span>.</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Because of this limitation we had to decompile its source code and create custom implementation of </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Gles2WatchFaceService</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> which fills in EGL config with necessary values.</span></span></div>
<span style="font-family: inherit;"><br /></span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Also other well-known developer of live wallpapers and watch faces </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Kittehface Software</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> </span><a href="http://www.kittehface.com/2014/12/opengl-config-crash-on-smart-watches.html" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline;">reported </span></a><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">that Moto360 doesn’t work with valid 16-bit color so we use failsafe 32-bit only for all devices. We express a huge gratitude to Kittehface for explaining this bug and saving possibly days of fixing it for us and other developers.</span></span><br />
<br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">We won't provide full decompiled code <span style="font-family: inherit;">of <i>Gles2WatchFaceService</i> here because its code can be c<span style="font-family: inherit;">hanged in<span style="font-family: inherit;"> any upcoming update of API.</span></span> <span style="font-family: inherit;">Here are al<span style="font-family: inherit;">l changes we<span style="font-family: inherit;">'ve made to</span></span></span></span> decompil<span style="font-family: inherit;">ed</span> <i>Gles2WatchFaceService</i><span style="font-family: inherit;">:</span></span></span><br />
<br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">1. Update EGL con<span style="font-family: inherit;">fig to <span style="font-family: inherit;">request depth componen<span style="font-family: inherit;">t:</span></span></span> </span> </span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: "Courier New",Courier,monospace;">private static final int[] EGL_CONFIG_ATTRIB_LIST = new int[]{<br /> EGL14.EGL_RENDERABLE_TYPE, 4,<br /> EGL14.EGL_RED_SIZE, 8,<br /> EGL14.EGL_GREEN_SIZE, 8,<br /> EGL14.EGL_BLUE_SIZE, 8,<br /> EGL14.EGL_ALPHA_SIZE, 8,<br /> EGL14.EGL_DEPTH_SIZE, 16, // this was missing<br /> EGL14.EGL_NONE};</span><br /> </span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">2. Make <i>mInsetBottom</i> and <i>mInset</i><span style="font-family: inherit;"><i>Le</i><span style="font-family: inherit;"><i>ft</i> var<span style="font-family: inherit;">iables </span>accessi<span style="font-family: inherit;">ble from subclasses.</span></span></span> Th<span style="font-family: inherit;">ese values</span> <span style="font-family: inherit;">will be used to proper<span style="font-family: inherit;">ly <span style="font-family: inherit;">update <span style="font-family: inherit;">viewport<span style="font-family: inherit;">. <span style="font-family: inherit;">Either add getters or <span style="font-family: inherit;">make the<span style="font-family: inherit;">m <i>protected</i></span></span></span>:</span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;">protected int mInsetBottom = 0;<br />protected int mInsetLeft = 0;</span></span></span></span></span></span></span></div>
<span style="font-family: inherit;"><br /></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">glViewport()</span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Official documentation has some very brief and vague mentioning of handling devices with different screens using </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">onApplyWindowInsets() </span><span style="background-color: transparent; color: black; font-size: 15px; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">method</span></span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">. It is said that you should use this method to adapt for displays with bottom “chin”. This is necessary to properly adjust view on “flat <span style="font-family: inherit;">tire</span>” display of Moto 360.</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Without having Moto360 in hands it was unclear how to use it so our first attempt to run watch face on this watch resulted in misplaced viewport - shifted up by bottom inset size. Pretty strange is that this was not the case with Tilt watch face - it was centered properly. To find the cause of this issue we had to take a look at decompiled source code of </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Gles2WatchFaceService</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> again. It was caused by usage of </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">glViewport()</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">. In our renderer we use off-screen rendering for bloom effect, and use </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">glViewport()</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> to switch between small off-screen render target and on-screen viewport. In order to avoid misplaced viewport it is necessary to take into account bottom inset when setting </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">glViewport()</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">.</span></span></div>
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Opaque mode in normal and ambient mode </span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">For reasons unknown it is not possible to render opaque peek cards in ambient mode. You can alter appearance of peek cards in normal mode but in ambient mode they are 100% translucent. You will have to render some black rectangle underneath peek card using </span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">getPeekCardPosition()</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> method. In our case it is enough to use small pick card sizes so they don’t overlap with digits but in general it is a good idea to resize viewport according to received dimensions of peek card.</span></span></div>
<span style="font-family: inherit;"></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Sendin<span style="font-family: inherit;">g messages with settings update to watch</span> </span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">In our companion settings activity on watch we use <span style="font-family: inherit;"><span style="font-family: inherit;"><a href="https://github.com/LarsWerkman/HoloColorPicker" target="_blank">HoloColor</a><span style="font-family: inherit;"><a href="https://github.com/LarsWerkman/HoloColorPicker" target="_blank">P</a><span style="font-family: inherit;"><a href="https://github.com/LarsWerkman/HoloColorPicker" target="_blank">icker</a> </span></span>to choose colors. <span style="font-family: inherit;"><span style="font-family: inherit;">Col<span style="font-family: inherit;">or of watch face gets updated immediately <span style="font-family: inherit;">on every change o<span style="font-family: inherit;">f color in color picker. <span style="font-family: inherit;">However, this lead<span style="font-family: inherit;">s to cer<span style="font-family: inherit;">tain problems. It is possible to update colors too fast by continuously swipin<span style="font-family: inherit;">g color picker</span>, and queue of messages to watch <span style="font-family: inherit;">can't handle <span style="font-family: inherit;">updates fast enough. <span style="font-family: inherit;">It is simply not designed to work <span style="font-family: inherit;">this way and last messages in really <span style="font-family: inherit;">long queue may not be delivered to watch. Nothing w<span style="font-family: inherit;">on't crash but<span style="font-family: inherit;"> Data L<span style="font-family: inherit;">ayer API <span style="font-family: inherit;">may simply</span> skip last messages from <span style="font-family: inherit;">queue</span>.</span></span></span></span> </span>To fix this proble<span style="font-family: inherit;">m we'<span style="font-family: inherit;">re throttling updates of color picker by <span style="font-family: inherit;">using </span><i>Handler</i> <span style="font-family: inherit;">with 500 ms interval<span style="font-family: inherit;"><span style="font-family: inherit;">:</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;">Handler handlerUpdateColorBackground = new Handler();</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><br />Runnable runnableUpdateColorBackground = new Runnable() {<br /> @Override<br /> public void run() {</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> // update o<span style="font-family: inherit;">nl<span style="font-family: inherit;">y i<span style="font-family: inherit;">f <span style="font-family: inherit;">color was changed</span></span></span></span><br /> if (pickedColorBackground != lastSentColorBackground) {<br /> sendConfigUpdateMessage(KEY_BACKGROUND_COLOR, pickedColorBackground);<br /> }<br /> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> handlerUpdateColorBackground.postDelayed(this, 500);</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><br /> // update last <span style="font-family: inherit;">color sent to watch</span> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> lastSentColorBackground = pickedColorBackground;<br /> }<br />};</span> </span> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span> </span></span></span></span></div>
<br />
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"></span></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Overall impression of Android Wear hardware</span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Most of Android Wear smart watches on market have Snapdragon 400 in them - ASUS, Sony, Samsung and LG uses it in all of their devices. This SoC has quite impressive quad-core CPU with clock speeds up to 1.2 GHz which is more than enough for smartwatch. Its Adreno 305 GPU may seem to be out-of-date at a glance. But watches have quite low resolution of 320x320 pixels so its power is enough to render quite complex 3D scenes at 60 fps. Even Moto 360 with its aged OMAP3630 SoC (as revealed in this </span><a href="https://www.ifixit.com/Teardown/Motorola+Moto+360+Teardown/28891" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline;">iFixit teardown</span></a><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">) has powerful enough PowerVR SGX530 in it which also provides good 3D performance at required resolution.</span></span></div>
<span style="font-family: inherit;"><br /></span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">For example, applying bloom rendered at 128x128 resolution and blurred 4x times doesn’t seem to affect performance at all - Adreno 305 easily handles this additional task. However, for these watch faces it is enough to use even quite low-res texture of 64x64 size with 2 blur cycles to achieve good visual quality.</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">There are numerous videos that showcase games like Temple Run 2 and GTA running on Android Wear without any lag which also showcases performance of GPUs found in smart watches.</span></span></div>
<span style="font-family: inherit;"></span>
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">Shaders used in this app</span></span></span></h2>
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">To reduce the amount of draw calls and CPU work, digits animation is done in vertex shader. To illustrate it, here is the model of transition between ‘5’ and ‘0’:</span></span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtNsCWu8hqm0mBLBY0-n5METOt-Ou_ye11WmD72CmX0wHavszjC9_qMsqNOeGqJWx1XVEe90C8d7w1chbqfdGmaXcStqt7byowrhDFzTAOjuNWFLHKMJtJb2MzHII9csIr8svPxMkOiDM/s1600/5-0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtNsCWu8hqm0mBLBY0-n5METOt-Ou_ye11WmD72CmX0wHavszjC9_qMsqNOeGqJWx1XVEe90C8d7w1chbqfdGmaXcStqt7byowrhDFzTAOjuNWFLHKMJtJb2MzHII9csIr8svPxMkOiDM/s1600/5-0.png" /></a></div>
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;"> As you can see, its vertices are divided into three different groups:</span></span></span><br />
<ul>
<li><span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">yellow parts are not animated - they represent parts which are in common for ‘5’ and ‘0’;</span></span></span></li>
<li><span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">dark grey represent parts which will move down during animation - they are not used in ‘0’ digit;</span></span></span></li>
<li><span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">light grey represent parts which will move up during animation - they are used in ‘0’ but not in ‘5’.</span></span></span></li>
</ul>
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"><span style="font-family: inherit;">This information is coded in UV data - V coordinate is set 0 for still parts, 1 for parts moving up and -1 for parts moving down. Additionally, U coordinate is used to alter the phase of animation so that cylinders aren’t moving altogether:</span></span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKKmvLHq8ieI-MFyNU3aj8nuRk0J3eej0h2rzLjmoyFq2oo2jxzxNBAAb2PUm_740LorEMuozBpWKX74Qd8Wr0OgBQ6pNnw_klVEu0rJb0mKbY0fVKkeP1AEdLRsgfUxyScjqygRzqzbU/s1600/uvs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKKmvLHq8ieI-MFyNU3aj8nuRk0J3eej0h2rzLjmoyFq2oo2jxzxNBAAb2PUm_740LorEMuozBpWKX74Qd8Wr0OgBQ6pNnw_klVEu0rJb0mKbY0fVKkeP1AEdLRsgfUxyScjqygRzqzbU/s1600/uvs.png" height="320" width="258" /></a></div>
<br />
<span style="font-family: inherit;"><span style="font-size: small;">Pixel shader is very simple - the lower Z coordinate of model gets, the closer to black fragment is rendered.<span style="font-family: inherit;"> </span>This represents transition between digits ‘3’ and ‘4’ (yes it is incorrectly mirrored in RenderMonkey) - you can see parts of ‘4’ being shifted downwards and parts of ‘3’ popped to the top:</span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKVxZV_NCrm4YEu7l2HqY3MhqOgu-OWtATarHqYazVz_NCLcxEmGRMFjTSnxy3YqewUL02K4oycOQJXyV9iSwnF8Xa9H7xeI76LFnTuIa5zUBrjz0x80zEtEnUWeveJVGrVUSo6_xN6vc/s1600/341.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKVxZV_NCrm4YEu7l2HqY3MhqOgu-OWtATarHqYazVz_NCLcxEmGRMFjTSnxy3YqewUL02K4oycOQJXyV9iSwnF8Xa9H7xeI76LFnTuIa5zUBrjz0x80zEtEnUWeveJVGrVUSo6_xN6vc/s1600/341.png" /></a></div>
<br />
Here is the final result with black background which fades with bottom black of the model:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiaIFnJeKqut-2BtfxsotUVlp4LUeyOmKbUZgDW9S9uO8Rgurdn8WXmeQJH0l70tTiKe0sWO0TiprxqGtRL00cCFpeFlD2AuzG0Y0uH3pT1tOVZDK-q6Rqjnqq2plSG9oJnZxFZjoIBkQ/s1600/342.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiaIFnJeKqut-2BtfxsotUVlp4LUeyOmKbUZgDW9S9uO8Rgurdn8WXmeQJH0l70tTiKe0sWO0TiprxqGtRL00cCFpeFlD2AuzG0Y0uH3pT1tOVZDK-q6Rqjnqq2plSG9oJnZxFZjoIBkQ/s1600/342.png" /></a></div>
<br />
Below is the code of vertex shader:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">uniform mat4 view_proj_matrix;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">uniform float uAnim;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">uniform float uHeight;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">uniform float uHeightColor;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">uniform float uHeightOffset;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">varying vec2 Texcoord;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">varying float vColor;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">void main( void )</span><br />
<span style="font-family: "Courier New",Courier,monospace;">{</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> vec2 uv = gl_MultiTexCoord0.xy;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> vec4 pos = rm_Vertex;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> pos.z += uHeight * uv.y * clamp(uAnim + uv.x, 0.0, 1.0);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;"> gl_Position = view_proj_matrix * pos;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> Texcoord = uv;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;"> vColor = (uHeightColor + pos.z + uHeightOffset) / uHeightColor;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
uAnim is responsible for animation - it is set in range of [-1..1]<br />
uHeight is a multiplier for height animation - it differs for different digits ‘fonts’ and basically represents the height of single ‘pillar’ of a digit - 40 units for font in pictures shown.<br />
uHeightColor and uHeightOffset are used to better control fading colors to black. uHeightColor = 40 and uHeightOffset = 0 are used in the pictures above but they are different for other fonts.<br />
<br />
Pixel shader’s code:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">uniform vec4 uColor;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">uniform vec4 uColorBottom;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">varying float vColor;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">void main( void )</span><br />
<span style="font-family: "Courier New",Courier,monospace;">{</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> gl_FragColor = mix(uColorBottom, uColor, clamp(vColor, 0.0, 1.0));</span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
As you can see, pretty much everything is calculated in fragment shader already.<br />
Archive containing RenderMonkey project with shaders: <a href="https://dl.dropboxusercontent.com/u/20585920/shaders_watchface.zip">shaders_watchface.zip</a><br />
<br />
<h2 dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 10pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 21px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Publishing</span></span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: inherit;"><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">When publishing app to Google Play, you will need to tick the “</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: italic; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">Distribute your app on Android Wear</span><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;">” checkbox. This initiates review of your app for compliance with </span><a href="https://developer.android.com/distribute/essentials/quality/wear.html" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: underline; vertical-align: baseline;">Wear App Quality</span></a><span style="background-color: transparent; color: black; font-size: 15px; font-style: normal; font-variant: normal; font-weight: normal; text-decoration: none; vertical-align: baseline;"> guidelines. In our case it took just an hour or two to receive a “green light” email informing that app conforms to guidelines for watch face apps.</span></span></div>
<span style="font-family: inherit;"></span>
<span style="font-family: inherit;"></span>
<br />
<h2>
<span style="font-family: inherit;"><span style="color: #0000ee;"><span style="font-family: inherit;"> </span></span></span>
</h2>
<div style="text-align: center;">
You can get our <i>Axiom Watch Faces</i> app on Google Play:</div>
<div style="text-align: center;">
<a href="https://play.google.com/store/apps/details?id=com.axiomworks.watchfacedigital"><img alt="Android app on Google Play" src="https://developer.android.com/images/brand/en_app_rgb_wo_45.png" /></a></div>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com3tag:blogger.com,1999:blog-8436614286557982927.post-85117390527601855092013-11-16T05:55:00.000-08:002014-03-30T09:29:28.362-07:00Developer's notes IV. Presets for screen recording in Android 4.4 KitKatOne of the most useful Android 4.4 features for me is screen recording. This allows us to record videos of live wallpapers and upload them to YouTube. Before this we had to record videos either with camcorder (looks quite blurry) or from emulator (low-res and a bit laggy). So right after updating my Nexus 7 (1st gen) and Nexus 10 to Android KitKat I've decided to try this feature out.<br />
<br />
<a name='more'></a><br />
<br />
Basically, everybody making app reviews will find this feature extremely useful <br />
<br />
To record videos in Android 4.4 you should use <i>adb shell screenrecord</i> command. You can read Android documentation on this here: <a href="http://developer.android.com/tools/help/adb.html#screenrecord">http://developer.android.com/tools/help/adb.html#screenrecord</a>. After reading docs, first thing you would like to do is to launch utility with <i>--help</i> option:<br />
<blockquote class="tr_bq">
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: small;">c:\Program Files (x86)\Android\android-sdk\platform-tools>adb shell screenrecord --help<br />Usage: screenrecord [options] <filename><br /><br />Records the device's display to a .mp4 file.<br /><br />Options:<br />--size WIDTHxHEIGHT<br /> Set the video size, e.g. "1280x720". Default is the device's main<br /> display resolution (if supported), 1280x720 if not. For best results,<br /> use a size supported by the AVC encoder.<br />--bit-rate RATE<br /> Set the video bit rate, in megabits per second. Default 4Mbps.<br />--time-limit TIME<br /> Set the maximum recording time, in seconds. Default / maximum is 180.<br />--rotate<br /> Rotate the output 90 degrees.<br />--verbose<br /> Display interesting information on stdout.<br />--help<br /> Show this message.<br /><br />Recording continues until Ctrl-C is hit or the time limit is reached.</span></span></span></blockquote>
<br />
Recording on default settings will result in skipped frames and quality will be worse than you'd expect. On N7 you will get laggy video and on N10 you will get ugly compression artifacts - default 4Mbps bit rate is simply not enough for its enormous 2560x1600 resolution. So I've picked presets which I believe are optimal for my needs and don't cause UI lag when recording. When playing around with options, please note that you need to specify bit rate in bps, not Mbps, but yes, it reads "<i>in megabits per second</i>" :)<br />
<br />
Here are command line parameters for screen recording which I believe are the most optimal:<br />
<br />
Nexus 4:<br />
<blockquote>
<span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;">adb shell screenrecord /sdcard/video.mp4 --bit-rate 8000000 --size 1280x768</span></span></blockquote>
Nexus 10:<br />
<blockquote>
<span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;">adb shell screenrecord /sdcard/video.mp4 --bit-rate 8000000 --size 1280x720</span></span></blockquote>
Nexus 7 1st gen:<br />
<blockquote>
<span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;">adb shell screenrecord /sdcard/video.mp4 --size 960x540 --bit-rate 5000000</span></span></blockquote>
I don't use <i>--time-limit</i> option but just hit <i>Ctrl+C</i> after I finished recording.<br />
Because<i> adb shell</i> command simply executes any shell commands on device you can call <i>screenrecord</i> command directly on your device in <a href="https://play.google.com/store/apps/details?id=jackpal.androidterm">Android Terminal Emulator</a> app. But you need to run it with root permissions to be able to actually record videos so if device is not rooted you have to use <i>adb shell</i> from your PC. Example:<br />
<blockquote class="tr_bq">
<span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;">screenrecord /sdcard/video.mp4 --bit-rate 8000000 --size 1280x720</span></span></blockquote>
<br />
Also note that if you rotate device when recording you will get
cropped video - you will have to record landscape and portrait videos
separately.<br />
<br />
So Nexus 10 is capable of recording quite good 720p videos with little to no frameskip (it is barely noticeable when recording usual apps like browser or GMail but becomes a little bit laggy recording full-screen animations like app drawer transitions). To my liking, videos recorded from Nexus 7 have more even framerate but on Nexus10 you can achieve more sharp image because of higher resolution. Nexus4 with its powerful Snapdragon S4 is capable of capturing full-size 1280x720 frame, and I haven't noticed a single sign of lag during recording (however, there were a few negligible frame-skips in recorded video) - device operated as smooth as always.<br />
<br />
Here is a sample video recorded from 1st-gen Nexus 7. Android doesn't record audio - I've added some free audio track in Youtube Video Manager.<br />
<br />
<div style="text-align: center;">
<iframe allowfullscreen="" frameborder="0" height="315" src="//www.youtube.com/embed/QE6lMnCNU0I" width="560"></iframe>
</div>
<br />
<br />
Two more samples from Nexus 4: <a href="https://www.youtube.com/watch?v=4MGSHE8mZFQ">https://www.youtube.com/watch?v=4MGSHE8mZFQ</a> and <a href="https://www.youtube.com/watch?v=v7NYbVyJRdg">https://www.youtube.com/watch?v=v7NYbVyJRdg</a> for vertical videos fans :)<br />
<br />
Feel free to post your presets for your devices in comments - may be you will achieve better quality than me :)Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com4tag:blogger.com,1999:blog-8436614286557982927.post-36025188788202830032013-10-16T04:08:00.000-07:002014-01-08T05:41:30.753-08:00Developer's notes III. Simple DOF effect with OpenGL ES 2.0Idea came from some video which was inspired by design of Deus Ex: Human Revolution, namely design elements with broken glass shards. In a few days we've completed final vision of scene and implemented first version of live wallpaper in one weekend. As result of this work we proudly present <a href="https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperglass">Shattered Glass 3D Live Wallpaper</a>. In this article we will explain rendering techniques used in creation of this live wallpaper.<br />
<a name='more'></a><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyLLLNLDdYCGEOpTHvu84bC3___pTtKCZCHZ9zHB_K0tTK7kHPNfmZc3FIlPXHgCrDWPJY81KzYAIyeuTTRO_3Y33pbOF5M6cIcNoBJ0eaLd_a0vbi_EM8PVjlZzzxm12Uf26Mmc6p99U/s1600/first.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxb2ld_EjK_0eFBI2dSsKfo3pC5pMOVNcHENM0cx1-hBcXfzo2TWQy3R3O38iugjmBsW9HuOAFmxvjgg5L_BSWo5X7L5BdkVvry-OddN1hwmaLWoBxBHbeeSwqEIR5w2E3HxzxcBhWOGE/s1600/title.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="131" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxb2ld_EjK_0eFBI2dSsKfo3pC5pMOVNcHENM0cx1-hBcXfzo2TWQy3R3O38iugjmBsW9HuOAFmxvjgg5L_BSWo5X7L5BdkVvry-OddN1hwmaLWoBxBHbeeSwqEIR5w2E3HxzxcBhWOGE/s400/title.jpg" width="400" /></a></div>
<br />
<h2>
Scene Composition</h2>
Scene is really simple. It contains a bunch of glass shards suspended mid-air. Shards are separated in two groups - closer to viewer objects in foreground and farther ones in foreground. To add more depth to scene background objects are blurred. Color of background can be changed. Camera moves according to left-right swipes on screen, adding movement to scene.<br />
Technically this is implemented the following way: Glass shards are placed around camera filling shape of cylinder. Shards placed farther than certain distance are rendered in separate texture and blurred. Colored background is rendered together with background. To combine background and foreground first is drawn plane with background objects (already blurred) and then foreground shards are drawn over them.<br />
In such way we've implemented simplified depth-of-field (DOF) effect which is enough for described scene. Full implementation of real DOF is a bit more complex and computation-heavy. It consists of rendering the full scene into two separate textures - one full-sized and one of smaller resolution and blurred and depth map of scene. After that it is needed to draw to screen both normal and blurred scenes blending them according to depth map and camera focal parameters.<br />
<h2>
Implementation</h2>
Because all objects in scene are transparent all rendering is performed with various blending modes. Writing to depth buffer is disabled to prevent objects from cull each other. There are not that much of objects in scene so amount of overdraw is not significant. To create reflections on glass shards we use a small (128x128 pixels) cubemap texture.<br />
<br />
Order of drawing background objects:<br />
<ol>
<li>Clear FBO with <i>glClearColor</i> set to desired color of background.</li>
<li>Draw mask over the whole framebuffer. Now we have decorative blurred "stains" all over background FBO instead of even color.</li>
<li>After this, draw shards for background. Note that FBO has resolution of 256x256 pixels, and image is very pixelated.</li>
<li>Blur the whole background. Now low resolution of FBO is barely noticeable.</li>
</ol>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglAYiR1puIUezbxZ5brQowbm9MTBdIxxpxnLD3S15HUB6uZ60Wzyl8wj4vAAYuAwCWhuLZfJHwnNszDrnry2OtI8gBjkfhNqvmEXSRqR-pbJ7EhnkUKrQLlDkn33nsiZAAZltKlK5u9AM/s1600/first.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="141" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglAYiR1puIUezbxZ5brQowbm9MTBdIxxpxnLD3S15HUB6uZ60Wzyl8wj4vAAYuAwCWhuLZfJHwnNszDrnry2OtI8gBjkfhNqvmEXSRqR-pbJ7EhnkUKrQLlDkn33nsiZAAZltKlK5u9AM/s400/first.jpg" width="400" /></a></div>
<br />
<ol>
</ol>
Order of drawing foreground:<br />
<ol>
<li>Clear screen.</li>
<li>Draw background from FBO texture.</li>
<li>Draw glass shards for foreground. These draw calls are performed without writing to depth buffer because all objects are transparent.</li>
</ol>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsvz92IILK2smOEzojG1OjWqWPVR-GZbJvIAAdxyF4D0pOt8PZAFGN7nOUsupncjNic-v-bJLl47KorJu9u04joB3s1X6x83RRy9LMSXsQZDm5CPrPdFOWWvzEpk7Em18zYWnpiv4xGtQ/s1600/second.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="141" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsvz92IILK2smOEzojG1OjWqWPVR-GZbJvIAAdxyF4D0pOt8PZAFGN7nOUsupncjNic-v-bJLl47KorJu9u04joB3s1X6x83RRy9LMSXsQZDm5CPrPdFOWWvzEpk7Em18zYWnpiv4xGtQ/s400/second.jpg" width="400" /></a></div>
<br />
<ol>
</ol>
<h2>
Blurring Background Layer</h2>
Blur is implemented by drawing source image between two framebuffers a few times with special shader. To keep shader simple, it can apply only either vertical or horizontal blur of image at a time, this behavior depends on values of uniforms. Such technique is called <i>ping-pong rendering</i>. It works this way: first texture from framebuffer A is rendered into framebuffer B with horizontal blur, and then backwards from B to A with vertical blur. This can be repeated in a few iterations to achieve necessary quality of blurred image.<br />
Notable is that modern phones and tablets (and surprisingly, quite old hardware, too) can perform not only single but multiple blur passes fast enough to keep frame rate high. Practically, Mali T604 of Nexus 10 can achieve steady 40-50 fps even with 6-8 passes of blurring 256x256 texture, and by pass I mean full horizontal + vertical blur.<br />
To achieve balance between performance on low-end devices and image quality we decided to use 3 blur iterations with 256x256 pixels resolution of render target.<br />
<h2>
Mali</h2>
In our <a href="http://androidworks-kea.blogspot.com/2013/08/developers-notes-ii-etc2-texture.html">previous post</a> we've made some not very appealing notes regarding nVidia's Tegra3 GPUs. This is not because we dislike nVidia for no reason - the same way we don't like any other GPU manufacturers providing buggy OpenGL drivers. For example, we've encountered a strange misbehavior of Nexus 10 OpenGL drivers while developing this live wallpaper. Problem is that rendering to texture is incorrect but only if orientation of device is different from default landscape. It is more than weird to see that orientation of device can affect rendering into external render target but it is present in current OpenGL drivers of Nexus 10. Strange but true.<br />
First thought was that I've missed some small part of GL context initialization. So I've decided to ask about this on Stack Overflow: <a href="http://stackoverflow.com/questions/17403197/nexus-10-render-to-external-rendertarget-works-only-in-landscape">http://stackoverflow.com/questions/17403197/nexus-10-render-to-external-rendertarget-works-only-in-landscape</a>. And that's the part where I should praise the work of ARM support team. In a couple of days I've received an email from ARM engineer with proposition to file this issue to <i>Mali Developer Center</i> forum. I've prepared a simple demo app to reproduce issue and filed an issue here: <a href="http://forums.arm.com/index.php?/topic/16894-nexus-10-render-to-external-rendertarget-works-only-in-landscape/page__gopid__41612">http://forums.arm.com/index.php?/topic/16894-nexus-10-render-to-external-rendertarget-works-only-in-landscape/page__gopid__41612</a>. And in a mere 4 days I've received a response informing about a bug in current version of Nexus 10 drivers. Even more than that, ARM have proposed a workaround which magically resolved my issue - you should call <i>glViewport()</i> after <i>glBindFramebuffer()</i>.<br />
There is an issue in Google bug tracker too: <a href="http://code.google.com/p/android/issues/detail?id=57391">http://code.google.com/p/android/issues/detail?id=57391</a> Unfortunately, it doesn't have any definite official answer from Google yet. If you wish to improve quality of Nexus 10 software please star this issue to drag Google attention to it.<br />
<h2>
Result</h2>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/YOzn3FTp1Nw?feature=player_embedded' frameborder='0'></iframe></div>
<h2>
</h2>
Oleksandr Popovhttp://www.blogger.com/profile/16416641029130574652noreply@blogger.com1