How to update normals for correct lighting when transforming vertex buffer instances

Hello,
I am following the guide for drawing multiple vertex buffer instances.

In my code below, I am rendering 3 cubes using the same geometry, but each has a different rotation and translation:

fun main() = application {

    configure {
        width = 800
        height = 800
    }

    program {

        val boxGeometry = boxMesh(10.0, 10.0, 10.0)
        val transforms = vertexBuffer(vertexFormat {
            attribute("transform", VertexElementType.MATRIX44_FLOAT32)
        }, 3)

        extend(Orbital()) { eye = Vector3(0.0, 5.0, -30.0) }
        extend(Screenshots())

        extend {
            drawer.shadeStyle = shadeStyle {
                vertexTransform ="""
                    x_viewMatrix *= i_transform;
                """.trimIndent()

                fragmentTransform = """
                    float dot_prod = dot(-va_normal, p_light);
                    float normalized = (1 + dot_prod) / 2;
                    float light = clamp(normalized, 0.0, 1.0);
                    x_fill.rgb = vec3(light);
                """.trimIndent()
                parameter("light", Vector3(0.0, -1.0, 1.0))
            }

            transforms.put {
                for (i in 0 until transforms.vertexCount) {
                    val transform = transform {
                        translate(-15.0 + i * 15.0, 0.0, 0.0)
                        rotate(Vector3.UNIT_X, i * seconds * 8)
                    }
                    write(transform)
                }
            }

            drawer.vertexBufferInstances(
                    listOf(boxGeometry),
                    listOf(transforms),
                    DrawPrimitive.TRIANGLES,
                    transforms.vertexCount
            )
        }
    }
}

From the screenshot, the lighting is not applied correctly (it is rotating with the geometry).
I think I need to update the x_normal but I’m not sure how to do this based on the shader section of the guide.
Thanks for any advice.

EDIT:
It mostly works as expected if I change the shader code to this:

    drawer.shadeStyle = shadeStyle {
        vertexTransform ="""
            x_viewMatrix *= i_transform;
            x_viewNormalMatrix *= i_transform;
        """.trimIndent()

        fragmentTransform = """
            x_fill.rgb *= v_viewNormal.y;
        """.trimIndent()
        parameter("light", Vector3(0.0, -1.0, 1.0))
    }

Still not sure if this is the recommended approach.

Find out “One last thing” at https://learnopengl.com/Lighting/Basic-Lighting maybe that helps?

1 Like

I will write a longer reply when I can find the time. The idea is that you update x_modelMatrix and x_modelNormalMatix (likely works with the view versions too). However, for the normal matrix you need to multiply with a transform matrix that has translation and scale removed. You can calculate this matrix in the vertexTransform, but it requires an inverse (for a general case solution) and may be a bit computationally heavy. I usually add a second matrix to the instance attributes that is set to normalMatrix(someMatrix44)

1 Like

@edwin @abe thanks for the tips!
It works as expected by using the x_modelNormalMatrix and v_worldNormal:

        drawer.shadeStyle = shadeStyle {
            vertexTransform ="""
                x_viewMatrix *= i_transform;
                x_modelNormalMatrix *= i_transform;
            """.trimIndent()

            fragmentTransform = """
                float dot_prod = dot(v_worldNormal, p_light);
                float normalized = (1 + dot_prod) / 2;
                float light = clamp(normalized, 0.0, 1.0);
                x_fill.rgb = vec3(light);
            """.trimIndent()
            parameter("light", Vector3(0.0, 1.0, 0.0).normalized)
        }

I am finding a lot of openGL resources online, but everyone seems to use different shader conventions.