Cycles/Eevee: Implement disk and ellipse shapes for area lamps

The implementation is pretty straightforward.

In Cycles, sampling the shapes is currently done w.r.t. area instead of solid angle.

There is a paper on solid angle sampling for disks [1], but the described algorithm is based on
simply sampling the enclosing square and rejecting samples outside of the disk, which is not exactly
great for Cycles' RNG (we'd need to setup a LCG for the repeated sampling) and for GPU divergence.

Even worse, the algorithm is only defined for disks. For ellipses, the basic idea still works, but a
way to analytically calculate the solid angle is required. This is technically possible [2], but the
calculation is extremely complex and still requires a lookup table for the Heuman Lambda function.

Therefore, I've decided to not implement that for now, we could still look into it later on.

In Eevee, the code uses the existing ltc_evaluate_disk to implement the lighting calculations.

[1]: "Solid Angle Sampling of Disk and Cylinder Lights"
[2]: "Analytical solution for the solid angle subtended at any point by an ellipse via a point source radiation vector potential"

Reviewers: sergey, brecht, fclem

Differential Revision: https://developer.blender.org/D3171
This commit is contained in:
Lukas Stockner 2018-05-24 03:50:16 +02:00
parent e8c8ff4f86
commit 5505ba8d47
20 changed files with 297 additions and 119 deletions

View File

@ -909,9 +909,9 @@ class CYCLES_LAMP_PT_lamp(CyclesButtonsPanel, Panel):
col.prop(lamp, "shape", text="")
sub = col.column(align=True)
if lamp.shape == 'SQUARE':
if lamp.shape in {'SQUARE', 'DISK'}:
sub.prop(lamp, "size")
elif lamp.shape == 'RECTANGLE':
elif lamp.shape in {'RECTANGLE', 'ELLIPSE'}:
sub.prop(lamp, "size", text="Size X")
sub.prop(lamp, "size_y", text="Size Y")

View File

@ -162,10 +162,24 @@ void BlenderSync::sync_light(BL::Object& b_parent,
light->axisu = transform_get_column(&tfm, 0);
light->axisv = transform_get_column(&tfm, 1);
light->sizeu = b_area_lamp.size();
if(b_area_lamp.shape() == BL::AreaLamp::shape_RECTANGLE)
light->sizev = b_area_lamp.size_y();
else
light->sizev = light->sizeu;
switch(b_area_lamp.shape()) {
case BL::AreaLamp::shape_SQUARE:
light->sizev = light->sizeu;
light->round = false;
break;
case BL::AreaLamp::shape_RECTANGLE:
light->sizev = b_area_lamp.size_y();
light->round = false;
break;
case BL::AreaLamp::shape_DISK:
light->sizev = light->sizeu;
light->round = true;
break;
case BL::AreaLamp::shape_ELLIPSE:
light->sizev = b_area_lamp.size_y();
light->round = true;
break;
}
light->type = LIGHT_AREA;
break;
}

View File

