From 0497ff9ac78c040c456ce781e50eb384f0ee68e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BENEDEK=20L=C3=A1szl=C3=B3?= Date: Fri, 18 Oct 2024 16:12:01 +0200 Subject: [PATCH] normal mapping, basic pbr --- assets/shader/deferred/deferred.fs | 5 +- assets/shader/deferred/deferred.vs | 7 ++ assets/shader/pbr/pbr.fs | 151 +++++++++++++++++++++++++++ assets/shader/pbr/pbr.vs | 13 +++ assets/shader/screen/screen-debug.fs | 63 +++++++++++ assets/shader/screen/screen.fs | 25 +++-- main.go | 26 +++-- types/gbuffer/gbuffer.go | 63 ++++++----- types/geometry/geometry.go | 104 +++++++++++++++--- 9 files changed, 402 insertions(+), 55 deletions(-) create mode 100644 assets/shader/pbr/pbr.fs create mode 100644 assets/shader/pbr/pbr.vs create mode 100644 assets/shader/screen/screen-debug.fs diff --git a/assets/shader/deferred/deferred.fs b/assets/shader/deferred/deferred.fs index f7dc3c9..06d0e16 100644 --- a/assets/shader/deferred/deferred.fs +++ b/assets/shader/deferred/deferred.fs @@ -9,6 +9,7 @@ uniform sampler2D s_metalic; in vec3 pos; in vec3 norm; in vec2 tex; +in mat3 TBN; layout (location = 0) out vec3 albedo; layout (location = 1) out vec3 normal; @@ -16,10 +17,10 @@ layout (location = 2) out vec3 material; layout (location = 3) out vec3 position; void main() { - position = pos; albedo = texture(s_albedo, tex).rgb; - normal = normalize(texture(s_normal, tex).rgb * 2.0 - 1.0); + normal = normalize(TBN * (texture(s_normal, tex).rgb * 2.0 - 1.0)); material.x = texture(s_specular, tex).x; material.y = texture(s_roughness, tex).y; material.z = texture(s_metalic, tex).z; + position = pos; } diff --git a/assets/shader/deferred/deferred.vs b/assets/shader/deferred/deferred.vs index 9183fe1..307f1d6 100644 --- a/assets/shader/deferred/deferred.vs +++ b/assets/shader/deferred/deferred.vs @@ -3,6 +3,7 @@ layout (location = 0) in vec3 _pos; layout (location = 1) in vec2 _tex; layout (location = 2) in vec3 _norm; +layout (location = 3) in vec3 _tan; uniform mat4 projection; uniform mat4 view; @@ -11,8 +12,14 @@ uniform mat4 model; out vec3 pos; out vec3 norm; out vec2 tex; +out mat3 TBN; void main() { + vec3 T = normalize(vec3(model * vec4(_tan, 0))); + vec3 N = normalize(vec3(model * vec4(_norm, 0))); + vec3 B = cross(N, T); + TBN = mat3(T, B, N); + gl_Position = projection * view * model * vec4(_pos, 1.0); pos = vec3(model * vec4(_pos, 1.0)); norm = mat3(transpose(inverse(model))) * _norm; diff --git a/assets/shader/pbr/pbr.fs b/assets/shader/pbr/pbr.fs new file mode 100644 index 0000000..97efe33 --- /dev/null +++ b/assets/shader/pbr/pbr.fs @@ -0,0 +1,151 @@ +#version 460 core + +// gbuffer textures +uniform sampler2D s_albedo; +uniform sampler2D s_depth; +uniform sampler2D s_normal; +uniform sampler2D s_material; +uniform sampler2D s_position; + +// lights +#define MAX_LIGHT_COUNT 100 +#define POINT 0 +#define SPOT 1 +#define DIRECTIONAL 2 +#define AMBIENT 3 + +struct light_t { + vec3 color; + vec3 position; + vec3 direction; + float intensity; + int type; +}; + +uniform light_t lights[MAX_LIGHT_COUNT]; +uniform int light_count; + +uniform vec3 view_pos; + +// from vertex shader +in vec3 pos; +in vec2 tex; + +out vec4 FragColor; + +vec3 albedo; +float depth; +vec3 normal; +vec3 material; // specular, roughness, metalic +vec3 position; + +// float specular; +float roughness; +float metalic; + +const float PI = 3.14159265359; + +float DistributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness*roughness; + float a2 = a*a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH*NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return nom / denom; +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +void main() { + albedo = pow(texture(s_albedo, tex).rgb, vec3(2.2)); + depth = texture(s_depth, tex).x; + normal = normalize(texture(s_normal, tex).rgb); + material = texture(s_material, tex).xyz; + { + // specular = material.x; + roughness = 1 - material.y; + metalic = material.z; + // metalic = 0; + } + position = texture(s_position, tex).xyz; + + vec3 N = normal; + vec3 V = normalize(view_pos - position); + + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metalic); + + vec3 Lo = vec3(0.0); + for(int i = 0; i < light_count; i++) + { + // calculate per-light radiance + vec3 L = normalize(lights[i].position - position); + vec3 H = normalize(V + L); + float distance = length(lights[i].position - position); + float attenuation = 1.0 / (distance * distance); + vec3 radiance = lights[i].color * lights[i].intensity * attenuation; + + // Cook-Torrance BRDF + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero + vec3 specular = numerator / denominator; + + // kS is equal to Fresnel + vec3 kS = F; + // for energy conservation, the diffuse and specular light can't + // be above 1.0 (unless the surface emits light); to preserve this + // relationship the diffuse component (kD) should equal 1.0 - kS. + vec3 kD = vec3(1.0) - kS; + // multiply kD by the inverse metalness such that only non-metals + // have diffuse lighting, or a linear blend if partly metal (pure metals + // have no diffuse light). + kD *= 1.0 - metalic; + + // scale light by NdotL + float NdotL = max(dot(N, L), 0.0); + + // add to outgoing radiance Lo + Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again + } + + vec3 ambient = vec3(0.03) * albedo * 1.0; + vec3 color = ambient + Lo; + + color = color / (color + vec3(1.0)); + color = pow(color, vec3(1.0/2.2)); + + FragColor = vec4(color, 1.0); +} diff --git a/assets/shader/pbr/pbr.vs b/assets/shader/pbr/pbr.vs new file mode 100644 index 0000000..27ddbeb --- /dev/null +++ b/assets/shader/pbr/pbr.vs @@ -0,0 +1,13 @@ +#version 460 core + +layout (location = 0) in vec3 _pos; +layout (location = 1) in vec2 _tex; + +out vec3 pos; +out vec2 tex; + +void main() { + gl_Position = vec4(_pos.xy, 0, 1); + pos = _pos; + tex = _tex; +} diff --git a/assets/shader/screen/screen-debug.fs b/assets/shader/screen/screen-debug.fs new file mode 100644 index 0000000..3a8cc4b --- /dev/null +++ b/assets/shader/screen/screen-debug.fs @@ -0,0 +1,63 @@ +#version 460 core + +// gbuffer textures +uniform sampler2D s_albedo; +uniform sampler2D s_depth; +uniform sampler2D s_normal; +uniform sampler2D s_material; +uniform sampler2D s_position; + +// lights +#define MAX_LIGHT_COUNT 100 +#define POINT 0 +#define SPOT 1 +#define DIRECTIONAL 2 +#define AMBIENT 3 + +struct light_t { + vec3 color; + vec3 position; + vec3 direction; + float intensity; + int type; +}; + +uniform light_t lights[MAX_LIGHT_COUNT]; +uniform int light_count; + +uniform vec3 view_pos; + +// from vertex shader +in vec3 pos; +in vec2 tex; + +out vec4 FragColor; + +vec3 albedo; +float depth; +vec3 normal; +vec3 material; // specular, roughness, metalic +vec3 position; + +float specular; +float roughness; +float metalic; + +void main() { + // albedo, normal + // material, position + albedo = texture(s_albedo, tex*2).rgb; + depth = texture(s_depth, tex*2).x; + normal = normalize(texture(s_normal, tex*2).rgb); + material = texture(s_material, tex*2).xyz; + { + specular = material.x; + roughness = material.y; + metalic = material.z; + } + position = texture(s_position, tex*2).xyz; + + FragColor = vec4( + (pos.x < 0 ? (pos.y < 0 ? material : albedo) : (pos.y < 0 ? position : normal)), + 1); +} diff --git a/assets/shader/screen/screen.fs b/assets/shader/screen/screen.fs index b8b00c4..08e5e52 100644 --- a/assets/shader/screen/screen.fs +++ b/assets/shader/screen/screen.fs @@ -38,35 +38,46 @@ float depth; vec3 normal; vec3 material; // specular, roughness, metalic vec3 position; +vec2 texture_coords; float specular; float roughness; float metalic; vec3 calc_light(light_t light) { + // diffuse vec3 light_dir = normalize(light.position - position); + float diff = max(dot(normal, light_dir), 0); + vec3 diffuse_light = diff * light.color * light.intensity; + + // specular + float specular_strength = 0.5; vec3 view_dir = normalize(view_pos - position); vec3 reflect_dir = reflect(-light_dir, normal); - float diffuse = max(dot(normal, light_dir), 0.0); - float specular = pow(max(dot(view_dir, reflect_dir), 0.0), 1 - roughness) * specular; + float spec = pow(max(dot(view_dir, reflect_dir), 0), 32); + vec3 specular_light = specular_strength * spec * light.color * light.intensity * specular; - return (diffuse + specular) * light.intensity * light.color / max(pow(length(light.position - position), 2), 1); + // attenuation + float dist = length(light.position - position); + float attenuation = 1 / max(pow(dist, 2), 1); + + return (diffuse_light + specular_light) * attenuation; } void main() { - albedo = texture(s_albedo, tex).rgb; + albedo = pow(texture(s_albedo, tex).rgb, vec3(2.2)); depth = texture(s_depth, tex).x; - normal = texture(s_normal, tex).rgb; + normal = normalize(texture(s_normal, tex).rgb); material = texture(s_material, tex).xyz; { specular = material.x; - roughness = material.y; + roughness = 1 - material.y; metalic = material.z; } position = texture(s_position, tex).xyz; - vec3 light_result; + vec3 light_result = vec3(0.1); for (int i = 0; i < light_count; i++) { light_result += calc_light(lights[i]); } diff --git a/main.go b/main.go index 2c2811d..1dcd07d 100644 --- a/main.go +++ b/main.go @@ -73,11 +73,13 @@ func main() { } defer deferredShader.Delete() - vertexSource, err = os.ReadFile("assets/shader/screen/screen.vs") + vertexSource, err = os.ReadFile("assets/shader/pbr/pbr.vs") if err != nil { panic(err) } - fragmentSource, err = os.ReadFile("assets/shader/screen/screen.fs") + // fragmentSource, err = os.ReadFile("assets/shader/screen/screen-debug.fs") + // fragmentSource, err = os.ReadFile("assets/shader/screen/screen.fs") + fragmentSource, err = os.ReadFile("assets/shader/pbr/pbr.fs") if err != nil { panic(err) } @@ -144,10 +146,23 @@ func main() { } defer metalic.Delete() + white, err := texture.Load("assets/textures/white.png") + if err != nil { + panic(err) + } + defer white.Delete() + + black, err := texture.Load("assets/textures/black.png") + if err != nil { + panic(err) + } + defer black.Delete() + // lights lights := []light.Light{ - {Color: [3]float32{0, 1, 0}, Position: mgl32.Vec3{3, 3, 3}, Intensity: 15, Type: light.POINT}, - {Color: [3]float32{1, 0, 1}, Position: mgl32.Vec3{3, 3, -3}, Intensity: 15, Type: light.POINT}, + {Color: [3]float32{1, 1, 1}, Position: mgl32.Vec3{0, 10, 0}, Intensity: 40, Type: light.POINT}, + {Color: [3]float32{1, 0, 0}, Position: mgl32.Vec3{5, 5, 5}, Intensity: 30, Type: light.POINT}, + {Color: [3]float32{0, 0, 1}, Position: mgl32.Vec3{-5, 5, -5}, Intensity: 30, Type: light.POINT}, } // gbuffer @@ -161,14 +176,13 @@ func main() { // transformations projection := mgl32.Perspective(mgl32.DegToRad(FOV), float32(WIDTH)/HEIGHT, 0.1, 1000) model := mgl32.Ident4() - controls := fpscontrols.Get(10, 10, mgl32.Vec3{0, 0, -10}, mgl32.Vec2{0, 0}, window) + controls := fpscontrols.Get(15, 10, mgl32.Vec3{0, 0, -10}, mgl32.Vec2{0, 0}, window) for !window.ShouldClose() { glfw.PollEvents() controls.Update() // first pass - // bind textures gl.Enable(gl.DEPTH_TEST) gbuff.Bind() diff --git a/types/gbuffer/gbuffer.go b/types/gbuffer/gbuffer.go index 6a1aa9d..f322387 100644 --- a/types/gbuffer/gbuffer.go +++ b/types/gbuffer/gbuffer.go @@ -12,7 +12,7 @@ type GBuffer struct { colorAttachments [4]uint32 // albedo, normal, material, position } -var attachments = []uint32{ +var attachments = [...]uint32{ gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, @@ -25,18 +25,23 @@ func (b *GBuffer) Bind() { } func (b *GBuffer) BindTextures() { + // albedo gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, b.colorAttachments[0]) + // depth gl.ActiveTexture(gl.TEXTURE1) gl.BindTexture(gl.TEXTURE_2D, b.depth) + // normal gl.ActiveTexture(gl.TEXTURE2) gl.BindTexture(gl.TEXTURE_2D, b.colorAttachments[1]) + // material gl.ActiveTexture(gl.TEXTURE3) gl.BindTexture(gl.TEXTURE_2D, b.colorAttachments[2]) + // position gl.ActiveTexture(gl.TEXTURE4) gl.BindTexture(gl.TEXTURE_2D, b.colorAttachments[3]) } @@ -54,14 +59,17 @@ func New(width int, height int) (buffer GBuffer, _ error) { gl.GenFramebuffers(1, &buffer.framebuffer) gl.BindFramebuffer(gl.FRAMEBUFFER, buffer.framebuffer) + var i uint32 = 0 + // albedo - gl.GenTextures(1, &buffer.colorAttachments[0]) - gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[0]) - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.UNSIGNED_BYTE, nil) + gl.GenTextures(1, &buffer.colorAttachments[i]) + gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[i]) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.FLOAT, nil) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.BindTexture(gl.TEXTURE_2D, 0) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, buffer.colorAttachments[0], 0) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+i, gl.TEXTURE_2D, buffer.colorAttachments[i], 0) + i++ // depth gl.GenTextures(1, &buffer.depth) @@ -73,31 +81,34 @@ func New(width int, height int) (buffer GBuffer, _ error) { gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, buffer.depth, 0) // normal - gl.GenTextures(1, &buffer.colorAttachments[1]) - gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[1]) - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.UNSIGNED_BYTE, nil) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) - gl.BindTexture(gl.TEXTURE_2D, 0) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, buffer.colorAttachments[1], 0) - - // material - gl.GenTextures(1, &buffer.colorAttachments[2]) - gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[2]) - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.UNSIGNED_BYTE, nil) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) - gl.BindTexture(gl.TEXTURE_2D, 0) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, buffer.colorAttachments[2], 0) - - // position - gl.GenTextures(1, &buffer.colorAttachments[3]) - gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[3]) + gl.GenTextures(1, &buffer.colorAttachments[i]) + gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[i]) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.FLOAT, nil) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.BindTexture(gl.TEXTURE_2D, 0) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT3, gl.TEXTURE_2D, buffer.colorAttachments[3], 0) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+i, gl.TEXTURE_2D, buffer.colorAttachments[i], 0) + i++ + + // material + gl.GenTextures(1, &buffer.colorAttachments[i]) + gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[i]) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.FLOAT, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.BindTexture(gl.TEXTURE_2D, 0) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+i, gl.TEXTURE_2D, buffer.colorAttachments[i], 0) + i++ + + // position + gl.GenTextures(1, &buffer.colorAttachments[i]) + gl.BindTexture(gl.TEXTURE_2D, buffer.colorAttachments[i]) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(width), int32(height), 0, gl.RGB, gl.FLOAT, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.BindTexture(gl.TEXTURE_2D, 0) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+i, gl.TEXTURE_2D, buffer.colorAttachments[i], 0) + i++ if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE { return buffer, errors.New("failed to create framebuffer") diff --git a/types/geometry/geometry.go b/types/geometry/geometry.go index 8bdc3ba..54529cb 100644 --- a/types/geometry/geometry.go +++ b/types/geometry/geometry.go @@ -1,7 +1,10 @@ package geometry import ( + "errors" + "github.com/go-gl/gl/v4.6-core/gl" + "github.com/go-gl/mathgl/mgl32" "github.com/udhos/gwob" ) @@ -9,6 +12,7 @@ import ( // position 3 * float // texture 2 * float // normal 3 * float +// tangent 3 * float type Geometry struct { vao uint32 @@ -46,18 +50,28 @@ func new(coords []float32, indicies []int) (Geometry, error) { gl.BindBuffer(gl.ARRAY_BUFFER, geometry.vbo) gl.BufferData(gl.ARRAY_BUFFER, len(coords)*4, gl.Ptr(coords), gl.STATIC_DRAW) + var i uint32 = 0 + // attributes // position - gl.VertexAttribPointerWithOffset(0, 3, gl.FLOAT, false, 8*4, 0) - gl.EnableVertexAttribArray(0) + gl.VertexAttribPointerWithOffset(i, 3, gl.FLOAT, false, 11*4, 0) + gl.EnableVertexAttribArray(i) + i++ // texture - gl.VertexAttribPointerWithOffset(1, 2, gl.FLOAT, false, 8*4, 3*4) - gl.EnableVertexAttribArray(1) + gl.VertexAttribPointerWithOffset(i, 2, gl.FLOAT, false, 11*4, 3*4) + gl.EnableVertexAttribArray(i) + i++ // normal - gl.VertexAttribPointerWithOffset(2, 3, gl.FLOAT, false, 8*4, 5*4) - gl.EnableVertexAttribArray(2) + gl.VertexAttribPointerWithOffset(2, 3, gl.FLOAT, false, 11*4, 5*4) + gl.EnableVertexAttribArray(i) + i++ + + // tangent + gl.VertexAttribPointerWithOffset(i, 3, gl.FLOAT, false, 11*4, 8*4) + gl.EnableVertexAttribArray(i) + i++ // ebo gl.GenBuffers(1, &geometry.ebo) @@ -68,23 +82,85 @@ func new(coords []float32, indicies []int) (Geometry, error) { return geometry, nil } -func LoadOBJ(path string) (geometries Geometry, err error) { +func LoadOBJ(path string) (geometry Geometry, err error) { o, err := gwob.NewObjFromFile(path, &gwob.ObjParserOptions{}) if err != nil { - return geometries, err + return geometry, err } - return new(o.Coord, o.Indices) + if !o.NormCoordFound { + return geometry, errors.New("missing normals in OBJ") + } + + if !o.TextCoordFound { + return geometry, errors.New("missing texture UVs in OBJ") + } + + // add tangent + coords := make([]float32, 0, len(o.Coord)*11) + tangents := make([][3]float32, len(o.Coord)/8) + + // iterate faces + for i := 0; i < len(o.Indices); i += 3 { + i0 := o.Indices[i+0] + i1 := o.Indices[i+1] + i2 := o.Indices[i+2] + + // positions + v0 := o.Coord[i0*8 : i0*8+3] + v1 := o.Coord[i1*8 : i1*8+3] + v2 := o.Coord[i2*8 : i2*8+3] + + // UVs + uv0 := o.Coord[i0*8+3 : i0*8+5] + uv1 := o.Coord[i1*8+3 : i1*8+5] + uv2 := o.Coord[i2*8+3 : i2*8+5] + + // edges + edge1 := [3]float32{v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]} + edge2 := [3]float32{v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]} + + // UV deltas + delta1 := [2]float32{uv1[0] - uv0[0], uv1[1] - uv0[1]} + delta2 := [2]float32{uv2[0] - uv0[0], uv2[1] - uv0[1]} + + // calculate tangents + f := 1.0 / (delta1[0]*delta2[1] - delta2[0]*delta1[1]) + tangent := [3]float32{ + f * (delta2[1]*edge1[0] - delta1[1]*edge2[0]), + f * (delta2[1]*edge1[1] - delta1[1]*edge2[1]), + f * (delta2[1]*edge1[2] - delta1[1]*edge2[2]), + } + + tangents[i0] = mgl32.Vec3(tangents[i0]).Add(tangent) + tangents[i1] = mgl32.Vec3(tangents[i1]).Add(tangent) + tangents[i2] = mgl32.Vec3(tangents[i2]).Add(tangent) + } + + // build coords + for i := 0; i < len(o.Coord)/8; i++ { + pos := o.Coord[i*8 : i*8+3] + uv := o.Coord[i*8+3 : i*8+5] + norm := o.Coord[i*8+5 : i*8+8] + tan := mgl32.Vec3(tangents[i]).Normalize() + + coords = append(coords, pos...) + coords = append(coords, uv...) + coords = append(coords, norm...) + coords = append(coords, tan[0:3]...) + } + + return new(coords, o.Indices) } func Screen() (Geometry, error) { return new( []float32{ - // xyz uv ijk - -1, -1, 0, 0, 0, 0, 0, 0, - 1, -1, 0, 1, 0, 0, 0, 0, - -1, 1, 0, 0, 1, 0, 0, 0, - 1, 1, 0, 1, 1, 0, 0, 0, + // xyz, uv, nrm, tan + -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, -1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + -1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, }, []int{ 0, 1, 2,