ospray / anari-ospray

Translation layer from ANARI to OSPRay, ANARILibrary and ANARIDevice "ospray".
Apache License 2.0
17 stars 5 forks source link

Light intensity should be in W/sr, not W/m^2 #4

Closed szellmann closed 2 years ago

szellmann commented 2 years ago

I found that when setting the intensity parameter on lights, anari-ospray would set the intensityQuantity to OSP_INTENSITY_QUANTITY_IRRADIANCE, which would correspond to units of W/m^2. According to the specs, intensity is however supposed to be in W/sr, which would correspond to OSP_INTENSITY_QUANTITY_INTENSITY. Not sure if this is deliberate, or a bug? (With irradiance being used, the light intensity oddly depends on model size, though.)

johguenther commented 2 years ago

This could be a stale state (removeParam and parameter precedence of intensity over (ir)radiance is currently not handled): Did you set something else before intensity?

szellmann commented 2 years ago

I'm setting the intensity once on construction, right after anariNewLight.

With

float intensity = 60.f;
anariSetParameter(anari.device, anari.light, "intensity", ANARI_FLOAT32, &intensity);

this is what I observe, on three different models:

dragon_intensity robot_intensity teapot_intensity

Switching to

float intensity = 60.f;
anariSetParameter(anari.device, anari.light, "radiance", ANARI_FLOAT32, &intensity);