@ -44,7 +44,7 @@ typedef struct LightSample {
*
* Note: light_p is modified when sample_coord is true.
*/
ccl_device_inline float area_light_sample(float3 P,
ccl_device_inline float rect_light_sample(float3 P,
float3 *light_p,
float3 axisu, float3 axisv,
float randu, float randv,
@ -125,6 +125,60 @@ ccl_device_inline float area_light_sample(float3 P,
return 0.0f;
}
ccl_device_inline float3 ellipse_sample(float3 ru, float3 rv, float randu, float randv)
{
to_unit_disk(&randu, &randv);
return ru*randu + rv*randv;
}
ccl_device float3 disk_light_sample(float3 v, float randu, float randv)
{
float3 ru, rv;
make_orthonormals(v, &ru, &rv);
return ellipse_sample(ru, rv, randu, randv);
}
ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv)
{
return normalize(D + disk_light_sample(D, randu, randv)*radius);
}
ccl_device float3 sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv)
{
return disk_light_sample(normalize(P - center), randu, randv)*radius;
}
ccl_device float spot_light_attenuation(float3 dir, float spot_angle, float spot_smooth, LightSample *ls)
{
float3 I = ls->Ng;
float attenuation = dot(dir, I);
if(attenuation <= spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - spot_angle;
if(t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t/spot_smooth);
}
return attenuation;
}
ccl_device float lamp_light_pdf(KernelGlobals *kg, const float3 Ng, const float3 I, float t)
{
float cos_pi = dot(Ng, I);
if(cos_pi <= 0.0f)
return 0.0f;
return t*t/cos_pi;
}
/* Background Light */
#ifdef __BACKGROUND_MIS__
@ -295,11 +349,19 @@ ccl_device_inline float background_portal_pdf(KernelGlobals *kg,
const ccl_global KernelLight *klight = &kernel_tex_fetch(__lights, portal);
float3 axisu = make_float3(klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
bool is_round = (klight->area.invarea < 0.0f);
if(!ray_quad_intersect(P, direction, 1e-4f, FLT_MAX, lightpos, axisu, axisv, dir, NULL, NULL, NULL, NULL))
if(!ray_quad_intersect(P, direction, 1e-4f, FLT_MAX, lightpos, axisu, axisv, dir, NULL, NULL, NULL, NULL, is_round))
continue;
portal_pdf += area_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
if(is_round) {
float t;
float3 D = normalize_len(lightpos - P, &t);
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
}
else {
portal_pdf += rect_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
}
}
if(ignore_portal >= 0) {
@ -349,15 +411,26 @@ ccl_device float3 background_portal_sample(KernelGlobals *kg,
const ccl_global KernelLight *klight = &kernel_tex_fetch(__lights, portal);
float3 axisu = make_float3(klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
bool is_round = (klight->area.invarea < 0.0f);
*pdf = area_light_sample(P, &lightpos,
axisu, axisv,
randu, randv,
true);
float3 D;
if(is_round) {
lightpos += ellipse_sample(axisu*0.5f, axisv*0.5f, randu, randv);
float t;
D = normalize_len(lightpos - P, &t);
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
}
else {
*pdf = rect_light_sample(P, &lightpos,
axisu, axisv,
randu, randv,
true);
D = normalize(lightpos - P);
}
*pdf /= num_possible;
*sampled_portal = p;
return normalize(lightpos - P);
return D;
}
portal--;
@ -458,55 +531,6 @@ ccl_device float background_light_pdf(KernelGlobals *kg, float3 P, float3 direct
/* Regular Light */
ccl_device float3 disk_light_sample(float3 v, float randu, float randv)
{
float3 ru, rv;
make_orthonormals(v, &ru, &rv);
to_unit_disk(&randu, &randv);
return ru*randu + rv*randv;
}
ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv)
{
return normalize(D + disk_light_sample(D, randu, randv)*radius);
}
ccl_device float3 sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv)
{
return disk_light_sample(normalize(P - center), randu, randv)*radius;
}
ccl_device float spot_light_attenuation(float3 dir, float spot_angle, float spot_smooth, LightSample *ls)
{
float3 I = ls->Ng;
float attenuation = dot(dir, I);
if(attenuation <= spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - spot_angle;
if(t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t/spot_smooth);
}
return attenuation;
}
ccl_device float lamp_light_pdf(KernelGlobals *kg, const float3 Ng, const float3 I, float t)
{
float cos_pi = dot(Ng, I);
if(cos_pi <= 0.0f)
return 0.0f;
return t*t/cos_pi;
}
ccl_device_inline bool lamp_light_sample(KernelGlobals *kg,
int lamp,
float randu, float randv,
@ -601,26 +625,39 @@ ccl_device_inline bool lamp_light_sample(KernelGlobals *kg,
float3 D = make_float3(klight->area.dir[0],
klight->area.dir[1],
klight->area.dir[2]);
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if(dot(ls->P - P, D) > 0.0f) {
return false;
}
float3 inplane = ls->P;
ls->pdf = area_light_sample(P, &ls->P,
axisu, axisv,
randu, randv,
true);
float3 inplane;
if(is_round) {
inplane = ellipse_sample(axisu*0.5f, axisv*0.5f, randu, randv);
ls->P += inplane;
ls->pdf = invarea;
}
else {
inplane = ls->P;
ls->pdf = rect_light_sample(P, &ls->P,
axisu, axisv,
randu, randv,
true);
inplane = ls->P - inplane;
}
inplane = ls->P - inplane;
ls->u = dot(inplane, axisu) * (1.0f / dot(axisu, axisu)) + 0.5f;
ls->v = dot(inplane, axisv) * (1.0f / dot(axisv, axisv)) + 0.5f;
ls->Ng = D;
ls->D = normalize_len(ls->P - P, &ls->t);
float invarea = klight->area.invarea;
ls->eval_fac = 0.25f*invarea;
if(is_round) {
ls->pdf *= lamp_light_pdf(kg, D, -ls->D, ls->t);
}
}
}
@ -731,7 +768,8 @@ ccl_device bool lamp_light_eval(KernelGlobals *kg, int lamp, float3 P, float3 D,
}
else if(type == LIGHT_AREA) {
/* area light */
float invarea = klight->area.invarea;
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if(invarea == 0.0f)
return false;
@ -754,14 +792,20 @@ ccl_device bool lamp_light_eval(KernelGlobals *kg, int lamp, float3 P, float3 D,
if(!ray_quad_intersect(P, D, 0.0f, t, light_P,
axisu, axisv, Ng,
&ls->P, &ls->t,
&ls->u, &ls->v))
&ls->u, &ls->v,
is_round))
{
return false;
}
ls->D = D;
ls->Ng = Ng;
ls->pdf = area_light_sample(P, &light_P, axisu, axisv, 0, 0, false);
if(is_round) {
ls->pdf = invarea * lamp_light_pdf(kg, Ng, -D, ls->t);
}
else {
ls->pdf = rect_light_sample(P, &light_P, axisu, axisv, 0, 0, false);
}
ls->eval_fac = 0.25f*invarea;
}
else {

View File

@ -117,6 +117,7 @@ NODE_DEFINE(Light)
SOCKET_FLOAT(sizeu, "Size U", 1.0f);
SOCKET_VECTOR(axisv, "Axis V", make_float3(0.0f, 0.0f, 0.0f));
SOCKET_FLOAT(sizev, "Size V", 1.0f);
SOCKET_BOOLEAN(round, "Round", false);
SOCKET_INT(map_resolution, "Map Resolution", 512);
@ -730,12 +731,15 @@ void LightManager::device_update_points(Device *,
float3 axisu = light->axisu*(light->sizeu*light->size);
float3 axisv = light->axisv*(light->sizev*light->size);
float area = len(axisu)*len(axisv);
float invarea = (area > 0.0f)? 1.0f/area: 1.0f;
if(light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f)? 1.0f/area: 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
if(light->use_mis && area > 0.0f)
if(light->use_mis && area != 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
@ -803,7 +807,10 @@ void LightManager::device_update_points(Device *,
float3 axisu = light->axisu*(light->sizeu*light->size);
float3 axisv = light->axisv*(light->sizev*light->size);
float area = len(axisu)*len(axisv);
float invarea = (area > 0.0f)? 1.0f/area: 1.0f;
if(light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f)? 1.0f/area: 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);

View File

@ -49,6 +49,7 @@ public:
float sizeu;
float3 axisv;
float sizev;
bool round;
Transform tfm;

View File

@ -186,12 +186,17 @@ ccl_device_forceinline bool ray_triangle_intersect(
#undef dot3
}
/* Tests for an intersection between a ray and a quad defined by
* its midpoint, normal and sides.
* If ellipse is true, hits outside the ellipse that's enclosed by the
* quad are rejected.
*/
ccl_device bool ray_quad_intersect(float3 ray_P, float3 ray_D,
float ray_mint, float ray_maxt,
float3 quad_P,
float3 quad_u, float3 quad_v, float3 quad_n,
float3 *isect_P, float *isect_t,
float *isect_u, float *isect_v)
float *isect_u, float *isect_v, bool ellipse)
{
/* Perform intersection test. */
float t = -(dot(ray_P, quad_n) - dot(quad_P, quad_n)) / dot(ray_D, quad_n);
@ -200,20 +205,23 @@ ccl_device bool ray_quad_intersect(float3 ray_P, float3 ray_D,
}
const float3 hit = ray_P + t*ray_D;
const float3 inplane = hit - quad_P;
const float u = dot(inplane, quad_u) / dot(quad_u, quad_u) + 0.5f;
if(u < 0.0f || u > 1.0f) {
const float u = dot(inplane, quad_u) / dot(quad_u, quad_u);
if(u < -0.5f || u > 0.5f) {
return false;
}
const float v = dot(inplane, quad_v) / dot(quad_v, quad_v) + 0.5f;
if(v < 0.0f || v > 1.0f) {
const float v = dot(inplane, quad_v) / dot(quad_v, quad_v);
if(v < -0.5f || v > 0.5f) {
return false;
}
if(ellipse && (u*u + v*v > 0.25f)) {
return false;
}
/* Store the result. */
/* TODO(sergey): Check whether we can avoid some checks here. */
if(isect_P != NULL) *isect_P = hit;
if(isect_t != NULL) *isect_t = t;
if(isect_u != NULL) *isect_u = u;
if(isect_v != NULL) *isect_v = v;
if(isect_u != NULL) *isect_u = u + 0.5f;
if(isect_v != NULL) *isect_v = v + 0.5f;
return true;
}

View File

@ -136,9 +136,9 @@ class DATA_PT_EEVEE_lamp(DataButtonsPanel, Panel):
elif lamp.type == 'AREA':
sub = sub.column(align=True)
sub.prop(lamp, "shape", text="")
if lamp.shape == 'SQUARE':
if lamp.shape in {'SQUARE', 'DISK'}:
sub.prop(lamp, "size")
elif lamp.shape == 'RECTANGLE':
elif lamp.shape in {'RECTANGLE', 'ELLIPSE'}:
sub.prop(lamp, "size", text="Size X")
sub.prop(lamp, "size_y", text="Size Y")
@ -228,9 +228,9 @@ class DATA_PT_area(DataButtonsPanel, Panel):
col.row().prop(lamp, "shape", expand=True)
sub = col.row(align=True)
if lamp.shape == 'SQUARE':
if lamp.shape in {'SQUARE', 'DISK'}:
sub.prop(lamp, "size")
elif lamp.shape == 'RECTANGLE':
elif lamp.shape in {'RECTANGLE', 'ELLIPSE'}:
sub.prop(lamp, "size", text="Size X")
sub.prop(lamp, "size_y", text="Size Y")

View File

@ -1591,7 +1591,7 @@ class VIEW3D_MT_object_specials(Menu):
props.data_path_item = "data.size"
props.header_text = "Lamp Size X: %.3f"
if lamp.shape == 'RECTANGLE':
if lamp.shape in {'RECTANGLE', 'ELLIPSE'}:
props = layout.operator("wm.context_modal_mouse", text="Size Y")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.size_y"

View File

@ -591,7 +591,7 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli)
}
else if (la->type == LA_AREA) {
evli->sizex = max_ff(0.0001f, la->area_size * scale[0] * 0.5f);
if (la->area_shape == LA_AREA_RECT) {
if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) {
evli->sizey = max_ff(0.0001f, la->area_sizey * scale[1] * 0.5f);
}
else {
@ -602,10 +602,18 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli)
evli->radius = max_ff(0.001f, la->area_size);
}
/* Lamp Type */
evli->lamptype = (float)la->type;
/* Make illumination power constant */
if (la->type == LA_AREA) {
power = 1.0f / (evli->sizex * evli->sizey * 4.0f * M_PI) * /* 1/(w*h*Pi) */
80.0f; /* XXX : Empirical, Fit cycles power */
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
evli->lamptype = LAMPTYPE_AREA_ELLIPSE;
/* Scale power to account for the lower area of the ellipse compared to the surrouding rectangle. */
power *= 4.0f / M_PI;
}
}
else if (la->type == LA_SPOT || la->type == LA_LOCAL) {
power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI) * /* 1/(4*r²*Pi²) */
@ -620,9 +628,6 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli)
}
mul_v3_fl(evli->color, power * la->energy);
/* Lamp Type */
evli->lamptype = (float)la->type;
/* No shadow by default */
evli->shadowid = -1.0f;
}

