summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2021-05-12 20:49:48 -0400
committerDavid Thompson <dthompson2@worcester.edu>2021-05-12 20:49:48 -0400
commitf88da6fb6d50568d8d1889cca3f9ed32c4451a06 (patch)
treebccf5fb2b2bd947a0c2a173d8f9ca7657907d8e3
parent21ab482f62b6f12469b56e7533a1b41d0ff7a0cd (diff)
graphics: model: Add support for dynamic lights in PBR models.
-rw-r--r--chickadee/graphics/model.scm45
-rw-r--r--chickadee/graphics/pbr.scm8
-rw-r--r--data/shaders/pbr-frag.glsl138
-rw-r--r--data/shaders/pbr-vert.glsl9
4 files changed, 131 insertions, 69 deletions
diff --git a/chickadee/graphics/model.scm b/chickadee/graphics/model.scm
index e291f39..2b500c7 100644
--- a/chickadee/graphics/model.scm
+++ b/chickadee/graphics/model.scm
@@ -32,6 +32,7 @@
#:use-module (chickadee graphics color)
#:use-module (chickadee graphics depth)
#:use-module (chickadee graphics engine)
+ #:use-module (chickadee graphics light)
#:use-module (chickadee graphics pbr)
#:use-module (chickadee graphics phong)
#:use-module (chickadee graphics shader)
@@ -65,18 +66,28 @@
;;;
(define-record-type <render-state>
- (%make-render-state renderer view-matrix model-matrix world-model-matrix)
+ (%make-render-state renderer view-matrix model-matrix world-model-matrix
+ camera-position lights ambient-light-color)
render-state?
(renderer render-state-renderer)
(view-matrix render-state-view-matrix)
(model-matrix render-state-model-matrix)
- (world-model-matrix render-state-world-model-matrix))
+ (world-model-matrix render-state-world-model-matrix)
+ (camera-position render-state-camera-position)
+ (lights render-state-lights)
+ (ambient-light-color render-state-ambient-light-color
+ set-render-state-ambient-light-color!))
+
+(define %default-ambient-light-color (make-color 0.1 0.1 0.1 1.0))
(define (make-render-state renderer)
(%make-render-state renderer
(make-identity-matrix4)
(make-identity-matrix4)
- (make-identity-matrix4)))
+ (make-identity-matrix4)
+ (vec3 0.0 0.0 0.0)
+ (make-vector %max-lights %disabled-light)
+ %default-ambient-light-color))
(define (render-state-reset! state)
(matrix4-identity! (render-state-view-matrix state))
@@ -95,6 +106,22 @@
matrix
(render-state-model-matrix state)))
+(define (set-render-state-camera-position! state position)
+ (vec3-copy! position (render-state-camera-position state)))
+
+(define (set-render-state-lights! state lights)
+ (let ((lv (render-state-lights state)))
+ (let loop ((i 0)
+ (lights lights))
+ (when (< i %max-lights)
+ (match lights
+ (()
+ (vector-set! lv i %disabled-light)
+ (loop (+ i 1) '()))
+ ((light . rest)
+ (vector-set! lv i light)
+ (loop (+ i 1) rest)))))))
+
;;;
;;; Primitive
@@ -118,7 +145,10 @@
(shader-apply/pbr (primitive-vertex-array primitive)
(primitive-material primitive)
(render-state-world-model-matrix state)
- (render-state-view-matrix state)))
+ (render-state-view-matrix state)
+ (render-state-camera-position state)
+ (render-state-lights state)
+ (render-state-ambient-light-color state)))
;;;
@@ -190,12 +220,17 @@
(define %depth-test (make-depth-test))
-(define (draw-model model model-matrix view-matrix)
+(define* (draw-model model model-matrix view-matrix camera-position #:key
+ (lights '())
+ (ambient-light-color %default-ambient-light-color))
(with-graphics-state ((g:depth-test %depth-test))
(let ((state (model-render-state model)))
(render-state-reset! state)
(render-state-view-matrix-mult! state view-matrix)
(render-state-model-matrix-mult! state model-matrix)
+ (set-render-state-camera-position! state camera-position)
+ (set-render-state-lights! state lights)
+ (set-render-state-ambient-light-color! state ambient-light-color)
;; TODO: Support drawing non-default scenes.
(draw-model-node (model-default-scene model) state))))
diff --git a/chickadee/graphics/pbr.scm b/chickadee/graphics/pbr.scm
index 836f8ee..0977579 100644
--- a/chickadee/graphics/pbr.scm
+++ b/chickadee/graphics/pbr.scm
@@ -120,7 +120,8 @@
(load-shader (scope-datadir "shaders/pbr-vert.glsl")
(scope-datadir "shaders/pbr-frag.glsl")))
-(define (shader-apply/pbr vertex-array material model-matrix view-matrix)
+(define (shader-apply/pbr vertex-array material model-matrix view-matrix
+ camera-position lights ambient-light-color)
(let* ((shader (graphics-variable-ref pbr-shader))
(vattrs (vertex-array-attributes vertex-array))
(sattrs (shader-attributes shader)))
@@ -143,4 +144,7 @@
(assv-ref vattrs
(attribute-location
(hash-ref sattrs "color0"))))
- #:material material))))
+ #:camera-position camera-position
+ #:material material
+ #:ambient-light-color ambient-light-color
+ #:lights lights))))
diff --git a/data/shaders/pbr-frag.glsl b/data/shaders/pbr-frag.glsl
index 73a6840..b025bb7 100644
--- a/data/shaders/pbr-frag.glsl
+++ b/data/shaders/pbr-frag.glsl
@@ -13,7 +13,6 @@ struct Material {
vec3 normalFactor;
bool normalTextureEnabled;
int normalTexcoord;
- vec3 occlusionFactor;
bool occlusionTextureEnabled;
int occlusionTexcoord;
vec3 emissiveFactor;
@@ -23,16 +22,23 @@ struct Material {
float alphaCutoff;
};
+struct Light {
+ bool enabled;
+ int type;
+ vec3 position;
+ vec3 direction;
+ vec4 color;
+ float cutOff;
+};
+
#ifdef GLSL120
attribute vec3 fragWorldPos;
-attribute vec3 fragCamPos;
attribute vec3 fragNormal;
attribute vec2 fragTexcoord0;
attribute vec2 fragTexcoord1
attribute vec4 fragColor0;
#else
in vec3 fragWorldPos;
-in vec3 fragCamPos;
in vec3 fragNormal;
in vec2 fragTexcoord0;
in vec2 fragTexcoord1;
@@ -44,7 +50,10 @@ out vec4 fragColor;
#endif
uniform Material material;
+uniform Light lights[4];
+uniform vec4 ambientLightColor;
uniform bool vertexColored;
+uniform vec3 cameraPosition;
uniform sampler2D baseColorTexture;
uniform sampler2D metallicRoughnessTexture;
uniform sampler2D normalTexture;
@@ -53,16 +62,6 @@ uniform sampler2D emissiveTexture;
const float PI = 3.14159265359;
-vec4 sampleTexture(sampler2D tex, bool enabled, int texcoord, vec3 factor, vec4 defaultColor) {
- if(enabled && texcoord == 0) {
- return texture(tex, fragTexcoord0) * vec4(factor, 1.0);
- } else if(enabled && texcoord == 1) {
- return texture(tex, fragTexcoord1) * vec4(factor, 1.0);
- } else {
- return defaultColor;
- }
-}
-
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
@@ -130,12 +129,19 @@ vec2 texcoord(int i) {
}
}
+vec4 sRGBtoLinear(vec4 srgb) {
+ return vec4(pow(srgb.r, 2.2),
+ pow(srgb.g, 2.2),
+ pow(srgb.b, 2.2),
+ srgb.a);
+}
+
float materialMetallic() {
float m = material.metallicFactor;
if(material.metallicRoughnessTextureEnabled) {
- m *= texture2D(metallicRoughnessTexture,
- texcoord(material.metallicRoughnessTexcoord)).b;
+ m *= texture(metallicRoughnessTexture,
+ texcoord(material.metallicRoughnessTexcoord)).b;
}
return m;
@@ -145,8 +151,8 @@ float materialRoughness() {
float r = material.roughnessFactor;
if(material.metallicRoughnessTextureEnabled) {
- r *= texture2D(metallicRoughnessTexture,
- texcoord(material.metallicRoughnessTexcoord)).g;
+ r *= texture(metallicRoughnessTexture,
+ texcoord(material.metallicRoughnessTexcoord)).g;
}
return r;
@@ -156,8 +162,9 @@ vec4 materialAlbedo() {
vec4 color = vec4(0.0, 0.0, 1.0, 1.0);
if(material.baseColorTextureEnabled) {
- color = texture2D(baseColorTexture,
- texcoord(material.baseColorTexcoord));
+ vec4 texColor = texture(baseColorTexture,
+ texcoord(material.baseColorTexcoord));
+ color = sRGBtoLinear(texColor);
}
color *= vec4(material.baseColorFactor, 1.0);
@@ -169,23 +176,33 @@ vec4 materialAlbedo() {
return color;
}
-vec3 materialOcclusion() {
- vec3 color = vec3(0.0);
+vec4 materialEmissive() {
+ vec4 color = vec4(0.0);
- if(material.occlusionTextureEnabled) {
- color = texture2D(occlusionTexture,
- texcoord(material.occlusionTexcoord)).rgb;
+ if(material.emissiveTextureEnabled) {
+ vec4 texColor = texture(emissiveTexture,
+ texcoord(material.emissiveTexcoord));
+ color = sRGBtoLinear(texColor);
}
- return color * material.occlusionFactor;
+ return color * vec4(material.emissiveFactor, 1.0);
+}
+
+vec3 materialOcclusion() {
+ if(material.occlusionTextureEnabled) {
+ return vec3(texture(occlusionTexture,
+ texcoord(material.occlusionTexcoord)).r);
+ } else {
+ return vec3(1.0);
+ }
}
vec3 materialNormal() {
vec3 normal;
if(material.normalTextureEnabled) {
- normal = texture2D(normalTexture,
- texcoord(material.normalTexcoord)).rgb * 2.0 - 1.0;
+ normal = texture(normalTexture,
+ texcoord(material.normalTexcoord)).rgb * 2.0 - 1.0;
} else {
normal = fragNormal;
}
@@ -193,38 +210,52 @@ vec3 materialNormal() {
return normalize(normal);
}
-struct PointLight {
- vec3 position;
- vec3 color;
-};
-
-PointLight lights[1];
-vec3 ambientLightColor = vec3(0.05);
-
void main(void) {
- // TODO: Support user supplied lights.
- lights[0].position = vec3(0.0, 0.2, 2.0);
- lights[0].color = vec3(1.0, 1.0, 1.0);
-
float metallic = materialMetallic();
float roughness = materialRoughness();
vec3 N = materialNormal();
- vec3 V = normalize(-fragCamPos - fragWorldPos);
- vec3 albedo = materialAlbedo().rgb;
+ vec3 V = normalize(cameraPosition - fragWorldPos);
+ vec4 rawAlbedo = materialAlbedo();
+ vec3 albedo = rawAlbedo.rgb;
vec3 ao = materialOcclusion();
vec3 F0 = mix(vec3(0.4), albedo, metallic);
// reflectance equation
vec3 Lo = vec3(0.0);
- for(int i = 0; i < 1; ++i)
+ for(int i = 0; i < 4; ++i)
{
- PointLight light = lights[i];
+ Light light = lights[i];
+
+ if(!light.enabled) {
+ continue;
+ }
+
+ vec3 L;
+ vec3 radiance;
+
// calculate per-light radiance
- vec3 L = normalize(light.position - fragWorldPos);
+ if(light.type == 0) { // point light
+ L = normalize(light.position - fragWorldPos);
+ float distance = length(light.position - fragWorldPos);
+ float attenuation = 1.0 / (distance * distance);
+ radiance = light.color.rgb * attenuation;
+ } else if(light.type == 1) { // directional light
+ L = normalize(-light.direction);
+ radiance = light.color.rgb;
+ } else if(light.type == 2) { // spotlight
+ L = normalize(light.position - fragWorldPos);
+ float theta = dot(L, normalize(-light.direction));
+
+ if(theta > light.cutOff) {
+ float distance = length(light.position - fragWorldPos);
+ float attenuation = 1.0 / (distance * distance);
+ radiance = light.color.rgb * attenuation;
+ } else {
+ continue;
+ }
+ }
+
vec3 H = normalize(V + L);
- float distance = length(light.position - fragWorldPos);
- float attenuation = 1.0 / (distance * distance);
- vec3 radiance = light.color * attenuation;
// cook-torrance brdf
float NDF = DistributionGGX(N, H, roughness);
@@ -244,23 +275,18 @@ void main(void) {
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
- // TODO: Process emissive color properly.
- Lo += sampleTexture(emissiveTexture,
- material.emissiveTextureEnabled,
- material.emissiveTexcoord,
- material.emissiveFactor,
- vec4(0.0, 0.0, 0.0, 0.0)).xyz;
+ // Add emissive color.
+ Lo += materialEmissive().rgb;
// Apply ambient lighting.
- vec3 ambient = ambientLightColor * albedo * ao;
+ vec3 ambient = ambientLightColor.xyz * albedo * ao;
vec3 color = ambient + Lo;
// Apply HDR.
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0 / 2.2));
- // TODO: Preserve alpha channel throughout lighting.
- vec4 finalColor = applyAlpha(vec4(color, 1.0));
+ vec4 finalColor = applyAlpha(vec4(color, rawAlbedo.a));
#ifdef GLSL330
fragColor = finalColor;
diff --git a/data/shaders/pbr-vert.glsl b/data/shaders/pbr-vert.glsl
index 530c564..bf43e81 100644
--- a/data/shaders/pbr-vert.glsl
+++ b/data/shaders/pbr-vert.glsl
@@ -21,14 +21,12 @@ attribute vec4 color0;
#ifdef GLSL120
varying vec3 fragWorldPos;
-varying vec3 fragCamPos;
varying vec3 fragNormal;
varying vec2 fragTexcoord0;
varying vec2 fragTexcoord1;
varying vec4 fragColor0;
#else
out vec3 fragWorldPos;
-out vec3 fragCamPos;
out vec3 fragNormal;
out vec2 fragTexcoord0;
out vec2 fragTexcoord1;
@@ -40,11 +38,10 @@ uniform mat4 view;
uniform mat4 projection;
void main(void) {
- fragWorldPos = (model * vec4(position.xyz, 1.0)).xyz;
- fragCamPos = (view * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
- fragNormal = normal;
+ fragWorldPos = vec3(model * vec4(position, 1.0));
+ fragNormal = mat3(model) * normal;
fragTexcoord0 = texcoord0;
fragTexcoord1 = texcoord1;
fragColor0 = color0;
- gl_Position = projection * view * model * vec4(position.xyz, 1.0);
+ gl_Position = projection * view * vec4(fragWorldPos, 1.0);
}