(the difference here is that I'm using "radiance", not "intensity", as parameter name) that's what I see:

robot_radiance teapot_radiance dragon_radiance

Here's the bounding boxes for the three models:

teapot: (-3,0,-2)(3.434,3.15,2)
dragon: (-0.09362,-0.048923,-0.050414)(0.111271,0.09552,0.041207)
robot: (-1.25214,-0.000540296,-0.341388)(1.16252,1.83436,0.25572)

So the observation I made is that the perceived intensity depends on the scale of the model, as on the dragon the light seems brightest. From that I concluded that the default intensityQuantity (OSPRay's default?) would just be irradiance, as that's a area-dependent quantity. But I can't say for sure, maybe my conclusion is wrong?

I have an own device, based on my own path tracing lib, that doesn't distinguish between the three modes, just assumes they're radiance, where the renderings look the same as when setting the radiance parameter w/ ospray.

szellmann commented 2 years ago

I refactored my code into a mini reproducer:

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <anari/anari.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION 1
#include "stb_image_write.h"

#define TEAPOT 0
#define DRAGON 1

int main(int argc, char** argv) {
    // might need to adjust this a bit, for absolute paths etc.:
    assert(strncmp(argv[1],"teapot.bin",10)==0||strncmp(argv[1],"dragon.bin",10)==0);
    int model = strncmp(argv[1],"teapot.bin",10)==0 ? TEAPOT : DRAGON;

    uint32_t numVerts=0, numFaces=0;
    FILE* fp = fopen(argv[1],"rb");
    fread(&numVerts,1,sizeof(numVerts),fp);
    float *vertex = (float *)malloc(numVerts*3*sizeof(float));
    fread(vertex,1,numVerts*3*sizeof(float),fp);
    fread(&numFaces,1,sizeof(numFaces),fp);
    uint32_t *index = (uint32_t *)malloc(numFaces*3*sizeof(uint32_t));
    fread(index,1,numFaces*3*sizeof(uint32_t),fp);
    // printf("%u %u\n",numVerts,numFaces);

    ANARILibrary library = anariLoadLibrary("environment");
    ANARIDevice device = anariNewDevice(library,"default");
    anariCommit(device,device);

    // Create world w/ single surface
    ANARIWorld world = anariNewWorld(device);

    ANARIGeometry geom = anariNewGeometry(device,"triangle");
    ANARIArray1D vertexPosition = anariNewArray1D(device,vertex,0,0,
                                                  ANARI_FLOAT32_VEC3,numVerts,0);
    ANARIArray1D primitiveIndex = anariNewArray1D(device,index,0,0,
                                                  ANARI_UINT32_VEC3,numFaces,0);
    anariSetParameter(device,geom,"vertex.position",ANARI_ARRAY1D,&vertexPosition);
    anariSetParameter(device,geom,"primitive.index",ANARI_ARRAY1D,&primitiveIndex);

    anariRelease(device,vertexPosition);
    anariRelease(device,primitiveIndex);
    anariCommit(device,geom);

    ANARIMaterial mat = anariNewMaterial(device,"matte");
    float kd[] = {.8f,.8f,.8f};
    anariSetParameter(device,mat,"color",ANARI_FLOAT32_VEC3,kd);

    ANARISurface surf = anariNewSurface(device);
    anariSetParameter(device,surf,"geometry",ANARI_GEOMETRY,&geom);
    anariSetParameter(device,surf,"material",ANARI_GEOMETRY,&mat);
    anariCommit(device,surf);

    ANARIArray1D surface = anariNewArray1D(device,&surf,0,0,ANARI_SURFACE,1,0);
    anariSetParameter(device,world,"surface",ANARI_ARRAY1D,&surface);

    // Set up the light source
    ANARILight l = anariNewLight(device,"point");
    ANARIArray1D light = anariNewArray1D(device,&l,0,0,ANARI_LIGHT,1,0);
    anariSetParameter(device,world,"light",ANARI_ARRAY1D, &light);

// >>>>> ADJUST HERE!
    float intensity = 60.f;
    anariSetParameter(device,l,"intensity",ANARI_FLOAT32,&intensity);
    //anariSetParameter(device,l,"radiance",ANARI_FLOAT32,&intensity);
// >>>>>

    if (model == TEAPOT) {
        float radius = .820481f;
        float pos[] = {3.434,3.15,2};
        anariSetParameter(device,l,"radius",ANARI_FLOAT32,&radius);
        anariSetParameter(device,l,"position",ANARI_FLOAT32_VEC3,pos);
    } else if (model == DRAGON) {
        float radius = .0266905f;
        float pos[] = {.111271,.09552,.041207};
        anariSetParameter(device,l,"radius",ANARI_FLOAT32,&radius);
        anariSetParameter(device,l,"position",ANARI_FLOAT32_VEC3,pos);
    }   

    anariCommit(device,l);

    anariCommit(device,world);

    anariRelease(device,surface);

    // Frame, renderer, etc.
    ANARIRenderer renderer = anariNewRenderer(device,"default");
    float bgcolor[] = {.3f,.3f,.3f,1.f};
    anariSetParameter(device,renderer,"backgroundColor",ANARI_FLOAT32_VEC4,bgcolor);
    anariCommit(device,renderer);
    ANARIFrame frame = anariNewFrame(device);
    anariSetParameter(device,frame,"world",ANARI_WORLD,&world);
    anariCommit(device,frame);
    ANARIDataType fbFormat = ANARI_UFIXED8_RGBA_SRGB;
    anariSetParameter(device,frame,"color",ANARI_DATA_TYPE,&fbFormat);
    anariSetParameter(device,frame,"renderer",ANARI_RENDERER,&renderer);
    ANARICamera camera = anariNewCamera(device,"perspective");
    float aspect = 1.f;
    anariSetParameter(device,camera,"aspect",ANARI_FLOAT32,&aspect);
    if (model == TEAPOT) {
        float pos[] = {0.217,1.575,9.17682};
        float view[] = {0,0,-9.17682};
        float up[] = {0,1,0};
        anariSetParameter(device,camera,"position",ANARI_FLOAT32_VEC3,pos);
        anariSetParameter(device,camera,"direction",ANARI_FLOAT32_VEC3,view);
        anariSetParameter(device,camera,"up",ANARI_FLOAT32_VEC3,up);
    } else if (model == DRAGON) {
        float pos[] = {0.0088255,0.0232985,0.293922};
        float view[] = {0,0,-0.298525};
        float up[] = {0,1,0};
        anariSetParameter(device,camera,"position",ANARI_FLOAT32_VEC3,pos);
        anariSetParameter(device,camera,"direction",ANARI_FLOAT32_VEC3,view);
        anariSetParameter(device,camera,"up",ANARI_FLOAT32_VEC3,up);
    }
    anariCommit(device,camera);
    anariSetParameter(device,frame,"camera",ANARI_CAMERA,&camera);
    uint32_t imgSize[] = {512,512};
    anariSetParameter(device,frame,"size",ANARI_UINT32_VEC2,imgSize);
    anariCommit(device,frame);

    int spp = 16;
    for (int i=0; i<spp; ++i) {
        anariRenderFrame(device,frame);
        anariFrameReady(device,frame,ANARI_WAIT);
    }
    const uint32_t *fbPointer = (uint32_t *)anariMapFrame(device,frame,"color");
    stbi_flip_vertically_on_write(1);
    stbi_write_png("intensity.png",512,512,4,fbPointer,512*sizeof(uint32_t));
    anariUnmapFrame(device,frame,"color");

    printf("done\n");

    // Omitting cleanup for brevity...
}

teapot.bin: https://drive.google.com/file/d/1YqohbsWLCplvqx2SHHMilTKb-ndv_MKu/view?usp=sharing dragon.bin: https://drive.google.com/file/d/1q_C_7yNypn7RYAfzRAq4Oy8otC_oD7bZ/view?usp=sharing

and the sole non-std dependency being: https://raw.githubusercontent.com/nothings/stb/master/stb_image_write.h

I realize that the report is a bit unstructured and am sorry for that. I wonder if the issue here is that setting intensity on an area light is just undefined behavior. Nevertheless, if one adjusts the radius, intensity, and radiance parameters, the behavior I observe is that for the dragon, whose bounding box's volume is way smaller than the teapot's, the perceived intensity is much higher. And I also observe the same behavior when radius is 0 and hence the light is a point light / virtual light source.

szellmann commented 2 years ago

Maybe providing a bit more context, this is what I'm doing in my app (the repro is a bit different, but based off of that):

1.) Set up a head light: radius=0, intensity=60, pos=camPos 2.) Orbit around the model, with about the camPos's you can see above