View File

@ -289,6 +289,9 @@ typedef struct EEVEE_Light {
float forwardvec[3], lamptype;
} EEVEE_Light;
/* Special type for elliptic area lamps, matches lamps_lib.glsl */
#define LAMPTYPE_AREA_ELLIPSE 100.0f
typedef struct EEVEE_Shadow {
float near, far, bias, exp;
float shadow_start, data_start, multi_shadow_count, shadow_blur;

View File

@ -47,6 +47,16 @@ float direct_diffuse_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector)
return ltc_evaluate_quad(corners, N);
}
float direct_diffuse_ellipse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
{
vec3 points[3];
points[0] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey;
points[1] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey;
points[2] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey;
return ltc_evaluate_disk(N, V, mat3(1), points);
}
float direct_diffuse_unit_disc(LightData ld, vec3 N, vec3 V)
{
float NL = dot(N, -ld.l_forward);
@ -107,6 +117,26 @@ vec3 direct_ggx_sphere(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughn
return spec;
}
vec3 direct_ggx_ellipse(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness, vec3 f0)
{
vec3 points[3];
points[0] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey;
points[1] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey;
points[2] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey;
vec2 uv = lut_coords(dot(N, V), sqrt(roughness));
vec3 brdf_lut = texture(utilTex, vec3(uv, 1.0)).rgb;
vec4 ltc_lut = texture(utilTex, vec3(uv, 0.0)).rgba;
mat3 ltc_mat = ltc_matrix(ltc_lut);
float bsdf = ltc_evaluate_disk(N, V, ltc_mat, points);
bsdf *= brdf_lut.b; /* Bsdf intensity */
vec3 spec = F_area(f0, brdf_lut.xy) * bsdf;
return spec;
}
vec3 direct_ggx_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness, vec3 f0)
{
vec3 corners[4];

View File

@ -15,11 +15,13 @@ layout(std140) uniform light_block {
};
/* type */
#define POINT 0.0
#define SUN 1.0
#define SPOT 2.0
#define HEMI 3.0
#define AREA 4.0
#define POINT 0.0
#define SUN 1.0
#define SPOT 2.0
#define HEMI 3.0
#define AREA_RECT 4.0
/* Used to define the area lamp shape, doesn't directly correspond to a Blender lamp type. */
#define AREA_ELLIPSE 100.0
#if defined(SHADOW_VSM)
#define ShadowSample vec2
@ -174,7 +176,7 @@ float light_visibility(LightData ld, vec3 W,
vis *= spotmask;
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
else if (ld.l_type == AREA) {
else if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
@ -253,9 +255,12 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
if (ld.l_type == SUN) {
return direct_diffuse_unit_disc(ld, N, V);
}
else if (ld.l_type == AREA) {
else if (ld.l_type == AREA_RECT) {
return direct_diffuse_rectangle(ld, N, V, l_vector);
}
else if (ld.l_type == AREA_ELLIPSE) {
return direct_diffuse_ellipse(ld, N, V, l_vector);
}
else {
return direct_diffuse_sphere(ld, N, l_vector);
}
@ -275,9 +280,12 @@ vec3 light_specular(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness
if (ld.l_type == SUN) {
return direct_ggx_unit_disc(ld, N, V, roughness, f0);
}
else if (ld.l_type == AREA) {
else if (ld.l_type == AREA_RECT) {
return direct_ggx_rectangle(ld, N, V, l_vector, roughness, f0);
}
else if (ld.l_type == AREA_ELLIPSE) {
return direct_ggx_ellipse(ld, N, V, l_vector, roughness, f0);
}
else {
return direct_ggx_sphere(ld, N, V, l_vector, roughness, f0);
}
@ -373,8 +381,11 @@ vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale)
/* XXX : Removing Area Power. */
/* TODO : put this out of the shader. */
float falloff;
if (ld.l_type == AREA) {
if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
vis *= (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0);
if (ld.l_type == AREA_ELLIPSE) {
vis *= M_PI * 0.25;
}
vis *= 0.3 * 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */
vis /= (l_vector.w * l_vector.w);
falloff = dot(N, l_vector.xyz / l_vector.w);
@ -412,7 +423,7 @@ vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale)
vis *= spotmask;
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
else if (ld.l_type == AREA) {
else if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
}

View File

@ -66,8 +66,11 @@ vec3 light_volume(LightData ld, vec4 l_vector)
/* XXX : Removing Area Power. */
/* TODO : put this out of the shader. */
/* See eevee_light_setup(). */
if (ld.l_type == AREA) {
if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
power = (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0);
if (ld.l_type == AREA_ELLIPSE) {
power *= M_PI * 0.25;
}
power *= 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */
}
else if (ld.l_type == SUN) {

View File

@ -72,7 +72,8 @@ static struct DRWShapeCache {
Gwn_Batch *drw_lamp;
Gwn_Batch *drw_lamp_shadows;
Gwn_Batch *drw_lamp_sunrays;
Gwn_Batch *drw_lamp_area;
Gwn_Batch *drw_lamp_area_square;
Gwn_Batch *drw_lamp_area_disk;
Gwn_Batch *drw_lamp_hemi;
Gwn_Batch *drw_lamp_spot;
Gwn_Batch *drw_lamp_spot_square;
@ -1122,9 +1123,9 @@ Gwn_Batch *DRW_cache_lamp_sunrays_get(void)
return SHC.drw_lamp_sunrays;
}
Gwn_Batch *DRW_cache_lamp_area_get(void)
Gwn_Batch *DRW_cache_lamp_area_square_get(void)
{
if (!SHC.drw_lamp_area) {
if (!SHC.drw_lamp_area_square) {
float v1[3] = {0.0f, 0.0f, 0.0f};
/* Position Only 3D format */
@ -1151,9 +1152,40 @@ Gwn_Batch *DRW_cache_lamp_area_get(void)
v1[1] = 0.5f;
GWN_vertbuf_attr_set(vbo, attr_id.pos, 7, v1);
SHC.drw_lamp_area = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO);
SHC.drw_lamp_area_square = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO);
}
return SHC.drw_lamp_area;
return SHC.drw_lamp_area_square;
}
Gwn_Batch *DRW_cache_lamp_area_disk_get(void)
{
#define NSEGMENTS 32
if (!SHC.drw_lamp_area_disk) {
/* Position Only 3D format */
static Gwn_VertFormat format = { 0 };
static struct { uint pos; } attr_id;
if (format.attrib_ct == 0) {
attr_id.pos = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
}
Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
GWN_vertbuf_data_alloc(vbo, 2*NSEGMENTS);
float v[3] = {0.0f, 0.5f, 0.0f};
GWN_vertbuf_attr_set(vbo, attr_id.pos, 0, v);
for (int a = 1; a < NSEGMENTS; a++) {
v[0] = 0.5f*sinf(2.f * (float)M_PI * a / NSEGMENTS);
v[1] = 0.5f*cosf(2.f * (float)M_PI * a / NSEGMENTS);
GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*a-1, v);
GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*a, v);
}
copy_v3_fl3(v, 0.0f, 0.5f, 0.0f);
GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*NSEGMENTS-1, v);
SHC.drw_lamp_area_disk = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO);
}
return SHC.drw_lamp_area_disk;
#undef NSEGMENTS
}
Gwn_Batch *DRW_cache_lamp_hemi_get(void)

View File

@ -78,7 +78,8 @@ struct Gwn_Batch *DRW_cache_field_cone_limit_get(void);
struct Gwn_Batch *DRW_cache_lamp_get(void);
struct Gwn_Batch *DRW_cache_lamp_shadows_get(void);
struct Gwn_Batch *DRW_cache_lamp_sunrays_get(void);
struct Gwn_Batch *DRW_cache_lamp_area_get(void);
struct Gwn_Batch *DRW_cache_lamp_area_square_get(void);
struct Gwn_Batch *DRW_cache_lamp_area_disk_get(void);
struct Gwn_Batch *DRW_cache_lamp_hemi_get(void);
struct Gwn_Batch *DRW_cache_lamp_spot_get(void);
struct Gwn_Batch *DRW_cache_lamp_spot_square_get(void);

View File

@ -175,7 +175,8 @@ typedef struct OBJECT_PrivateData {
DRWShadingGroup *lamp_distance;
DRWShadingGroup *lamp_buflimit;
DRWShadingGroup *lamp_buflimit_points;
DRWShadingGroup *lamp_area;
DRWShadingGroup *lamp_area_square;
DRWShadingGroup *lamp_area_disk;
DRWShadingGroup *lamp_hemi;
DRWShadingGroup *lamp_spot_cone;
DRWShadingGroup *lamp_spot_blend;
@ -1164,8 +1165,11 @@ static void OBJECT_cache_init(void *vedata)
stl->g_data->lamp_groundline = shgroup_groundlines_uniform_color(psl->non_meshes, ts.colorLamp);
stl->g_data->lamp_groundpoint = shgroup_groundpoints_uniform_color(psl->non_meshes, ts.colorLamp);
geom = DRW_cache_lamp_area_get();
stl->g_data->lamp_area = shgroup_instance(psl->non_meshes, geom);
geom = DRW_cache_lamp_area_square_get();
stl->g_data->lamp_area_square = shgroup_instance(psl->non_meshes, geom);
geom = DRW_cache_lamp_area_disk_get();
stl->g_data->lamp_area_disk = shgroup_instance(psl->non_meshes, geom);
geom = DRW_cache_lamp_hemi_get();
stl->g_data->lamp_hemi = shgroup_instance(psl->non_meshes, geom);
@ -1398,13 +1402,18 @@ static void DRW_shgroup_lamp(OBJECT_StorageList *stl, Object *ob, ViewLayer *vie
else if (la->type == LA_AREA) {
float size[3] = {1.0f, 1.0f, 1.0f}, sizemat[4][4];
if (la->area_shape == LA_AREA_RECT) {
if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) {
size[1] = la->area_sizey / la->area_size;
size_to_mat4(sizemat, size);
mul_m4_m4m4(shapemat, shapemat, sizemat);
}
DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area, color, &la->area_size, shapemat);
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area_disk, color, &la->area_size, shapemat);
}
else {
DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area_square, color, &la->area_size, shapemat);
}
}
/* Line and point going to the ground */

