diff --git a/.vscode/launch.json b/.vscode/launch.json index 6a639b7..cba0ece 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "(gdb) app", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/src/out/dbg/carcockpit", + "program": "${workspaceFolder}/src/out/dbg/carcockpit-opengles-xorg", "args": ["--window", "--res-path=../res"], "stopAtEntry": false, "cwd": "${workspaceFolder}/src", diff --git a/src/carcockpit/shaders/shader_pbr.cpp b/src/carcockpit/shaders/shader_pbr.cpp index 57a1b1a..bc72540 100644 --- a/src/carcockpit/shaders/shader_pbr.cpp +++ b/src/carcockpit/shaders/shader_pbr.cpp @@ -30,7 +30,7 @@ along with this program. If not, see . using namespace ruis::render; -constexpr ruis::vec4 default_light_position{5.0f, 5.0f, 5.0f, 1.0f}; +constexpr ruis::vec3 default_light_position{5.0f, 5.0f, 5.0f}; constexpr ruis::vec3 default_light_intensity{2.0f, 2.0f, 2.0f}; shader_pbr::shader_pbr() : @@ -44,10 +44,13 @@ shader_pbr::shader_pbr() : uniform highp mat4 matrix; // mvp matrix uniform highp mat4 mat4_mv; // modelview matrix + + // TODO: remove? uniform highp mat4 mat4_p; // projection matrix + uniform highp mat3 mat3_n; // normal matrix (mat3) - uniform vec4 light_position; + uniform vec3 light_position; uniform vec3 light_intensity; varying highp vec3 light_dir; @@ -57,23 +60,27 @@ shader_pbr::shader_pbr() : void main() { // Transform normal and tangent to eye space - vec3 norm = normalize(mat3_n * a2); - vec3 tang = normalize(mat3_n * a3); - vec3 binormal = normalize(mat3_n * a4); - // Matrix for transformation to tangent space - mat3 mat3_to_local = mat3( tang.x, binormal.x, norm.x, - tang.y, binormal.y, norm.y, - tang.z, binormal.z, norm.z ) ; - // Get the position in eye coordinates - vec3 pos = vec3( mat4_mv * a0 ); - // Transform light dir. and view dir. to tangent space - light_dir = normalize( mat3_to_local * (light_position.xyz - pos) ); - view_dir = mat3_to_local * normalize(-pos); - // Pass along the texture coordinate + vec3 normal = normalize(mat3_n * a2); + vec3 tangent = normalize(mat3_n * a3); + vec3 bitangent = normalize(mat3_n * a4); + + // matrix for transformation to tangent space + mat3 mat3_to_tangent = mat3( + tangent.x, bitangent.x, normal.x, + tangent.y, bitangent.y, normal.y, + tangent.z, bitangent.z, normal.z + ); + + // get the position in eye coordinates + vec3 pos = vec3( mat4_mv * a0 ); + + // Transform light direction and view direction to tangent space + light_dir = normalize( mat3_to_tangent * (light_position - pos) ); + view_dir = mat3_to_tangent * normalize(-pos); + tc = vec2(a1.x, 1.0 - a1.y); gl_Position = matrix * a0; - } - + } )qwertyuiop", R"qwertyuiop( precision highp float; @@ -87,14 +94,21 @@ shader_pbr::shader_pbr() : uniform sampler2D texture2; // roughness map tex uniform samplerCube texture3; // cube map - uniform vec4 light_position; + uniform vec3 light_position; uniform vec3 light_intensity; - const vec3 Kd = vec3(0.5, 0.5, 0.5); // Diffuse reflectivity - const vec3 Ka = vec3(0.1, 0.1, 0.1); // Ambient reflectivity - const vec3 Ks = vec3(0.7, 0.7, 0.7); // Specular reflectivity - - vec3 phong_model( vec3 norm, vec3 diffuse_reflectivity, float ambient_occlusion, float glossiness, float metalness ) + const vec3 Kd = vec3(0.5, 0.5, 0.5); // Diffuse reflectivity + const vec3 Ka = vec3(0.1, 0.1, 0.1); // Ambient reflectivity + const vec3 Ks = vec3(0.7, 0.7, 0.7); // Specular reflectivity + + // TODO: is it still called Phong? + vec3 phong_model( + vec3 norm, + vec3 diffuse_reflectivity, + float ambient_occlusion, + float glossiness, + float metalness + ) { vec3 r_env = reflect( view_dir, norm ); vec3 env_refl = textureCube( texture3, r_env).xyz; @@ -102,9 +116,9 @@ shader_pbr::shader_pbr() : vec3 r = reflect( -light_dir, norm ); float sDotN = max( dot(light_dir, norm) , 0.0 ); - vec3 ambient = (Ka) * light_intensity; - vec3 diffuse = max( Kd - metalness, 0.0 ) * sDotN * light_intensity; - vec3 spec = Ks * pow( max( dot(r, view_dir), 0.0 ), glossiness ) * light_intensity; + vec3 ambient = light_intensity * Ka; + vec3 diffuse = light_intensity * max( Kd - metalness, 0.0 ) * sDotN; + vec3 spec = light_intensity * Ks * pow( max( dot(r, view_dir), 0.0 ), glossiness ); return (( ambient + diffuse ) * diffuse_reflectivity + spec ) + (env_refl * metalness); } @@ -114,7 +128,9 @@ shader_pbr::shader_pbr() : vec3 arm = texture2D( texture2, tc ).xyz; float gloss = ((1.0 - pow(arm.y, 0.2) ) * 100.0 + 1.0 ); + // TODO: why multiply by 2 and subtract 1? Add comment. vec4 normal4 = 2.0 * texture2D( texture1, tc ) - 1.0; + vec3 normal = normalize(normal4.xyz); normal = vec3(normal.x, -normal.y, normal.z); @@ -129,7 +145,7 @@ shader_pbr::shader_pbr() : sampler_cube(this->get_uniform("texture3")), mat4_modelview(this->get_uniform("mat4_mv")), mat3_normal(this->get_uniform("mat3_n")), - vec4_light_position(this->get_uniform("light_position")), + vec3_light_position(this->get_uniform("light_position")), vec3_light_intensity(this->get_uniform("light_intensity")) {} @@ -137,7 +153,7 @@ void shader_pbr::render( const ruis::render::vertex_array& va, const r4::matrix4& mvp, const r4::matrix4& modelview, - const r4::matrix4& projection, + const r4::matrix4& projection, // TODO: remove? const ruis::render::texture_2d& tex_color, const ruis::render::texture_2d& tex_normal, const ruis::render::texture_2d& tex_roughness, @@ -169,7 +185,7 @@ void shader_pbr::render( normal.invert(); normal.transpose(); - this->set_uniform4f(this->vec4_light_position, light_pos[0], light_pos[1], light_pos[2], light_pos[3]); + this->set_uniform3f(this->vec3_light_position, light_pos[0], light_pos[1], light_pos[2]); this->set_uniform3f(this->vec3_light_intensity, light_int[0], light_int[1], light_int[2]); this->set_uniform_matrix4f(this->mat4_modelview, modelview); this->set_uniform_matrix3f(mat3_normal, normal); diff --git a/src/carcockpit/shaders/shader_pbr.hpp b/src/carcockpit/shaders/shader_pbr.hpp index 2a572bb..5a6b3ca 100644 --- a/src/carcockpit/shaders/shader_pbr.hpp +++ b/src/carcockpit/shaders/shader_pbr.hpp @@ -49,10 +49,11 @@ class shader_pbr : public ruis::render::opengles::shader_base GLint mat4_modelview; GLint mat3_normal; - GLint vec4_light_position; + GLint vec3_light_position; GLint vec3_light_intensity; shader_pbr(); + void render( const ruis::render::vertex_array& va, const r4::matrix4& mvp, diff --git a/src/carcockpit/shaders/shader_phong.cpp b/src/carcockpit/shaders/shader_phong.cpp index e7a736e..bacc557 100644 --- a/src/carcockpit/shaders/shader_phong.cpp +++ b/src/carcockpit/shaders/shader_phong.cpp @@ -50,8 +50,8 @@ shader_phong::shader_phong() : void main(void) { tc0 = vec2(a1.x, 1.0 - a1.y); - norm = normalize( mat3_n * a2 ); - pos = vec3( mat4_mv * a0 ); + norm = normalize( mat3_n * a2 ); + pos = vec3( mat4_mv * a0 ); gl_Position = matrix * a0; } )qwertyuiop", diff --git a/src/ruis/render/scene/gltf_loader.cpp b/src/ruis/render/scene/gltf_loader.cpp index 69c8a03..80675d3 100644 --- a/src/ruis/render/scene/gltf_loader.cpp +++ b/src/ruis/render/scene/gltf_loader.cpp @@ -630,10 +630,10 @@ utki::shared_ref gltf_loader::create_vao_with_tangen std::vector bitangents; // texture y-axis tangents.resize(num_vertices); - std::fill(tangents.begin(), tangents.end(), ruis::vec3(0, 0, 0)); + std::ranges::fill(tangents, ruis::vec3(0, 0, 0)); bitangents.resize(num_vertices); - std::fill(bitangents.begin(), bitangents.end(), ruis::vec3(0, 0, 0)); + std::ranges::fill(bitangents, ruis::vec3(0, 0, 0)); // Calculate the vertex tangents and bitangents. for (uint32_t i = 0; i < num_triangles; ++i) { @@ -652,30 +652,51 @@ utki::shared_ref gltf_loader::create_vao_with_tangen auto edge1 = p1 - p0; auto edge2 = p2 - p0; - auto tex_edge_1 = t1 - t0; - auto tex_edge_2 = t2 - t0; - - // Calculate the triangle face tangent and bitangent. - - auto det = tex_edge_1.cross(tex_edge_2); - - constexpr auto epsilon = ruis::real(1e-6f); + auto tex_edge1 = t1 - t0; + auto tex_edge2 = t2 - t0; + + // Calculate the triangle tangent and bitangent. + // + // We want to map vectors (1, 0) and (0, 1) from texture space to 3d space (to tangent and bitangent vectors). + // + // vec2 te1, te2 : triangle edges in texture space + // vec3 e1, e2 : triangle edges in 3d space + // + // Vectors (1, 0) and (0, 1) as a linear combination of te1 and te2: + // + // (1, 0) = te1 * a11 + te2 * a21 + // (0, 1) = te1 * a12 + te2 * a22 + // + // We need to solve these equations for a11, a12, a21, a22. The system of equations can be written in matrix + // form: + // + // | te1.x te2.x | * | a11 a12 | = | 1 0 | <-> T * A = I + // | te1.y te2.y | | a21 a22 | | 0 1 | + // + // Solving this matrix equation is actually finding a right inverse matrix A for matrix T. + // + // Then, tangent and bitangent vectors are linear combinations of e1 and e2 with same aXX coefficients. + // + // tangent = e1 * a11 + e2 * a21 + // bitangent = e1 * a12 + e2 * a22 + + auto t = r4::matrix( + tex_edge1, // row 0 + tex_edge2 // row 1 + ) + .transpose(); // rows become columns + + constexpr auto epsilon = ruis::real(1e-5f); auto tangent = ruis::vec3(1, 0, 0); auto bitangent = ruis::vec3(0, 1, 0); using std::abs; - if (abs(det) >= epsilon) { - auto det_reciprocal = ruis::real(1) / det; - - // TODO: figure out what is happening here and refactor with vector ops - tangent[0] = (tex_edge_2[1] * edge1[0] - tex_edge_1[1] * edge2[0]) * det_reciprocal; - tangent[1] = (tex_edge_2[1] * edge1[1] - tex_edge_1[1] * edge2[1]) * det_reciprocal; - tangent[2] = (tex_edge_2[1] * edge1[2] - tex_edge_1[1] * edge2[2]) * det_reciprocal; + if (abs(t.det()) >= epsilon) { + auto a = t.inv(); - bitangent[0] = (-tex_edge_2[0] * edge1[0] + tex_edge_1[0] * edge2[0]) * det_reciprocal; - bitangent[1] = (-tex_edge_2[0] * edge1[1] + tex_edge_1[0] * edge2[1]) * det_reciprocal; - bitangent[2] = (-tex_edge_2[0] * edge1[2] + tex_edge_1[0] * edge2[2]) * det_reciprocal; + tangent = edge1 * a[0][0] + edge2 * a[1][0]; + bitangent = edge1 * a[0][1] + edge2 * a[1][1]; } // Accumulate the tangents and bitangents. @@ -691,25 +712,23 @@ utki::shared_ref gltf_loader::create_vao_with_tangen // Orthogonalize and normalize the vertex tangents. for (uint32_t i = 0; i < num_vertices; ++i) { - // Gram-Schmidt orthogonalize tangent with normal. + // Tangent and bitangent are not necessarily ortogonal to each other, but those have to be + // ortogonal to the normal. - auto n_dot_t = normals[i] * tangents[i]; // dot product + // Ortogonalize the tangent and bitangent to the normal. - tangents[i] -= normals[i] * n_dot_t; + tangents[i] -= normals[i].dot(tangents[i]) * normals[i]; + bitangents[i] -= normals[i].dot(bitangents[i]) * normals[i]; tangents[i].normalize(); + bitangents[i].normalize(); - ruis::vec3 bitangent_other = normals[i].cross(tangents[i]); - - auto b_dot_b = bitangent_other * bitangents[i]; - - auto sign = b_dot_b < ruis::real(0) ? ruis::real(-1) : ruis::real(1); - - bitangents[i] = bitangent_other * sign; + // TODO: The triangle in texture space can be wound in different direction than in object space, + // need to flip the tangent basis to make normals from normal map point towards triangle normal direction. } - auto tangents_vbo = factory_v.create_vertex_buffer(utki::make_span(tangents)); - auto bitangents_vbo = factory_v.create_vertex_buffer(utki::make_span(bitangents)); + auto tangents_vbo = factory_v.create_vertex_buffer(tangents); + auto bitangents_vbo = factory_v.create_vertex_buffer(bitangents); auto vao = factory_v.create_vertex_array( {utki::shared_ref(position_accessor.get().vbo),