├── LICENSE ├── README.md ├── complexpbr ├── __init__.py ├── brdf_lut_calculator.py ├── ibl_f.frag ├── ibl_v.vert ├── min_f.frag ├── min_v.vert ├── output_brdf_lut.png ├── sample_output_brdf_lut.png └── simplepbr_license.txt ├── pyproject.toml └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Logan Bier. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # panda3d-complexpbr 2 | complexpbr is an IBL (Image-Based Lighting) rendering module which supports real-time reflections and post-processing effects in Panda3D. complexpbr supports realtime environment reflections for BSDF materials (the industry standard). Your machine must support GLSL version 430 or higher. Sample screenshots and minimum usage examples below. 3 | 4 | Featuring support for vertex displacement mapping, SSAO (Screen Space Ambient Occlusion), HSV color correction, Bloom, and Sobel based antialiasing in a screenspace kernel shader, which approximates temporal antialiasing. complexpbr.screenspace_init() automatically enables the AA, SSAO, and HSV color correction. To use the vertex displacement mapping, provide your displacement map as a shader input to your respective model node -- example below in the Usage section. 5 | 6 | By default, the environment reflections dynamically track the camera view. You may set a custom position with the 'env_cam_pos' apply_shader() input variable to IE fix the view to a skybox somewhere on the scene graph. This env_cam_pos variable can be updated live afterwards by setting base.env_cam_pos = Vec3(some_pos). The option to disable or re-enable dynamic reflections is available. 7 | 8 | The goal of this project is to provide extremely easy to use scene shaders to expose the full functionality of Panda3D rendering, including interoperation with CommonFilters and setting shaders on a per-node basis. 9 | 10 | ![complexpbr_screen_2](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/a8a7d360-6b52-4fa8-91f8-31f052421043) 11 | 12 | ![complexpbr_reflections_2](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/d6d3867a-6dfb-4512-8a79-de80bf35bc26) 13 | 14 | 10/30/23 Project Naer ([Project Naer complexpbr](https://github.com/rayanalysis/project-naer-complexpbr)) 15 | 16 | ![beige_screen_2](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/89428c83-5e6c-42d3-b30d-372e6ed8bd05) 17 | 18 | ![silver_screen_1](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/478329ca-ba7c-4adf-b3a1-ae730bf54cc1) 19 | 20 | 7/6/23 Lumberyard Bistro ([Amazon Lumberyard Bistro | NVIDIA Developer](https://developer.nvidia.com/orca/amazon-lumberyard-bistro)) 21 | 22 | ![bistro_exterior_11](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/0cd476bb-d313-41f4-b5ea-d793589711e4) 23 | 24 | ![bistro_interior_5](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/ad75afa7-e1ef-41ea-aae9-4bb1cea54135) 25 | 26 | ![bistro_exterior_10](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/79df6bd6-14d8-4d19-ae5f-45c3418a7607) 27 | 28 | 6/1/23 Sponza ([Intel GPU Research Samples](https://www.intel.com/content/www/us/en/developer/topic-technology/graphics-research/samples.html)) 29 | 30 | ![sponza_screen_1-Thu-Jun-01-08-28-36-2023-104](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/4e40e642-f363-4328-bf99-4056f449e28a) 31 | 32 | ## Minimal Usage: 33 | ```python 34 | from direct.showbase.ShowBase import ShowBase 35 | from panda3d.core import * 36 | import complexpbr 37 | 38 | class main(ShowBase): 39 | def __init__(self): 40 | super().__init__() 41 | 42 | complexpbr.apply_shader(self.render) 43 | # complexpbr.screenspace_init() # optional, starts the screenspace effects 44 | 45 | app = main() 46 | app.run() 47 | ``` 48 | 49 | ## Expanded Usage: 50 | ```python 51 | from direct.showbase.ShowBase import ShowBase 52 | import complexpbr 53 | 54 | class main(ShowBase): 55 | def __init__(self): 56 | super().__init__() 57 | 58 | # apply a scene shader with PBR IBL 59 | # node can be base.render or any model node, intensity is the desired AO 60 | # (ambient occlusion reflection) intensity (float, 0.0 to 1.0) 61 | # you may wish to define a specific position in your scene where the 62 | # cube map is rendered from, to IE have multiple skyboxes preloaded 63 | # somewhere on the scene graph and have their reflections map to your 64 | # models -- to achieve this, set env_cam_pos=Vec3(your_pos) 65 | # you may set base.env_cam_pos after this, and it will update in realtime 66 | # env_res is the cube map resolution, can only be set once upon first call 67 | 68 | complexpbr.apply_shader(self.render) 69 | # complexpbr.screenspace_init() # optional, starts the screenspace effects 70 | 71 | # apply_shader() with optional inputs 72 | # complexpbr.apply_shader(self.render, intensity=0.9, env_cam_pos=None, env_res=256, lut_fill=[1.0,0.0,0.0], custom_dir='shaders/') 73 | 74 | # initialize complexpbr's screenspace effects (SSAO, SSR, AA, HSV color correction) 75 | # this replaces CommonFilters functionality 76 | complexpbr.screenspace_init() 77 | 78 | # make the cubemap rendering static (performance boost) 79 | complexpbr.set_cubebuff_inactive() 80 | 81 | # make the cubemap rendering dynamic (this is the default state) 82 | complexpbr.set_cubebuff_active() 83 | 84 | # adjustment factors for the cubemap rendering height (as of version 0.5.5) 85 | base.complexpbr_map_z = 2.1 # manual additive/subtractive factor on the rendering height 86 | # automatically adjust the environment reflections such that they 87 | # update relative to the base.cam position during movement 88 | base.complexpbr_z_tracking = True # defaults to False 89 | 90 | # clean up the shader files (not recommended for distributable builds) 91 | # complexpbr.remove_shader_files() 92 | 93 | # example of how to apply hardware skinning 94 | fp_character = actor_data.player_character # this is an Actor() model 95 | fp_character.reparent_to(self.render) 96 | fp_character.set_scale(1) 97 | # set hardware skinning for the Actor() 98 | complexpbr.skin(fp_character) 99 | 100 | # example of how to use the vertex displacement mapping 101 | wood_sphere_3 = loader.load_model('assets/models/wood_sphere_3.gltf') 102 | wood_sphere_3.reparent_to(base.render) 103 | wood_sphere_3.set_pos(0,0,1) 104 | dis_tex = Texture() 105 | dis_tex.read('assets/textures/WoodFloor057_2K-PNG/WoodFloor057_2K_Displacement.png') 106 | wood_sphere_3.set_shader_input('displacement_map', dis_tex) 107 | wood_sphere_3.set_shader_input('displacement_scale', 0.1) 108 | 109 | # example of how to use the shader composition functionality 110 | complexpbr.apply_shader(test_sphere) # example sphere model 111 | # call the append_shader() function, you may modify just 1 or all of the 4 shader files 112 | custom_body_mod = 'float default_noise(vec2 n)\n{\nfloat n2 = fract(sin(dot(n.xy,vec2(11.78,77.443)))*44372.7263);\nreturn n2;\n}' 113 | custom_main_mod = 'o_color += default_noise(vec2(2.3,3.3));' 114 | custom_vert_body_mod = 'float default_noise(vec2 n)\n{\nreturn n[0];\n}' 115 | custom_vert_main_mod = 'float whatever = default_noise(vec2(2.3,3.3));' 116 | complexpbr.append_shader(test_sphere, custom_body_mod, custom_main_mod, custom_vert_body_mod, custom_vert_main_mod) 117 | 118 | # example of how to turn on Global Illumination (GI) 119 | self.main_bridge_tunnel.set_shader_input('shadow_boost', 0.3) # increases intrinsic brightness of a tunnel model 120 | 121 | # example of how to specify a custom shader directory (you must have created the folder first) 122 | complexpbr.apply_shader(self.render, custom_dir='shaders/') 123 | 124 | # example of how to set up bloom -- complexpbr.screenspace_init() must have been called first 125 | screen_quad = base.screen_quad 126 | 127 | screen_quad.set_shader_input("bloom_intensity", 0.25) 128 | screen_quad.set_shader_input("bloom_threshold", 0.3) 129 | screen_quad.set_shader_input("bloom_blur_width", 20) 130 | screen_quad.set_shader_input("bloom_samples", 4) 131 | 132 | # example of how to customize SSR 133 | screen_quad.set_shader_input('ssr_intensity', 2.0) 134 | screen_quad.set_shader_input('reflection_threshold', 1.6) # subtracts from intensity 135 | screen_quad.set_shader_input('ssr_step', 5.75) # helps determine reflect height 136 | screen_quad.set_shader_input('screen_ray_factor', 0.06) # detail factor 137 | screen_quad.set_shader_input('ssr_samples', 1) # determines total steps 138 | screen_quad.set_shader_input('ssr_depth_cutoff', 0.52) 139 | screen_quad.set_shader_input('ssr_depth_min', 0.49) 140 | 141 | # example of how to customize SSAO 142 | screen_quad.set_shader_input("ssao_samples", 32) # ssao_samples defaults to 6 143 | 144 | # example of how to HSV adjust the final image 145 | screen_quad.set_shader_input("hsv_g", 1.3) # hsv_g (saturation factor) defaults to 1.0 146 | screen_quad.set_shader_input("final_brightness", 1.3) # the final multiplicative brightness in screenspace 147 | 148 | # example of how to modify the specular contribution 149 | self.render.set_shader_input("specular_factor", 10.0) # the specular_factor defaults to 1.0 150 | 151 | # example of how to directly fill your BRDF LUT texture instead of providing one in your game folder 152 | complexpbr.apply_shader(base.render, 1.0, env_res=1024, lut_fill=[1.0,0.0,0.0]) # lut_fill=[red, green, blue] 153 | 154 | # if complexpbr.screenspace_init() has not been called, you may use CommonFilters 155 | # scene_filters = CommonFilters(base.win, base.cam) 156 | # scene_filters.set_bloom(size='medium') 157 | # scene_filters.set_exposure_adjust(1.1) 158 | # scene_filters.set_gamma_adjust(1.1) 159 | # scene_filters.set_blur_sharpen(0.9) 160 | ``` 161 | ## Installing with PyPI: 162 | ```bash 163 | pip install panda3d-complexpbr 164 | ``` 165 | 166 | ## Building: 167 | The module may be built using build. 168 | ```bash 169 | python -m build 170 | ``` 171 | ```bash 172 | pip install 'path/to/panda3d-complexpbr.whl' 173 | ``` 174 | 175 | ## Relase Notes: 176 | As of version 0.5.2, complexpbr will default to a dummy BRDF LUT which it creates on the fly. complexpbr will remind you that you may create a custom BRDF LUT with the provided 'brdf_lut_calculator.py' script or copy the sample one provided. This feature is automatic, so if you provide the output_brdf_lut.png file in your program directory, it will default to that .png image ignoring the lut_fill input. The sample 'output_brdf_lut.png' and the creation script can be found in the panda3d-complexpbr git repo. For advanced users there is an option to set the LUT image RGB fill values via apply_shader(lut_fill=[r,g,b]) . See Usage section for an example of lut_fill. 177 | 178 | As of version 0.5.3, hardware skinning support is provided via complexpbr.skin(your_actor) for models with skeletal animations. See Usage section for an example of hardware skinning. 179 | 180 | As of version 0.5.4, panda3d-complexpbr may be considered mature and ready for production use. complexpbr will endeavor to continue supporting CommonFilters, which is still receiving some contemporary updates. complexpbr is still open to pull requests, feature requests, and so forth to continue expanding the filtering capabilities of screenspace_init() within reason. 181 | 182 | As of version 0.5.6, dynamic environmental Z-tracking functionality has been expanded, and a function has been added to optionally clean up the created shader files. 183 | 184 | As of version 0.5.7, shader composition functionality has been expanded. Using "append_shader()", you may provide custom model-level fragment shader functions and modifications to the main loop. 185 | 186 | As of version 0.5.8, an approximation of Global Illumination (GI) is provided via the "shadow_boost" (float) shader input. This feature allows node level objects to partially self-illuminate to remain visible all around even when shadowed. Secondly, you may now specify a custom directory for complexpbr-generated shader files, IE complexpbr.apply_shader(..., custom_dir='shaders/') . Thirdly, a default lighting setup is provided as an option to apply_shader(..., default_lighting=True) . Lastly, the node-level vertex shader is now available for modification using "append_shader()". 187 | 188 | As of version 0.6.0, new shader inputs have been made available which increase the usability of scene color adjustment, brightness, and SSR. The remove_shader_files() function has been enhanced, and complexpbr now automatically keeps the shader directory clean while regenerating shader files. 189 | 190 | ## Requirements: 191 | 192 | - panda3d 193 | 194 | 195 | 6/1/23 Sponza ([Intel GPU Research Samples](https://www.intel.com/content/www/us/en/developer/topic-technology/graphics-research/samples.html)) 196 | 197 | ![sponza_screen_1-Thu-Jun-01-08-16-18-2023-26](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/5d6a603f-9da1-49a1-affb-042658f343ed) 198 | 199 | ![sponza_screen_1-Thu-Jun-01-08-17-47-2023-15](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/7fffc0f4-75b3-476b-a328-127d231b9171) 200 | 201 | ![sponza_screen_1-Thu-Jun-01-06-02-59-2023-22](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/913a5263-7750-47c1-b4c4-9f7dace84d6e) 202 | 203 | ![sponza_screen_2-Thu-Jun-01-05-56-06-2023-591](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/b5055164-3235-48fa-86a7-0f6e3222b903) 204 | 205 | ![sponza_screen_1-Fri-Jun-02-08-54-07-2023-428](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/7a5c3f1f-1bb9-4dec-9e92-92dc52f77f29) 206 | 207 | ![sponza_screen_1-Thu-Jun-01-05-39-29-2023-111](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/f366077b-b6d6-4c4a-896d-f456a06a53d1) 208 | 209 | ![sponza_screen_3-Thu-Jun-01-05-56-32-2023-657](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/23014163-4c7d-4a4d-9f6a-4b874ea364f2) 210 | 211 | ![sponza_screen_1-Thu-Jun-01-05-55-48-2023-540](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/ef2a71c3-169b-428c-a1a9-378c8906c644) 212 | 213 | ![sponza_screen_2-Thu-Jun-01-08-23-21-2023-1500](https://github.com/rayanalysis/panda3d-complexpbr/assets/3117958/9fbe97e8-d350-480e-bbca-9ef2d5a92b24) 214 | 215 | ![complexpbr_daytime_screen_1](https://user-images.githubusercontent.com/3117958/235431990-d8ea4364-2526-4739-963c-dce122815f2a.png) 216 | 217 | ![complexpbr_daytime_screen_2](https://user-images.githubusercontent.com/3117958/235431991-d1f40263-f442-46ed-98a7-056e6186c148.png) 218 | 219 | ![complexpbr_daytime_screen_3](https://user-images.githubusercontent.com/3117958/235432001-07091c4c-9bc1-4385-81d2-9d50c6fd61b9.png) 220 | 221 | ![complexpbr_screen_2](https://user-images.githubusercontent.com/3117958/234434099-c6add6ce-578c-4c03-a142-adcf955c14fc.png) 222 | 223 | ![complexpbr_screen_3](https://user-images.githubusercontent.com/3117958/234434136-9418663d-2304-451b-a318-d3cb4d945a8b.png) 224 | 225 | Vertex Displacement Mapping: 226 | 227 | ![complexpbr_screen_4](https://user-images.githubusercontent.com/3117958/234434178-1e14fa32-2be4-4072-ae15-9ee235d8c036.png) 228 | 229 | -------------------------------------------------------------------------------- /complexpbr/__init__.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | from pathlib import Path 3 | from panda3d.core import Shader, ShaderAttrib, TextureStage, TexGenAttrib, NodePath 4 | from panda3d.core import Texture, ATS_none, Vec3, Vec4, AuxBitplaneAttrib, PNMImage, AntialiasAttrib 5 | from panda3d.core import load_prc_file_data 6 | from direct.stdpy import threading2 7 | from direct.filter.FilterManager import FilterManager 8 | from panda3d.core import PointLight, Spotlight, AmbientLight, PerspectiveLens 9 | 10 | 11 | complexpbr_init = True 12 | shader_dir = os.path.join(os.path.dirname(__file__), '') 13 | 14 | def set_cubebuff_inactive(): 15 | def set_thread(): 16 | time.sleep(.5) 17 | base.cube_buffer.set_active(0) 18 | return threading2._start_new_thread(set_thread,()) 19 | 20 | def set_cubebuff_active(): 21 | def set_thread(): 22 | time.sleep(.5) 23 | base.cube_buffer.set_active(1) 24 | return threading2._start_new_thread(set_thread,()) 25 | 26 | def rotate_cubemap(task): 27 | base.complexpbr_map.set_h(base.render,base.cam.get_h(base.render)) 28 | base.complexpbr_map.set_p(base.render,base.cam.get_p(base.render) + 90) 29 | cam_pos = base.cam.get_pos(base.render) 30 | if base.env_cam_pos is not None: 31 | base.complexpbr_map.set_pos(base.env_cam_pos[0],base.env_cam_pos[1],base.env_cam_pos[2]+base.complexpbr_map_z) 32 | else: 33 | base.complexpbr_map.set_pos(cam_pos[0],cam_pos[1],cam_pos[2]+base.complexpbr_map_z) 34 | 35 | if base.complexpbr_z_tracking: 36 | cam_relative_pos = base.cam.get_pos(base.render) 37 | cam_relative_pos[2] = cam_relative_pos[2]-(2 * cam_relative_pos[2]) 38 | base.env_cam_pos = cam_relative_pos 39 | 40 | base.cam_pos = cam_pos 41 | 42 | return task.cont 43 | 44 | def screenspace_init(): 45 | with open(os.path.join(shader_dir, 'min_v.vert')) as shaderfile: 46 | shaderstr = shaderfile.read() 47 | remove_ss_files() 48 | out_v = open(base.complexpbr_custom_dir + 'min_v.vert', 'w') 49 | out_v.write(shaderstr) 50 | out_v.close() 51 | 52 | with open(os.path.join(shader_dir, 'min_f.frag')) as shaderfile: 53 | shaderstr = shaderfile.read() 54 | out_v = open(base.complexpbr_custom_dir + 'min_f.frag', 'w') 55 | out_v.write(shaderstr) 56 | out_v.close() 57 | 58 | auxbits = 0 59 | auxbits |= AuxBitplaneAttrib.ABOAuxNormal 60 | 61 | filter_manager = FilterManager(base.win, base.cam) 62 | scene_tex = Texture("scene_tex") 63 | depth_tex = Texture("depth_tex") 64 | normal_tex = Texture("normal_tex") 65 | all_tex = {} 66 | screen_quad = filter_manager.render_scene_into(colortex=scene_tex, 67 | auxbits=auxbits, 68 | depthtex=depth_tex, 69 | auxtex=normal_tex, 70 | textures=all_tex) 71 | Texture.set_textures_power_2(ATS_none) 72 | window_size = [base.win.get_x_size(),base.win.get_y_size()] 73 | camera_near = base.camLens.get_near() 74 | camera_far = base.camLens.get_far() 75 | 76 | bloom_intensity = 0.0 # default Bloom to 0.0 / off 77 | bloom_blur_width = 10 78 | bloom_samples = 6 79 | bloom_threshold = 0.7 80 | ssr_intensity = 0.5 81 | ssr_step = 4.0 82 | ssr_fresnel_pow = 3.0 83 | ssr_samples = 0 # default SSR to 0.0 / off 84 | screen_ray_factor = 0.1 85 | ssr_depth_cutoff = 0.6 86 | ssr_depth_min = 0.5 87 | ssao_samples = 6 88 | reflection_threshold = 0.1 89 | hsv_r = 1.0 90 | hsv_g = 1.0 91 | hsv_b = 1.0 92 | final_brightness = 1.0 93 | 94 | vert = base.complexpbr_custom_dir + 'min_v.vert' 95 | frag = base.complexpbr_custom_dir + 'min_f.frag' 96 | shader = Shader.load(Shader.SL_GLSL, vert, frag) 97 | screen_quad.set_shader(shader) 98 | screen_quad.set_shader_input("window_size", window_size) 99 | screen_quad.set_shader_input("scene_tex", scene_tex) 100 | screen_quad.set_shader_input("depth_tex", depth_tex) 101 | screen_quad.set_shader_input("normal_tex", normal_tex) 102 | screen_quad.set_shader_input("cameraNear", camera_near) 103 | screen_quad.set_shader_input("cameraFar", camera_far) 104 | screen_quad.set_shader_input("bloom_intensity", bloom_intensity) 105 | screen_quad.set_shader_input("bloom_threshold", bloom_threshold) 106 | screen_quad.set_shader_input("bloom_blur_width", bloom_blur_width) 107 | screen_quad.set_shader_input("bloom_samples", bloom_samples) 108 | screen_quad.set_shader_input("ssr_intensity", ssr_intensity) 109 | screen_quad.set_shader_input("ssr_step", ssr_step) 110 | screen_quad.set_shader_input("ssr_fresnel_pow", ssr_fresnel_pow) 111 | screen_quad.set_shader_input("ssr_samples", ssr_samples) 112 | screen_quad.set_shader_input("screen_ray_factor", screen_ray_factor) 113 | screen_quad.set_shader_input("ssr_depth_cutoff", ssr_depth_cutoff) 114 | screen_quad.set_shader_input("ssr_depth_min", ssr_depth_min) 115 | screen_quad.set_shader_input("ssao_samples", ssao_samples) 116 | screen_quad.set_shader_input("reflection_threshold", reflection_threshold) 117 | screen_quad.set_shader_input("hsv_r", hsv_r) 118 | screen_quad.set_shader_input("hsv_g", hsv_g) # HSV saturation adjustment 119 | screen_quad.set_shader_input("hsv_b", hsv_b) 120 | screen_quad.set_shader_input("final_brightness", final_brightness) 121 | 122 | base.screen_quad = screen_quad 123 | base.render.set_antialias(AntialiasAttrib.MMultisample) 124 | 125 | def complexpbr_rig_init(node, intensity, lut_fill, shadow_boost): 126 | load_prc_file_data('', 'hardware-animated-vertices #t') 127 | load_prc_file_data('', 'framebuffer-srgb #t') 128 | load_prc_file_data('', 'framebuffer-depth-32 1') 129 | load_prc_file_data('', 'gl-depth-zero-to-one #f') 130 | load_prc_file_data('', 'gl-cube-map-seamless 1') 131 | load_prc_file_data('', 'framebuffer-multisample 1') 132 | load_prc_file_data('', 'multisamples 4') 133 | 134 | brdf_lut_tex = Texture("complexpbr_lut") 135 | brdf_lut_image = PNMImage() 136 | brdf_lut_image.clear(x_size=base.win.get_x_size(),y_size=base.win.get_y_size(),num_channels=4) 137 | brdf_lut_image.fill(red=lut_fill[0],green=lut_fill[1],blue=lut_fill[2]) 138 | # brdf_lut_image.alpha_fill(1.0) 139 | brdf_lut_tex.load(brdf_lut_image) 140 | brdf_lut_ext_tex = Path('output_brdf_lut.png') 141 | 142 | if brdf_lut_ext_tex.is_file(): 143 | brdf_lut_tex = loader.load_texture('output_brdf_lut.png') 144 | else: 145 | brdf_lut_tex.load(brdf_lut_image) 146 | 147 | shader_cam_pos = Vec3(base.cam.get_pos(base.render)) 148 | displacement_scale_val = 0.0 # default to 0 to avoid having to check for displacement 149 | displacement_map = Texture() 150 | specular_factor = 1.0 151 | 152 | node.set_shader(base.complexpbr_shader) 153 | 154 | node.set_tex_gen(TextureStage.get_default(), TexGenAttrib.MWorldCubeMap) 155 | node.set_shader_input("cubemaptex", base.cube_buffer.get_texture()) 156 | node.set_shader_input("brdfLUT", brdf_lut_tex) 157 | node.set_shader_input("ao", intensity) 158 | node.set_shader_input("shadow_boost", shadow_boost) 159 | node.set_shader_input("displacement_scale", displacement_scale_val) 160 | node.set_shader_input("displacement_map", displacement_map) 161 | node.set_shader_input("specular_factor", specular_factor) 162 | 163 | base.task_mgr.add(rotate_cubemap) 164 | 165 | base.complexpbr_skin_attrib = ShaderAttrib.make(base.complexpbr_shader) 166 | base.complexpbr_skin_attrib = base.complexpbr_skin_attrib.set_flag(ShaderAttrib.F_hardware_skinning, True) 167 | 168 | def skin(node): 169 | node.set_attrib(base.complexpbr_skin_attrib) 170 | 171 | def apply_shader(node=None,intensity=1.0,env_cam_pos=None,env_res=256,lut_fill=[1.0,0.0,0.0],complexpbr_z_tracking=False, 172 | custom_dir='',default_lighting=False,shadow_boost=0.0): 173 | global complexpbr_init 174 | 175 | base.complexpbr_custom_dir = custom_dir 176 | 177 | with open(os.path.join(shader_dir, 'ibl_v.vert')) as shaderfile: 178 | shaderstr = shaderfile.read() 179 | remove_ibl_files() 180 | out_v = open(base.complexpbr_custom_dir + 'ibl_v.vert', 'w') 181 | out_v.write(shaderstr) 182 | out_v.close() 183 | 184 | with open(os.path.join(shader_dir, 'ibl_f.frag')) as shaderfile: 185 | shaderstr = shaderfile.read() 186 | out_v = open(base.complexpbr_custom_dir + 'ibl_f.frag', 'w') 187 | out_v.write(shaderstr) 188 | out_v.close() 189 | 190 | if complexpbr_init: 191 | complexpbr_init = False 192 | 193 | vert = base.complexpbr_custom_dir + 'ibl_v.vert' 194 | frag = base.complexpbr_custom_dir + 'ibl_f.frag' 195 | 196 | base.complexpbr_shader = Shader.load(Shader.SL_GLSL, vert, frag) 197 | 198 | base.complexpbr_map = NodePath('cuberig') 199 | base.cube_buffer = base.win.make_cube_map('cubemap', env_res, base.complexpbr_map) 200 | base.complexpbr_map.reparent_to(base.render) 201 | base.complexpbr_map_z = 0 202 | base.env_cam_pos = env_cam_pos 203 | base.complexpbr_z_tracking = complexpbr_z_tracking 204 | base.complexpbr_append_shader_count = 0 205 | 206 | complexpbr_rig_init(node, intensity=intensity, lut_fill=lut_fill, shadow_boost=shadow_boost) 207 | 208 | if default_lighting: 209 | try: 210 | complexpbr_default_lighting() 211 | except: 212 | print('complexpbr message: Default lighting setup failed.') 213 | 214 | def append_shader(node=None,frag_body_mod='',frag_main_mod='',vert_body_mod='',vert_main_mod='',intensity=1.0,env_cam_pos=None, 215 | env_res=256,lut_fill=[1.0,0.0,0.0],complexpbr_z_tracking=False,shadow_boost=0.0): 216 | 217 | vert = base.complexpbr_custom_dir + "ibl_v.vert" 218 | frag = base.complexpbr_custom_dir + "ibl_f.frag" 219 | 220 | extant_append_shaders = [] 221 | if base.complexpbr_custom_dir == '': 222 | local_shader_dir = os.listdir() 223 | else: 224 | local_shader_dir = os.listdir(base.complexpbr_custom_dir) 225 | 226 | for item in local_shader_dir: 227 | if 'ibl_f_' in item: 228 | item = item.strip('ibl_f_').strip('.frag') 229 | extant_append_shaders.append(int(item)) 230 | 231 | extant_append_shaders = sorted(extant_append_shaders) 232 | 233 | try: 234 | top_extant_shader_val = extant_append_shaders.pop() 235 | base.complexpbr_append_shader_count = top_extant_shader_val + 1 236 | except: 237 | base.complexpbr_append_shader_count = 1 238 | 239 | append_shader_file = '' 240 | input_body_reached = False 241 | main_reached = False 242 | end_reached = False 243 | 244 | input_frag_body_mod = frag_body_mod 245 | input_frag_main_mod = frag_main_mod 246 | 247 | input_vert_body_mod = vert_body_mod 248 | input_vert_main_mod = vert_main_mod 249 | 250 | # fragment modification begins 251 | if input_frag_body_mod != '' or input_frag_main_mod != '': 252 | with open(frag) as shaderfile: 253 | shaderstr = shaderfile.read() 254 | for line in shaderstr.split('\n'): 255 | append_shader_file += (line + '\n') 256 | if 'uniform float shadow_boost' in line: 257 | break 258 | 259 | append_shader_file += (input_frag_body_mod + '\n') 260 | # print(append_shader_file) 261 | 262 | for line in shaderstr.split('\n'): 263 | if 'const float LIGHT_CUTOFF' in line: 264 | # print(line) 265 | # print('input body reached') 266 | input_body_reached = True 267 | 268 | if 'void main' in line: 269 | main_reached = True 270 | 271 | if input_body_reached and not main_reached: 272 | append_shader_file += (line + '\n') 273 | 274 | main_reached = False 275 | 276 | for line in shaderstr.split('\n'): 277 | if 'void main' in line: 278 | main_reached = True 279 | 280 | if 'outputNormal = texture(p3d_Texture2, v_texcoord).rgb * 0.5 + vec3(0.5);' in line: 281 | end_reached = True 282 | # print(line) 283 | # print('end reached') 284 | 285 | if main_reached and not end_reached: 286 | append_shader_file += (line + '\n') 287 | 288 | append_shader_file += (input_frag_main_mod + '\n') 289 | 290 | end_reached = False 291 | 292 | for line in shaderstr.split('\n'): 293 | if 'outputNormal = texture(p3d_Texture2, v_texcoord).rgb * 0.5 + vec3(0.5);' in line: 294 | end_reached = True 295 | # print('end reached') 296 | 297 | if end_reached: 298 | append_shader_file += (line + '\n') 299 | 300 | out_v = open(base.complexpbr_custom_dir + 'ibl_f_' + str(base.complexpbr_append_shader_count) + '.frag', 'w') 301 | 302 | for line in append_shader_file.split('\n'): 303 | out_v.write(line) 304 | out_v.write('\n') 305 | 306 | out_v.close() 307 | 308 | frag = base.complexpbr_custom_dir + 'ibl_f_' + str(base.complexpbr_append_shader_count) + '.frag' 309 | 310 | # vertex modification begins 311 | append_shader_file = '' 312 | input_body_reached = False 313 | main_reached = False 314 | end_reached = False 315 | 316 | if input_vert_body_mod != '' or input_vert_main_mod != '': 317 | extant_append_shaders = [] 318 | 319 | for item in local_shader_dir: 320 | if 'ibl_v_' in item: 321 | item = item.strip('ibl_v_').strip('.vert') 322 | extant_append_shaders.append(int(item)) 323 | 324 | extant_append_shaders = sorted(extant_append_shaders) 325 | 326 | try: 327 | top_extant_shader_val = extant_append_shaders.pop() 328 | base.complexpbr_append_shader_count = top_extant_shader_val + 1 329 | except: 330 | base.complexpbr_append_shader_count = 1 331 | 332 | with open(vert) as shaderfile: 333 | shaderstr = shaderfile.read() 334 | for line in shaderstr.split('\n'): 335 | append_shader_file += (line + '\n') 336 | if 'uniform float displacement_scale;' in line: 337 | break 338 | 339 | append_shader_file += (input_vert_body_mod + '\n') 340 | # print(append_shader_file) 341 | 342 | for line in shaderstr.split('\n'): 343 | if 'uniform struct p3d_LightSourceParameters {' in line: 344 | # print(line) 345 | # print('input body reached') 346 | input_body_reached = True 347 | 348 | if 'void main' in line: 349 | main_reached = True 350 | 351 | if input_body_reached and not main_reached: 352 | append_shader_file += (line + '\n') 353 | 354 | main_reached = False 355 | 356 | for line in shaderstr.split('\n'): 357 | if 'void main' in line: 358 | main_reached = True 359 | 360 | if 'gl_Position = p3d_ProjectionMatrix * model_view_displaced_vertex;' in line: 361 | end_reached = True 362 | # print(line) 363 | # print('end reached') 364 | 365 | if main_reached and not end_reached: 366 | append_shader_file += (line + '\n') 367 | 368 | append_shader_file += (input_vert_main_mod + '\n') 369 | 370 | end_reached = False 371 | 372 | for line in shaderstr.split('\n'): 373 | if 'gl_Position = p3d_ProjectionMatrix * model_view_displaced_vertex;' in line: 374 | end_reached = True 375 | # print('end reached') 376 | 377 | if end_reached: 378 | append_shader_file += (line + '\n') 379 | 380 | out_v = open(base.complexpbr_custom_dir + 'ibl_v_' + str(base.complexpbr_append_shader_count) + '.vert', 'w') 381 | 382 | for line in append_shader_file.split('\n'): 383 | out_v.write(line) 384 | out_v.write('\n') 385 | 386 | out_v.close() 387 | 388 | vert = base.complexpbr_custom_dir + 'ibl_v_' + str(base.complexpbr_append_shader_count) + '.vert' 389 | 390 | append_shader = Shader.load(Shader.SL_GLSL, vert, frag) 391 | node.set_shader(append_shader) 392 | 393 | def create_locate_base_dir(): 394 | if base.complexpbr_custom_dir == '': 395 | local_shader_dir = os.listdir() 396 | else: 397 | local_shader_dir = os.listdir(base.complexpbr_custom_dir) 398 | 399 | return local_shader_dir 400 | 401 | def remove_shader_files(): 402 | local_shader_dir = create_locate_base_dir() 403 | shader_file_strings = ['ibl_f', 'ibl_v', 'min_f', 'min_v'] 404 | 405 | for item in local_shader_dir: 406 | for fs in shader_file_strings: 407 | if fs in item: 408 | os.remove(base.complexpbr_custom_dir + item) 409 | 410 | def remove_ibl_files(): 411 | local_shader_dir = create_locate_base_dir() 412 | shader_file_strings = ['ibl_f', 'ibl_v'] 413 | 414 | for item in local_shader_dir: 415 | for fs in shader_file_strings: 416 | if fs in item: 417 | os.remove(base.complexpbr_custom_dir + item) 418 | 419 | def remove_ss_files(): 420 | local_shader_dir = create_locate_base_dir() 421 | shader_file_strings = ['min_f', 'min_v'] 422 | 423 | for item in local_shader_dir: 424 | for fs in shader_file_strings: 425 | if fs in item: 426 | os.remove(base.complexpbr_custom_dir + item) 427 | 428 | def complexpbr_default_lighting(): 429 | amb_light = AmbientLight('amb_light') 430 | amb_light.set_color(Vec4(Vec3(1),1)) 431 | amb_light_node = base.render.attach_new_node(amb_light) 432 | base.render.set_light(amb_light_node) 433 | 434 | slight_1 = Spotlight('slight_1') 435 | slight_1.set_color(Vec4(Vec3(5),1)) 436 | slight_1.set_shadow_caster(True, 8192, 8192) 437 | # slight_1.set_attenuation((0.5,0,0.000005)) 438 | lens = PerspectiveLens() 439 | slight_1.set_lens(lens) 440 | slight_1.get_lens().set_fov(120) 441 | slight_1_node = base.render.attach_new_node(slight_1) 442 | slight_1_node.set_pos(50, 50, 90) 443 | slight_1_node.look_at(0,0,0.5) 444 | base.render.set_light(slight_1_node) 445 | 446 | env_light_1 = PointLight('env_light_1') 447 | env_light_1.set_color(Vec4(Vec3(1),1)) 448 | env_light_1 = base.render.attach_new_node(env_light_1) 449 | env_light_1.set_pos(0,0,-1) 450 | 451 | class Shaders: 452 | def __init__(self): 453 | return 454 | -------------------------------------------------------------------------------- /complexpbr/brdf_lut_calculator.py: -------------------------------------------------------------------------------- 1 | # This script loads an input texture named 'input_texture.png', applies the BRDF calculations to it, 2 | # and saves the result as 'output_brdf_lut.png'. 3 | 4 | import numpy as np 5 | import OpenGL # this is PyOpenGL 6 | # if the pip install of PyOpenGL isn't working, install from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl 7 | from OpenGL.GL import * 8 | from OpenGL.GLUT import * 9 | from OpenGL.GLU import * 10 | from PIL import Image 11 | 12 | def compile_shader(source, shader_type): 13 | shader = glCreateShader(shader_type) 14 | glShaderSource(shader, source) 15 | glCompileShader(shader) 16 | print('Compiling BRDF LUT shader...') 17 | return shader 18 | 19 | def create_program(vertex_shader, fragment_shader): 20 | program = glCreateProgram() 21 | glAttachShader(program, vertex_shader) 22 | glAttachShader(program, fragment_shader) 23 | glLinkProgram(program) 24 | print('Creating BRDF LUT program...') 25 | return program 26 | 27 | vertex_shader_src = ''' 28 | #version 330 core 29 | layout (location = 0) in vec3 aPos; 30 | layout (location = 1) in vec2 aTexCoord; 31 | 32 | out vec2 TexCoord; 33 | 34 | void main() 35 | { 36 | gl_Position = vec4(aPos, 1.0); 37 | TexCoord = aTexCoord; 38 | } 39 | ''' 40 | 41 | fragment_shader_src = ''' 42 | #version 330 core 43 | out vec4 FragColor; 44 | in vec2 TexCoord; 45 | 46 | uniform sampler2D inputTexture; 47 | uniform float aspectRatio; 48 | 49 | const float PI = 3.14159265359; 50 | 51 | float RadicalInverse_VdC(uint bits) 52 | { 53 | bits = (bits << 16u) | (bits >> 16u); 54 | bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); 55 | bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); 56 | bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); 57 | bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); 58 | return float(bits) * 2.3283064365386963e-10; // / 0x100000000 59 | } 60 | 61 | vec2 Hammersley(uint i, uint N) 62 | { 63 | return vec2(float(i)/float(N), RadicalInverse_VdC(i)); 64 | } 65 | 66 | vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) 67 | { 68 | float a = roughness*roughness; 69 | 70 | float phi = 2.0 * PI * Xi.x; 71 | float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); 72 | float sinTheta = sqrt(1.0 - cosTheta*cosTheta); 73 | 74 | vec3 H; 75 | H.x = cos(phi) * sinTheta; 76 | H.y = sin(phi) * sinTheta; 77 | H.z = cosTheta; 78 | 79 | vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); 80 | vec3 tangent = normalize(cross(up, N)); 81 | vec3 bitangent = cross(N, tangent); 82 | 83 | vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; 84 | return normalize(sampleVec); 85 | } 86 | 87 | void main() 88 | { 89 | vec2 uv = TexCoord * vec2(1.0, aspectRatio); 90 | vec3 N = vec3(0.0, 0.0, 1.0); 91 | vec3 V = vec3(sqrt(1.0 - uv.y) * cos(2.0 * PI * uv.x), sqrt(1.0 - uv.y) * sin(2.0 * PI * uv.x), uv.y); 92 | 93 | vec4 inputColor = texture(inputTexture, TexCoord); 94 | 95 | float roughness = inputColor.r; 96 | uint SAMPLE_COUNT = uint(1024u * max(inputColor.g, 0.001)); 97 | 98 | float A = 0.0; 99 | float B = 0.0; 100 | 101 | for(uint i = 0u; i < SAMPLE_COUNT; ++i) 102 | { 103 | vec2 Xi = Hammersley(i, SAMPLE_COUNT); 104 | vec3 H = ImportanceSampleGGX(Xi, N, roughness); 105 | vec3 L = 2.0 * dot(V, H) * H - V; 106 | 107 | 108 | float NdotL = max(L.z, 0.0); 109 | float NdotV = max(V.z, 0.0); 110 | float VdotH = max(dot(V, H), 0.0); 111 | float NdotH = max(H.z, 0.0); 112 | 113 | if(NdotL > 0.0) 114 | { 115 | float G = min(1.0, min((2.0 * NdotH * NdotV) / VdotH, (2.0 * NdotH * NdotL) / VdotH)); 116 | float G_Vis = G * VdotH / (NdotH * NdotV); 117 | float Fc = pow(1.0 - VdotH, 5.0); 118 | 119 | A += (1.0 - Fc) * G_Vis; 120 | B += Fc * G_Vis; 121 | } 122 | } 123 | A /= float(SAMPLE_COUNT); 124 | B /= float(SAMPLE_COUNT); 125 | 126 | FragColor = vec4(A, B, 0.0, 0.0); 127 | } 128 | ''' 129 | 130 | def capture_lut(): 131 | # initialize GLUT 132 | glutInit() 133 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH) 134 | glutInitWindowSize(512, 512) 135 | glutCreateWindow(b'BRDF LUT Generator') 136 | 137 | # compile shaders and create a program 138 | vertex_shader = compile_shader(vertex_shader_src, GL_VERTEX_SHADER) 139 | fragment_shader = compile_shader(fragment_shader_src, GL_FRAGMENT_SHADER) 140 | shader_program = create_program(vertex_shader, fragment_shader) 141 | 142 | # generate a quad to render the texture 143 | quad_vertices = np.array([ 144 | -1.0, 1.0, 0.0, 0.0, 1.0, 145 | -1.0, -1.0, 0.0, 0.0, 0.0, 146 | 1.0, 1.0, 0.0, 1.0, 1.0, 147 | 1.0, -1.0, 0.0, 1.0, 0.0 148 | ], dtype=np.float32) 149 | 150 | vertex_array_object = glGenVertexArrays(1) 151 | glBindVertexArray(vertex_array_object) 152 | 153 | vertex_buffer_object = glGenBuffers(1) 154 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object) 155 | glBufferData(GL_ARRAY_BUFFER, quad_vertices, GL_STATIC_DRAW) 156 | 157 | glEnableVertexAttribArray(0) 158 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, None) 159 | glEnableVertexAttribArray(1) 160 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12)) 161 | 162 | glUseProgram(shader_program) 163 | 164 | # load the input texture 165 | input_image = Image.open('input_texture.png') # provide some precaptured framebuffer texture from your program/game here 166 | input_image_data = np.array(input_image, dtype=np.uint8) 167 | 168 | # set a fixed size for the output texture 169 | output_texture_size_1 = 1080 170 | output_texture_size_2 = 1920 171 | 172 | # calculate the aspect ratio 173 | aspect_ratio = float(input_image.width) / float(input_image.height) 174 | 175 | input_texture = glGenTextures(1) 176 | glBindTexture(GL_TEXTURE_2D, input_texture) 177 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, input_image.width, input_image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, input_image_data) 178 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 179 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 180 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) 181 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) 182 | 183 | # set the input texture as a uniform in the shader 184 | glUniform1i(glGetUniformLocation(shader_program, "inputTexture"), 0) 185 | 186 | # pass the aspect ratio to the shader 187 | glUniform1f(glGetUniformLocation(shader_program, "aspectRatio"), aspect_ratio) 188 | 189 | # generate framebuffer to render the output texture 190 | framebuffer = glGenFramebuffers(1) 191 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer) 192 | 193 | # generate the output texture 194 | output_texture = glGenTextures(1) 195 | glBindTexture(GL_TEXTURE_2D, output_texture) 196 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, output_texture_size_1, output_texture_size_2, 0, GL_RGBA, GL_FLOAT, None) 197 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 198 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 199 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) 200 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) 201 | 202 | # attach the output texture to the framebuffer 203 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, output_texture, 0) 204 | 205 | # bind the input texture to a texture unit and set the uniform sampler2D in the shader 206 | glActiveTexture(GL_TEXTURE0) 207 | glBindTexture(GL_TEXTURE_2D, input_texture) 208 | glUniform1i(glGetUniformLocation(shader_program, "inputTexture"), 0) 209 | 210 | # render the output texture 211 | glViewport(0, 0, output_texture_size_1, output_texture_size_2) 212 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) 213 | 214 | # read the output texture data 215 | output_texture_data = glReadPixels(0, 0, output_texture_size_1, output_texture_size_2, GL_RGBA, GL_FLOAT) 216 | output_texture_data = np.frombuffer(output_texture_data, dtype=np.float32).reshape((output_texture_size_1, output_texture_size_2, 4)) 217 | 218 | # save the output texture to a file 219 | output_texture_image = (np.nan_to_num(output_texture_data) * 65535).astype(np.uint16) # convert to 16-bit 220 | Image.fromarray(output_texture_image, mode='RGBA').save('output_brdf_lut.png') 221 | print('Saved BRDF LUT image...') 222 | # return Image.fromarray(output_texture_image, mode='RGBA') 223 | 224 | # clean up 225 | glDeleteTextures(2, [input_texture, output_texture]) 226 | glDeleteFramebuffers(1, [framebuffer]) 227 | glDeleteVertexArrays(1, [vertex_array_object]) 228 | glDeleteBuffers(1, [vertex_buffer_object]) 229 | glDeleteProgram(shader_program) 230 | glDeleteShader(vertex_shader) 231 | glDeleteShader(fragment_shader) 232 | glutDestroyWindow(glutGetWindow()) 233 | 234 | print("Output texture generated and saved as output_brdf_lut.png") 235 | 236 | capture_lut() 237 | -------------------------------------------------------------------------------- /complexpbr/ibl_f.frag: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | #ifndef MAX_LIGHTS 4 | #define MAX_LIGHTS 20 5 | #endif 6 | 7 | uniform sampler2D p3d_Texture0; 8 | uniform sampler2D p3d_Texture1; 9 | uniform sampler2D p3d_Texture2; 10 | uniform sampler2D p3d_Texture3; 11 | uniform samplerCube cubemaptex; 12 | uniform sampler2D brdfLUT; 13 | // layout(rgba32f) uniform image2D outputNormalNorm; 14 | layout(location=1) out vec3 outputNormal; 15 | 16 | in vec3 v_position; 17 | in vec4 v_color; 18 | in mat3 v_tbn; 19 | in vec2 v_texcoord; 20 | 21 | in vec4 v_shadow_pos[MAX_LIGHTS]; 22 | 23 | uniform float ao; 24 | uniform float specular_factor; 25 | uniform float shadow_boost; 26 | 27 | const float LIGHT_CUTOFF = 0.001; 28 | const float SPOTSMOOTH = 0.1; 29 | 30 | out vec4 o_color; 31 | 32 | const float PI = 3.14159265359; 33 | 34 | uniform struct p3d_MaterialParameters { 35 | vec4 baseColor; 36 | vec4 emission; 37 | float roughness; 38 | float metallic; 39 | } p3d_Material; 40 | 41 | vec3 fresnelSchlick(float cosTheta, vec3 F0) 42 | { 43 | return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 44 | } 45 | 46 | float DistributionGGX(vec3 N, vec3 H, float roughness) 47 | { 48 | float a = roughness * roughness; 49 | float a2 = a * a; 50 | float NdotH = max(dot(N, H), 0.0); 51 | float NdotH2 = NdotH * NdotH; 52 | 53 | float num = a2; 54 | float denom = (NdotH2 * (a2 - 1.0) + 1.0); 55 | denom = PI * denom * denom; 56 | 57 | return num / denom; 58 | } 59 | 60 | float GeometrySchlickGGX(float NdotV, float roughness) 61 | { 62 | float r = (roughness + 1.0); 63 | float k = (r * r) / 8.0; 64 | 65 | float num = NdotV; 66 | float denom = NdotV * (1.0 - k) + k; 67 | 68 | return num / denom; 69 | } 70 | 71 | float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) 72 | { 73 | float NdotV = max(dot(N, V), 0.0); 74 | float NdotL = max(dot(N, L), 0.0); 75 | float ggx2 = GeometrySchlickGGX(NdotV, roughness); 76 | float ggx1 = GeometrySchlickGGX(NdotL, roughness); 77 | 78 | return ggx1 * ggx2; 79 | } 80 | 81 | vec3 getIBL(vec3 N, vec3 V, vec3 F0, vec3 diffuse_color, float roughness) 82 | { 83 | vec3 R = reflect(-V, N); 84 | vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0); 85 | vec3 kD = vec3(1.0) - kS; 86 | 87 | vec3 irradiance = texture(cubemaptex, N).rgb; 88 | vec3 diffuse = irradiance * diffuse_color; 89 | 90 | const float MAX_REFLECTION_LOD = 4.0; 91 | vec3 prefilteredColor = vec3(0.0); 92 | if (roughness < 0.7) 93 | prefilteredColor = textureLod(cubemaptex, R, roughness * MAX_REFLECTION_LOD).rgb; 94 | else if (roughness >= 0.7) 95 | if (roughness < 0.99) 96 | prefilteredColor = textureLod(cubemaptex, R, roughness * MAX_REFLECTION_LOD).rgb * vec3(0.04); 97 | else if (roughness >= 0.99) 98 | prefilteredColor = prefilteredColor; 99 | vec2 brdf = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; 100 | vec3 specular = prefilteredColor * (kS * brdf.x + brdf.y); 101 | vec3 ao_final = (kD * diffuse + specular) * ao; 102 | 103 | return (ao_final * (specular * specular_factor)); 104 | } 105 | 106 | uniform struct p3d_LightSourceParameters { 107 | vec4 position; 108 | vec4 diffuse; 109 | vec4 specular; 110 | vec3 attenuation; 111 | vec3 spotDirection; 112 | float spotCosCutoff; 113 | sampler2DShadow shadowMap; 114 | mat4 shadowViewMatrix; 115 | } p3d_LightSource[MAX_LIGHTS]; 116 | 117 | uniform struct p3d_LightModelParameters { 118 | vec4 ambient; 119 | } p3d_LightModel; 120 | 121 | uniform vec4 p3d_ColorScale; 122 | 123 | struct FunctionParameters { 124 | float n_dot_l; 125 | float n_dot_v; 126 | float n_dot_h; 127 | float l_dot_h; 128 | float v_dot_h; 129 | float roughness; 130 | float metallic; 131 | vec3 reflection0; 132 | vec3 diffuse_color; 133 | vec3 specular_color; 134 | }; 135 | 136 | // Smith GGX with fast sqrt approximation (see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)) 137 | float visibility_occlusion(FunctionParameters func_params) { 138 | float r = func_params.roughness; 139 | float r2 = r * r; 140 | float n_dot_l = func_params.n_dot_l; 141 | float n_dot_v = func_params.n_dot_v; 142 | float ggxv = n_dot_l * (n_dot_v * (1.0 - r) + r); 143 | float ggxl = n_dot_v * (n_dot_l * (1.0 - r) + r); 144 | return max(0.0, 0.5 / (ggxv + ggxl)); 145 | } 146 | 147 | // GGX/Trowbridge-Reitz 148 | float microfacet_distribution(FunctionParameters func_params) { 149 | float roughness2 = func_params.roughness * func_params.roughness; 150 | float f = (func_params.n_dot_h * func_params.n_dot_h) * (roughness2 - 1.0) + 1.0; 151 | return roughness2 / (PI * f * f); 152 | } 153 | 154 | // Lambert 155 | float diffuse_function(FunctionParameters func_params) { 156 | return 1.0 / PI; 157 | } 158 | 159 | float normal_blur(in float x, in float sig) 160 | { 161 | return 0.3989*exp(-0.5*x*x/(sig*sig))/sig; 162 | } 163 | 164 | void main() 165 | { 166 | vec3 N = normalize(v_tbn * (2.0 * texture(p3d_Texture2, v_texcoord).rgb - 1.0)); 167 | // vec3 N = normalize((2.0 * texture(p3d_Texture2, v_texcoord).rgb - 1.0)); 168 | vec3 V = normalize(-v_position); 169 | 170 | // sample the albedo texture 171 | vec4 albedo = p3d_Material.baseColor * v_color * p3d_ColorScale * texture(p3d_Texture0, v_texcoord); 172 | 173 | // sample the metal-rough texture 174 | vec4 metalRough = texture(p3d_Texture1, v_texcoord); 175 | float metallic = clamp(p3d_Material.metallic * metalRough.b, 0.0, 1.0); 176 | float roughness = clamp(p3d_Material.roughness * metalRough.g, 0.0, 1.0); 177 | 178 | // sample the emission texture 179 | vec3 emission = p3d_Material.emission.rgb * texture(p3d_Texture3, v_texcoord).rgb; 180 | 181 | vec3 F0 = vec3(0.04); 182 | F0 = mix(F0, albedo.rgb, metallic); 183 | 184 | vec3 diffuse_color = (albedo.rgb * (vec3(1.0) - F0)) * (1.0 - metallic); 185 | vec3 spec_color = F0; 186 | 187 | // vec3 color = vec3(0.0); 188 | vec4 color = vec4(vec3(0.0), albedo.a); 189 | 190 | // compute the direct lighting from light sources 191 | for (int i = 0; i < MAX_LIGHTS; ++i) { 192 | vec3 lightcol = p3d_LightSource[i].diffuse.rgb; 193 | 194 | if (dot(lightcol, lightcol) < LIGHT_CUTOFF) { 195 | continue; 196 | } 197 | 198 | vec3 light_pos = p3d_LightSource[i].position.xyz - v_position * p3d_LightSource[i].position.w; 199 | vec3 l = normalize(light_pos); 200 | vec3 h = normalize(l + V); 201 | float dist = length(light_pos); 202 | vec3 att_const = p3d_LightSource[i].attenuation; 203 | float attenuation_factor = 1.0 / (att_const.x + att_const.y + att_const.z * dist * dist); 204 | float spotcos = dot(normalize(p3d_LightSource[i].spotDirection), -l); 205 | float spotcutoff = p3d_LightSource[i].spotCosCutoff; 206 | float shadowSpot = smoothstep(spotcutoff-SPOTSMOOTH, spotcutoff+SPOTSMOOTH, spotcos); 207 | 208 | float shadowCaster = textureProj(p3d_LightSource[i].shadowMap, v_shadow_pos[i]); 209 | float shadow = shadowSpot * shadowCaster * attenuation_factor; 210 | 211 | FunctionParameters func_params; 212 | func_params.n_dot_l = clamp(dot(N, l), 0.0, 1.0); 213 | func_params.n_dot_v = clamp(abs(dot(N, V)), 0.0, 1.0); 214 | func_params.n_dot_h = clamp(dot(N, h), 0.0, 1.0); 215 | func_params.l_dot_h = clamp(dot(l, h), 0.0, 1.0); 216 | func_params.v_dot_h = clamp(dot(V, h), 0.0, 1.0); 217 | func_params.roughness = roughness; 218 | func_params.metallic = metallic; 219 | func_params.reflection0 = spec_color; 220 | func_params.diffuse_color = diffuse_color; 221 | func_params.specular_color = spec_color; 222 | 223 | float V = visibility_occlusion(func_params); // V = G / (4 * n_dot_l * n_dot_v) 224 | float D = microfacet_distribution(func_params); 225 | 226 | vec3 diffuse_contrib = (diffuse_color * p3d_LightModel.ambient.rgb) * diffuse_function(func_params); 227 | vec3 spec_contrib = vec3(F0 * V * D); 228 | color.rgb += func_params.n_dot_l * lightcol * (diffuse_contrib + spec_contrib) * shadow; 229 | color.rgb += albedo.rgb * shadow_boost; // node-level shadow boost heuristic 230 | } 231 | 232 | vec3 ibl = getIBL(N, V, F0, diffuse_color, roughness); 233 | o_color = vec4(ibl + emission + color.rgb, color.a); 234 | // o_color = vec4(v_tbn * texture(p3d_Texture2, v_texcoord).rgb, 1) 235 | // o_color = vec4(v_tbn, 1); 236 | // o_color = vec4(N, color.a); 237 | // send the normal texture to post 238 | // imageStore(outputNormalNorm, coord, vec4(texture(p3d_Texture2, v_texcoord).rgb * 0.5 + vec3(0.5),1)); 239 | outputNormal = texture(p3d_Texture2, v_texcoord).rgb * 0.5 + vec3(0.5); 240 | // outputNormal = N * 0.5 + vec3(0.5); 241 | // outputNormal = N; 242 | } 243 | -------------------------------------------------------------------------------- /complexpbr/ibl_v.vert: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | #ifndef MAX_LIGHTS 4 | #define MAX_LIGHTS 20 5 | #endif 6 | 7 | uniform mat4 p3d_ProjectionMatrix; 8 | uniform mat4 p3d_ModelViewMatrix; 9 | uniform mat3 p3d_NormalMatrix; 10 | uniform mat4 p3d_TextureMatrix; 11 | uniform mat4 p3d_ModelMatrix; 12 | 13 | // skinning 14 | uniform mat4 p3d_TransformTable[100]; 15 | in vec4 transform_weight; 16 | in vec4 transform_index; 17 | 18 | in vec3 p3d_Normal; 19 | in vec4 p3d_Vertex; 20 | in vec4 p3d_Color; 21 | in vec4 p3d_Tangent; 22 | in vec2 p3d_MultiTexCoord0; 23 | 24 | out vec3 v_position; 25 | out vec4 v_color; 26 | out mat3 v_tbn; 27 | out vec2 v_texcoord; 28 | 29 | uniform sampler2D displacement_map; 30 | uniform float displacement_scale; 31 | 32 | uniform struct p3d_LightSourceParameters { 33 | vec4 position; 34 | vec4 diffuse; 35 | vec4 specular; 36 | vec3 attenuation; 37 | vec3 spotDirection; 38 | float spotCosCutoff; 39 | sampler2DShadow shadowMap; 40 | mat4 shadowViewMatrix; 41 | } p3d_LightSource[MAX_LIGHTS]; 42 | 43 | out vec4 v_shadow_pos[MAX_LIGHTS]; 44 | 45 | void main() { 46 | mat4 skin_matrix = ( 47 | p3d_TransformTable[int(transform_index.x)] * transform_weight.x + 48 | p3d_TransformTable[int(transform_index.y)] * transform_weight.y + 49 | p3d_TransformTable[int(transform_index.z)] * transform_weight.z + 50 | p3d_TransformTable[int(transform_index.w)] * transform_weight.w); 51 | 52 | vec3 normal = normalize(p3d_NormalMatrix * (skin_matrix * vec4(p3d_Normal.xyz, 0.0)).xyz); 53 | vec3 tangent = normalize(p3d_NormalMatrix * (skin_matrix * vec4(p3d_Tangent.xyz, 0.0)).xyz); 54 | vec3 bitangent = cross(normal, tangent) * p3d_Tangent.w; 55 | v_tbn = mat3(tangent, bitangent, normal); 56 | 57 | v_color = p3d_Color; 58 | v_texcoord = (p3d_TextureMatrix * vec4(p3d_MultiTexCoord0, 0.0, 1.0)).xy; 59 | float displacement = texture(displacement_map, v_texcoord).r * displacement_scale; 60 | vec4 displaced_vertex = skin_matrix * p3d_Vertex + vec4(normal, 0.0) * displacement; 61 | vec4 model_view_displaced_vertex = p3d_ModelViewMatrix * displaced_vertex; 62 | v_position = vec3(model_view_displaced_vertex); 63 | 64 | for (int i = 0; i < p3d_LightSource.length(); ++i) { 65 | v_shadow_pos[i] = p3d_LightSource[i].shadowViewMatrix * model_view_displaced_vertex; 66 | } 67 | 68 | gl_Position = p3d_ProjectionMatrix * model_view_displaced_vertex; 69 | } 70 | -------------------------------------------------------------------------------- /complexpbr/min_f.frag: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | uniform sampler2D scene_tex; // albedo 4 | uniform sampler2D depth_tex; // depth 5 | uniform sampler2D normal_tex; // normal 6 | uniform vec2 window_size; 7 | uniform mat4 p3d_ViewMatrix; 8 | uniform mat4 p3d_ProjectionMatrixInverse; 9 | 10 | // SSAO 11 | const float radius = 0.5; 12 | const float bias = 0.025; 13 | uniform int ssao_samples; 14 | 15 | // Bloom 16 | uniform float bloom_intensity; 17 | uniform float bloom_threshold; 18 | uniform int bloom_blur_width; 19 | uniform int bloom_samples; 20 | 21 | // SSR 22 | uniform float ssr_intensity; 23 | uniform float ssr_step; 24 | uniform float ssr_fresnel_pow; 25 | uniform int ssr_samples; 26 | uniform float screen_ray_factor; 27 | uniform float ssr_depth_cutoff; 28 | uniform float ssr_depth_min; 29 | 30 | // HSV 31 | uniform float hsv_r = 1.0; 32 | uniform float hsv_g = 1.0; 33 | uniform float hsv_b = 1.0; 34 | 35 | // camera 36 | uniform float cameraNear; 37 | uniform float cameraFar; 38 | 39 | // screenspace-level global reflection intensity 40 | uniform float reflection_threshold; 41 | 42 | // final brightness factor 43 | uniform float final_brightness; 44 | 45 | in vec2 texcoord; 46 | in vec3 tbn_tangent; 47 | in vec3 tbn_bitangent; 48 | in vec3 tbn_normal; 49 | in vec3 vertex_pos_view; 50 | 51 | out vec4 o_color; 52 | 53 | mat3 vx = mat3( 54 | 1.,2.,1., 55 | 0.,0.,0., 56 | -1.,-2.,-1. 57 | ); 58 | 59 | mat3 vy = mat3( 60 | 1.,0.,-1., 61 | 2.,0.,0., 62 | -1.,-2.,-1. 63 | ); 64 | 65 | float hash(float n) 66 | { 67 | return fract(sin(n) * 43758.5453); 68 | } 69 | 70 | vec3 randomSample(int i, vec2 uv) 71 | { 72 | float r1 = hash(float(i) + 1.0 + uv.x * uv.y * 1000.0); 73 | float r2 = hash(float(i) + 21.0 + uv.y * uv.x * 1000.0); 74 | 75 | float phi = 2.0 * 3.14159265 * r1; 76 | float cosTheta = 1.0 - r2; 77 | float sinTheta = sqrt(1.0 - cosTheta * cosTheta); 78 | 79 | return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); 80 | } 81 | 82 | float normalBlur(in float x, in float sig) 83 | { 84 | return 0.3989*exp(-0.5*x*x/(sig*sig))/sig; 85 | } 86 | 87 | float luminance(vec3 color) 88 | { 89 | return dot(color, vec3(0.299, 0.587, 0.114)); 90 | } 91 | 92 | struct SSRout { 93 | vec3 color; 94 | float intensity; 95 | }; 96 | 97 | SSRout screenSpaceReflection(vec2 uv, float linearDepth, vec3 normal) 98 | { 99 | vec3 viewPos = vec3(uv, linearDepth); 100 | viewPos = (p3d_ProjectionMatrixInverse * vec4(viewPos, 1.0)).xyz; 101 | vec3 reflect_color = vec3(0.0); 102 | 103 | vec3 reflectedRay = reflect(viewPos, normal); 104 | vec3 screenSpaceRay = (p3d_ProjectionMatrixInverse * vec4(reflectedRay, 0.0)).xyz; 105 | screenSpaceRay.xy /= screenSpaceRay.z; 106 | 107 | vec2 rayStep = screenSpaceRay.xy * screen_ray_factor; 108 | vec2 rayPosition = uv; 109 | 110 | for (int i = 0; i < ssr_samples; i++) 111 | { 112 | rayPosition -= rayStep; 113 | 114 | float depth = 1.0 - texture(depth_tex, rayPosition).r + 0.5; 115 | vec3 position = vec3(rayPosition * 2.0 - 1.0, depth); 116 | position = (p3d_ProjectionMatrixInverse * vec4(position, 1.0)).xyz; 117 | 118 | if (abs(viewPos.z - position.z) < ssr_step) 119 | { 120 | if (depth <= ssr_depth_cutoff) 121 | { 122 | if (depth >= ssr_depth_min) 123 | { 124 | float fresnel = pow(1.0 - dot(normal, normalize(viewPos)), ssr_fresnel_pow); 125 | vec3 color = texture(scene_tex, rayPosition).rgb; 126 | reflect_color = mix(reflect_color, color, fresnel); 127 | } 128 | } 129 | } 130 | } 131 | 132 | SSRout result; 133 | result.color = reflect_color; 134 | result.intensity = ssr_intensity; 135 | return result; 136 | } 137 | 138 | float ssao(in vec2 uv, in vec3 viewPos, in vec3 viewNormal) 139 | { 140 | float occlusion = 0.0; 141 | float sampleDepth; 142 | 143 | for (int i = 0; i < ssao_samples; ++i) 144 | { 145 | vec3 randomSampleVec = randomSample(i, uv); 146 | vec3 samplePosOffset = radius * (viewNormal * randomSampleVec.z + vec3(randomSampleVec.xy * vec2(radius), 0.0)); 147 | vec3 ao_sample = viewPos + samplePosOffset; 148 | vec4 samplePos = p3d_ProjectionMatrixInverse * vec4(ao_sample, 1.0); 149 | samplePos.xyz /= samplePos.w; 150 | samplePos.xy = (samplePos.xy * 0.5 + 0.5) * window_size; 151 | 152 | sampleDepth = texture(depth_tex, samplePos.xy / window_size).r; 153 | 154 | float rangeCheck = smoothstep(0.0, 1.0, radius / abs(viewPos.z - sampleDepth)); 155 | occlusion += (sampleDepth <= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck; 156 | } 157 | 158 | return 1.0 - occlusion / 16.0; 159 | } 160 | 161 | vec3 getViewPos(vec2 uv, float depth) 162 | { 163 | vec3 viewPos = vec3(uv * 2.0 - 1.0, depth); 164 | vec4 worldPos = p3d_ProjectionMatrixInverse * vec4(viewPos, 1.0); 165 | worldPos.xyz = worldPos.xyz * 0.0011; 166 | return worldPos.xyz / worldPos.w; 167 | } 168 | 169 | mat3 getNormalMatrix(mat4 viewMatrix) 170 | { 171 | return transpose(inverse(mat3(viewMatrix))); 172 | } 173 | 174 | vec3 transformNormalToViewSpace(vec3 tangentSpaceNormal) 175 | { 176 | return normalize(getNormalMatrix(p3d_ViewMatrix) * tangentSpaceNormal); 177 | } 178 | 179 | vec3 getViewNormal(vec3 tbn_tangent, vec3 tbn_bitangent, vec3 tbn_normal) 180 | { 181 | vec3 normalTex = texture(normal_tex, texcoord).rgb * 2.0 - 1.0; 182 | vec3 tangentSpaceNormal = normalize(tbn_tangent * normalTex.x + tbn_bitangent * normalTex.y + tbn_normal * normalTex.z); 183 | 184 | return normalize(transformNormalToViewSpace(tangentSpaceNormal)); 185 | } 186 | 187 | vec3 rgb2hsv(vec3 c) { 188 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 189 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); 190 | vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); 191 | 192 | float d = q.x - min(q.w, q.y); 193 | float e = 1.0e-10; 194 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 195 | } 196 | 197 | vec3 hsv2rgb(vec3 c) { 198 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 199 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 200 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 201 | } 202 | 203 | float brightness(vec3 color) 204 | { 205 | const vec3 W = vec3(0.2126, 0.7152, 0.0722); 206 | return dot(color, W); 207 | } 208 | 209 | vec3 bloomAA(vec3 color, vec2 uv) 210 | { 211 | vec3 bloom = vec3(0.0); 212 | vec3 aa_contrib = vec3(0.0); 213 | vec2 texelSize = 1.0 / window_size; 214 | int aaBlurWidth = 10; 215 | float totalBlurWeight = 0.0; 216 | 217 | if (bloom_intensity > 0) 218 | { 219 | for (int i = -bloom_samples; i <= bloom_samples; ++i) 220 | { 221 | for (int j = -bloom_samples; j <= bloom_samples; ++j) 222 | { 223 | vec2 offset = vec2(i, j) * texelSize; 224 | vec3 bloom_sample = texture(scene_tex, uv + offset).rgb; 225 | 226 | float brightness_value = brightness(bloom_sample); 227 | float intensity = brightness_value > bloom_threshold ? brightness_value : 0.0; 228 | float blur = normalBlur(length(offset), bloom_blur_width); 229 | bloom += bloom_sample * intensity * bloom_intensity * blur; 230 | totalBlurWeight += blur; 231 | } 232 | } 233 | 234 | // normalize bloom effect 235 | bloom /= totalBlurWeight; 236 | } 237 | 238 | // AA loop 239 | mat3 Inter; 240 | 241 | for (int i = 0; i < 3; i++) 242 | { 243 | for (int j = 0; j < 3; j++) 244 | { 245 | vec2 offset = (vec2(i - 1, j - 1) * texelSize); 246 | vec3 aa_sample = texture(scene_tex, uv + offset).rgb; 247 | Inter[i][j] = length(aa_sample); 248 | } 249 | } 250 | 251 | // calculate AA response 252 | float fx = dot(vx[0], Inter[0]) + dot(vx[1], Inter[1]) + dot(vx[2], Inter[2]); 253 | float fy = dot(vy[0], Inter[0]) + dot(vy[1], Inter[1]) + dot(vy[2], Inter[2]); 254 | float fo = sqrt(pow(fx, 2.) + pow(fy, 2.)); 255 | fo = smoothstep(0.1, 0.7, fo); 256 | 257 | float kern[11]; 258 | for (int w = 0; w <= 5; w++) { 259 | kern[5 + w] = kern[5 - w] = normalBlur(float(w), aaBlurWidth); 260 | } 261 | 262 | for (int i = 0; i <= 5; i++) { 263 | for (int z = 0; z <= 5; z++) { 264 | aa_contrib += kern[5 + z] * kern[5 + i] * texture(scene_tex, (uv + (vec2(float(i), float(z)) * texelSize))).rgb * vec3(fo); 265 | } 266 | } 267 | 268 | // combine bloom and AA contributions 269 | vec3 combined = color + bloom + aa_contrib; 270 | 271 | return combined; 272 | } 273 | 274 | void main() { 275 | vec3 color = texture(scene_tex, texcoord).rgb; 276 | vec4 depth = texture(depth_tex, texcoord); 277 | float depth_fl = 1.0 - depth.r + 0.5; 278 | vec3 viewPos = getViewPos(texcoord, depth_fl); 279 | vec3 viewNormal = getViewNormal(tbn_tangent, tbn_bitangent, tbn_normal); 280 | 281 | SSRout ssrOut = screenSpaceReflection(texcoord, depth_fl, viewNormal); 282 | // blend the object color with the reflection color based on the intensity 283 | color = mix(color, ssrOut.color, max(ssrOut.intensity - reflection_threshold, 0.0)); 284 | 285 | // apply SSAO to the final color 286 | float occlusion = ssao(texcoord, viewPos, viewNormal); 287 | color *= occlusion; 288 | // combined bloom/AA loop 289 | color = bloomAA(color, texcoord); 290 | 291 | vec3 hsvColor = rgb2hsv(color); 292 | hsvColor *= vec3(hsv_r, hsv_g, hsv_b); 293 | color = hsv2rgb(hsvColor); 294 | 295 | o_color = vec4(color * vec3(final_brightness), 1.0); 296 | } 297 | -------------------------------------------------------------------------------- /complexpbr/min_v.vert: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | uniform mat4 p3d_ModelViewProjectionMatrix; 4 | 5 | uniform mat4 p3d_ProjectionMatrix; 6 | uniform mat4 p3d_ModelViewMatrix; 7 | 8 | in vec4 p3d_Vertex; 9 | in vec2 p3d_MultiTexCoord0; 10 | in vec3 p3d_Normal; 11 | in vec4 p3d_Tangent; 12 | 13 | out vec2 texcoord; 14 | out vec3 tbn_tangent; 15 | out vec3 tbn_bitangent; 16 | out vec3 tbn_normal; 17 | out vec3 vertex_pos_view; 18 | 19 | void main() { 20 | vec3 normal = normalize(mat3(p3d_ModelViewMatrix) * p3d_Normal); 21 | vec3 tangent = normalize(mat3(p3d_ModelViewMatrix) * p3d_Tangent.xyz); 22 | vec3 bitangent = cross(normal, tangent) * p3d_Tangent.w; 23 | mat3 tbn_matrix = mat3(tangent, bitangent, normal); 24 | 25 | vec4 current_vertex = p3d_ModelViewMatrix * p3d_Vertex; 26 | vertex_pos_view = current_vertex.xyz / current_vertex.w; 27 | texcoord = p3d_MultiTexCoord0; 28 | 29 | tbn_tangent = tbn_matrix[0]; 30 | tbn_bitangent = tbn_matrix[1]; 31 | tbn_normal = tbn_matrix[2]; 32 | 33 | gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; 34 | } -------------------------------------------------------------------------------- /complexpbr/output_brdf_lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayanalysis/panda3d-complexpbr/ec6ed18e4080e347603aaf30edb1d9044ab7d704/complexpbr/output_brdf_lut.png -------------------------------------------------------------------------------- /complexpbr/sample_output_brdf_lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayanalysis/panda3d-complexpbr/ec6ed18e4080e347603aaf30edb1d9044ab7d704/complexpbr/sample_output_brdf_lut.png -------------------------------------------------------------------------------- /complexpbr/simplepbr_license.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Mitchell Stokes 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 61", 4 | "wheel", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [project] 9 | version = "0.6.1" 10 | name = "panda3d-complexpbr" 11 | authors = [ 12 | {name = "Logan Bier"} 13 | ] 14 | description = "IBL rendering module supporting real-time reflections and post-processing effects in Panda3D" 15 | readme = "README.md" 16 | keywords = ["panda3d", "gamedev", "pbr", "IBL", "real-time"] 17 | license = {text = "BSD-3-Clause"} 18 | classifiers = [ 19 | "Development Status :: 4 - Beta", 20 | "License :: OSI Approved :: BSD License", 21 | "Programming Language :: Python :: 3", 22 | ] 23 | dependencies = [ 24 | "panda3d >= 1.10.8", 25 | "typing_extensions ~= 4.7", 26 | ] 27 | requires-python = ">= 3.10" 28 | 29 | [project.urls] 30 | homepage = "https://github.com/rayanalysis/panda3d-complexpbr" 31 | 32 | [tool.setuptools] 33 | packages = ["complexpbr"] 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='panda3d-complexpbr', 5 | version='0.6.1', 6 | packages=['complexpbr'], 7 | package_data={ 8 | "": ["*.txt","*.vert","*.frag","*.png"], 9 | } 10 | ) 11 | --------------------------------------------------------------------------------