View File

@ -631,6 +631,10 @@ static int apply_objects_internal(
la->area_shape = LA_AREA_RECT;
la->area_sizey = la->area_size;
}
else if ((la->area_shape == LA_AREA_DISK) && !keeps_aspect_ratio) {
la->area_shape = LA_AREA_ELLIPSE;
la->area_sizey = la->area_size;
}
la->area_size *= rsmat[0][0];
la->area_sizey *= rsmat[1][1];

View File

@ -129,7 +129,7 @@ static void manipulator_area_lamp_prop_matrix_get(
const Lamp *la = mpr_prop->custom_func.user_data;
matrix[0][0] = la->area_size;
matrix[1][1] = (la->area_shape == LA_AREA_RECT) ? la->area_sizey : la->area_size;
matrix[1][1] = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE) ? la->area_sizey : la->area_size;
}
static void manipulator_area_lamp_prop_matrix_set(
@ -140,7 +140,7 @@ static void manipulator_area_lamp_prop_matrix_set(
BLI_assert(mpr_prop->type->array_length == 16);
Lamp *la = mpr_prop->custom_func.user_data;
if (la->area_shape == LA_AREA_RECT) {
if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) {
la->area_size = len_v3(matrix[0]);
la->area_sizey = len_v3(matrix[1]);
}
@ -185,9 +185,11 @@ static void WIDGETGROUP_lamp_area_refresh(const bContext *C, wmManipulatorGroup
copy_m4_m4(mpr->matrix_basis, ob->obmat);
RNA_enum_set(mpr->ptr, "transform",
ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE |
((la->area_shape == LA_AREA_SQUARE) ? ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE_UNIFORM : 0));
int flag = ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE;
if (ELEM(la->area_shape, LA_AREA_SQUARE, LA_AREA_DISK)) {
flag |= ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE_UNIFORM;
}
RNA_enum_set(mpr->ptr, "transform", flag);
/* need to set property here for undo. TODO would prefer to do this in _init */
WM_manipulator_target_property_def_func(

View File

@ -148,6 +148,8 @@ typedef struct Lamp {
#define LA_AREA_RECT 1
#define LA_AREA_CUBE 2
#define LA_AREA_BOX 3
#define LA_AREA_DISK 4
#define LA_AREA_ELLIPSE 5
#endif /* __DNA_LAMP_TYPES_H__ */

View File

@ -394,6 +394,8 @@ static void rna_def_area_lamp(BlenderRNA *brna)
static const EnumPropertyItem prop_areashape_items[] = {
{LA_AREA_SQUARE, "SQUARE", 0, "Square", ""},
{LA_AREA_RECT, "RECTANGLE", 0, "Rectangle", ""},
{LA_AREA_DISK, "DISK", 0, "Disk", ""},
{LA_AREA_ELLIPSE, "ELLIPSE", 0, "Ellipse", ""},
{0, NULL, 0, NULL, NULL}
};