{"id":32,"date":"2026-05-03T21:40:13","date_gmt":"2026-05-03T21:40:13","guid":{"rendered":"https:\/\/blog.metu.edu.tr\/e263308\/?p=32"},"modified":"2026-05-03T21:40:14","modified_gmt":"2026-05-03T21:40:14","slug":"ceng469-plane-oddysey","status":"publish","type":"post","link":"https:\/\/blog.metu.edu.tr\/e263308\/2026\/05\/03\/ceng469-plane-oddysey\/","title":{"rendered":"CENG469: Plane Oddysey"},"content":{"rendered":"<p>Hi, this is my blogpost regarding the HW2 of CENG469. The objective this time was to improve upon the previous homework, adding a controllable plane enviornment mapping and animated water.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Migration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I started by copying over hw1 and examining the provided starter code. I merged the utility files that I had edited with the new ones and added the imgui library.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Plane<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My first task was to add the plane. I changed the camera struct into two structs, one keeping the plane&#8217;s position rotation and speed and another that keeps the camera&#8217;s offset and rotation. I added the keyboard shortcuts for zooming, plane and camera rotations. I implemented the  and repurposed the code from last homework&#8217;s U-mode camera to implement the camera&#8217;s rotation while using the ordinary rotation for the plane. Then I changed the movement code such that the current speed moves the plane along the +z axis and <code>w<\/code>\/<code>s<\/code> buttons change the speed. Finally I added the code that offsets the camera in its +z direction from the plane.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When implementing this with the information fresh in my mind from studying for the midterm, I realized that I had written quat rotation code wrong in the previous homework. Instead of <code>v'=qvq<sup>*<\/sup><\/code>,  I was using <code>v'=vq<\/code>. However, upon fixing this everything broke. I eventually learned that glm overrides quat multiplication such that it rotates a vector, not multiply it directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I added the variables to import the parts of the plane model and the draw calls (with the appropriate offsets) for each one. I added a time variable to the <code>GLState<\/code> that I used to determine the rotation of the propeller. Later I made the propeller rotation also dependent on the plane&#8217;s speed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Textures<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With that done I moved on to the textures. I realized that with 4 models and 6 textures the code for drawing the plane was quite cumbersome to have in the main loop, so I created a struct to hold all its texture, model and shader variables and moved the draw code to a single method of the struct. Also seeing how the starter code had different shaders for different things instead of changing the mode uniform of one shader, I created seperate shaders for the plane and terrain. The plane shader simply maps the given texture as color.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To draw the terrain I created another struct that holds all the terrain textures and handles the pipeline. This made the main loop nicer and easier to dollow. For its fragment shader I used the fragment <code>x-z<\/code> coordinates as the  <code>u-v<\/code> coordinates. Instead of the sharp color transitions in the hw1 I wanted the textures to smoothly change, so I removed the if-else gates and used 2 mix calls mix the textures. I ended up not using the shore texture in the shader.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"612\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020-1024x612.png\" alt=\"\" class=\"wp-image-33\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020-1024x612.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020-300x179.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020-768x459.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020-1536x917.png 1536w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_215020.png 1584w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Texture mapping<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Tonemapping<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With that done the next goal was tonemapping. I looked up how to do this in OpenGL and found an official aritcle with some sample code. I created another struct that&#8217;d keep the texture buffers and shaders. I implemented the process using the apis already used in the project following the example of the sample code. I also read the tonemapping using mipmapping paper to see how to implement that part of the code. Finally I named the method <code>endRender<\/code> and placedits call at the end of the main loop. The tonemapped image looked significantly brighter but upon comparing it with the texture files I decided it was correct.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"611\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_220241-1024x611.png\" alt=\"\" class=\"wp-image-34\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_220241-1024x611.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_220241-300x179.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_220241-768x458.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_220241.png 1320w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Texturing<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Enviorment Mapping<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I decided to create another method under the same struct called <code>startRender<\/code> that&#8217;d have the enviornment mapping code. I implemented the new enviornment.frag by loosely following the cubemap generation code in the slides. However as the camera could face any direction unlike a cubemap I had to improvise. I devised that if I pass the gaze direction and the field of vision to the fragment shader I could use <code>atan<\/code> and <code>asin<\/code> to find the <code>i-j<\/code> coordinates (<code>u-v<\/code> of the spherical image).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my first implementation I figured that if I just calculated the <code>i-j<\/code> coordinates of the gaze vector I could multiply the fragment&#8217;s <code>u-v<\/code> coordinates with the FOV to find the correct i-j coordinates. Initially everything was sideways, but eventually I figured out that I was assigning i to j and vice versa. After fixing that it seemingly worked, but upon rotating the camera it was obvious something was off.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"748\" style=\"aspect-ratio: 1242 \/ 748;\" width=\"1242\" controls src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Video_2026-05-03_22-32-09.mp4\"><\/video><figcaption class=\"wp-element-caption\">Flat looking enviornment<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I thought a lot about what is wrong and the conclusion was that I&#8217;m sampling spherically from the sphere instead of linearly.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"786\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_224238_Samsung-Notes-1024x786.png\" alt=\"\" class=\"wp-image-37\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_224238_Samsung-Notes-1024x786.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_224238_Samsung-Notes-300x230.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_224238_Samsung-Notes-768x590.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_224238_Samsung-Notes.png 1440w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Flat looking enviornment explanation<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To mitigate this I decided I needed to find the vectors of the 4 screen edges and linearly interplate them. I thought the range the previous algorithm sampled was correct, so if I could linearly sample the range it would produce correct results. For example for the horizotal edges I used the previous algorithm to find the <code>i<\/code> coordinate of left and right edges, used <code>sin<\/code> and <code>cosine<\/code> to turn them into vectors, linearly interpolated the vectors and turned the result back into an <code>i<\/code> coordinate.  However the outcome was even worse.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"748\" style=\"aspect-ratio: 1242 \/ 748;\" width=\"1242\" controls src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Video_2026-05-03_22-32-30.mp4\"><\/video><figcaption class=\"wp-element-caption\">Fisheye looking enviornment<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I thought long about this but I could not figure out what was going wrong. However I suspect it is related to how it is possible to draw a triangle with 3 right angles on a sphere.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"841\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_230938_Samsung-Notes-1024x841.png\" alt=\"\" class=\"wp-image-39\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_230938_Samsung-Notes-1024x841.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_230938_Samsung-Notes-300x246.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_230938_Samsung-Notes-768x630.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_230938_Samsung-Notes.png 1440w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Fisheye looking enviornment suspicion<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">All my efforts to fix this were fruitless and eventually I also discovered that I was dividing the wrong variable by <code>2pi<\/code> so it actually looked like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"619\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_231450-1024x619.png\" alt=\"\" class=\"wp-image-40\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_231450-1024x619.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_231450-300x181.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_231450-768x464.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/Screenshot_20260503_231450.png 1304w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Edge of the enviornment<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Which made me think I wasn&#8217;t mapping the texture spherically, just sliding a flat image in a fancy way. I reverted the scaling fix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I gave up and asked an LLM what I should do. It produced some ray tracing looking code and when I adapted it to the codebase I saw that it looked significantly more correct. There was still significant parallax between the mountains and clouds but I figured it&#8217;s natural given that the mapped enviornment is infinitely far away and real life clouds are sometimes lower than mountains.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I added a couple more HDRIs that can be cycled through using the <code>v<\/code> button. I also kept my 2 failed shaders, and they can be cycled using the <code>b<\/code> button.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reflections<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next task was the reflections. I transferred the sky textures to a sky struct so it is consistent for different objects. I also attempted to place all the terrain generation code inside the terrain object but moving it outside the main loop caused errors so I reverted it. I added the roughness textures to the objects and changed the terrain and plane shaders to sample from the enviornment map.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I experimented with many schemes to make ground look less shiny while obeying the roughness textures. I tried using higher mipmap bias while sampling the enviornment to blur it but this caused the enviornment to look flat shaded, as the pixel they were sampling from was more affected by the normal than slight changes in the viewing direction. I also tried taking multiple samples around the reflection ray and averaging them but it didn&#8217;t look susbtantially different. I ended up just using a combination of albedo + reflection sampled enviornment + normal sampled enviornment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I found 2 problems witht the result. First was that there was even more significant parallax between the water reflection and the actual enviornment map. I assumed this is inevitable since both sample from directions and the parallax wouldn&#8217;t be visible once the waves were animated. The second problem was the giant line that sometimes appeared in the sky was also visible in the reflections.<\/p>\n\n\n\n<div class=\"wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-794e3cfa wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"770\" height=\"656\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-1.png\" alt=\"\" class=\"wp-image-42\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-1.png 770w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-1-300x256.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-1-768x654.png 768w\" sizes=\"auto, (max-width: 770px) 100vw, 770px\" \/><figcaption class=\"wp-element-caption\">Line in the sky<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"787\" height=\"558\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image.png\" alt=\"\" class=\"wp-image-41\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image.png 787w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-300x213.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-768x545.png 768w\" sizes=\"auto, (max-width: 787px) 100vw, 787px\" \/><figcaption class=\"wp-element-caption\">Orange lines in the reflections<\/figcaption><\/figure>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">All the lines, no matter how far they were and if they were reflected, were 1-2 pixels wide. I thought this was a gap between the two edges of the enviornment texture and tried to limit the <code>i-j<\/code> to [0.01, 0.99] range but it didn&#8217;t work. Some time later I found a webpage where a similar problem was seen in blender and it was caused by mipmaps. So I changed the TextureGL struct to allow disabling mipmaps and the lines disappeared.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Performance<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At this point I needed to do the homework on my laptop, and realized that it ran terribly. And since the framerate was so low the plane was very sluggish as well. I changed the movement code to be based on delta time. I eventually figured that it was running on the iGPU, but to mitigate potential performance problems anyways I added two new options.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first option monitors the framerate and if it is too low automatically decreases the tesselation rate or increases it if there&#8217;s headroom. It is toggled via m and since it worked reasonably well it is on by default. However it does have the bug that if the program is minimized it thinks it&#8217;s running badly and keeps decreasing the tesselation ratio. It also sometimes gets stuck oscillating between two ratios if the GPU in the system is more powerful than the CPU.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second option changes the sampling function from a uniform density to one that is denser around the plane and is toggled via n. Unfortunately this didn&#8217;t work well (the function might&#8217;ve been too sharp) so I  left it off by default. This was a good call since enabling it at all on my desktop crashes the process.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Waves<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Finally I added the animated waves. Initially I wanted to make it a seperate mesh, but I decided it would be easier and more performant if I modified the 0-height vertecies of the terrain mesh. To prevent coastline weirdness I offset the <code>sin<\/code> to <code>[-2, 0]<\/code> range. I had some difficulties regarding the normals but eventually figured out that I wasn&#8217;t supposed to apply inverse model matrix to them, and this fixed them.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"561\" src=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2-1024x561.png\" alt=\"\" class=\"wp-image-43\" srcset=\"https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2-1024x561.png 1024w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2-300x164.png 300w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2-768x421.png 768w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2-1536x841.png 1536w, https:\/\/blog.metu.edu.tr\/e263308\/files\/2026\/05\/image-2.png 1727w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Waves<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the waves now, I seem to have messed up the time component at some point as their movement is impercemptible unless the plane is stopped and they are watched carefully. And maybe I shouldve done something to make them look less line-y when viewed from afar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This was a nice sequel to the previous homework. I did some parts better than others. Some parts could be better but I&#8217;m satisfied with the results.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hi, this is my blogpost regarding the HW2 of CENG469. The objective this time was to improve upon the previous homework, adding a controllable plane enviornment mapping and animated water. Migration I started by copying over hw1 and examining the provided starter code. I merged the utility files that I had edited with the new [&hellip;]<\/p>\n","protected":false},"author":9576,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[4],"tags":[],"class_list":["post-32","post","type-post","status-publish","format-standard","hentry","category-ceng469"],"_links":{"self":[{"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/posts\/32","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/users\/9576"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/comments?post=32"}],"version-history":[{"count":0,"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/posts\/32\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/media?parent=32"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/categories?post=32"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e263308\/wp-json\/wp\/v2\/tags?post=32"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}