{"id":13838110,"url":"https://github.com/piellardj/diamond-webgl","last_synced_at":"2026-02-12T04:38:34.272Z","repository":{"id":65368526,"uuid":"361703553","full_name":"piellardj/diamond-webgl","owner":"piellardj","description":"Ray-tracing engine for real-time diamond rendering in WebGL","archived":false,"fork":false,"pushed_at":"2024-01-15T21:06:34.000Z","size":2384,"stargazers_count":127,"open_issues_count":0,"forks_count":23,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-01-12T16:25:10.611Z","etag":null,"topics":["diamond","fresnel","optics","photorealistic-rendering","raytracing","real-time","refraction","snell","webgl"],"latest_commit_sha":null,"homepage":"https://piellardj.github.io/diamond-webgl/","language":null,"has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piellardj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-26T10:09:01.000Z","updated_at":"2024-11-26T03:01:23.000Z","dependencies_parsed_at":"2023-02-19T05:45:35.975Z","dependency_job_id":"192bacd8-97f9-448c-ab85-3fe4897e6381","html_url":"https://github.com/piellardj/diamond-webgl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piellardj%2Fdiamond-webgl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piellardj%2Fdiamond-webgl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piellardj%2Fdiamond-webgl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piellardj%2Fdiamond-webgl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piellardj","download_url":"https://codeload.github.com/piellardj/diamond-webgl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239615186,"owners_count":19668847,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["diamond","fresnel","optics","photorealistic-rendering","raytracing","real-time","refraction","snell","webgl"],"created_at":"2024-08-04T15:01:37.597Z","updated_at":"2026-02-12T04:38:34.215Z","avatar_url":"https://github.com/piellardj.png","language":null,"funding_links":["https://www.paypal.com/donate/?hosted_button_id=AF7H7GEJTL95E"],"categories":["Others"],"sub_categories":[],"readme":"# diamond-webgl\nThis is a real-time rendering engine for diamonds and gemstones. It allows precise control of the cut, and customization of various optical properties to simulate countless types of materials. It can also generate ASET images. The precision is more than enough to create the familiar hearts and arrows patterns of ideal cut diamonds.\n\nThe engine runs fully on GPU and uses both rasterization and ray-tracing, as well as post-processing techniques such as bloom and screen-space antialiasing.\n\nIt supports many gem shapes, and can render several gems simultaneously.\n\n\u003cdiv style=\"background: rgba(200,200,200,0.25);padding: 8px;border-radius: 8px;box-shadow: 0 0 6px rgba(0,0,0,0.5);\"\u003eIf you want to see the latest version of the engine or want to discuss commercial use, please go to my dedicated website:\n\u003cdiv style=\"text-align:center;margin-top:1em\"\u003e\n    \u003ca href=\"https://www.3djewelryviewer.com\"\u003e\n        \u003cp\u003e3djewelryviewer.com ↗\u003c/p\u003e\n        \u003cimg src=\"docs/3djewelryviewer/illustration_500.jpg\" alt=\"Illustration for 3djewelryviewer.com\" style=\"display:block;margin:.5em auto 0;\"/\u003e\n    \u003c/a\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\nSee it live [here](https://piellardj.github.io/diamond-webgl/).\n\n[![Donate](https://raw.githubusercontent.com/piellardj/piellardj.github.io/master/images/readme/donate-paypal.svg)](https://www.paypal.com/donate/?hosted_button_id=AF7H7GEJTL95E)\n\n## Preview\n\n![Example of ring](src/readme/ring1.png)\n\n![Round brilliant cut diamond](src/readme/bloom_final.png)\n\n![Example of ring](src/readme/ring2.png)\n\n![Examples of cuts](src/readme/models.png)\n\n![Hearts pattern](src/readme/hearts.png)\n\n![Arrows pattern](src/readme/ASET_arrows.png)\n\n![Geometry](src/readme/geometry.png)\n\n![Emerald](src/readme/emerald.png)\n\n## Diamond\n### Diamond cut\nThe beauty of a diamond resides not only in the purity of the gem, but also in the way it is cut. The purity affects mostly light absorption, while the cut affects the way light is reflected. The goal is to reflect as much light as possible towards the viewer (that is, towards the top of the gem) so that the diamond appears the brightest.\n\nOne of the most popular diamond cuts is the brilliant cut. Subtle variations in the proportions change greatly the way light is reflected and can make the difference between a mediocre and an ideal diamond. It is described by a few key lengths:\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Diagram of a round brilliant cut\" src=\"src/readme/diamond_brilliant_cut.png\"/\u003e\n    \u003cp\u003e\n        \u003ci\u003eDiagram of a round brilliant cut.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n### ASET evaluation\nA common tool to evaluate the quality of the cut of a diamond is the Angular Spectrum Evaluation Tool (ASET). Such an image helps to check the way the diamond gives light back, which depends on the cut quality and the clarity of the gem.\n\nASET images can either be taken with in with an ASET scope, or be computed. Here is what it represents:\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Explanation of ASET\" src=\"src/readme/ASET_meaning.png\"/\u003e\n    \u003cp\u003e\n        \u003ci\u003eThis is what an ASET image represents.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\nOn an ASET image, the blue is the light that comes directly from above, the green from the sides, and the red from between the two. The black parts are parts that don't reflect the light at all.\nThe ASET image of a high quality diamond should:\n- be as bright as possible\n- exhibit a good symmetry\n- have as much red as possible\n- have blue areas that are very distinct from the red and green ones. This contributes to creating good contrasts between bright and dark areas.\n\nTo generate an ASET image, orthographic projection should be used to avoid deformations due to perspective projection, especially when being too close to the gem. This project is accurate enough to generate realistic ASET images in real time. For instance, here are the visualizations of a diamond with an ideal brilliant cut, and their decomposition:\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Arrows visible with ASET\" src=\"src/readme/ASET_arrows_decomposition.png\"/\u003e\n    \u003cp\u003e\n        \u003ci\u003eViewed from top, the arrows of the diamond are visible in the blue component of ASET.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Hearts visible with ASET\" src=\"src/readme/ASET_hearts_decomposition.png\"/\u003e\n    \u003cp\u003e\n        \u003ci\u003eViewed from bottom, the hearts of the diamond are visible in the red component of ASET.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n## Implementation details\n\n### Geometrical optics\nThe behaviour of light rays as they go through the diamond can be described by a few simple rules.\n\n#### Refractive index\nA medium such as diamond is defined by a refractive index n, which is defined as:\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cimg alt=\"Refractive index definition\" src=\"src/readme/formula_refractive_index.png\"/\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e\n            \u003cul\u003e\n                \u003cli\u003ec is the speed of light in void\u003c/li\u003e\n                \u003cli\u003ev the speed of light in the medium\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\nThis is why a refractive index is always greater than 1. I approximated air to have a refractive index of 1.\n\nThe speed of light in the medium is defined as:\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cimg alt=\"Light speed formula\" src=\"src/readme/formula_light_speed.png\"/\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e\n            \u003cul\u003e\n                \u003cli\u003eλ is the wavelength\u003c/li\u003e\n                \u003cli\u003ef is the frequency\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\nThe perceived color is linked to the frequency, which is independent of media. This formula shows that for a given medium, each color has a unique refractive index. This is how a prism turns white light into a visible rainbow.\n\n#### Snell's law\nWhen a incident ray r\u003csub\u003ei\u003c/sub\u003e hits an boundary between two media (for instance a side of the diamond), it is split in two:\n- a part of the ray (r\u003csub\u003er\u003c/sub\u003e) is reflected\n- another part (r\u003csub\u003et\u003c/sub\u003e) is transmitted through the medium\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Illustration of ray splitting\" src=\"src/readme/reflection_refraction.png\" width=\"400px\"/\u003e\n\u003c/div\u003e\n\nHere all angles are considered to be in [0, π/2] and are defined relatively to the local normal of the surface.\nThe reflected ray has the same angle as the incident ray (θ\u003csub\u003ei\u003c/sub\u003e = θ\u003csub\u003er\u003c/sub\u003e).\nThe relationship between θ\u003csub\u003ei\u003c/sub\u003e and θ\u003csub\u003et\u003c/sub\u003e is described by Snell's law:\n\n![Snell's formula](src/readme/formula_snell.png)\n\nThis formula shows that:\n- if the ray is entering a medium with a higher refraction index, the transmitted ray will be closer to the normal than the incident ray;\n- if the ray is entering a medium with a lower refraction index, it is the other way around.\n\n#### Fresnel\nThe intensity of the incident ray is split between the reflected ray and the transmitted ray (assuming there is no loss):\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cimg alt=\"Ray splitting formula\" src=\"src/readme/formula_energy_split.png\"/\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e\n            \u003cul\u003e\n                \u003cli\u003eF is the intensity of the incident ray\u003c/li\u003e\n                \u003cli\u003eF\u003csub\u003er\u003c/sub\u003e is the intensity of the reflected ray\u003c/li\u003e\n                \u003cli\u003eF\u003csub\u003et\u003c/sub\u003e is the intensity of the transmitted ray\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\nFresnel gives us these coefficients:\n\n![Fresnel formula](src/readme/formula_fresnel.png)\n\nThis formula shows that the closer to the normal the incident ray is, the less reflective the surface is. This phenomenon is very natural and can be observed everywhere: if you are by a lake and look at the water at your feet, you clearly see the ground underneath the surface (no reflection due to perpendicular incident ray), however if you look in the distance, you only see the sky reflected by the water surface (high reflection due almost parallel incident ray).\n\n\n#### Total reflection\n\nIf the light ray is entering a medium with a lower refractive index, the transmitted ray will be further away to the surface than the incident ray.\n\nThe critical angle (θ\u003csub\u003ec\u003c/sub\u003e) defines the maximum angle the incident ray can have to create a transmitted ray. Beyond this angle, the entirety of the incident ray is reflected. This is called a total reflection.\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Total reflection illustration\" src=\"src/readme/total_reflection.png\" width=\"400px\"/\u003e\n\u003c/div\u003e\n\nThis angle is simply provided by the Snell formula when θ\u003csub\u003et\u003c/sub\u003e = π/2:\n\n![Formula of the critial angle](src/readme/formula_critical_angle.png)\n\n#### Beer absorption\nNo medium is completely transparent: the light is a partially absorbed by the material it is traveling through. The more opaque the medium, the more light is absorbed. This is described by Beer's law:\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cimg alt=\"Beer's law formula\" src=\"src/readme/formula_beer.png\"/\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e\n            \u003cul\u003e\n                \u003cli\u003eL is the loss of intensity\u003c/li\u003e\n                \u003cli\u003eα is the absorbance of the medium\u003c/li\u003e\n                \u003cli\u003ed is the distance traveled through the medium\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n### Post processing\n\n#### Sparkle effect\nA small sparkle effect is performed. It is essentially a bloom with a bidirectional blur:\n1. rendering to an off-screen texture, at full size\n2. copying this texture into a smaller one (to make the next part cheaper) and extracting the bright parts\n3. blurring the small texture (I don't use a gaussian blur, but simply blur in two directions in one pass to create a sparkle effect)\n4. finally, combining both the full-sized and small texture\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cdiv\u003e\n        \u003cimg src=\"src/readme/bloom_chain.png\"/\u003e\n    \u003c/div\u003e\n    \u003cp\u003e\n        \u003ci\u003eThese are the steps for the sparkle post-processing effect.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n#### Antialiasing\nIn this scene there are 2 types of aliasing:\n- on the edges of the geometry. This one is due to the rasterization.\n- inside the diamond. This one is due to the processing of each fragment which might lead to neighbour fragments having very different colors.\n\nOn my machine, asking for antialiasing with the `antialias` [WebGL flag](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext) only removes the first type of aliasing. This makes me think it uses a kind of MSAA because a SSAA would also remove the fragment aliasing. I am not sure it is part of the WebGL spec, it is probably implementation-dependent.\n\nWhen doing post-processing, I first render a scene to an off-screen texture. Unfortunately, WebGL 1 does not support antialiasing when rendering to a texture. To antialias the scene, I have two options:\n- do a kind of SSAA where I use a texture twice larger than the screen, which I downsize back to screen dimensions later. I cannot do this because it would lead to way too many fragments being processed (each fragment takes a lot of processing power in a ray-tracing application).\n- or apply myself antialiasing as a step of post-processing without upsizing any buffer. This is the approach I chose.\n\nI implemented a simplified FXAA algorithm:\n1. to avoid treating each color channel separately, I first turn the image into greyscale by computing the luminance of each pixel. The luminance is defined as `(0.299 x red) + (0.587 x green) + (0.114 x blue)`, as suggested by W3C [here](https://www.w3.org/TR/AERT/#color-contrast).\n2. I then determine which areas need antialiasing: for each texel, I sample its luminance and the luminance of the 8 closest neighbours.\n3. I then sort the neighbours into 2 categories: the ones that look like the central texel, and the others\n4. I then use this binary categorization to determine the direction if the edge (if there is one): mostly horizontal or vertical. Below is an example of a texel (in red) that is part of an horizontal edge because the difference between (1, 2, 3) and (6, 7, 8) is greater than the difference between (1, 4, 6) and (3, 5, 8).\\\n![Antialiasing explanation](src/readme/antialiasing_direction_horizontal.png)\n5. Finally, I apply a blur in that direction.\n\nThis algorithm provides a good antialiasing in one pass with only 9 texture fetches and cheap computation. To improve it, I would need to sample a larger neighbourhood, but I don't think it is worth it. If you want to go further, [here is a great description of FXAA](http://blog.simonrodriguez.fr/articles/30-07-2016_implementing_fxaa.html).\n\nHere is the result I obtain:\n\n\u003cdiv style=\"text-align:center\"\u003e\n    \u003cimg alt=\"Antialiasing result\" src=\"src/readme/antialiasing_magnified_4x.png\"/\u003e\n    \u003cp\u003e\n        \u003ci\u003eSource image on the left, antialiased image on the right. Notice how most edges are antialiased, while still preserving sharp details such as dots and vertical/horizontal/diagonal lines.\u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n## Other approaches\nThis ray tracing project is quite processing-heavy for the GPU because for each fragment and each ray rebound, we have to check intersection with all facets of the gem. For a typical diamond, it is 89 intersections. (In reality it is a bit less than this because the shader tries to skip facets that are behind.) The overall complexity is roughly FRAGMENTS_COUNTxREBOUNDS_COUNTxFACETS_COUNTS.\n\nAnother approach would be to first generate a cube map of the diamond sides seen from the center of the gem, and then sample this texture directly in the direction of the ray. This way, the complexity is only FRAGMENTS_COUNTxREBOUNDS_COUNT. However, this leads to 2 issues:\n- first, the precision is dependent of the resolution of the cube map\n- then, this approach is by essence not accurate, because the diamond is not a sphere, so its projection would be deformed.  An adjustment mechanism would be necessary, which would cost more texture fetches. \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiellardj%2Fdiamond-webgl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiellardj%2Fdiamond-webgl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiellardj%2Fdiamond-webgl/lists"}