From 96945792e66999585afe779bc0a708c84362a350 Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Thu, 15 Aug 2024 11:40:28 -0400 Subject: [PATCH] feat: More Lights! [flame_3d] (#3250) More Lights! More fun! I am still trying to get arrays to work (not even the fancy SSBOs, just plain fixed arrays). In the meanwhile this puts lights as separate objects. This supports: * point lights * ambient lights * colors * intensity ![image](https://github.com/user-attachments/assets/a2f75a8a-9c64-42d1-bbe5-bdf58fa7df69) --------- Co-authored-by: Jochum van der Ploeg --- .github/.cspell/people_usernames.txt | 1 + .../shaders/spatial_material.shaderbundle | Bin 33224 -> 58920 bytes .../shaders/standard_material.shaderbundle | Bin 35504 -> 0 bytes packages/flame_3d/example/lib/main.dart | 37 +++- .../flame_3d/example/lib/rotating_light.dart | 5 +- .../flame_3d/lib/src/camera/world_3d.dart | 3 +- .../lib/src/components/light_component.dart | 21 ++- .../flame_3d/lib/src/extensions/color.dart | 8 +- .../lib/src/graphics/graphics_device.dart | 2 +- .../flame_3d/lib/src/resources/light.dart | 4 +- .../src/resources/light/ambient_light.dart | 15 ++ .../lib/src/resources/light/light.dart | 10 +- .../lib/src/resources/light/light_source.dart | 14 +- .../src/resources/light/lighting_info.dart | 48 +++++ .../lib/src/resources/light/point_light.dart | 9 + .../lib/src/resources/light/spot_light.dart | 11 -- .../resources/material/spatial_material.dart | 17 +- .../lib/src/resources/mesh/vertex.dart | 5 +- .../lib/src/resources/shader/shader.dart | 17 +- .../flame_3d/shaders/spatial_material.frag | 175 +++++++++++++++--- 20 files changed, 332 insertions(+), 70 deletions(-) delete mode 100644 packages/flame_3d/assets/shaders/standard_material.shaderbundle create mode 100644 packages/flame_3d/lib/src/resources/light/ambient_light.dart create mode 100644 packages/flame_3d/lib/src/resources/light/lighting_info.dart create mode 100644 packages/flame_3d/lib/src/resources/light/point_light.dart delete mode 100644 packages/flame_3d/lib/src/resources/light/spot_light.dart diff --git a/.github/.cspell/people_usernames.txt b/.github/.cspell/people_usernames.txt index eba11cfaaee..1e3bb244abc 100644 --- a/.github/.cspell/people_usernames.txt +++ b/.github/.cspell/people_usernames.txt @@ -20,3 +20,4 @@ tavian # tavianator.com videon # github.com/markvideon wolfenrain # github.com/wolfenrain xaha # github.com/xvrh +luan # github.com/luanpotter \ No newline at end of file diff --git a/packages/flame_3d/assets/shaders/spatial_material.shaderbundle b/packages/flame_3d/assets/shaders/spatial_material.shaderbundle index fec0fef819e2925c2df25782b9e1ef914593aa4a..016d3a9ed3328153593235dc3454d277d5a9a818 100644 GIT binary patch literal 58920 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 delta 5700 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 @override FutureOr onLoad() async { world.addAll([ + LightComponent.ambient( + intensity: 1.0, + ), RotatingLight(), + LightComponent.point( + position: Vector3(0, 0.1, 0), + color: const Color(0xFFFF00FF), + ), + MeshComponent( + mesh: SphereMesh( + radius: 0.05, + material: SpatialMaterial( + albedoTexture: ColorTexture( + const Color(0xFFFF00FF), + ), + ), + ), + position: Vector3(0, 0.1, 0), + ), + + LightComponent.point( + position: Vector3(-2, 3, 2), + color: const Color(0xFFFF2255), + ), + MeshComponent( + mesh: SphereMesh( + radius: 0.05, + material: SpatialMaterial( + albedoTexture: ColorTexture( + const Color(0xFFFF2255), + ), + ), + ), + position: Vector3(-2, 4, 2), + ), + // Add a player box PlayerBox(), @@ -50,7 +85,7 @@ class ExampleGame3D extends FlameGame mesh: SphereMesh( radius: 1, material: SpatialMaterial( - albedoTexture: ColorTexture(Colors.purple), + albedoTexture: ColorTexture(Colors.green), ), ), ), diff --git a/packages/flame_3d/example/lib/rotating_light.dart b/packages/flame_3d/example/lib/rotating_light.dart index efc749164f2..8ba9cb048a8 100644 --- a/packages/flame_3d/example/lib/rotating_light.dart +++ b/packages/flame_3d/example/lib/rotating_light.dart @@ -1,12 +1,15 @@ import 'dart:math'; +import 'dart:ui'; import 'package:flame_3d/components.dart'; import 'package:flame_3d/game.dart'; class RotatingLight extends LightComponent { RotatingLight() - : super.spot( + : super.point( position: Vector3.zero(), + color: const Color(0xFF00FF00), + intensity: 20.0, ); @override diff --git a/packages/flame_3d/lib/src/camera/world_3d.dart b/packages/flame_3d/lib/src/camera/world_3d.dart index 3954d835803..d7ee43e12d7 100644 --- a/packages/flame_3d/lib/src/camera/world_3d.dart +++ b/packages/flame_3d/lib/src/camera/world_3d.dart @@ -69,8 +69,9 @@ class World3D extends flame.World with flame.HasGameReference { image.dispose(); } + // TODO(luan): consider making this a fixed-size array later void _prepareDevice() { - device.lights = lights; + device.lightingInfo.lights = lights; } // TODO(wolfenrain): this is only here for testing purposes diff --git a/packages/flame_3d/lib/src/components/light_component.dart b/packages/flame_3d/lib/src/components/light_component.dart index ea73c664e26..095d8b6ad35 100644 --- a/packages/flame_3d/lib/src/components/light_component.dart +++ b/packages/flame_3d/lib/src/components/light_component.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flame_3d/camera.dart'; import 'package:flame_3d/components.dart'; import 'package:flame_3d/game.dart'; @@ -10,13 +12,28 @@ class LightComponent extends Component3D { super.position, }); - LightComponent.spot({ + LightComponent.point({ Vector3? position, + Color color = const Color(0xFFFFFFFF), + double intensity = 1.0, }) : this( - source: SpotLight(), + source: PointLight( + color: color, + intensity: intensity, + ), position: position, ); + LightComponent.ambient({ + Color color = const Color(0xFFFFFFFF), + double intensity = 0.2, + }) : this( + source: AmbientLight( + color: color, + intensity: intensity, + ), + ); + final LightSource source; late final Light _light = Light( diff --git a/packages/flame_3d/lib/src/extensions/color.dart b/packages/flame_3d/lib/src/extensions/color.dart index b8579e1446f..9d06264fcf5 100644 --- a/packages/flame_3d/lib/src/extensions/color.dart +++ b/packages/flame_3d/lib/src/extensions/color.dart @@ -3,6 +3,10 @@ import 'dart:ui'; extension ColorExtension on Color { /// Returns a Float32List that represents the color as a vector. - Float32List get storage => - Float32List.fromList([red / 255, green / 255, blue / 255, opacity]); + Float32List get storage => Float32List.fromList([ + opacity, + red.toDouble() / 255, + green.toDouble() / 255, + blue.toDouble() / 255, + ]); } diff --git a/packages/flame_3d/lib/src/graphics/graphics_device.dart b/packages/flame_3d/lib/src/graphics/graphics_device.dart index 8da9abc4ea2..a5e5117a5ee 100644 --- a/packages/flame_3d/lib/src/graphics/graphics_device.dart +++ b/packages/flame_3d/lib/src/graphics/graphics_device.dart @@ -50,7 +50,7 @@ class GraphicsDevice { /// Must be set by the rendering pipeline before elements are bound. /// Can be accessed by elements in their bind method. - Iterable lights = []; + final LightingInfo lightingInfo = LightingInfo(); /// Begin a new rendering batch. /// diff --git a/packages/flame_3d/lib/src/resources/light.dart b/packages/flame_3d/lib/src/resources/light.dart index 646ac51a7da..150a240e8c4 100644 --- a/packages/flame_3d/lib/src/resources/light.dart +++ b/packages/flame_3d/lib/src/resources/light.dart @@ -1,3 +1,5 @@ +export 'light/ambient_light.dart'; export 'light/light.dart'; export 'light/light_source.dart'; -export 'light/spot_light.dart'; +export 'light/lighting_info.dart'; +export 'light/point_light.dart'; diff --git a/packages/flame_3d/lib/src/resources/light/ambient_light.dart b/packages/flame_3d/lib/src/resources/light/ambient_light.dart new file mode 100644 index 00000000000..5ef07ba93d3 --- /dev/null +++ b/packages/flame_3d/lib/src/resources/light/ambient_light.dart @@ -0,0 +1,15 @@ +import 'dart:ui' show Color; + +import 'package:flame_3d/resources.dart'; + +class AmbientLight extends LightSource { + AmbientLight({ + super.color = const Color(0xFFFFFFFF), + super.intensity = 0.2, + }); + + void apply(Shader shader) { + shader.setColor('AmbientLight.color', color); + shader.setFloat('AmbientLight.intensity', intensity); + } +} diff --git a/packages/flame_3d/lib/src/resources/light/light.dart b/packages/flame_3d/lib/src/resources/light/light.dart index 64748417b41..e688caefcff 100644 --- a/packages/flame_3d/lib/src/resources/light/light.dart +++ b/packages/flame_3d/lib/src/resources/light/light.dart @@ -19,11 +19,9 @@ class Light extends Resource { required this.source, }) : super(null); - void apply(Shader shader) { - shader.setVector3('Light.position', transform.position); - // apply additional parameters - source.apply(shader); + void apply(int index, Shader shader) { + shader.setVector3('Light$index.position', transform.position); + shader.setColor('Light$index.color', source.color); + shader.setFloat('Light$index.intensity', source.intensity); } - - static UniformSlot shaderSlot = UniformSlot.value('Light', {'position'}); } diff --git a/packages/flame_3d/lib/src/resources/light/light_source.dart b/packages/flame_3d/lib/src/resources/light/light_source.dart index 3b96a99a84d..7b7992f924a 100644 --- a/packages/flame_3d/lib/src/resources/light/light_source.dart +++ b/packages/flame_3d/lib/src/resources/light/light_source.dart @@ -1,8 +1,16 @@ +import 'dart:ui' show Color; + import 'package:flame_3d/resources.dart'; /// Describes the properties of a light source. -/// There are three types of light sources: directional, point, and spot. -/// Currently only [SpotLight] is implemented. +/// There are three types of light sources: point, directional, and spot. +/// Currently only [PointLight] is implemented. abstract class LightSource { - void apply(Shader shader); + final Color color; + final double intensity; + + LightSource({ + required this.color, + required this.intensity, + }); } diff --git a/packages/flame_3d/lib/src/resources/light/lighting_info.dart b/packages/flame_3d/lib/src/resources/light/lighting_info.dart new file mode 100644 index 00000000000..f46cf77c3e3 --- /dev/null +++ b/packages/flame_3d/lib/src/resources/light/lighting_info.dart @@ -0,0 +1,48 @@ +import 'package:flame_3d/resources.dart'; + +class LightingInfo { + Iterable lights = []; + + void apply(Shader shader) { + _applyAmbientLight(shader); + _applyPointLights(shader); + } + + void _applyAmbientLight(Shader shader) { + final ambient = _extractAmbientLight(lights); + ambient.apply(shader); + } + + void _applyPointLights(Shader shader) { + final pointLights = lights.where((e) => e.source is PointLight); + final numLights = pointLights.length; + if (numLights > 3) { + // temporary, until we support dynamic arrays + throw Exception('At most 3 point lights are allowed'); + } + + shader.setUint('LightsInfo.numLights', numLights); + for (final (idx, light) in pointLights.indexed) { + light.apply(idx, shader); + } + } + + AmbientLight _extractAmbientLight(Iterable lights) { + final ambient = lights.where((e) => e.source is AmbientLight); + if (ambient.isEmpty) { + return AmbientLight(); + } + if (ambient.length > 1) { + throw Exception('At most one ambient light is allowed'); + } + return ambient.first.source as AmbientLight; + } + + static List shaderSlots = [ + UniformSlot.value('AmbientLight', {'color', 'intensity'}), + UniformSlot.value('LightsInfo', {'numLights'}), + UniformSlot.value('Light0', {'position', 'color', 'intensity'}), + UniformSlot.value('Light1', {'position', 'color', 'intensity'}), + UniformSlot.value('Light2', {'position', 'color', 'intensity'}), + ]; +} diff --git a/packages/flame_3d/lib/src/resources/light/point_light.dart b/packages/flame_3d/lib/src/resources/light/point_light.dart new file mode 100644 index 00000000000..7e60bcfe3f6 --- /dev/null +++ b/packages/flame_3d/lib/src/resources/light/point_light.dart @@ -0,0 +1,9 @@ +import 'package:flame_3d/resources.dart'; + +/// A point light that emits light in all directions equally. +class PointLight extends LightSource { + PointLight({ + required super.color, + required super.intensity, + }); +} diff --git a/packages/flame_3d/lib/src/resources/light/spot_light.dart b/packages/flame_3d/lib/src/resources/light/spot_light.dart deleted file mode 100644 index 58eef1eccad..00000000000 --- a/packages/flame_3d/lib/src/resources/light/spot_light.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flame_3d/resources.dart'; - -/// A point light that emits light in all directions equally. -class SpotLight extends LightSource { - // TODO(luanpotter): add color, intensity, etc - - @override - void apply(Shader shader) { - // - } -} diff --git a/packages/flame_3d/lib/src/resources/material/spatial_material.dart b/packages/flame_3d/lib/src/resources/material/spatial_material.dart index 2944f4187ea..dd12426b6bb 100644 --- a/packages/flame_3d/lib/src/resources/material/spatial_material.dart +++ b/packages/flame_3d/lib/src/resources/material/spatial_material.dart @@ -9,9 +9,8 @@ class SpatialMaterial extends Material { SpatialMaterial({ Texture? albedoTexture, Color albedoColor = const Color(0xFFFFFFFF), - this.metallic = 0, - this.metallicSpecular = 0.5, - this.roughness = 1.0, + this.metallic = 0.8, + this.roughness = 0.6, }) : albedoTexture = albedoTexture ?? Texture.standard, super( vertexShader: Shader( @@ -27,10 +26,9 @@ class SpatialMaterial extends Material { UniformSlot.value('Material', { 'albedoColor', 'metallic', - 'metallicSpecular', 'roughness', }), - Light.shaderSlot, + ...LightingInfo.shaderSlots, UniformSlot.value('Camera', {'position'}), ], ), @@ -53,8 +51,6 @@ class SpatialMaterial extends Material { double metallic; - double metallicSpecular; - double roughness; @override @@ -77,7 +73,6 @@ class SpatialMaterial extends Material { ..setTexture('albedoTexture', albedoTexture) ..setVector3('Material.albedoColor', _albedoCache) ..setFloat('Material.metallic', metallic) - ..setFloat('Material.metallicSpecular', metallicSpecular) ..setFloat('Material.roughness', roughness); } @@ -88,11 +83,7 @@ class SpatialMaterial extends Material { } void _applyLights(GraphicsDevice device) { - final light = device.lights.firstOrNull; - if (light == null) { - return; - } - light.apply(fragmentShader); + device.lightingInfo.apply(fragmentShader); } static final _library = gpu.ShaderLibrary.fromAsset( diff --git a/packages/flame_3d/lib/src/resources/mesh/vertex.dart b/packages/flame_3d/lib/src/resources/mesh/vertex.dart index f61e446dd26..c1fd8fac805 100644 --- a/packages/flame_3d/lib/src/resources/mesh/vertex.dart +++ b/packages/flame_3d/lib/src/resources/mesh/vertex.dart @@ -24,9 +24,8 @@ class Vertex { _storage = Float32List.fromList([ ...position.storage, // 1, 2, 3 ...texCoord.storage, // 4, 5 - ...color.storage, // 6,7,8 - // TODO(wolfenrain): fix normals not working properly - ...(normal ?? Vector3.zero()).storage, // 9, 10, 11 + ...color.storage, // 6, 7, 8, 9 + ...(normal ?? Vector3.zero()).storage, // 10, 11, 12 ]); Float32List get storage => _storage; diff --git a/packages/flame_3d/lib/src/resources/shader/shader.dart b/packages/flame_3d/lib/src/resources/shader/shader.dart index 70272352153..436edf78a66 100644 --- a/packages/flame_3d/lib/src/resources/shader/shader.dart +++ b/packages/flame_3d/lib/src/resources/shader/shader.dart @@ -1,4 +1,6 @@ import 'dart:collection'; +import 'dart:typed_data'; +import 'dart:ui'; import 'package:flame_3d/game.dart'; import 'package:flame_3d/graphics.dart'; @@ -36,8 +38,15 @@ class Shader extends Resource { /// Set a [Vector4] at the given [key] on the buffer. void setVector4(String key, Vector4 vector) => _setValue(key, vector.storage); + /// Set an [int] (encoded as uint) at the given [key] on the buffer. + void setUint(String key, int value) { + _setValue(key, _encodeUint32(value, Endian.little)); + } + /// Set a [double] at the given [key] on the buffer. - void setFloat(String key, double value) => _setValue(key, [value]); + void setFloat(String key, double value) { + _setValue(key, [value]); + } /// Set a [Matrix2] at the given [key] on the buffer. void setMatrix2(String key, Matrix2 matrix) => _setValue(key, matrix.storage); @@ -48,6 +57,8 @@ class Shader extends Resource { /// Set a [Matrix4] at the given [key] on the buffer. void setMatrix4(String key, Matrix4 matrix) => _setValue(key, matrix.storage); + void setColor(String key, Color color) => _setValue(key, color.storage); + void bind(GraphicsDevice device) { for (final slot in _slots) { _instances[slot.name]?.bind(device); @@ -91,4 +102,8 @@ class Shader extends Resource { return (_instances[keys.first], keys.elementAtOrNull(1)) as (T, String?); } + + static Float32List _encodeUint32(int value, Endian endian) { + return (ByteData(16)..setUint32(0, value, endian)).buffer.asFloat32List(); + } } diff --git a/packages/flame_3d/shaders/spatial_material.frag b/packages/flame_3d/shaders/spatial_material.frag index fe3fa27c524..511f94a8f0e 100644 --- a/packages/flame_3d/shaders/spatial_material.frag +++ b/packages/flame_3d/shaders/spatial_material.frag @@ -1,5 +1,11 @@ #version 460 core +// implementation based on https://learnopengl.com/PBR/Lighting + +// #define NUM_LIGHTS 8 +#define PI 3.14159265359 +#define EPSILON 0.0001 + in vec2 fragTexCoord; in vec4 fragColor; in vec3 fragPosition; @@ -9,48 +15,169 @@ out vec4 outColor; uniform sampler2D albedoTexture; // Albedo texture +// material info + uniform Material { vec3 albedoColor; float metallic; - float metallicSpecular; float roughness; } material; -uniform Light { +// light info + +uniform AmbientLight { + vec3 color; + float intensity; +} ambientLight; + +uniform LightsInfo { + uint numLights; +} lightsInfo; + +// uniform Light { +// vec3 position; +// vec3 color; +// float intensity; +// } lights[NUM_LIGHTS]; + +uniform Light0 { vec3 position; -} light; + vec3 color; + float intensity; +} light0; + +uniform Light1 { + vec3 position; + vec3 color; + float intensity; +} light1; + +uniform Light2 { + vec3 position; + vec3 color; + float intensity; +} light2; + +// camera info uniform Camera { vec3 position; } camera; -// Schlick GGX function -float SchlickGGX(float NdotV, float roughness) -{ - float k = (roughness * roughness) / 2.0; - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - return nom / denom; +vec3 fresnelSchlick(float cosTheta, vec3 f0) { + return f0 + (1.0 - f0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +float distributionGGX(vec3 normal, vec3 halfwayDir, float roughness) { + float a = roughness * roughness; + float a2 = a * a; + float num = a2; + + float NdotH = max(dot(normal, halfwayDir), 0.0); + float NdotH2 = NdotH * NdotH; + float b = (NdotH2 * (a2 - 1.0) + 1.0); + float denom = PI * b * b; + + return num / denom; +} + +float geometrySchlickGGX(float NdotV, float roughness) { + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +float geometrySmith(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) { + float NdotV = max(dot(normal, viewDir), 0.0); + float NdotL = max(dot(normal, lightDir), 0.0); + float ggx2 = geometrySchlickGGX(NdotV, roughness); + float ggx1 = geometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec3 processLight( + vec3 lightPos, + vec3 lightColor, + float lightIntensity, + vec3 baseColor, + vec3 normal, + vec3 viewDir, + vec3 diffuse +) { + vec3 lightDirVec = lightPos - fragPosition; + vec3 lightDir = normalize(lightDirVec); + float distance = length(lightDirVec) + EPSILON; + vec3 halfwayDir = normalize(viewDir + lightDir); + + float attenuation = lightIntensity / (distance * distance); + vec3 radiance = lightColor * attenuation; + + // cook-torrance brdf + float ndf = distributionGGX(normal, halfwayDir, material.roughness); + float g = geometrySmith(normal, viewDir, lightDir, material.roughness); + vec3 f = fresnelSchlick(max(dot(halfwayDir, viewDir), 0.0), diffuse); + + vec3 kS = f; // reflection/specular fraction + vec3 kD = (vec3(1.0) - kS) * (1.0 - material.metallic); // refraction/diffuse fraction + + vec3 numerator = ndf * g * f; + float denominator = 4.0 * max(dot(normal, viewDir), 0.0) * max(dot(normal, lightDir), 0.0) + EPSILON; + vec3 specular = numerator / denominator; + + // add to outgoing radiance Lo + float NdotL = max(dot(normal, lightDir), 0.0); + return (kD * baseColor / PI + specular) * radiance * NdotL; } void main() { - vec3 viewDir = normalize(camera.position - fragPosition); - vec3 lightDir = normalize(light.position - fragPosition); - vec3 halfwayDir = normalize(viewDir + lightDir); + vec3 normal = normalize(fragNormal); + vec3 viewDir = normalize(camera.position - fragPosition); + + vec3 baseColor = material.albedoColor; + baseColor *= texture(albedoTexture, fragTexCoord).rgb; + + vec3 baseAmbient = vec3(0.03) * baseColor * ambientLight.color * ambientLight.intensity; + vec3 ao = vec3(1.0); // white - no ambient occlusion for now + vec3 ambient = baseAmbient * baseColor * ao; + + vec3 f0 = vec3(0.04); + vec3 diffuse = mix(f0, baseColor, material.metallic); + + vec3 lo = vec3(0.0); + + if (lightsInfo.numLights > 0) { + vec3 light0Pos = light0.position; + vec3 light0Color = light0.color; + float light0Intensity = light0.intensity; + + lo += processLight(light0Pos, light0Color, light0Intensity, baseColor, normal, viewDir, diffuse); + } + + if (lightsInfo.numLights > 1) { + vec3 light1Pos = light1.position; + vec3 light1Color = light1.color; + float light1Intensity = light1.intensity; + + lo += processLight(light1Pos, light1Color, light1Intensity, baseColor, normal, viewDir, diffuse); + } - vec3 normal = normalize(fragNormal); - float NdotV = max(dot(normal, viewDir), 0.0); - float fresnel = SchlickGGX(NdotV, material.roughness); + if (lightsInfo.numLights > 2) { + vec3 light2Pos = light2.position; + vec3 light2Color = light2.color; + float light2Intensity = light2.intensity; - float NdotL = max(dot(normal, lightDir), 0.0); - float NdotH = max(dot(normal, halfwayDir), 0.0); - float specular = SchlickGGX(NdotL, material.roughness) * SchlickGGX(NdotH, material.roughness); + lo += processLight(light2Pos, light2Color, light2Intensity, baseColor, normal, viewDir, diffuse); + } - vec3 baseColor = material.albedoColor; - baseColor *= texture(albedoTexture, fragTexCoord).rgb; + vec3 color = ambient + lo; - vec3 diffuse = mix(baseColor, vec3(0.04, 0.04, 0.04), material.metallic); - vec3 finalColor = (diffuse + specular * material.metallicSpecular) * NdotL * fresnel; + color = color / (color + vec3(1.0)); + color = pow(color, vec3(1.0 / 2.2)); - outColor = vec4(finalColor, 1.0); + outColor = vec4(color, 1.0); } \ No newline at end of file