Crude ies lamp support

This commit is contained in:
Lubos Lenco 2017-08-17 14:37:04 +02:00
parent f50a47008d
commit af2bd70e52
7 changed files with 214 additions and 15 deletions

172
Assets/ies/load_ies.py Normal file
View file

@ -0,0 +1,172 @@
# IES parser based on:
# https://github.com/tobspr/RenderPipeline
# Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import re
import os
import math
import bpy
import random
def load(filepath):
global _vertical_angles
global _horizontal_angles
global _candela_values
KEYWORD_REGEX = re.compile(r"\[([A-Za-z0-8_-]+)\](.*)")
PROFILES = [
"IESNA:LM-63-1986",
"IESNA:LM-63-1991",
"IESNA91",
"IESNA:LM-63-1995",
"IESNA:LM-63-2002",
"ERCO Leuchten GmbH BY: ERCO/LUM650/8701",
"ERCO Leuchten GmbH"
]
with open(filepath, "r") as handle:
lines = handle.readlines()
lines = [i.strip() for i in lines]
# Parse version header
first_line = lines.pop(0)
if first_line not in PROFILES:
raise "Unsupported Profile: " + first_line
# Extracts the keywords
keywords = {}
while lines:
line = lines.pop(0)
if not line.startswith("["):
if line != "TILT=NONE":
continue
lines.insert(0, line)
break
else:
match = KEYWORD_REGEX.match(line)
if match:
key, val = match.group(1, 2)
keywords[key.strip()] = val.strip()
else:
raise "Invalid keyword line: " + line
# Next line should be TILT=NONE according to the spec
if lines.pop(0) != "TILT=NONE":
raise "Expected TILT=NONE line, but none found!"
# From now on, lines do not matter anymore, instead everything is space seperated
new_parts = (' '.join(lines)).replace(",", " ").split()
def read_int():
return int(new_parts.pop(0))
def read_float():
return float(new_parts.pop(0))
# Amount of Lamps
if read_int() != 1:
raise "Only 1 Lamp supported!"
# Extract various properties
lumen_per_lamp = read_float()
candela_multiplier = read_float()
num_vertical_angles = read_int()
num_horizontal_angles = read_int()
if num_vertical_angles < 1 or num_horizontal_angles < 1:
raise "Invalid of vertical/horizontal angles!"
photometric_type = read_int()
unit_type = read_int()
# Check for a correct unit type, should be 1 for meters and 2 for feet
if unit_type not in [1, 2]:
raise "Invalid unit type"
width = read_float()
length = read_float()
height = read_float()
ballast_factor = read_float()
future_use = read_float()
input_watts = read_float()
_vertical_angles = [read_float() for i in range(num_vertical_angles)]
_horizontal_angles = [read_float() for i in range(num_horizontal_angles)]
_candela_values = []
candela_scale = 0.0
for i in range(num_horizontal_angles):
vertical_data = [read_float() for i in range(num_vertical_angles)]
candela_scale = max(candela_scale, max(vertical_data))
_candela_values += vertical_data
# Rescale values, divide by maximum
_candela_values = [i / candela_scale for i in _candela_values]
generate_texture()
def generate_texture():
tex = bpy.data.images.new("iestexture", width=128, height=128, float_buffer=True) # R16
resolution_vertical = 128
resolution_horizontal = 128
for vert in range(resolution_vertical):
for horiz in range(resolution_horizontal):
vert_angle = vert / (resolution_vertical - 1.0)
vert_angle = math.cos(vert_angle * math.pi) * 90.0 + 90.0
horiz_angle = horiz / (resolution_horizontal - 1.0) * 360.0
candela = get_candela_value(vert_angle, horiz_angle)
x = vert
y = horiz
i = x + y * resolution_horizontal
tex.pixels[i * 4] = candela
tex.pixels[i * 4 + 1] = candela
tex.pixels[i * 4 + 2] = candela
tex.pixels[i * 4 + 3] = 1.0
def get_candela_value(vertical_angle, horizontal_angle):
# Assume a dataset without horizontal angles
return get_vertical_candela_value(0, vertical_angle)
def get_vertical_candela_value(horizontal_angle_idx, vertical_angle):
if vertical_angle < 0.0:
return 0.0
if vertical_angle > _vertical_angles[len(_vertical_angles) - 1]:
return 0.0
for vertical_index in range(1, len(_vertical_angles)):
curr_angle = _vertical_angles[vertical_index]
if curr_angle > vertical_angle:
prev_angle = _vertical_angles[vertical_index - 1]
prev_value = get_candela_value_from_index(vertical_index - 1, horizontal_angle_idx)
curr_value = get_candela_value_from_index(vertical_index, horizontal_angle_idx)
lerp = (vertical_angle - prev_angle) / (curr_angle - prev_angle)
assert lerp >= 0.0 and lerp <= 1.0
return curr_value * lerp + prev_value * (1.0 - lerp)
return 0.0
def get_candela_value_from_index(vertical_angle_idx, horizontal_angle_idx):
index = vertical_angle_idx + horizontal_angle_idx * len(_vertical_angles)
return _candela_values[index]
filepath = "/Users/lubos/Desktop/ies/JellyFish.ies"
load(filepath)

View file

