2

So I have an array of OpenTK.Vector3 that I want to pass into a shader, but it seems that GL.Uniform3 has no overload for that. How should I go about doing this? I want to use an unsized array, and iterate over it in the shader

Frag Shader:

#version 330 core

uniform vec4 ambient; //ambient lightining

uniform vec3[2] lightDir; //directional light direction array
uniform vec4[2] lightColor; //light color array

smooth in vec4 fragColor;

in vec3 _normal;

out vec4 outputColor;

void main(void){
  vec4 diff = vec4(0.0f);
  
  //iterates over all lights and calculate diffuse
  for(int i = 0; i < lightDir.length(); i++) {
    diff += max(dot(_normal, normalize(-lightDir[i])), 0.0f) * lightColor[i];
  } 

  outputColor = fragColor * (diff + ambient);
}

F# Code:

    let lamps = [
    {
      direction = Vector3(0.0f, -1.0f, 0.0f)
      color = Vector4(0.0f, 0.0f, 1.0f, 1.0f)
    },
    {
      direction = Vector3(2.0f, 1.0f, 1.0f)
      color = Vector4(1.0f, 0.0f, 0.0f, 1.0f)
    }
  ]

  let ambientLocation = GL.GetUniformLocation(program, "ambient")
  GL.Uniform4(ambientLocation, Vector4(0.2f, 0.2f, 0.2f, 1.0f))

  let lightDirLocation = GL.GetUniformLocation(program, "lightDir")
  GL.Uniform3(lightDirLocation, (lamps |> List.map (fun l -> l.direction) |> List.toArray))

  let lightColorLocation = GL.GetUniformLocation(program, "lightColor")
  GL.Uniform4(lightColorLocation, (lamps |> List.map (fun l -> l.color) |> List.toArray))

The Editor informs me that Vector3 [] is not compatible with Vector3 or Vector3 ref How should I go about this?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174

2 Answers2

1

You should use this overloaded version of GL.Uniform3 which receives an array of float32 (aka single).

In this way, you must convert the array of Vector3 to an array of float32. The code could be:

GL.Uniform3(
    lightDirLocation,
    2,
    lamps |> Seq.map (fun l -> [ l.direction.X; l.direction.Y; l.direction.Z ]) |> Seq.concat |> Array.ofSeq
)

Notice the use of Seq.xxx to avoid intermediate lists/arrays (which consume memory).

Or you can use a more idiomatic code as below:

GL.Uniform3(
    lightDirLocation,
    2,
    [| for l in lamps do
        yield l.direction.X
        yield l.direction.Y
        yield l.direction.Z |]
)

In general, the less number of calls to GL.Uniform (or any function that transfers data from CPU to GPU), the better - in term of performance. Thus, whenever possible, we should collect all data into one array and issue one call to GL.Uniform. That would be more efficient than calling GL.Uniform multiple times with multiple chunks of data.


By the way, if most of the time you are using arrays which are transformed from lamps then you should declare lamps as array too: lamps = [| ... |]. So in your original code you don't have to call List.toArray at the end.

Nghia Bui
  • 3,694
  • 14
  • 21
0

A simple work around is to get the locations for the uniform variables lightDir[0], lightDir[1], lightColor[0] and lightColor[1] separately and to set them separately, too:

for i = 0 to 1 do
    let lightDirLoc = GL.GetUniformLocation(program, "lightDir[" + i.ToString() + "]")
    GL.Uniform3(lightDirLoc, lamps[i].direction);

    let lightColorLoc = GL.GetUniformLocation(program, "lightColor[" + i.ToString() + "]")
    GL.Uniform4(lightColorLoc, lamps[i].color);
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • yeah, but that doesn't scale well if I want to have multiple objects to be passed... unless I could do it in a loop you think that if I did a for loop it could work? – João Vítor Costa Jul 28 '20 at 09:52