What I observed was that on smaller models, the light was really bright, and on bigger models, such as the teapot, the light intensity was "about right". So seemingly, the behavior depends on the scale of the model.

3.) I incidentally also observed the same behavior when setting radius = length(bbox.max-bbox.min)*.1f;

With the intensity property only depending on solid angle (when being set as "intensity"), I'm thus surprised by this behavior. The light should, IMO, always have the same brightness in this case. The various combinations (radius 0, radius depending on bbox, intensity being set as "intensity", or as "power" or "radiance") one can test out with the repro above.

johguenther commented 2 years ago

OK, I think I get the issue now. You use a "point"/sphere/area light, which exhibits a quadratic falloff and is thus not perfect for use as "headlight" (because you'd need some heuristics to scale its brightness depending on the (average) distance to the objects, or an automatic exposure / tonemapping). For a headlight I'd suggest to use a directional light with direction=camDir (optionally with angularDiameter to get soft shadows).

For the "point"/sphere light the difference between intensity and radiance is a "constant" factor of π light_radius2. The only (?) light types where quantity power (and thus the brightness of the light*) would depend on the size of the scene are lights in infinity (directional and hdri), which is quite hard to handle and thus we don't support power for them (neither in ANARI nor in OSPRay).

szellmann commented 2 years ago

Correct. That's the issue. The behavior is different than the OpenGL point lights that I'm obviously more used to (in fact, in my own lib I support constant, linear, and quadratic attenuation factors for delta lights, where the latter defaults to 0). Incidentally, when setting intensity and non-zero radius on anari / OSPRay "point" lights, the resulting spherical light behaves as if it were a delta light in this regard. That's what caused the issue here. So it's my misunderstanding. Sorry for the noise - and closing the issue.