@ -7,6 +7,9 @@ precision mediump float;
#include "../compiled.glsl"
#include "../std/brdf.glsl"
#include "../std/math.glsl"
#ifdef _LampIES
#include "../std/ies.glsl"
#endif
#ifdef _VoxelGIDirect
#include "../std/conetrace.glsl"
#endif
@ -51,6 +54,9 @@ uniform sampler2D gbuffer1;
#ifdef _DFRS
//!uniform sampler3D sdftex;
#endif
#ifdef _LampIES
//!uniform sampler2D texIES;
#endif
uniform mat4 invVP;
uniform mat4 LWVP;
@ -167,6 +173,9 @@ void main() {
// Per-light
#ifndef _NoLampFalloff
visibility *= attenuate(distance(p, lightPos));
#endif
#ifdef _LampIES
visibility *= iesAttenuation(-l);
#endif
if (lightType == 2) { // Spot
float spotEffect = dot(lightDir, l);

View file

@ -46,6 +46,11 @@
"link": "_lampColorTexture",
"ifdef": ["_LampColTex"]
},
{
"name": "texIES",
"link": "_iesTexture",
"ifdef": ["_LampIES"]
},
{
"name": "shadowsBias",
"link": "_lampShadowsBias"

View file

@ -1,25 +1,32 @@
sampler2D texIES;
uniform sampler2D texIES;
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float iesAttenuation(vec3 L, ShadowLightInfo light) {
float iesAttenuation(vec3 l) {
const float PI = 3.1415926535;
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
// Sample direction into light space
vec3 iesSampleDirection = mul(light.worldToLight , -L);
// vec3 iesSampleDirection = mul(light.worldToLight , -L);
// Cartesian to spherical
// Texture encoded with cos( phi ), scale from -1 - >1 to 0 - >1
float phiCoord = (iesSampleDirection.z * 0.5f) + 0.5f;
float theta = atan2 (iesSampleDirection.y , iesSampleDirection .x);
float thetaCoord = theta * (1.0 / (PI * 2.0));
float iesProfileScale = texture(texIES, vec2(thetaCoord, phiCoord)).r;
return iesProfileScale;
// float phiCoord = (iesSampleDirection.z * 0.5f) + 0.5f;
// float theta = atan2 (iesSampleDirection.y , iesSampleDirection .x);
// float thetaCoord = theta * (1.0 / (PI * 2.0));
// float iesProfileScale = texture(texIES, vec2(thetaCoord, phiCoord)).r;
// return iesProfileScale;
// 1D texture
vec3 pl = normalize(p - lightPos);
float f = asin(dot(pl, l)) / PI + 0.5;
return texture(texIES, vec2(f, 0.0)).r;
// vec3 pl = normalize(p - lightPos);
// float f = asin(dot(pl, l)) / PI + 0.5;
// return texture(texIES, vec2(f, 0.0)).r;
// 1D texture
float cosTheta = dot(lightToPos, lightDir);
float angle = acos(cosTheta) * (1.0 / PI);
return texture(texIES, vec2(angle, 0.0), 0.0).r;
// float cosTheta = dot(lightToPos, lightDir);
// float angle = acos(cosTheta) * (1.0 / PI);
// return texture(texIES, vec2(angle, 0.0), 0.0).r;
// Based on https://github.com/tobspr/RenderPipeline
float hor = acos(l.z) / PI;
float vert = atan(l.x, l.y) * (1.0 / (PI * 2.0)) + 0.5;
return texture(texIES, vec2(hor, vert)).r;
}

View file

@ -83,6 +83,10 @@ def build_node_tree(world):
if wrd.generate_lamp_texture != '':
bpy.data.worlds['Arm'].world_defs += '_LampColTex'
if wrd.generate_lamp_ies_texture != '':
bpy.data.worlds['Arm'].world_defs += '_LampIES'
assets.add_embedded_data('iestexture.png')
if not wrd.generate_lamp_falloff:
bpy.data.worlds['Arm'].world_defs += '_NoLampFalloff'

View file

@ -535,6 +535,7 @@ def init_properties():
('Uncharted', 'Uncharted', 'Uncharted')],
name='Tonemap', description='Tonemapping operator', default='Filmic', update=assets.invalidate_shader_cache)
bpy.types.World.generate_lamp_texture = bpy.props.StringProperty(name="Lamp Texture", default="")
bpy.types.World.generate_lamp_ies_texture = bpy.props.StringProperty(name="Lamp IES Texture", default="")
bpy.types.World.generate_lens_texture = bpy.props.StringProperty(name="Lens Texture", default="")
bpy.types.World.generate_lamp_falloff = bpy.props.BoolProperty(name="Lamp Falloff", default=True, update=assets.invalidate_shader_cache)
bpy.types.World.generate_fisheye = bpy.props.BoolProperty(name="Fish Eye", default=False, update=assets.invalidate_shader_cache)

View file

@ -376,6 +376,7 @@ class WorldPropsPanel(bpy.types.Panel):
layout.prop(wrd, 'generate_fisheye')
layout.prop(wrd, 'generate_vignette')
layout.prop(wrd, 'generate_lamp_texture')
layout.prop(wrd, 'generate_lamp_ies_texture')
layout.prop(wrd, 'generate_lens_texture')
layout.prop(wrd, 'generate_lamp_falloff')