xash3d-fwgs/ref_vk/shaders/ray.rgen
Ivan 'provod' Avdeev a9d5e4dd22 rtx: draw skybox on SURF_SKY surfaces only
- change how shadows for environment lights work: should cast light only when hitting SURF_SKY
- add SBT_RECORD_SIZE to specialization; need this for sky/shadow closest hit shader

fix #140
2021-11-25 11:53:44 -08:00

540 lines
20 KiB
Plaintext

#version 460 core
#extension GL_GOOGLE_include_directive : require
#include "ray_common.glsl"
#include "ray_kusochki.glsl"
#include "noise.glsl"
#include "brdf.h"
//#define DEBUG_LIGHT_CULLING
// FIXME what should these be?
const float shadow_offset_fudge = .1;
const float pdf_culling_threshold = 100.;
const float color_factor = 600.;
const float color_culling_threshold = 600./color_factor;
const float throughput_threshold = 1e-3;
layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 256.;
layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 32768;
layout (constant_id = 7) const uint SBT_RECORD_SIZE = 64;
//const uint LIGHT_CLUSTER_SIZE = 2 + MAX_VISIBLE_POINT_LIGHTS + MAX_VISIBLE_SURFACE_LIGHTS;
//const uint LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET = 0;
//const uint LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET = 1;
//const uint LIGHT_CLUSTER_DLIGHTS_DATA_OFFSET = 2;
//const uint LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET = 3 + MAX_VISIBLE_DLIGHTS;
layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color;
layout(set = 0, binding = 9, rgba16f) uniform image2D out_image_diffuse_gi;
layout(set = 0, binding = 10, rgba16f) uniform image2D out_image_specular;
layout(set = 0, binding = 11, rgba16f) uniform image2D out_image_additive;
layout(set = 0, binding = 12, rgba16f) uniform image2D out_image_normals;
layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;
layout(set = 0, binding = 2) uniform UBO {
mat4 inv_proj, inv_view;
} ubo;
layout (set = 0, binding = 7/*, align=4*/) uniform UBOLights { Lights lights; };
layout (set = 0, binding = 8, align = 1) readonly buffer UBOLightClusters {
ivec3 grid_min, grid_size;
//uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET];
LightCluster clusters[MAX_LIGHT_CLUSTERS];
} light_grid;
layout (push_constant) uniform PC_ {
PushConstants push_constants;
};
layout(location = PAYLOAD_LOCATION_OPAQUE) rayPayloadEXT RayPayloadOpaque payload_opaque;
layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow;
layout(location = PAYLOAD_LOCATION_ADDITIVE) rayPayloadEXT RayPayloadAdditive payload_additive;
bool shadowed(vec3 pos, vec3 dir, float dist) {
payload_shadow.hit_type = SHADOW_HIT;
const uint flags = 0
//| gl_RayFlagsCullFrontFacingTrianglesEXT
//| gl_RayFlagsOpaqueEXT
| gl_RayFlagsTerminateOnFirstHitEXT
| gl_RayFlagsSkipClosestHitShaderEXT
;
traceRayEXT(tlas,
flags,
GEOMETRY_BIT_OPAQUE,
0, 0, SHADER_OFFSET_MISS_SHADOW,
pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW);
return payload_shadow.hit_type == SHADOW_HIT;
}
// TODO join with just shadowed()
bool shadowedSky(vec3 pos, vec3 dir, float dist) {
payload_shadow.hit_type = SHADOW_HIT;
const uint flags = 0
//| gl_RayFlagsCullFrontFacingTrianglesEXT
//| gl_RayFlagsOpaqueEXT
//| gl_RayFlagsTerminateOnFirstHitEXT
//| gl_RayFlagsSkipClosestHitShaderEXT
;
traceRayEXT(tlas,
flags,
GEOMETRY_BIT_OPAQUE,
SHADER_OFFSET_HIT_SHADOW, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW,
pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW);
return payload_shadow.hit_type != SHADOW_SKY;
}
// This is an entry point for evaluation of all other BRDFs based on selected configuration (for direct light)
void evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 diffuse, out vec3 specular) {
// Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...)
const BrdfData data = prepareBRDFData(N, L, V, material);
// Ignore V and L rays "below" the hemisphere
//if (data.Vbackfacing || data.Lbackfacing) return vec3(0.0f, 0.0f, 0.0f);
// Eval specular and diffuse BRDFs
specular = evalSpecular(data);
diffuse = evalDiffuse(data);
// Combine specular and diffuse layers
#if COMBINE_BRDFS_WITH_FRESNEL
// Specular is already multiplied by F, just attenuate diffuse
diffuse *= vec3(1.) - data.F;
#endif
}
void sampleSurfaceTriangle(
vec3 color, vec3 view_dir, MaterialProperties material /* TODO BrdfData instead is supposedly more efficient */,
mat4x3 emissive_transform, mat3 emissive_transform_normal,
uint triangle_index, uint index_offset, uint vertex_offset,
out vec3 diffuse, out vec3 specular)
{
diffuse = specular = vec3(0.);
const uint first_index_offset = index_offset + triangle_index * 3;
// TODO this is not entirely correct -- need to mix between all normals, or have this normal precomputed
const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset;
const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset;
const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset;
const vec3 v1 = (emissive_transform * vec4(vertices[vi1].pos, 1.)).xyz;
const vec3 v2 = (emissive_transform * vec4(vertices[vi2].pos, 1.)).xyz;
const vec3 v3 = (emissive_transform * vec4(vertices[vi3].pos, 1.)).xyz;
// TODO projected uniform sampling
const vec3 sample_pos = mix(mix(v1, v2, rand01()), v3, rand01());
vec3 light_dir = sample_pos - payload_opaque.hit_pos_t.xyz;
const float light_dir_normal_dot = dot(light_dir, payload_opaque.normal);
if (light_dir_normal_dot <= 0.)
#ifdef DEBUG_LIGHT_CULLING
return vec3(1., 0., 1.) * color_factor;
#else
return;
#endif
// Consider area light sources as planes, take the first normal
const vec3 normal = normalize(emissive_transform_normal * vertices[vi1].normal);
const float light_dot = -dot(light_dir, normal);
if (light_dot <= 0.)
#ifdef DEBUG_LIGHT_CULLING
return vec3(1., 0., 0.) * color_factor;
#else
return;
#endif
// TODO emissive normals and areas can be precomputed
const float area = .5 * length(cross(v1 - v2, v1 - v3));
const float light_dist2 = dot(light_dir, light_dir);
float pdf = light_dist2 / (area * light_dot);
if (pdf > pdf_culling_threshold)
#ifdef DEBUG_LIGHT_CULLING
return vec3(0., 1., 0.) * color_factor;
#else
return;
#endif
color /= pdf;
if (dot(color,color) < color_culling_threshold)
#ifdef DEBUG_LIGHT_CULLING
return vec3(0., 1., 0.) * color_factor;
#else
return;
#endif
light_dir = normalize(light_dir);
// TODO sample emissive texture
evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, diffuse, specular);
diffuse *= color;
specular *= color;
vec3 combined = diffuse + specular;
if (dot(combined,combined) < color_culling_threshold)
#ifdef DEBUG_LIGHT_CULLING
return vec3(1., 1., 0.) * color_factor;
#else
return;
#endif
if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir, sqrt(light_dist2))) {
diffuse = specular = vec3(0.);
}
}
void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {
diffuse = specular = vec3(0.);
const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights);
for (uint j = 0; j < num_point_lights; ++j) {
const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]);
vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput;
if (dot(color,color) < color_culling_threshold)
continue;
const vec4 origin_r = lights.point_lights[i].origin_r;
const float stopdot = lights.point_lights[i].color_stopdot.a;
const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz;
const float stopdot2 = lights.point_lights[i].dir_stopdot2.a;
const bool not_environment = (lights.point_lights[i].environment == 0);
const vec3 light_dir = not_environment ? (origin_r.xyz - payload_opaque.hit_pos_t.xyz) : -dir; // TODO need to randomize sampling direction for environment soft shadow
const vec3 light_dir_norm = normalize(light_dir);
const float light_dot = dot(light_dir_norm, payload_opaque.normal);
if (light_dot < 1e-5)
continue;
const float spot_dot = -dot(light_dir_norm, dir);
if (spot_dot < stopdot2)
continue;
float spot_attenuation = 1.f;
if (spot_dot < stopdot)
spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2);
float fdist = 1.f;
float light_dist = 1e4; // TODO this is supposedly not the right way to do shadows for environment lights. qrad checks for hitting SURF_SKY, and maybe we should too?
if (not_environment) {
#if 1
const float d2 = dot(light_dir, light_dir);
const float r2 = origin_r.w * origin_r.w;
light_dist = sqrt(d2);
fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2));
#else
const float d2 = dot(light_dir, light_dir);
//const float r2 = origin_r.w * origin_r.w;
light_dist = sqrt(d2);
//const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2));
//const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2));
fdist = (light_dist > 1.) ? 1.f / d2 : 1.f; // qrad workaround
#endif
}
const float pdf = 1.f / (fdist * light_dot * spot_attenuation);
color /= pdf;
// if (dot(color,color) < color_culling_threshold)
// continue;
vec3 ldiffuse, lspecular;
evalSplitBRDF(payload_opaque.normal, light_dir_norm, view_dir, material, ldiffuse, lspecular);
ldiffuse *= color;
lspecular *= color;
vec3 combined = ldiffuse + lspecular;
if (dot(combined,combined) < color_culling_threshold)
continue;
if (not_environment) {
if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge))
continue;
} else {
// for environment light check that we've hit SURF_SKY
if (shadowedSky(payload_opaque.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge))
continue;
}
diffuse += ldiffuse;
specular += lspecular;
} // for all lights
}
void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {
diffuse = specular = vec3(0.);
const ivec3 light_cell = ivec3(floor(payload_opaque.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min;
const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y)));
if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS)
return; // throughput * vec3(1., 0., 0.);
// const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET;
// const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]);
// const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]);
// const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET;
//C = vec3(float(num_emissive_surfaces));
//C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces)));
//C += .3 * fract(vec3(light_cell) / 4.);
const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces);
float sampling_light_scale = 1.;
#if 0
const uint max_lights_per_frame = 4;
uint begin_i = 0, end_i = num_emissive_kusochki;
if (end_i > max_lights_per_frame) {
begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame);
end_i = begin_i + max_lights_per_frame;
sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame);
}
for (uint i = begin_i; i < end_i; ++i) {
#else
for (uint i = 0; i < num_emissive_kusochki; ++i) {
#endif
const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]);
if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) {
if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end)
continue;
}
const EmissiveKusok ek = lights.kusochki[index_into_emissive_kusochki];
const uint emissive_kusok_index = lights.kusochki[index_into_emissive_kusochki].kusok_index;
const Kusok ekusok = kusochki[emissive_kusok_index];
// TODO streamline matrices layouts
const mat4x3 emissive_transform = mat4x3(
vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x),
vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y),
vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z),
vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w)
);
const mat3 emissive_transform_normal = transpose(inverse(mat3(emissive_transform)));
if (emissive_kusok_index == uint(payload_opaque.kusok_index))
continue;
const uint triangle_index = rand_range(ekusok.triangles);
vec3 ldiffuse, lspecular;
sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, emissive_transform_normal, triangle_index, ekusok.index_offset, ekusok.vertex_offset, ldiffuse, lspecular);
diffuse += ldiffuse * sampling_light_scale;
specular += lspecular * sampling_light_scale;
} // for all emissive kusochki
vec3 ldiffuse, lspecular;
computePointLights(cluster_index, throughput, view_dir, material, ldiffuse, lspecular);
diffuse += ldiffuse;
specular += lspecular;
}
// Additive translucency
vec3 traceAdditive(vec3 origin, vec3 direction, float ray_distance) {
const uint flags = 0
/* TODO try without*/ | gl_RayFlagsCullFrontFacingTrianglesEXT
//| gl_RayFlagsOpaqueEXT
| gl_RayFlagsSkipClosestHitShaderEXT
;
const uint sbt_offset = 0;
const uint sbt_stride = 0;
payload_additive.color = vec3(0.);
payload_additive.ray_distance = ray_distance;
traceRayEXT(tlas, flags, GEOMETRY_BIT_ADDITIVE,
sbt_offset, sbt_stride, SHADER_OFFSET_MISS_EMPTY,
origin, 0., direction, ray_distance + additive_soft_overshoot,
PAYLOAD_LOCATION_ADDITIVE);
return payload_additive.color * color_factor;
}
void main() {
rand01_state = push_constants.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337;
vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.;
vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz;
vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1);
vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz;
payload_opaque.material_index = 0;
payload_opaque.t_offset = .0;
payload_opaque.pixel_cone_spread_angle = push_constants.pixel_cone_spread_angle;
float out_material_index = 0.;
vec3 out_additive = vec3(0.);
vec3 out_diffuse_gi = vec3(0.);
vec3 out_specular = vec3(0.);
// Can be specular or diffuse_gi based on first bounce
vec3 out_accumulated = vec3(0.);
int first_bounce_brdf_type = 0;
int brdfType = SPECULAR_TYPE;
vec3 throughput = vec3(1.);
for (int bounce = 0; bounce < push_constants.bounces; ++bounce) {
const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT;
const uint sbt_offset = 0;
const uint sbt_stride = 0;
const float L = 10000.; // Why 10k?
traceRayEXT(tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_REFRACTIVE,
sbt_offset, sbt_stride, SHADER_OFFSET_MISS_REGULAR,
origin, 0., direction, L,
PAYLOAD_LOCATION_OPAQUE);
vec3 additive = traceAdditive(origin, direction, payload_opaque.hit_pos_t.w <= 0. ? L : payload_opaque.hit_pos_t.w);
// Sky/envmap
if (payload_opaque.kusok_index < 0) {
if (bounce == 0) {
out_additive += payload_opaque.emissive * color_factor + additive;
} else {
out_accumulated += throughput * (payload_opaque.emissive * color_factor + additive);
}
break;
}
#ifdef DEBUG_LIGHT_CULLING
// light clusters debugging
{
const ivec3 light_cell = ivec3(floor(payload_opaque.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min;
const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y)));
if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) {
C = vec3(1., 0., 0.) * color_factor;
break;
}
const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces);
for (uint i = 0; i < num_emissive_kusochki; ++i) {
const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]);
if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) {
if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end)
continue;
}
C = vec3(0., 0., 1.) * color_factor;
}
/* const uvec3 cellrand = pcg3d(uvec3(light_cell)); */
/* C = .02 * color_factor * vec3( */
/* uintToFloat01(cellrand.r), */
/* uintToFloat01(cellrand.g), */
/* uintToFloat01(cellrand.b)); */
//break;
}
#endif
MaterialProperties material;
material.baseColor = payload_opaque.base_color;
material.metalness = payload_opaque.metalness;
material.emissive = payload_opaque.emissive;
material.roughness = payload_opaque.roughness;
// material.roughness = uintToFloat01(xxhash32(uvec3(abs(floor(payload_opaque.hit_pos_t.xyz/64.)))));
// material.metalness = step(.5, xxhash32(uvec3(abs(floor(payload_opaque.hit_pos_t.xyz/32.)))));
const vec3 shadingNormal = payload_opaque.normal;
const vec3 geometryNormal = payload_opaque.geometry_normal;
if (bounce == 0) { //brdfType == SPECULAR_TYPE)
out_additive = payload_opaque.emissive + additive;
additive = vec3(0.);
out_material_index = float(payload_opaque.material_index);
imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.base_color, 0.));
imageStore(out_image_normals, ivec2(gl_LaunchIDEXT.xy), vec4(geometryNormal.xy, shadingNormal.xy));
payload_opaque.base_color = vec3(1.);
//out_material_index = float(kusochki[payload_opaque.kusok_index].tex_roughness);
#if 0
//imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(fract(payload_opaque.debug.xy), 0., 0.));
//imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.kusok_index));
imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.roughness));
imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(0));
imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(0.));
imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(clamp(payload_opaque.normal, vec3(0.), vec3(1.)), 0.));
return;
#endif
}
// TODO should we do this after reflect/transmit decision?
#define SKIP_TRASMITTED_LIGHT
#ifndef SKIP_TRASMITTED_LIGHT
C += computeLighting(throughput, -direction, material);
if (bounce == push_constants.bounces - 1)
break;
#else
vec3 prev_throughput = throughput;
#endif
const vec3 V = -direction;
if (material.metalness == 1.0f && material.roughness == 0.0f) {
// Fast path for mirrors
brdfType = SPECULAR_TYPE;
} else {
// Decide whether to sample diffuse or specular BRDF (based on Fresnel term)
float brdfProbability = getBrdfProbability(material, V, shadingNormal);
if (rand01() < brdfProbability) {
brdfType = SPECULAR_TYPE;
throughput /= brdfProbability;
} else {
// Refraction
if (rand01() < payload_opaque.transmissiveness) {
throughput *= material.baseColor;
direction = refract(direction, payload_opaque.geometry_normal, .8);
origin = payload_opaque.hit_pos_t.xyz - payload_opaque.geometry_normal * shadow_offset_fudge;
continue;
}
brdfType = DIFFUSE_TYPE;
throughput /= (1.0f - brdfProbability);
}
}
#ifdef SKIP_TRASMITTED_LIGHT
vec3 diffuse, specular;
computeLighting(prev_throughput, -direction, material, diffuse, specular);
if (bounce == 0) {
out_diffuse_gi += diffuse;
out_specular += specular;
} else {
out_accumulated += payload_opaque.base_color * (diffuse + specular);
out_accumulated += prev_throughput * additive;
}
if (bounce == push_constants.bounces - 1)
break;
#endif
vec2 u = vec2(rand01(), rand01());
vec3 brdfWeight;
if (!evalIndirectCombinedBRDF(u, shadingNormal, geometryNormal, V, material, brdfType, direction, brdfWeight)) {
break; // Ray was eaten by the surface :(
}
throughput *= brdfWeight;
if (dot(throughput, throughput) < throughput_threshold)
break;
origin = payload_opaque.hit_pos_t.xyz;
if (bounce == 0)
first_bounce_brdf_type = brdfType;
} // for all bounces
if (first_bounce_brdf_type == DIFFUSE_TYPE) {
out_diffuse_gi += out_accumulated;
} else {
out_specular += out_accumulated;
}
imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(out_diffuse_gi / color_factor, out_material_index));
imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(out_specular / color_factor, 0.));
imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(out_additive / color_factor, 0.));
}