{"id":388526,"date":"2024-06-29T07:59:43","date_gmt":"2024-06-29T07:59:43","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=388526"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T07:59:43","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=388526","title":{"rendered":"<span>Water Simulation<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>So, I finally found a moment to write a bit about how we created the water for TReload. Our basic goal was to flood all of the levels with acid &#8212; a lot of acid, as the flooded area is massive \ud83d\ude42 Here\u2019s one of the results which we got out of this process:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/582\/392\/13b\/58239213b5769a0a5471565735151cc2.jpg\" width=\"2560\" height=\"1245\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/582\/392\/13b\/58239213b5769a0a5471565735151cc2.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>Visually, the acid was supposed to be similar to dirty water with a yellow tinge. A bit like this:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/293\/dc0\/371\/293dc0371093d9344802326e241ad4b7.jpg\" width=\"974\" height=\"355\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/293\/dc0\/371\/293dc0371093d9344802326e241ad4b7.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>The acid needs to support physical interaction with objects thrown into it, i.e. it needs to have waves and foam. Secondly, it should be possible to see through the murkiness of the water. We have also thought about adding a slight wind to the levels, but we\u2019re not sure yet.<\/p>\n<p>The development of our acid consisted of various stages:<\/p>\n<ul>\n<li>\n<p>Developing instruments for manipulating the acid (more specifically, the instrument allowing us to draw texture masks for the foam)<\/p>\n<\/li>\n<li>\n<p>Developing the acid itself: shaders, materials, rules of interaction, sound effects<\/p>\n<\/li>\n<\/ul>\n<h3>Drawing the foam texture masks<\/h3>\n<p>This mechanism is pretty simple. There are essentially two textures:<\/p>\n<ul>\n<li>\n<p>The texture mask for the foam (which we\u2019ll call \u2018mask\u2019)<\/p>\n<\/li>\n<li>\n<p>The texture for the brush (which we\u2019ll call \u2018brush\u2019)<\/p>\n<\/li>\n<\/ul>\n<p>The key to the task is to correctly blit the \u2018brush\u2019 with the \u2018mask\u2019 (using the corresponding scale and offset for the brush in order to project it correctly, onto the right area of the mask).<\/p>\n<p>In order to be able to move the brush around the model and draw, we need the coordinates of the intersection point of the model and the brush to be carried over to the<strong> <\/strong>UV space.<\/p>\n<p>There are two ways to do this:<\/p>\n<ul>\n<li>\n<p>Using the MeshCollider, getting from it \u201ctexcoord.xy\u201d for the area of intersection of the Raycast ray. In this case, the coordinates will already be transferred to UV coordinates, so we just need to project the brush texture onto the right area of the mask.<\/p>\n<\/li>\n<li>\n<p>Using BoxCollider and transferring the worldSpace brush coordinates to the UV coordinates manually<\/p>\n<\/li>\n<\/ul>\n<p>We chose the second option:<\/p>\n<ul>\n<li>\n<p>We added BoxCollider to the acid model<\/p>\n<\/li>\n<li>\n<p>We made a RayCast<\/p>\n<\/li>\n<li>\n<p>The worldSpace point of intersection between the brush ray and the acid gets transferred to acidLocalSpace<\/p>\n<\/li>\n<li>\n<p>This point is then transferred to UV space. For this, we divided the coordinates of the intersection point by the acid\u2019s dimensions:<\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/9b9\/881\/c6c\/9b9881c6c494d727b91dea55e7927dd3.jpg\" width=\"300\" height=\"225\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9b9\/881\/c6c\/9b9881c6c494d727b91dea55e7927dd3.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h3>Improvements: Cancel mechanism (ctrl+z)<\/h3>\n<p>When we draw an acid mask with a brush, we introduce changes to the mask, which often need to be cancelled, for example if too much foam is created or if the contrasts are too sharp. In order to introduce a cancel mechanism, we had to change our approach: we created an orthographic camera which only renders a layer of brushes. The camera dimensions corresponded to the dimensions of the acid. In the area of intersection between the brush and the mask, a brush mesh was created (rendered by the camera), and then \u2018Blit\u2019 was performed on the mask. As such, we created the possibility to cancel actions.<\/p>\n<p>Here\u2019s a short demonstration of how the mask-drawing system works:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/612209ab8ff4968fafa2a93a\" data-style=\"\" id=\"612209ab8ff4968fafa2a93a\" width=\"\"><\/div>\n<h3>Waves<\/h3>\n<p>We tried several different methods for creating the waves:<\/p>\n<ul>\n<li>\n<p>Drawing flat waves on the acid texture\u00a0<\/p>\n<\/li>\n<li>\n<p>Creating waves with a geometry shader over the top of the acid<\/p>\n<\/li>\n<li>\n<p>Tessellation + GPU Instancing\u00a0<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/620\/a63\/779\/620a637796b58d129f78f48e84a8f42b.png\" width=\"1492\" height=\"624\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/620\/a63\/779\/620a637796b58d129f78f48e84a8f42b.png\"\/><figcaption><\/figcaption><\/figure>\n<p>The right-hand side of the drawing shows a wave created by a geometry shader. The left-hand side shows the flat wave.<\/p>\n<p>In the end, we decided against these waves, and chose instead to make more realistic waves which are able to take into account interference and generate foam as they propagate.<\/p>\n<h3>Generating waves<\/h3>\n<p>I\u2019ll describe this in general terms: there is a 2D \u201cfluctuations\u201d equation, which gets solved at each new frame. This equation allows us to generate wave propagation. Below are some more useful materials on this topic: <a href=\"http:\/\/www.3dcpptutorials.sk\/index.php?id=48\" rel=\"noopener noreferrer nofollow\"><u>link 1<\/u><\/a><\/p>\n<p>Some useful source code for Unity: <a href=\"https:\/\/github.com\/AsehesL\/UnityWaveEquation\" rel=\"noopener noreferrer nofollow\"><u>link 2<\/u><\/a><\/p>\n<p>Here\u2019s an example of the waves I managed to generate (using standard Unity tessellation and a standard shader):<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/288\/aab\/9d7\/288aab9d7a1dcba91078247fa6db139c.jpg\" width=\"2080\" height=\"1290\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/288\/aab\/9d7\/288aab9d7a1dcba91078247fa6db139c.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h3>The structure of the water\u2019s surface and propagation of waves over neighbouring segments<\/h3>\n<p>The water\u2019s surface is made up of NxN objects with LOD. As they get further away, the objects with LOD substitute each other so that at any given distance, instead of four different objects with LOD, only one is drawn:\u00a0<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/99f\/0ce\/aa3\/99f0ceaa3e5c867cea299e74bf93ec55.jpg\" width=\"458\" height=\"165\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/99f\/0ce\/aa3\/99f0ceaa3e5c867cea299e74bf93ec55.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>So, the water surface is essentially a \u2018smart\u2019 net made out of various sections. Let\u2019s imagine that the water\u2019s dimensions are 8&#215;8 and the wave source is in the unit [2,4]:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4ae\/14c\/01b\/4ae14c01bfbbed31337c4f9e5e00badc.png\" width=\"800\" height=\"800\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4ae\/14c\/01b\/4ae14c01bfbbed31337c4f9e5e00badc.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Next, we reserve a certain number of neighbouring units (one from each side in my example) and project the wave propagation texture onto this section of the water\u2019s surface. The projection of the wave propagation texture is shown in red. So, we till and offset the texture for each section of the water.\u00a0<\/p>\n<p>Searching for the area which the waves should be drawn on:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220af5abf7c98fe8a3343b\" data-style=\"\" id=\"61220af5abf7c98fe8a3343b\" width=\"\"><\/div>\n<p>By the way, if the wave source is not on the edge of the water, we position the texture with the waves in such a way that it doesn\u2019t go over the edges of the water (this isn\u2019t shown by the video).\u00a0<\/p>\n<p>Here, we projected the texture which the waves are supposed to be drawn on (by setting up \u2018tilling and offset\u2019)<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b2cdc7e91a24fcac471\" data-style=\"\" id=\"61220b2cdc7e91a24fcac471\" width=\"\"><\/div>\n<p>So, the waves propagate onto the adjoining objects too i.e. beyond just one section of the water.<\/p>\n<p>Here are the results of the water simulation and tessellation:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b51920526a288c7e1e1\" data-style=\"\" id=\"61220b51920526a288c7e1e1\" width=\"\"><\/div>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b679b18798fbe34cc2d\" data-style=\"\" id=\"61220b679b18798fbe34cc2d\" width=\"\"><\/div>\n<h3>Generating waves from complex objects<\/h3>\n<p>Up until now I have explained everything in simplified terms and referenced literature on the topic of how to generate <em>circular <\/em>waves. But what if we have a parallelepiped or a capsule (or some kind of non-convex object) falling into the water?\u00a0<\/p>\n<p>To achieve a \u2018realistic\u2019 wave form for more complex objects, we did the following:<\/p>\n<ul>\n<li>\n<p>We rendered the falling object in the _FallTex texture (the orthographic camera renders the depth values of the falling objects multiplied by their fall speed)<\/p>\n<\/li>\n<li>\n<p>Then, the _FallTex texture is blurred and this effect is repeated in the wave texture<\/p>\n<\/li>\n<\/ul>\n<p>So, we interfere with the water simulation process, and add new values to the simulation (new wave sources).<\/p>\n<p>Here you can see the results of the wave simulation for complex objects:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b8c920526a288c7e1e2\" data-style=\"\" id=\"61220b8c920526a288c7e1e2\" width=\"\"><\/div>\n<h3>The propagation of waves onto far-off segments<\/h3>\n<p>This is one of the more problematic tasks which we had to deal with. The waves can only be spread out providing additional textures are used. The player can\u2019t just fly from place to place above the water at the speed of a bullet. So, while we do have the option to stop generating waves which are far enough away, the waves which are located close to the player still need to be smoothly carried over from one segment of the water to another as they propagate.<\/p>\n<p>Here is how the water behaves moving between different sections of the water simulation:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220bc6abf7c98fe8a3343c\" data-style=\"\" id=\"61220bc6abf7c98fe8a3343c\" width=\"\"><\/div>\n<p>To avoid distortions in the water, we need to have two textures: one to simulate the liquid, and one to recopy onto itself the wave section whilst the player is moving (i.e. as the central water section shifts). If we don\u2019t do this, we can get bugs like this:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/82a\/96f\/75f\/82a96f75fe536cbdf59870f33d311b44.png\" width=\"1322\" height=\"1582\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/82a\/96f\/75f\/82a96f75fe536cbdf59870f33d311b44.png\"\/><figcaption><\/figcaption><\/figure>\n<p>This is what happens if the object which has fallen into the water swims from [2,4] to [3,4]<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fac\/028\/33c\/fac02833ce51371d8374758e1813b5fa.png\" width=\"800\" height=\"800\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fac\/028\/33c\/fac02833ce51371d8374758e1813b5fa.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Then, to make the various sections seem like one integrated body of water, we copy part of the texture from the current water simulation into the second texture. Next we make the second texture the main one and continue propagating the waves on this texture.<\/p>\n<p>Why are we copying the texture? Because we need a way to start a new simulation whilst retaining the result of the old simulation (on the picture above you can see this result in the yellow area).<\/p>\n<h3>Artifacts<\/h3>\n<p>If the object\/wave source is located on the boundary between different segments of the water, then artifacts can appear when the wave propagation textures are copied:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a2c\/0b6\/726\/a2c0b6726de6af731be54bece53e90aa.png\" width=\"2152\" height=\"1426\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a2c\/0b6\/726\/a2c0b6726de6af731be54bece53e90aa.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b0a\/199\/991\/b0a199991b2120e836ff890692c5c0f1.png\" width=\"1294\" height=\"1544\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b0a\/199\/991\/b0a199991b2120e836ff890692c5c0f1.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/623\/221\/c86\/623221c86d3f116c8fa19ea7b2099d20.png\" width=\"1282\" height=\"1556\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/623\/221\/c86\/623221c86d3f116c8fa19ea7b2099d20.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/158\/6a1\/9a7\/1586a19a761956798f00da01d203ff39.png\" width=\"1322\" height=\"1478\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/158\/6a1\/9a7\/1586a19a761956798f00da01d203ff39.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Me testing the tessellation and attempting to join together Tessellation + GPU Instancing in the Standard shader:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220c869b18798fbe34cc2e\" data-style=\"\" id=\"61220c869b18798fbe34cc2e\" width=\"\"><\/div>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/7d1\/548\/da3\/7d1548da31ab127925b59b5bb0e402de.jpg\" width=\"2364\" height=\"1328\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d1\/548\/da3\/7d1548da31ab127925b59b5bb0e402de.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/3ab\/f16\/4ad\/3abf164adffd8f85ccb945bb03df9265.jpg\" width=\"2560\" height=\"1245\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3ab\/f16\/4ad\/3abf164adffd8f85ccb945bb03df9265.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220cb86f06c2a29f4b461f\" data-style=\"\" id=\"61220cb86f06c2a29f4b461f\" width=\"\"><\/div>\n<p>Well, that\u2019s all for today!<\/p>\n<p>I hope this article was useful reading and gave non-programmers an idea of the difficulties we face developing games \ud83d\ude42<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/574072\/\"> https:\/\/habr.com\/ru\/articles\/574072\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>So, I finally found a moment to write a bit about how we created the water for TReload. Our basic goal was to flood all of the levels with acid &#8212; a lot of acid, as the flooded area is massive \ud83d\ude42 Here\u2019s one of the results which we got out of this process:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Visually, the acid was supposed to be similar to dirty water with a yellow tinge. A bit like this:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>The acid needs to support physical interaction with objects thrown into it, i.e. it needs to have waves and foam. Secondly, it should be possible to see through the murkiness of the water. We have also thought about adding a slight wind to the levels, but we\u2019re not sure yet.<\/p>\n<p>The development of our acid consisted of various stages:<\/p>\n<ul>\n<li>\n<p>Developing instruments for manipulating the acid (more specifically, the instrument allowing us to draw texture masks for the foam)<\/p>\n<\/li>\n<li>\n<p>Developing the acid itself: shaders, materials, rules of interaction, sound effects<\/p>\n<\/li>\n<\/ul>\n<h3>Drawing the foam texture masks<\/h3>\n<p>This mechanism is pretty simple. There are essentially two textures:<\/p>\n<ul>\n<li>\n<p>The texture mask for the foam (which we\u2019ll call \u2018mask\u2019)<\/p>\n<\/li>\n<li>\n<p>The texture for the brush (which we\u2019ll call \u2018brush\u2019)<\/p>\n<\/li>\n<\/ul>\n<p>The key to the task is to correctly blit the \u2018brush\u2019 with the \u2018mask\u2019 (using the corresponding scale and offset for the brush in order to project it correctly, onto the right area of the mask).<\/p>\n<p>In order to be able to move the brush around the model and draw, we need the coordinates of the intersection point of the model and the brush to be carried over to the<strong> <\/strong>UV space.<\/p>\n<p>There are two ways to do this:<\/p>\n<ul>\n<li>\n<p>Using the MeshCollider, getting from it \u201ctexcoord.xy\u201d for the area of intersection of the Raycast ray. In this case, the coordinates will already be transferred to UV coordinates, so we just need to project the brush texture onto the right area of the mask.<\/p>\n<\/li>\n<li>\n<p>Using BoxCollider and transferring the worldSpace brush coordinates to the UV coordinates manually<\/p>\n<\/li>\n<\/ul>\n<p>We chose the second option:<\/p>\n<ul>\n<li>\n<p>We added BoxCollider to the acid model<\/p>\n<\/li>\n<li>\n<p>We made a RayCast<\/p>\n<\/li>\n<li>\n<p>The worldSpace point of intersection between the brush ray and the acid gets transferred to acidLocalSpace<\/p>\n<\/li>\n<li>\n<p>This point is then transferred to UV space. For this, we divided the coordinates of the intersection point by the acid\u2019s dimensions:<\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<h3>Improvements: Cancel mechanism (ctrl+z)<\/h3>\n<p>When we draw an acid mask with a brush, we introduce changes to the mask, which often need to be cancelled, for example if too much foam is created or if the contrasts are too sharp. In order to introduce a cancel mechanism, we had to change our approach: we created an orthographic camera which only renders a layer of brushes. The camera dimensions corresponded to the dimensions of the acid. In the area of intersection between the brush and the mask, a brush mesh was created (rendered by the camera), and then \u2018Blit\u2019 was performed on the mask. As such, we created the possibility to cancel actions.<\/p>\n<p>Here\u2019s a short demonstration of how the mask-drawing system works:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/612209ab8ff4968fafa2a93a\" data-style=\"\" id=\"612209ab8ff4968fafa2a93a\" width=\"\"><\/div>\n<h3>Waves<\/h3>\n<p>We tried several different methods for creating the waves:<\/p>\n<ul>\n<li>\n<p>Drawing flat waves on the acid texture\u00a0<\/p>\n<\/li>\n<li>\n<p>Creating waves with a geometry shader over the top of the acid<\/p>\n<\/li>\n<li>\n<p>Tessellation + GPU Instancing\u00a0<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>The right-hand side of the drawing shows a wave created by a geometry shader. The left-hand side shows the flat wave.<\/p>\n<p>In the end, we decided against these waves, and chose instead to make more realistic waves which are able to take into account interference and generate foam as they propagate.<\/p>\n<h3>Generating waves<\/h3>\n<p>I\u2019ll describe this in general terms: there is a 2D \u201cfluctuations\u201d equation, which gets solved at each new frame. This equation allows us to generate wave propagation. Below are some more useful materials on this topic: <a href=\"http:\/\/www.3dcpptutorials.sk\/index.php?id=48\" rel=\"noopener noreferrer nofollow\"><u>link 1<\/u><\/a><\/p>\n<p>Some useful source code for Unity: <a href=\"https:\/\/github.com\/AsehesL\/UnityWaveEquation\" rel=\"noopener noreferrer nofollow\"><u>link 2<\/u><\/a><\/p>\n<p>Here\u2019s an example of the waves I managed to generate (using standard Unity tessellation and a standard shader):<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>The structure of the water\u2019s surface and propagation of waves over neighbouring segments<\/h3>\n<p>The water\u2019s surface is made up of NxN objects with LOD. As they get further away, the objects with LOD substitute each other so that at any given distance, instead of four different objects with LOD, only one is drawn:\u00a0<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>So, the water surface is essentially a \u2018smart\u2019 net made out of various sections. Let\u2019s imagine that the water\u2019s dimensions are 8&#215;8 and the wave source is in the unit [2,4]:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Next, we reserve a certain number of neighbouring units (one from each side in my example) and project the wave propagation texture onto this section of the water\u2019s surface. The projection of the wave propagation texture is shown in red. So, we till and offset the texture for each section of the water.\u00a0<\/p>\n<p>Searching for the area which the waves should be drawn on:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220af5abf7c98fe8a3343b\" data-style=\"\" id=\"61220af5abf7c98fe8a3343b\" width=\"\"><\/div>\n<p>By the way, if the wave source is not on the edge of the water, we position the texture with the waves in such a way that it doesn\u2019t go over the edges of the water (this isn\u2019t shown by the video).\u00a0<\/p>\n<p>Here, we projected the texture which the waves are supposed to be drawn on (by setting up \u2018tilling and offset\u2019)<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b2cdc7e91a24fcac471\" data-style=\"\" id=\"61220b2cdc7e91a24fcac471\" width=\"\"><\/div>\n<p>So, the waves propagate onto the adjoining objects too i.e. beyond just one section of the water.<\/p>\n<p>Here are the results of the water simulation and tessellation:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b51920526a288c7e1e1\" data-style=\"\" id=\"61220b51920526a288c7e1e1\" width=\"\"><\/div>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b679b18798fbe34cc2d\" data-style=\"\" id=\"61220b679b18798fbe34cc2d\" width=\"\"><\/div>\n<h3>Generating waves from complex objects<\/h3>\n<p>Up until now I have explained everything in simplified terms and referenced literature on the topic of how to generate <em>circular <\/em>waves. But what if we have a parallelepiped or a capsule (or some kind of non-convex object) falling into the water?\u00a0<\/p>\n<p>To achieve a \u2018realistic\u2019 wave form for more complex objects, we did the following:<\/p>\n<ul>\n<li>\n<p>We rendered the falling object in the _FallTex texture (the orthographic camera renders the depth values of the falling objects multiplied by their fall speed)<\/p>\n<\/li>\n<li>\n<p>Then, the _FallTex texture is blurred and this effect is repeated in the wave texture<\/p>\n<\/li>\n<\/ul>\n<p>So, we interfere with the water simulation process, and add new values to the simulation (new wave sources).<\/p>\n<p>Here you can see the results of the wave simulation for complex objects:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220b8c920526a288c7e1e2\" data-style=\"\" id=\"61220b8c920526a288c7e1e2\" width=\"\"><\/div>\n<h3>The propagation of waves onto far-off segments<\/h3>\n<p>This is one of the more problematic tasks which we had to deal with. The waves can only be spread out providing additional textures are used. The player can\u2019t just fly from place to place above the water at the speed of a bullet. So, while we do have the option to stop generating waves which are far enough away, the waves which are located close to the player still need to be smoothly carried over from one segment of the water to another as they propagate.<\/p>\n<p>Here is how the water behaves moving between different sections of the water simulation:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220bc6abf7c98fe8a3343c\" data-style=\"\" id=\"61220bc6abf7c98fe8a3343c\" width=\"\"><\/div>\n<p>To avoid distortions in the water, we need to have two textures: one to simulate the liquid, and one to recopy onto itself the wave section whilst the player is moving (i.e. as the central water section shifts). If we don\u2019t do this, we can get bugs like this:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>This is what happens if the object which has fallen into the water swims from [2,4] to [3,4]<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Then, to make the various sections seem like one integrated body of water, we copy part of the texture from the current water simulation into the second texture. Next we make the second texture the main one and continue propagating the waves on this texture.<\/p>\n<p>Why are we copying the texture? Because we need a way to start a new simulation whilst retaining the result of the old simulation (on the picture above you can see this result in the yellow area).<\/p>\n<h3>Artifacts<\/h3>\n<p>If the object\/wave source is located on the boundary between different segments of the water, then artifacts can appear when the wave propagation textures are copied:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Me testing the tessellation and attempting to join together Tessellation + GPU Instancing in the Standard shader:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220c869b18798fbe34cc2e\" data-style=\"\" id=\"61220c869b18798fbe34cc2e\" width=\"\"><\/div>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/61220cb86f06c2a29f4b461f\" data-style=\"\" id=\"61220cb86f06c2a29f4b461f\" width=\"\"><\/div>\n<p>Well, that\u2019s all for today!<\/p>\n<p>I hope this article was useful reading and gave non-programmers an idea of the difficulties we face developing games \ud83d\ude42<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/574072\/\"> https:\/\/habr.com\/ru\/articles\/574072\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-388526","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/388526","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=388526"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/388526\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=388526"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=388526"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=388526"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}