Compare commits

...

22 Commits

Author SHA1 Message Date
2c30554504 Moises Jpelaez - CPU particle visual fixes 2026-06-24 20:33:46 -07:00
37c8779d12 Patch_5 2026-06-24 20:23:12 -07:00
6b704ff469 Merge pull request 'main' (#7) from Dante/LNXSDK:main into main
Reviewed-on: #7
2026-06-24 07:15:29 +00:00
2a3bff5a18 Nishita to Sky 2026-06-24 00:12:30 -07:00
91482071b8 merge upstream 2026-06-24 07:06:56 +00:00
38151eb233 Update 2026-06-24 00:05:37 -07:00
78ea055aea Update Aura 2026-06-23 14:54:15 -07:00
db2482dbe2 Update leenkx/Sources/leenkx/logicnode/LeenkxSendMessageNode.hx 2026-06-23 05:58:41 +00:00
5ae6a9e698 Merge pull request 'main' (#6) from Dante/LNXSDK:main into main
Reviewed-on: #6
2026-06-23 02:39:31 +00:00
6db83e559b merge upstream 2026-06-23 02:07:46 +00:00
e7ec872747 FSR buffer 2026-06-22 19:07:01 -07:00
31226a3871 Merge pull request 'Update leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx' (#5) from Dante/LNXSDK:main into main
Reviewed-on: #5
2026-06-22 23:57:45 +00:00
9be3240d6c Update leenkx/Sources/leenkx/renderpath/RenderPathDeferred.hx 2026-06-22 22:42:52 +00:00
96134404a2 Compute 0 2026-06-15 20:07:42 -07:00
3aac63d255 Update Kha/Backends/Kore-HL/kha/korehl/graphics4/Graphics.hx 2026-06-10 05:55:02 +00:00
a8d6095204 Update Kha/Backends/Kore-HL/kha/korehl/graphics4/Graphics.hx 2026-06-10 02:37:45 +00:00
960095d0d5 Upload files to "Krom" 2026-06-09 23:02:56 +00:00
2b221338ae Merge pull request 'main' (#4) from Dante/LNXSDK:main into main
Reviewed-on: #4
2026-06-02 04:36:04 +00:00
7ef0d59cfc Update Krom 2026-06-01 20:46:47 -07:00
da1d893342 Update Kmake 2026-06-01 20:41:12 -07:00
7b5a72036a Update leenkx/Sources/leenkx/renderpath/DynamicResolutionScale.hx 2026-05-31 18:50:44 +00:00
694b226345 Wrong 2026-05-31 18:48:47 +00:00
79 changed files with 2899 additions and 1711 deletions

View File

@ -1,397 +1,397 @@
package kha.korehl.graphics4;
import kha.arrays.Float32Array;
import kha.graphics4.ComputeShader;
import kha.graphics4.CubeMap;
import kha.graphics4.MipMapFilter;
import kha.graphics4.PipelineState;
import kha.graphics4.ShaderStorageBuffer;
import kha.graphics4.TextureAddressing;
import kha.graphics4.TextureFilter;
import kha.graphics4.Usage;
import kha.graphics4.VertexBuffer;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.Canvas;
import kha.Image;
import kha.Video;
import kha.Color;
class Graphics implements kha.graphics4.Graphics {
var target: Canvas;
public function new(target: Canvas = null) {
this.target = target;
}
public function vsynced(): Bool {
return kinc_graphics_vsynced();
}
public function refreshRate(): Int {
return kinc_graphics_refreshrate();
}
public function clear(?color: Color, ?z: FastFloat, ?stencil: Int): Void {
var flags: Int = 0;
if (color != null)
flags |= 1;
if (z != null)
flags |= 2;
if (stencil != null)
flags |= 4;
kinc_graphics_clear(flags, color == null ? 0 : color.value, z, stencil);
}
public function viewport(x: Int, y: Int, width: Int, height: Int): Void {
kinc_graphics_viewport(x, y, width, height);
}
public function setVertexBuffer(vertexBuffer: kha.graphics4.VertexBuffer): Void {
kinc_graphics_set_vertexbuffer(vertexBuffer._buffer);
}
public function setVertexBuffers(vertexBuffers: Array<kha.graphics4.VertexBuffer>): Void {
kinc_graphics_set_vertexbuffers(vertexBuffers.length > 0 ? vertexBuffers[0]._buffer : null,
vertexBuffers.length > 1 ? vertexBuffers[1]._buffer : null, vertexBuffers.length > 2 ? vertexBuffers[2]._buffer : null,
vertexBuffers.length > 3 ? vertexBuffers[3]._buffer : null, vertexBuffers.length);
}
public function setIndexBuffer(indexBuffer: kha.graphics4.IndexBuffer): Void {
kinc_graphics_set_indexbuffer(indexBuffer._buffer);
}
public function maxTextureSize(): Int {
return 4096;
}
public function supportsNonPow2Textures(): Bool {
return false;
}
public function setCubeMap(unit: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {
if (cubeMap == null)
return;
if (cubeMap._texture != null)
kinc_graphics_set_cubemap_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._texture);
else
kinc_graphics_set_cubemap_target(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._renderTarget);
}
public function setCubeMapDepth(unit: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {
if (cubeMap == null)
return;
kinc_graphics_set_cubemap_depth(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._renderTarget);
}
public function scissor(x: Int, y: Int, width: Int, height: Int): Void {
kinc_graphics_scissor(x, y, width, height);
}
public function disableScissor(): Void {
kinc_graphics_disable_scissor();
}
public function instancedRenderingAvailable(): Bool {
return true;
}
public function setTextureParameters(unit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {
kinc_graphics_set_texture_parameters(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, uAddressing, vAddressing, minificationFilter,
magnificationFilter, mipmapFilter);
}
public function setTexture3DParameters(unit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {
kinc_graphics_set_texture3d_parameters(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, uAddressing, vAddressing, wAddressing, minificationFilter,
magnificationFilter, mipmapFilter);
}
public function setTextureCompareMode(unit: kha.graphics4.TextureUnit, enabled: Bool) {
kinc_graphics_set_texture_compare_mode(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, enabled);
}
public function setCubeMapCompareMode(unit: kha.graphics4.TextureUnit, enabled: Bool) {
kinc_graphics_set_cube_map_compare_mode(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, enabled);
}
public function setTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
if (texture._texture != null)
kinc_graphics_set_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._texture);
else
kinc_graphics_set_render_target(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._renderTarget);
}
public function setTextureArray(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
kinc_graphics_set_texture_array(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._textureArray);
}
public function setTextureDepth(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
kinc_graphics_set_texture_depth(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._renderTarget);
}
public function setVideoTexture(unit: kha.graphics4.TextureUnit, texture: kha.Video): Void {
if (texture == null)
return;
kinc_graphics_set_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, Image.fromVideo(texture)._texture);
}
public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
kinc_graphics_set_image_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._texture);
}
public function maxBoundTextures(): Int {
return 8;
}
public function setPipeline(pipe: PipelineState): Void {
pipe.set();
}
public function setStencilReferenceValue(value: Int): Void {}
public function setBool(location: kha.graphics4.ConstantLocation, value: Bool): Void {
kinc_graphics_set_bool(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setInt(location: kha.graphics4.ConstantLocation, value: Int): Void {
kinc_graphics_set_int(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setInt2(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int): Void {
kinc_graphics_set_int2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2);
}
public function setInt3(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int): Void {
kinc_graphics_set_int3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3);
}
public function setInt4(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void {
kinc_graphics_set_int4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3, value4);
}
public function setInts(location: kha.graphics4.ConstantLocation, values: kha.arrays.Int32Array): Void {
kinc_graphics_set_ints(cast(location, kha.korehl.graphics4.ConstantLocation)._location, values.getData(), values.length);
}
public function setFloat(location: kha.graphics4.ConstantLocation, value: FastFloat): Void {
kinc_graphics_set_float(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setFloat2(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat): Void {
kinc_graphics_set_float2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2);
}
public function setFloat3(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void {
kinc_graphics_set_float3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3);
}
public function setFloat4(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void {
kinc_graphics_set_float4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3, value4);
}
public function setVector2(location: kha.graphics4.ConstantLocation, value: FastVector2): Void {
kinc_graphics_set_float2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y);
}
public function setVector3(location: kha.graphics4.ConstantLocation, value: FastVector3): Void {
kinc_graphics_set_float3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y, value.z);
}
public function setVector4(location: kha.graphics4.ConstantLocation, value: FastVector4): Void {
kinc_graphics_set_float4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y, value.z, value.w);
}
public function setFloats(location: kha.graphics4.ConstantLocation, values: Float32Array): Void {
kinc_graphics_set_floats(cast(location, kha.korehl.graphics4.ConstantLocation)._location, values.getData(), values.length);
}
public inline function setMatrix(location: kha.graphics4.ConstantLocation, matrix: FastMatrix4): Void {
kinc_graphics_set_matrix(cast(location, kha.korehl.graphics4.ConstantLocation)._location, matrix._00, matrix._10, matrix._20, matrix._30, matrix._01,
matrix._11, matrix._21, matrix._31, matrix._02, matrix._12, matrix._22, matrix._32, matrix._03, matrix._13, matrix._23, matrix._33);
}
public inline function setMatrix3(location: kha.graphics4.ConstantLocation, matrix: FastMatrix3): Void {
kinc_graphics_set_matrix3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, matrix._00, matrix._10, matrix._20, matrix._01, matrix._11,
matrix._21, matrix._02, matrix._12, matrix._22);
}
public function drawIndexedVertices(start: Int = 0, count: Int = -1): Void {
if (count < 0)
kinc_graphics_draw_all_indexed_vertices();
else
kinc_graphics_draw_indexed_vertices(start, count);
}
public function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1): Void {
if (count < 0)
kinc_graphics_draw_all_indexed_vertices_instanced(instanceCount);
else
kinc_graphics_draw_indexed_vertices_instanced(instanceCount, start, count);
}
function renderToTexture(additionalRenderTargets: Array<Canvas>): Void {
if (additionalRenderTargets != null) {
var len = additionalRenderTargets.length;
var rt0 = cast(target, Image)._renderTarget;
var rt1 = len > 0 ? cast(additionalRenderTargets[0], Image)._renderTarget : null;
var rt2 = len > 1 ? cast(additionalRenderTargets[1], Image)._renderTarget : null;
var rt3 = len > 2 ? cast(additionalRenderTargets[2], Image)._renderTarget : null;
var rt4 = len > 3 ? cast(additionalRenderTargets[3], Image)._renderTarget : null;
var rt5 = len > 4 ? cast(additionalRenderTargets[4], Image)._renderTarget : null;
var rt6 = len > 5 ? cast(additionalRenderTargets[5], Image)._renderTarget : null;
var rt7 = len > 6 ? cast(additionalRenderTargets[6], Image)._renderTarget : null;
kinc_graphics_render_to_textures(rt0, rt1, rt2, rt3, rt4, rt5, rt6, rt7, len + 1);
}
else {
kinc_graphics_render_to_texture(cast(target, Image)._renderTarget);
}
}
public function begin(additionalRenderTargets: Array<Canvas> = null): Void {
if (target == null)
kinc_graphics_restore_render_target();
else
renderToTexture(additionalRenderTargets);
}
public function beginFace(face: Int): Void {
kinc_graphics_render_to_face(cast(target, CubeMap)._renderTarget, face);
}
public function beginEye(eye: Int): Void {}
public function end(): Void {}
public function flush(): Void {
kinc_graphics_flush();
}
public function setShaderStorageBuffer(buffer: ShaderStorageBuffer, index: Int) {
// Kore::Compute::setBuffer(buffer->buffer, index);
}
public function setComputeShader(shader: ComputeShader) {
kinc_g4_set_compute_shader(shader._shader);
}
public function compute(x: Int, y: Int, z: Int) {
kinc_g4_compute(x, y, z);
}
@:hlNative("std", "kinc_graphics_clear") static function kinc_graphics_clear(flags: Int, color: Int, z: FastFloat, stencil: Int): Void {}
@:hlNative("std", "kinc_graphics_vsynced") static function kinc_graphics_vsynced(): Bool {
return false;
}
@:hlNative("std", "kinc_graphics_refreshrate") static function kinc_graphics_refreshrate(): Int {
return 0;
}
@:hlNative("std", "kinc_graphics_viewport") static function kinc_graphics_viewport(x: Int, y: Int, width: Int, height: Int): Void {}
@:hlNative("std", "kinc_graphics_set_vertexbuffer") static function kinc_graphics_set_vertexbuffer(buffer: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_vertexbuffers") static function kinc_graphics_set_vertexbuffers(b0: Pointer, b1: Pointer, b2: Pointer, b3: Pointer,
count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_indexbuffer") static function kinc_graphics_set_indexbuffer(buffer: Pointer): Void {}
@:hlNative("std", "kinc_graphics_scissor") static function kinc_graphics_scissor(x: Int, y: Int, width: Int, height: Int): Void {}
@:hlNative("std", "kinc_graphics_disable_scissor") static function kinc_graphics_disable_scissor(): Void {}
@:hlNative("std", "kinc_graphics_set_texture_parameters") static function kinc_graphics_set_texture_parameters(unit: Pointer, uAddressing: Int,
vAddressing: Int, minificationFilter: Int, magnificationFilter: Int, mipmapFilter: Int): Void {}
@:hlNative("std", "kinc_graphics_set_texture3d_parameters") static function kinc_graphics_set_texture3d_parameters(unit: Pointer, uAddressing: Int,
vAddressing: Int, wAddressing: Int, minificationFilter: Int, magnificationFilter: Int, mipmapFilter: Int): Void {}
@:hlNative("std", "kinc_graphics_set_texture_compare_mode") static function kinc_graphics_set_texture_compare_mode(unit: Pointer, enabled: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_cube_map_compare_mode") static function kinc_graphics_set_cube_map_compare_mode(unit: Pointer, enabled: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_texture") static function kinc_graphics_set_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_texture_depth") static function kinc_graphics_set_texture_depth(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_texture_array") static function kinc_graphics_set_texture_array(unit: Pointer, textureArray: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_render_target") static function kinc_graphics_set_render_target(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_texture") static function kinc_graphics_set_cubemap_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_target") static function kinc_graphics_set_cubemap_target(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_depth") static function kinc_graphics_set_cubemap_depth(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_image_texture") static function kinc_graphics_set_image_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_bool") static function kinc_graphics_set_bool(location: Pointer, value: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_int") static function kinc_graphics_set_int(location: Pointer, value: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int2") static function kinc_graphics_set_int2(location: Pointer, value1: Int, value2: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int3") static function kinc_graphics_set_int3(location: Pointer, value1: Int, value2: Int, value3: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int4") static function kinc_graphics_set_int4(location: Pointer, value1: Int, value2: Int, value3: Int,
value4: Int): Void {}
@:hlNative("std", "kinc_graphics_set_ints") static function kinc_graphics_set_ints(location: Pointer, values: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_float") static function kinc_graphics_set_float(location: Pointer, value: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float2") static function kinc_graphics_set_float2(location: Pointer, value1: FastFloat, value2: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float3") static function kinc_graphics_set_float3(location: Pointer, value1: FastFloat, value2: FastFloat,
value3: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float4") static function kinc_graphics_set_float4(location: Pointer, value1: FastFloat, value2: FastFloat,
value3: FastFloat, value4: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_floats") static function kinc_graphics_set_floats(location: Pointer, values: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_matrix") static function kinc_graphics_set_matrix(location: Pointer, _00: FastFloat, _10: FastFloat, _20: FastFloat,
_30: FastFloat, _01: FastFloat, _11: FastFloat, _21: FastFloat, _31: FastFloat, _02: FastFloat, _12: FastFloat, _22: FastFloat, _32: FastFloat,
_03: FastFloat, _13: FastFloat, _23: FastFloat, _33: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_matrix3") static function kinc_graphics_set_matrix3(location: Pointer, _00: FastFloat, _10: FastFloat,
_20: FastFloat, _01: FastFloat, _11: FastFloat, _21: FastFloat, _02: FastFloat, _12: FastFloat, _22: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_draw_all_indexed_vertices") static function kinc_graphics_draw_all_indexed_vertices(): Void {}
@:hlNative("std", "kinc_graphics_draw_indexed_vertices") static function kinc_graphics_draw_indexed_vertices(start: Int, count: Int): Void {}
@:hlNative("std",
"kinc_graphics_draw_all_indexed_vertices_instanced") static function kinc_graphics_draw_all_indexed_vertices_instanced(instanceCount: Int): Void {}
@:hlNative("std", "kinc_graphics_draw_indexed_vertices_instanced") static function kinc_graphics_draw_indexed_vertices_instanced(instanceCount: Int,
start: Int, count: Int): Void {}
@:hlNative("std", "kinc_graphics_restore_render_target") static function kinc_graphics_restore_render_target(): Void {}
@:hlNative("std", "kinc_graphics_render_to_texture") static function kinc_graphics_render_to_texture(renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_render_to_textures") static function kinc_graphics_render_to_textures(rt0: Pointer, rt1: Pointer, rt2: Pointer,
rt3: Pointer, rt4: Pointer, rt5: Pointer, rt6: Pointer, rt7: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_render_to_face") static function kinc_graphics_render_to_face(renderTarget: Pointer, face: Int): Void {}
@:hlNative("std", "kinc_graphics_flush") static function kinc_graphics_flush(): Void {}
@:hlNative("std", "kinc_g4_set_compute_shader") static function kinc_g4_set_compute_shader(shader: Pointer): Void {}
@:hlNative("std", "kinc_g4_compute") static function kinc_g4_compute(x: Int, y: Int, z: Int): Void {}
}
package kha.korehl.graphics4;
import kha.arrays.Float32Array;
import kha.graphics4.ComputeShader;
import kha.graphics4.CubeMap;
import kha.graphics4.MipMapFilter;
import kha.graphics4.PipelineState;
import kha.graphics4.ShaderStorageBuffer;
import kha.graphics4.TextureAddressing;
import kha.graphics4.TextureFilter;
import kha.graphics4.Usage;
import kha.graphics4.VertexBuffer;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.Canvas;
import kha.Image;
import kha.Video;
import kha.Color;
class Graphics implements kha.graphics4.Graphics {
var target: Canvas;
public function new(target: Canvas = null) {
this.target = target;
}
public function vsynced(): Bool {
return kinc_graphics_vsynced();
}
public function refreshRate(): Int {
return kinc_graphics_refreshrate();
}
public function clear(?color: Color, ?z: FastFloat, ?stencil: Int): Void {
var flags: Int = 0;
if (color != null)
flags |= 1;
if (z != null)
flags |= 2;
if (stencil != null)
flags |= 4;
kinc_graphics_clear(flags, color == null ? 0 : color.value, z, stencil);
}
public function viewport(x: Int, y: Int, width: Int, height: Int): Void {
kinc_graphics_viewport(x, y, width, height);
}
public function setVertexBuffer(vertexBuffer: kha.graphics4.VertexBuffer): Void {
kinc_graphics_set_vertexbuffer(vertexBuffer._buffer);
}
public function setVertexBuffers(vertexBuffers: Array<kha.graphics4.VertexBuffer>): Void {
kinc_graphics_set_vertexbuffers(vertexBuffers.length > 0 ? vertexBuffers[0]._buffer : null,
vertexBuffers.length > 1 ? vertexBuffers[1]._buffer : null, vertexBuffers.length > 2 ? vertexBuffers[2]._buffer : null,
vertexBuffers.length > 3 ? vertexBuffers[3]._buffer : null, vertexBuffers.length);
}
public function setIndexBuffer(indexBuffer: kha.graphics4.IndexBuffer): Void {
kinc_graphics_set_indexbuffer(indexBuffer._buffer);
}
public function maxTextureSize(): Int {
return 4096;
}
public function supportsNonPow2Textures(): Bool {
return false;
}
public function setCubeMap(unit: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {
if (cubeMap == null)
return;
if (cubeMap._texture != null)
kinc_graphics_set_cubemap_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._texture);
else
kinc_graphics_set_cubemap_target(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._renderTarget);
}
public function setCubeMapDepth(unit: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void {
if (cubeMap == null)
return;
kinc_graphics_set_cubemap_depth(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, cubeMap._renderTarget);
}
public function scissor(x: Int, y: Int, width: Int, height: Int): Void {
kinc_graphics_scissor(x, y, width, height);
}
public function disableScissor(): Void {
kinc_graphics_disable_scissor();
}
public function instancedRenderingAvailable(): Bool {
return true;
}
public function setTextureParameters(unit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {
kinc_graphics_set_texture_parameters(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, uAddressing, vAddressing, minificationFilter,
magnificationFilter, mipmapFilter);
}
public function setTexture3DParameters(unit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {
kinc_graphics_set_texture3d_parameters(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, uAddressing, vAddressing, wAddressing, minificationFilter,
magnificationFilter, mipmapFilter);
}
public function setTextureCompareMode(unit: kha.graphics4.TextureUnit, enabled: Bool) {
kinc_graphics_set_texture_compare_mode(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, enabled);
}
public function setCubeMapCompareMode(unit: kha.graphics4.TextureUnit, enabled: Bool) {
kinc_graphics_set_cube_map_compare_mode(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, enabled);
}
public function setTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
if (texture._texture != null)
kinc_graphics_set_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._texture);
else
kinc_graphics_set_render_target(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._renderTarget);
}
public function setTextureArray(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
kinc_graphics_set_texture_array(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._textureArray);
}
public function setTextureDepth(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
kinc_graphics_set_texture_depth(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._renderTarget);
}
public function setVideoTexture(unit: kha.graphics4.TextureUnit, texture: kha.Video): Void {
if (texture == null)
return;
kinc_graphics_set_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, Image.fromVideo(texture)._texture);
}
public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
kinc_graphics_set_image_texture(cast(unit, kha.korehl.graphics4.TextureUnit)._unit, texture._texture);
}
public function maxBoundTextures(): Int {
return 8;
}
public function setPipeline(pipe: PipelineState): Void {
pipe.set();
}
public function setStencilReferenceValue(value: Int): Void {}
public function setBool(location: kha.graphics4.ConstantLocation, value: Bool): Void {
kinc_graphics_set_bool(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setInt(location: kha.graphics4.ConstantLocation, value: Int): Void {
kinc_graphics_set_int(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setInt2(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int): Void {
kinc_graphics_set_int2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2);
}
public function setInt3(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int): Void {
kinc_graphics_set_int3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3);
}
public function setInt4(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void {
kinc_graphics_set_int4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3, value4);
}
public function setInts(location: kha.graphics4.ConstantLocation, values: kha.arrays.Int32Array): Void {
kinc_graphics_set_ints(cast(location, kha.korehl.graphics4.ConstantLocation)._location, values.getData(), values.length);
}
public function setFloat(location: kha.graphics4.ConstantLocation, value: FastFloat): Void {
kinc_graphics_set_float(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value);
}
public function setFloat2(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat): Void {
kinc_graphics_set_float2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2);
}
public function setFloat3(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void {
kinc_graphics_set_float3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3);
}
public function setFloat4(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void {
kinc_graphics_set_float4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value1, value2, value3, value4);
}
public function setVector2(location: kha.graphics4.ConstantLocation, value: FastVector2): Void {
kinc_graphics_set_float2(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y);
}
public function setVector3(location: kha.graphics4.ConstantLocation, value: FastVector3): Void {
kinc_graphics_set_float3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y, value.z);
}
public function setVector4(location: kha.graphics4.ConstantLocation, value: FastVector4): Void {
kinc_graphics_set_float4(cast(location, kha.korehl.graphics4.ConstantLocation)._location, value.x, value.y, value.z, value.w);
}
public function setFloats(location: kha.graphics4.ConstantLocation, values: Float32Array): Void {
kinc_graphics_set_floats(cast(location, kha.korehl.graphics4.ConstantLocation)._location, values.getData(), values.length);
}
public inline function setMatrix(location: kha.graphics4.ConstantLocation, matrix: FastMatrix4): Void {
kinc_graphics_set_matrix(cast(location, kha.korehl.graphics4.ConstantLocation)._location, matrix._00, matrix._10, matrix._20, matrix._30, matrix._01,
matrix._11, matrix._21, matrix._31, matrix._02, matrix._12, matrix._22, matrix._32, matrix._03, matrix._13, matrix._23, matrix._33);
}
public inline function setMatrix3(location: kha.graphics4.ConstantLocation, matrix: FastMatrix3): Void {
kinc_graphics_set_matrix3(cast(location, kha.korehl.graphics4.ConstantLocation)._location, matrix._00, matrix._10, matrix._20, matrix._01, matrix._11,
matrix._21, matrix._02, matrix._12, matrix._22);
}
public function drawIndexedVertices(start: Int = 0, count: Int = -1): Void {
if (count < 0)
kinc_graphics_draw_all_indexed_vertices();
else
kinc_graphics_draw_indexed_vertices(start, count);
}
public function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1): Void {
if (count < 0)
kinc_graphics_draw_all_indexed_vertices_instanced(instanceCount);
else
kinc_graphics_draw_indexed_vertices_instanced(instanceCount, start, count);
}
function renderToTexture(additionalRenderTargets: Array<Canvas>): Void {
if (additionalRenderTargets != null) {
var len = additionalRenderTargets.length;
var rt0 = cast(target, Image)._renderTarget;
var rt1 = len > 0 ? cast(additionalRenderTargets[0], Image)._renderTarget : null;
var rt2 = len > 1 ? cast(additionalRenderTargets[1], Image)._renderTarget : null;
var rt3 = len > 2 ? cast(additionalRenderTargets[2], Image)._renderTarget : null;
var rt4 = len > 3 ? cast(additionalRenderTargets[3], Image)._renderTarget : null;
var rt5 = len > 4 ? cast(additionalRenderTargets[4], Image)._renderTarget : null;
var rt6 = len > 5 ? cast(additionalRenderTargets[5], Image)._renderTarget : null;
var rt7 = len > 6 ? cast(additionalRenderTargets[6], Image)._renderTarget : null;
kinc_graphics_render_to_textures(rt0, rt1, rt2, rt3, rt4, rt5, rt6, rt7, len + 1);
}
else {
kinc_graphics_render_to_texture(cast(target, Image)._renderTarget);
}
}
public function begin(additionalRenderTargets: Array<Canvas> = null): Void {
if (target == null)
kinc_graphics_restore_render_target();
else
renderToTexture(additionalRenderTargets);
}
public function beginFace(face: Int): Void {
kinc_graphics_render_to_face((target is CubeMap) ? cast(target, CubeMap)._renderTarget : cast(target, Image)._renderTarget, face);
}
public function beginEye(eye: Int): Void {}
public function end(): Void {}
public function flush(): Void {
kinc_graphics_flush();
}
public function setShaderStorageBuffer(buffer: ShaderStorageBuffer, index: Int) {
// Kore::Compute::setBuffer(buffer->buffer, index);
}
public function setComputeShader(shader: ComputeShader) {
kinc_g4_set_compute_shader(shader._shader);
}
public function compute(x: Int, y: Int, z: Int) {
kinc_g4_compute(x, y, z);
}
@:hlNative("std", "kinc_graphics_clear") static function kinc_graphics_clear(flags: Int, color: Int, z: FastFloat, stencil: Int): Void {}
@:hlNative("std", "kinc_graphics_vsynced") static function kinc_graphics_vsynced(): Bool {
return false;
}
@:hlNative("std", "kinc_graphics_refreshrate") static function kinc_graphics_refreshrate(): Int {
return 0;
}
@:hlNative("std", "kinc_graphics_viewport") static function kinc_graphics_viewport(x: Int, y: Int, width: Int, height: Int): Void {}
@:hlNative("std", "kinc_graphics_set_vertexbuffer") static function kinc_graphics_set_vertexbuffer(buffer: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_vertexbuffers") static function kinc_graphics_set_vertexbuffers(b0: Pointer, b1: Pointer, b2: Pointer, b3: Pointer,
count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_indexbuffer") static function kinc_graphics_set_indexbuffer(buffer: Pointer): Void {}
@:hlNative("std", "kinc_graphics_scissor") static function kinc_graphics_scissor(x: Int, y: Int, width: Int, height: Int): Void {}
@:hlNative("std", "kinc_graphics_disable_scissor") static function kinc_graphics_disable_scissor(): Void {}
@:hlNative("std", "kinc_graphics_set_texture_parameters") static function kinc_graphics_set_texture_parameters(unit: Pointer, uAddressing: Int,
vAddressing: Int, minificationFilter: Int, magnificationFilter: Int, mipmapFilter: Int): Void {}
@:hlNative("std", "kinc_graphics_set_texture3d_parameters") static function kinc_graphics_set_texture3d_parameters(unit: Pointer, uAddressing: Int,
vAddressing: Int, wAddressing: Int, minificationFilter: Int, magnificationFilter: Int, mipmapFilter: Int): Void {}
@:hlNative("std", "kinc_graphics_set_texture_compare_mode") static function kinc_graphics_set_texture_compare_mode(unit: Pointer, enabled: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_cube_map_compare_mode") static function kinc_graphics_set_cube_map_compare_mode(unit: Pointer, enabled: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_texture") static function kinc_graphics_set_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_texture_depth") static function kinc_graphics_set_texture_depth(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_texture_array") static function kinc_graphics_set_texture_array(unit: Pointer, textureArray: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_render_target") static function kinc_graphics_set_render_target(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_texture") static function kinc_graphics_set_cubemap_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_target") static function kinc_graphics_set_cubemap_target(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_cubemap_depth") static function kinc_graphics_set_cubemap_depth(unit: Pointer, renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_image_texture") static function kinc_graphics_set_image_texture(unit: Pointer, texture: Pointer): Void {}
@:hlNative("std", "kinc_graphics_set_bool") static function kinc_graphics_set_bool(location: Pointer, value: Bool): Void {}
@:hlNative("std", "kinc_graphics_set_int") static function kinc_graphics_set_int(location: Pointer, value: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int2") static function kinc_graphics_set_int2(location: Pointer, value1: Int, value2: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int3") static function kinc_graphics_set_int3(location: Pointer, value1: Int, value2: Int, value3: Int): Void {}
@:hlNative("std", "kinc_graphics_set_int4") static function kinc_graphics_set_int4(location: Pointer, value1: Int, value2: Int, value3: Int,
value4: Int): Void {}
@:hlNative("std", "kinc_graphics_set_ints") static function kinc_graphics_set_ints(location: Pointer, values: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_float") static function kinc_graphics_set_float(location: Pointer, value: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float2") static function kinc_graphics_set_float2(location: Pointer, value1: FastFloat, value2: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float3") static function kinc_graphics_set_float3(location: Pointer, value1: FastFloat, value2: FastFloat,
value3: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_float4") static function kinc_graphics_set_float4(location: Pointer, value1: FastFloat, value2: FastFloat,
value3: FastFloat, value4: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_floats") static function kinc_graphics_set_floats(location: Pointer, values: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_set_matrix") static function kinc_graphics_set_matrix(location: Pointer, _00: FastFloat, _10: FastFloat, _20: FastFloat,
_30: FastFloat, _01: FastFloat, _11: FastFloat, _21: FastFloat, _31: FastFloat, _02: FastFloat, _12: FastFloat, _22: FastFloat, _32: FastFloat,
_03: FastFloat, _13: FastFloat, _23: FastFloat, _33: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_set_matrix3") static function kinc_graphics_set_matrix3(location: Pointer, _00: FastFloat, _10: FastFloat,
_20: FastFloat, _01: FastFloat, _11: FastFloat, _21: FastFloat, _02: FastFloat, _12: FastFloat, _22: FastFloat): Void {}
@:hlNative("std", "kinc_graphics_draw_all_indexed_vertices") static function kinc_graphics_draw_all_indexed_vertices(): Void {}
@:hlNative("std", "kinc_graphics_draw_indexed_vertices") static function kinc_graphics_draw_indexed_vertices(start: Int, count: Int): Void {}
@:hlNative("std",
"kinc_graphics_draw_all_indexed_vertices_instanced") static function kinc_graphics_draw_all_indexed_vertices_instanced(instanceCount: Int): Void {}
@:hlNative("std", "kinc_graphics_draw_indexed_vertices_instanced") static function kinc_graphics_draw_indexed_vertices_instanced(instanceCount: Int,
start: Int, count: Int): Void {}
@:hlNative("std", "kinc_graphics_restore_render_target") static function kinc_graphics_restore_render_target(): Void {}
@:hlNative("std", "kinc_graphics_render_to_texture") static function kinc_graphics_render_to_texture(renderTarget: Pointer): Void {}
@:hlNative("std", "kinc_graphics_render_to_textures") static function kinc_graphics_render_to_textures(rt0: Pointer, rt1: Pointer, rt2: Pointer,
rt3: Pointer, rt4: Pointer, rt5: Pointer, rt6: Pointer, rt7: Pointer, count: Int): Void {}
@:hlNative("std", "kinc_graphics_render_to_face") static function kinc_graphics_render_to_face(renderTarget: Pointer, face: Int): Void {}
@:hlNative("std", "kinc_graphics_flush") static function kinc_graphics_flush(): Void {}
@:hlNative("std", "kinc_g4_set_compute_shader") static function kinc_g4_set_compute_shader(shader: Pointer): Void {}
@:hlNative("std", "kinc_g4_compute") static function kinc_g4_compute(x: Int, y: Int, z: Int): Void {}
}

View File

@ -114,6 +114,7 @@ extern class Krom {
static function windowWidth(id: Int): Int;
static function windowHeight(id: Int): Int;
static function setWindowTitle(id: Int, title: String): Void;
static function windowSetForeground(id: Int): Void;
static function screenDpi(): Int;
static function systemId(): String;
static function requestShutdown(): Void;

View File

@ -30,15 +30,15 @@ class LoaderImpl {
}
public static function loadSoundFromDescription(desc: Dynamic, done: kha.Sound->Void, failed: AssetError->Void) {
var sound = Krom.loadSound(desc.files[0]);
if (sound == null) {
var sound = new kha.krom.Sound(desc.files[0]);
if (sound.uncompressedData == null) {
failed({
url: desc.files.join(","),
error: "Could not load sound(s)",
});
}
else {
done(new kha.krom.Sound(Bytes.ofData(sound)));
done(sound);
}
}

View File

@ -1,22 +1,25 @@
package kha.graphics4;
import haxe.io.Bytes;
import kha.Blob;
class ComputeShader {
public function new(sources: Array<Blob>, files: Array<String>) {
}
public function delete(): Void {
}
public function getConstantLocation(name: String): ConstantLocation {
return null;
}
public function getTextureUnit(name: String): TextureUnit {
return null;
}
}
package kha.graphics4;
import haxe.io.Bytes;
import kha.Blob;
class ComputeShader {
public var shader_: Dynamic;
public function new(sources: Array<Blob>, files: Array<String>) {
shader_ = Krom.createShaderCompute(sources[0].toBytes().getData());
}
public function delete(): Void {
Krom.deleteShaderCompute(shader_);
shader_ = null;
}
public function getConstantLocation(name: String): ConstantLocation {
return Krom.getConstantLocationCompute(shader_, name);
}
public function getTextureUnit(name: String): TextureUnit {
return Krom.getTextureUnitCompute(shader_, name);
}
}

View File

@ -261,10 +261,10 @@ class Graphics implements kha.graphics4.Graphics {
}
public function setComputeShader(shader: ComputeShader) {
Krom.setShaderCompute(shader.shader_);
}
public function compute(x: Int, y: Int, z: Int) {
Krom.compute(x, y, z);
}
}

View File

@ -2,24 +2,28 @@ package kha.krom;
import haxe.io.Bytes;
using StringTools;
class Sound extends kha.Sound {
public function new(bytes: Bytes) {
public function new(filename: String) {
super();
var count = Std.int(bytes.length / 4);
uncompressedData = new kha.arrays.Float32Array(count);
for (i in 0...count) {
uncompressedData[i] = bytes.getFloat(i * 4);
}
var sound = Krom.loadSound(filename);
if (sound != null) {
var bytes = Bytes.ofData(sound.buffer);
var count = Std.int(bytes.length / 4);
uncompressedData = new kha.arrays.Float32Array(count);
for (i in 0...count) {
uncompressedData[i] = bytes.getFloat(i * 4);
}
compressedData = null;
this.sampleRate = sound.sampleRate;
this.channels = sound.channels;
this.length = sound.length;
}
}
override public function uncompress(done: Void->Void): Void {
done();
}
override public function unload(): Void {
super.unload();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 281 KiB

View File

@ -68,7 +68,7 @@ class Sound implements Resource {
var soundBytes = output.getBytes();
var count = Std.int(soundBytes.length / 4);
if (header.channel == 1) {
length = count / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
length = count / header.sampleRate;
uncompressedData = new kha.arrays.Float32Array(count * 2);
for (i in 0...count) {
uncompressedData[i * 2 + 0] = soundBytes.getFloat(i * 4);
@ -76,7 +76,7 @@ class Sound implements Resource {
}
}
else {
length = count / 2 / kha.audio2.Audio.samplesPerSecond; // header.sampleRate;
length = count / 2 / header.sampleRate;
uncompressedData = new kha.arrays.Float32Array(count);
for (i in 0...count) {
uncompressedData[i] = soundBytes.getFloat(i * 4);

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,11 @@
/* Various sky functions
* =====================
*
* Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License)
* Single scattering model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License)
*
* Changes to the original implementation:
* - r and pSun parameters of nishita_atmosphere() are already normalized
* - Some original parameters of nishita_atmosphere() are replaced with pre-defined values
* - r and pSun parameters of single_scatter_atmosphere() are already normalized
* - Some original parameters of single_scatter_atmosphere() are replaced with pre-defined values
* - Implemented air, dust and ozone density node parameters (see Blender source)
* - Replaced the inner integral calculation with a LUT lookup
*
@ -22,8 +22,8 @@
#include "std/math.glsl"
uniform sampler2D nishitaLUT;
uniform vec2 nishitaDensity;
uniform sampler2D singleScatterLUT;
uniform vec2 skyDensity;
#ifndef PI
#define PI 3.141592
@ -32,33 +32,33 @@ uniform vec2 nishitaDensity;
#define HALF_PI 1.570796
#endif
#define nishita_iSteps 16
#define single_scatter_iSteps 16
// These values are taken from Cycles code if they
// exist there, otherwise they are taken from the example
// in the glsl-atmosphere repo
#define nishita_sun_intensity 22.0
#define nishita_atmo_radius 6420e3
#define nishita_rayleigh_scale 8e3
#define nishita_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6)
#define nishita_mie_scale 1.2e3
#define nishita_mie_coeff 2e-5
#define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction")
#define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy
#define single_scatter_sun_intensity 22.0
#define single_scatter_atmo_radius 6420e3
#define single_scatter_rayleigh_scale 8e3
#define single_scatter_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6)
#define single_scatter_mie_scale 1.2e3
#define single_scatter_mie_coeff 2e-5
#define single_scatter_mie_dir 0.76 // Aerosols anisotropy ("direction")
#define single_scatter_mie_dir_sq 0.5776 // Squared aerosols anisotropy
// Values from [Hill: 60]
#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652)
vec3 nishita_lookupLUT(const float height, const float sunTheta) {
vec3 single_scatter_lookupLUT(const float height, const float sunTheta) {
vec2 coords = vec2(
sqrt(height * (1 / nishita_atmo_radius)),
sqrt(height * (1 / single_scatter_atmo_radius)),
0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1))
);
return textureLod(nishitaLUT, coords, 0.0).rgb;
return textureLod(singleScatterLUT, coords, 0.0).rgb;
}
/* See raySphereIntersection() in leenkx/Sources/renderpath/Nishita.hx */
vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) {
/* See raySphereIntersection() in leenkx/Sources/renderpath/Sky.hx */
vec2 single_scatter_rsi(const vec3 r0, const vec3 rd, const float sr) {
float a = dot(rd, rd);
float b = 2.0 * dot(rd, r0);
float c = dot(r0, r0) - (sr * sr);
@ -74,12 +74,12 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) {
* pSun: normalized sun direction
* rPlanet: planet radius
*/
vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) {
vec3 single_scatter_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) {
// Calculate the step size of the primary ray
vec2 p = nishita_rsi(r0, r, nishita_atmo_radius);
vec2 p = single_scatter_rsi(r0, r, single_scatter_atmo_radius);
if (p.x > p.y) return vec3(0.0);
p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x);
float iStepSize = (p.y - p.x) / float(nishita_iSteps);
p.y = min(p.y, single_scatter_rsi(r0, r, rPlanet).x);
float iStepSize = (p.y - p.x) / float(single_scatter_iSteps);
// Primary ray time
float iTime = 0.0;
@ -96,18 +96,18 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
float mu = dot(r, pSun);
float mumu = mu * mu;
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq));
float pMie = 3.0 / (8.0 * PI) * ((1.0 - single_scatter_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + single_scatter_mie_dir_sq - 2.0 * mu * single_scatter_mie_dir, 1.5) * (2.0 + single_scatter_mie_dir_sq));
// Sample the primary ray
for (int i = 0; i < nishita_iSteps; i++) {
for (int i = 0; i < single_scatter_iSteps; i++) {
// Calculate the primary ray sample position and height
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
float iHeight = length(iPos) - rPlanet;
// Calculate the optical depth of the Rayleigh and Mie scattering for this step
float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize;
float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize;
float odStepRlh = exp(-iHeight / single_scatter_rayleigh_scale) * skyDensity.x * iStepSize;
float odStepMie = exp(-iHeight / single_scatter_mie_scale) * skyDensity.y * iStepSize;
// Accumulate optical depth
iOdRlh += odStepRlh;
@ -116,12 +116,12 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
// Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the
// inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith).
float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun)));
vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta);
vec3 jAttn = single_scatter_lookupLUT(iHeight, sunTheta);
// Calculate attenuation
vec3 iAttn = exp(-(
nishita_mie_coeff * iOdMie
+ nishita_rayleigh_coeff * iOdRlh
single_scatter_mie_coeff * iOdMie
+ single_scatter_rayleigh_coeff * iOdRlh
// + 0 for ozone
));
vec3 attn = iAttn * jAttn;
@ -136,7 +136,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
iTime += iStepSize;
}
return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie);
return single_scatter_sun_intensity * (pRlh * single_scatter_rayleigh_coeff * totalRlh + pMie * single_scatter_mie_coeff * totalMie);
}
vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) {
@ -149,7 +149,76 @@ vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const f
float mu = sqrt(invDist * invDist);
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
return 1 + (1.0 - step(1.0, dist)) * nishita_sun_intensity * intensity * limb_darkening;
return 1 + (1.0 - step(1.0, dist)) * single_scatter_sun_intensity * intensity * limb_darkening;
}
uniform sampler2D multiScatterLUT;
uniform vec4 multiScatterParams; // x=elevation, y=rotation, z=angular_diameter, w=intensity
uniform vec4 multiScatterSunBottom; // xyz=sun_bottom, w=earth_intersection_angle
uniform vec3 multiScatterSunTop;
// XYZ to sRGB/Rec.709 conversion (D65 white point)
vec3 xyz_to_rgb(vec3 xyz) {
return vec3(
3.2406 * xyz.x - 1.5372 * xyz.y - 0.4986 * xyz.z,
-0.9689 * xyz.x + 1.8758 * xyz.y + 0.0415 * xyz.z,
0.0557 * xyz.x - 0.2040 * xyz.y + 1.0570 * xyz.z
);
}
float sky_elevation_to_v(float elevation) {
float abs_el = abs(elevation);
float l = sign(elevation) * sqrt(abs_el / 1.5707963);
float v = (l + 1.0) * 0.5;
return clamp(v, 0.0, 1.0);
}
vec3 multi_scatter_sample_lut(vec3 dir, float sun_rotation) {
float azimuth = atan(dir.x, dir.y);
float elevation = asin(clamp(dir.z, -1.0, 1.0));
azimuth -= sun_rotation;
float u = fract(azimuth / (2.0 * PI));
float v = sky_elevation_to_v(elevation);
return textureLod(multiScatterLUT, vec2(u, v), 0.0).rgb;
}
vec3 multi_scatter_sun_disc(vec3 dir, vec3 sun_dir, float angular_diameter, float intensity) {
float dist = distance(dir, sun_dir) / (angular_diameter * 0.5);
if (dist > 1.0) return vec3(0.0);
float invDist = 1.0 - dist;
float mu = sqrt(invDist * invDist);
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
float sun_elev = multiScatterParams.x;
float dir_elev = asin(clamp(dir.z, -1.0, 1.0));
float t = clamp((dir_elev - (sun_elev - angular_diameter * 0.5)) / angular_diameter, 0.0, 1.0);
vec3 sun_color = mix(multiScatterSunBottom.rgb, multiScatterSunTop, t) * intensity * limb_darkening;
return xyz_to_rgb(sun_color);
}
vec3 multi_scatter_atmosphere(vec3 dir) {
float sun_elevation = multiScatterParams.x;
float sun_rotation = multiScatterParams.y;
float angular_diameter = multiScatterParams.z;
float sun_intensity = multiScatterParams.w;
vec3 xyz = multi_scatter_sample_lut(dir, sun_rotation);
vec3 radiance = xyz_to_rgb(xyz);
if (sun_intensity > 0.0) {
vec3 computed_sun_dir = vec3(
sin(sun_rotation) * cos(sun_elevation),
cos(sun_rotation) * cos(sun_elevation),
sin(sun_elevation)
);
radiance += multi_scatter_sun_disc(dir, computed_sun_dir, angular_diameter, sun_intensity);
}
return radiance;
}
#endif

View File

@ -88,58 +88,103 @@ vec4 rayCast(vec3 dir) {
}
#endif //SSR
vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor1) {
tcnor0 = hitXY / 3.0;
vec3 n0 = textureLod(sdetail, tcnor0 + vec2(speed / 60.0, speed / 120.0), 0.0).rgb;
tcnor1 = hitXY / 6.0 + n0.xy / 20.0;
vec3 n1 = textureLod(sbase, tcnor1 + vec2(speed / 40.0, speed / 80.0), 0.0).rgb;
return normalize(n0 + n1 - 1.0);
}
void main() {
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
if (gdepth == 1.0) {
fragColor = vec4(0.0);
return;
}
// Eye below water
if (eye.z < waterLevel) {
fragColor = vec4(0.0);
return;
}
// Displace surface
vec3 vray = normalize(viewRay);
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
float speed = time * 2.0 * waterSpeed;
p.z += sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed) / 50.0 * waterDisplace;
bool isSky = (gdepth == 1.0);
// Ray-plane intersection with water surface (z = waterLevel)
float denom = dot(vray, vec3(0.0, 0.0, 1.0));
float tWater = (waterLevel - eye.z) / denom;
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
if (eye.z < waterLevel) {
vec2 tc = texCoord;
float fogFactor;
if (hasWaterHit && denom > 0.0) {
// Looking up at water surface - apply normal distortion
vec3 hit = eye + tWater * vray;
vec2 tc0, tc1;
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tc0, tc1);
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
fogFactor = clamp(tWater * waterDensity, 0.0, 0.95);
} else {
// Looking forward/down - distort via water surface above fragment
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
vec2 wxy = isSky ? (eye.xy + vray.xy * 50.0) : p.xy;
vec2 tc0, tc1;
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
float waterDist = isSky ? 50.0 : length(p - eye);
fogFactor = clamp(waterDist * waterDensity, 0.0, 0.95);
}
vec3 refracted = textureLod(tex, tc, 0.0).rgb;
fragColor.rgb = mix(refracted, waterColor, fogFactor);
fragColor.a = 1.0;
return;
}
// Above water
if (p.z > waterLevel) {
if (!hasWaterHit || denom >= 0.0) {
fragColor = vec4(0.0);
return;
}
if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon
vec3 p = isSky ? (eye + tWater * vray) : getPos(eye, eyeLook, vray, gdepth, cameraProj);
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
if (!isSky && p.z > waterLevel) {
fragColor = vec4(0.0);
return;
}
// Displace surface
p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed)
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
/ 50.0 * waterDisplace;
// Hit plane to determine uvs
vec3 v = normalize(eye - p.xyz);
float t = -(dot(eye, vec3(0.0, 0.0, 1.0)) - waterLevel) / dot(v, vec3(0.0, 0.0, 1.0));
vec3 v = normalize(eye - p);
float t = (waterLevel - eye.z) / dot(v, vec3(0.0, 0.0, 1.0));
vec3 hit = eye + t * v;
hit.xy *= waterFreq;
hit.z += waterLevel;
// Sample normal maps
vec2 tcnor0 = hit.xy / 3.0;
vec3 n0 = textureLod(sdetail, tcnor0 + vec2(speed / 60.0, speed / 120.0), 0.0).rgb;
vec2 tcnor0, tcnor1;
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tcnor0, tcnor1);
vec2 tcnor1 = hit.xy / 6.0 + n0.xy / 20.0;
vec3 n1 = textureLod(sbase, tcnor1 + vec2(speed / 40.0, speed / 80.0), 0.0).rgb;
vec3 n2 = normalize(((n1 + n0) / 2.0) * 2.0 - 1.0);
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
vec2 tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
// Refraction
vec2 tc;
if (isSky) {
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
} else {
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
}
// Light
float fresnel = 1.0 - max(dot(n2, v), 0.0);
fresnel = pow(fresnel, 30.0) * 0.45;
fresnel = 0.02 + 0.98 * pow(fresnel, 5.0);
vec3 r = reflect(-v, n2);
#ifdef _Rad
vec3 reflectedEnv = textureLod(senvmapRadiance, envMapEquirect(r), 0).rgb;
vec3 reflectedEnv = textureLod(senvmapRadiance, envMapEquirect(r), 0).rgb;
#else
const vec3 reflectedEnv = vec3(0.5);
#endif
vec3 refracted = textureLod(tex, tc, 0.0).rgb;
#ifdef _SSR
float roughness = 0.1;//unpackFloat(g0.b).y;
//if (roughness == 1.0) { fragColor.rgb = vec3(0.0); return; }
@ -147,8 +192,8 @@ void main() {
float spec = 0.9;//fract(textureLod(gbuffer1, texCoord, 0.0).a);
//if (spec == 0.0) { fragColor.rgb = vec3(0.0); return; }
vec3 viewNormal = n2;
vec3 viewPos = getPosView(viewRay, gdepth, cameraProj);
vec3 viewNormal = V3 * n2;
vec3 viewPos = isSky ? vec3(0.0) : getPosView(viewRay, gdepth, cameraProj);
vec3 reflected = reflect(normalize(viewPos), viewNormal);
hitCoord = viewPos;
@ -158,7 +203,6 @@ void main() {
vec3 dir = reflected * (1.0 - rand(texCoord) * ssrJitter * roughness) * 2.0;
#endif
// * max(ssrMinRayStep, -viewPos.z)
vec4 coords = rayCast(dir);
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
@ -177,20 +221,32 @@ void main() {
#else
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
#endif
fragColor.rgb *= waterColor;
fragColor.rgb += clamp(pow(max(dot(r, ld), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0), 0.0, 2.0);
fragColor.rgb *= 1.0 - (clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9));
fragColor.a = clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0);
// Water color tint - blend rather than multiply to preserve brightness
float colorMix = isSky ? 0.7 : 0.5;
fragColor.rgb = mix(fragColor.rgb, fragColor.rgb * waterColor, colorMix * horizonFactor);
// Blinn-Phong specular using half-vector, faded at horizon
vec3 h = normalize(v + ld);
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor;
// Depth fog - blend toward waterColor with depth, faded at horizon
float depthFog = clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9);
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
// Alpha fades smoothly at horizon instead of hard cut
fragColor.a = isSky ? horizonFactor : clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0);
// Foam
float fd = abs(p.z - waterLevel);
// Foam - based on actual geometry depth below water surface
float fd = isSky ? 1.0 : abs(p.z - waterLevel);
if (fd < 0.1) {
// Based on foam by Owen Deery
// http://fire-face.com/personal/water
vec3 foamMask0 = textureLod(sfoam, tcnor0 * 10, 0.0).rgb;
vec3 foamMask1 = textureLod(sfoam, tcnor1 * 11, 0.0).rgb;
vec3 foam = vec3(1.0) - foamMask0.rrr - foamMask1.bbb;
float fac = 1.0 - (fd * (1.0 / 0.1));
fragColor.rgb = mix(fragColor.rgb, clamp(foam, 0.0, 1.0), clamp(fac, 0.0, 1.0));
// Distance-based LOD blurs foam at range to reduce noise
float foamLod = clamp(tWater / 15.0, 0.0, 5.0);
vec2 foamUV0 = tcnor0 * 3.0 + vec2(speed / 30.0, speed / 50.0);
vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0);
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
vec3 foamMask1 = textureLod(sfoam, foamUV1, foamLod).rgb;
float foamStrength = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
float fac = (1.0 - (fd * (1.0 / 0.1))) * horizonFactor;
fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5);
}
}

View File

@ -672,7 +672,7 @@ class RenderPath {
}
#if (rp_voxels != "Off")
public function getComputeShader(handle: String): kha.compute.Shader {
public function getComputeShader(handle: String): kha.graphics4.ComputeShader {
return Reflect.field(kha.Shaders, handle + "_comp");
}
#end

View File

@ -348,7 +348,13 @@ typedef TWorldData = {
@:optional public var turbidity: Null<FastFloat>;
@:optional public var ground_albedo: Null<FastFloat>;
@:optional public var envmap: String;
@:optional public var nishita_density: Float32Array; // Rayleigh, Mie, ozone
@:optional public var sky_density: Float32Array; // Air, dust/aerosol, ozone density
@:optional public var sky_sun_elevation: Null<FastFloat>;
@:optional public var sky_sun_rotation: Null<FastFloat>;
@:optional public var sky_sun_size: Null<FastFloat>;
@:optional public var sky_sun_intensity: Null<FastFloat>;
@:optional public var sky_altitude: Null<FastFloat>;
@:optional public var sky_sun_disc: Null<Int>; // 0 or 1
}
#if js

View File

@ -394,7 +394,7 @@ class ParticleSystemCPU {
if (physics.hasScaleRamp && physics.rampPositions.length > 1) {
var normalizedAge: FastFloat = physics.age / physics.lifetime;
var scaleMultiplier: FastFloat = interpolateRampValue(normalizedAge, physics.rampPositions, physics.rampColors);
var finalScale: FastFloat = scale * (particleScale * (1 - physics.scaleRampSizeFactor) + scaleMultiplier * physics.scaleRampSizeFactor);
var finalScale: FastFloat = 1 + (scaleMultiplier - 1) * physics.scaleRampSizeFactor;
particle.transform.scale.setFrom(physics.baseScale.clone().mult(finalScale));
}

View File

@ -1,27 +0,0 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetHosekWilkiePropertiesNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
return switch (from) {
case 0:
world.turbidity;
case 1:
world.ground_albedo;
case 2:
new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]);
default:
null;
}
return null;
}
}

View File

@ -1,29 +0,0 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetNishitaPropertiesNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
return switch (from) {
case 0:
world.nishita_density[0];
case 1:
world.nishita_density[1];
case 2:
world.nishita_density[2];
case 3:
new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]);
default:
null;
}
return null;
}
}

View File

@ -0,0 +1,15 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetWorldColorNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var col = iron.Scene.active.world.raw.background_color;
return new Vec4(((col >> 16) & 0xff) / 255, ((col >> 8) & 0xff) / 255, (col & 0xff) / 255, 1.0);
}
}

View File

@ -0,0 +1,50 @@
package leenkx.logicnode;
import iron.math.Vec4;
class GetWorldSkyNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world.raw;
if (property0 == 'hosek') {
return switch (from) {
case 0: world.turbidity != null ? world.turbidity : 1.0;
case 1: world.ground_albedo != null ? world.ground_albedo : 0.0;
case 2: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
else if (property0 == 'single') {
return switch (from) {
case 0: world.sky_density != null ? world.sky_density[0] : 1.0;
case 1: world.sky_density != null ? world.sky_density[1] : 1.0;
case 2: world.sky_density != null ? world.sky_density[2] : 1.0;
case 3: world.sky_altitude != null ? world.sky_altitude : 0.0;
case 4: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
else { // multi
return switch (from) {
case 0: world.sky_density != null ? world.sky_density[0] : 1.0;
case 1: world.sky_density != null ? world.sky_density[1] : 1.0;
case 2: world.sky_density != null ? world.sky_density[2] : 1.0;
case 3: world.sky_sun_elevation != null ? world.sky_sun_elevation : 0.0;
case 4: world.sky_sun_rotation != null ? world.sky_sun_rotation : 0.0;
case 5: world.sky_sun_size != null ? world.sky_sun_size : 0.545;
case 6: world.sky_sun_intensity != null ? world.sky_sun_intensity : 1.0;
case 7: world.sky_altitude != null ? world.sky_altitude : 0.0;
case 8: world.sky_sun_disc != null ? world.sky_sun_disc : 1;
case 9: world.sun_direction != null ? new Vec4(world.sun_direction[0], world.sun_direction[1], world.sun_direction[2]) : new Vec4(0, 0, 1);
default: null;
}
}
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class GetWorldTextureNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var world = iron.Scene.active.world;
return switch (from) {
case 0: world.probe != null ? world.probe.raw.strength : 1.0;
case 1: world.raw.envmap != null ? world.raw.envmap : '';
default: null;
}
}
}

View File

@ -20,6 +20,7 @@ class LeenkxSendMessageNode extends LogicNode {
}
override function run(from:Int) {
#if js
var connection = inputs[1].get();
if (connection == null) return;
var api: String = inputs[2].get();
@ -348,6 +349,7 @@ class LeenkxSendMessageNode extends LogicNode {
return;
}
}
#end
}
}

View File

@ -1,41 +0,0 @@
package leenkx.logicnode;
import leenkx.renderpath.HosekWilkie;
import iron.math.Vec4;
class SetHosekWilkiePropertiesNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
if(property0 == 'Turbidity/Ground Albedo'){
world.raw.turbidity = inputs[1].get();
world.raw.ground_albedo = inputs[2].get();
}
if(property0 == 'Turbidity')
world.raw.turbidity = inputs[1].get();
if(property0 == 'Ground Albedo')
world.raw.ground_albedo = inputs[1].get();
if(property0 == 'Sun Direction'){
var vec:Vec4 = inputs[1].get();
world.raw.sun_direction[0] = vec.x;
world.raw.sun_direction[1] = vec.y;
world.raw.sun_direction[2] = vec.z;
}
HosekWilkie.recompute(world);
runOutput(0);
}
}

View File

@ -1,45 +0,0 @@
package leenkx.logicnode;
import leenkx.renderpath.Nishita;
import iron.math.Vec4;
class SetNishitaPropertiesNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
if(property0 == 'Density'){
world.raw.nishita_density[0] = inputs[1].get();
world.raw.nishita_density[1] = inputs[2].get();
world.raw.nishita_density[2] = inputs[3].get();
}
if(property0 == 'Air')
world.raw.nishita_density[0] = inputs[1].get();
if(property0 == 'Dust')
world.raw.nishita_density[1] = inputs[1].get();
if(property0 == 'Ozone')
world.raw.nishita_density[2] = inputs[1].get();
if(property0 == 'Sun Direction'){
var vec:Vec4 = inputs[1].get();
world.raw.sun_direction[0] = vec.x;
world.raw.sun_direction[1] = vec.y;
world.raw.sun_direction[2] = vec.z;
}
Nishita.recompute(world);
runOutput(0);
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
import iron.math.Vec4;
class SetWorldColorNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
var vec:Vec4 = inputs[1].get();
var r = Std.int(Math.max(0, Math.min(1, vec.x)) * 255);
var g = Std.int(Math.max(0, Math.min(1, vec.y)) * 255);
var b = Std.int(Math.max(0, Math.min(1, vec.z)) * 255);
raw.background_color = (r << 16) | (g << 8) | b;
runOutput(0);
}
}

View File

@ -0,0 +1,63 @@
package leenkx.logicnode;
import leenkx.renderpath.Sky;
import leenkx.renderpath.HosekWilkie;
import iron.math.Vec4;
class SetWorldSkyNode extends LogicNode {
public var property0:String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
if (property0 == 'hosek') {
raw.turbidity = inputs[1].get();
raw.ground_albedo = inputs[2].get();
var vec:Vec4 = inputs[3].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
HosekWilkie.recompute(world);
}
else if (property0 == 'single') {
if (raw.sky_density == null) raw.sky_density = new kha.arrays.Float32Array(3);
raw.sky_density[0] = inputs[1].get();
raw.sky_density[1] = inputs[2].get();
raw.sky_density[2] = inputs[3].get();
raw.sky_altitude = inputs[4].get();
var vec:Vec4 = inputs[5].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
Sky.recomputeSingleScatter(world);
}
else if (property0 == 'multi') {
if (raw.sky_density == null) raw.sky_density = new kha.arrays.Float32Array(3);
raw.sky_density[0] = inputs[1].get();
raw.sky_density[1] = inputs[2].get();
raw.sky_density[2] = inputs[3].get();
raw.sky_sun_elevation = inputs[4].get();
raw.sky_sun_rotation = inputs[5].get();
raw.sky_sun_size = inputs[6].get();
raw.sky_sun_intensity = inputs[7].get();
raw.sky_altitude = inputs[8].get();
raw.sky_sun_disc = inputs[9].get() ? 1 : 0;
var vec:Vec4 = inputs[10].get();
if (raw.sun_direction == null) raw.sun_direction = new kha.arrays.Float32Array(3);
raw.sun_direction[0] = vec.x;
raw.sun_direction[1] = vec.y;
raw.sun_direction[2] = vec.z;
Sky.recomputeMultiScatter(world);
}
runOutput(0);
}
}

View File

@ -0,0 +1,25 @@
package leenkx.logicnode;
class SetWorldTextureNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var world = iron.Scene.active.world;
var raw = world.raw;
if (world.probe != null) {
world.probe.raw.strength = inputs[1].get();
}
var envmap:String = inputs[2].get();
if (envmap != null && envmap != '' && envmap != raw.envmap) {
raw.envmap = envmap;
world.loadEnvmap(function(w) {});
}
runOutput(0);
}
}

View File

@ -18,7 +18,7 @@ class Uniforms {
iron.object.Uniforms.externalTextureLinks = [textureLink];
iron.object.Uniforms.externalVec2Links = [vec2Link];
iron.object.Uniforms.externalVec3Links = [vec3Link];
iron.object.Uniforms.externalVec4Links = [];
iron.object.Uniforms.externalVec4Links = [vec4Link];
iron.object.Uniforms.externalFloatLinks = [floatLink];
iron.object.Uniforms.externalFloatsLinks = [floatsLink];
iron.object.Uniforms.externalIntLinks = [intLink];
@ -26,9 +26,13 @@ class Uniforms {
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
switch (link) {
case "_nishitaLUT": {
if (leenkx.renderpath.Nishita.data == null) leenkx.renderpath.Nishita.recompute(Scene.active.world);
return leenkx.renderpath.Nishita.data.lut;
case "_singleScatterLUT": {
if (leenkx.renderpath.Sky.singleScatterData == null) leenkx.renderpath.Sky.recomputeSingleScatter(Scene.active.world);
return leenkx.renderpath.Sky.singleScatterData.lut;
}
case "_multiScatterLUT": {
if (leenkx.renderpath.Sky.multiScatterData == null) leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
return leenkx.renderpath.Sky.multiScatterData.lut;
}
#if lnx_ltc
case "_ltcMat": {
@ -169,6 +173,17 @@ class Uniforms {
}
}
#end
case "_multiScatterSunTop": {
if (leenkx.renderpath.Sky.multiScatterData == null) {
leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
}
if (leenkx.renderpath.Sky.multiScatterData != null) {
v = iron.object.Uniforms.helpVec;
v.x = leenkx.renderpath.Sky.multiScatterData.sunTop.x;
v.y = leenkx.renderpath.Sky.multiScatterData.sunTop.y;
v.z = leenkx.renderpath.Sky.multiScatterData.sunTop.z;
}
}
}
return v;
}
@ -176,13 +191,13 @@ class Uniforms {
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
var v: Vec4 = null;
switch (link) {
case "_nishitaDensity": {
case "_skyDensity": {
var w = Scene.active.world;
if (w != null) {
v = iron.object.Uniforms.helpVec;
// We only need Rayleigh and Mie density in the sky shader -> Vec2
v.x = w.raw.nishita_density[0];
v.y = w.raw.nishita_density[1];
v.x = w.raw.sky_density[0];
v.y = w.raw.sky_density[1];
}
}
}
@ -190,6 +205,35 @@ class Uniforms {
return v;
}
public static function vec4Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
var v: Vec4 = null;
switch (link) {
case "_multiScatterParams": {
var w = Scene.active.world;
if (w != null && w.raw.sky_sun_elevation != null) {
v = iron.object.Uniforms.helpVec;
v.x = w.raw.sky_sun_elevation;
v.y = w.raw.sky_sun_rotation != null ? w.raw.sky_sun_rotation : 0.0;
v.z = w.raw.sky_sun_size != null ? w.raw.sky_sun_size : 0.545;
v.w = w.raw.sky_sun_intensity != null ? w.raw.sky_sun_intensity : 1.0;
}
}
case "_multiScatterSunBottom": {
if (leenkx.renderpath.Sky.multiScatterData == null) {
leenkx.renderpath.Sky.recomputeMultiScatter(Scene.active.world);
}
if (leenkx.renderpath.Sky.multiScatterData != null) {
v = iron.object.Uniforms.helpVec;
v.x = leenkx.renderpath.Sky.multiScatterData.sunBottom.x;
v.y = leenkx.renderpath.Sky.multiScatterData.sunBottom.y;
v.z = leenkx.renderpath.Sky.multiScatterData.sunBottom.z;
v.w = leenkx.renderpath.Sky.multiScatterData.earthIntersectionAngle;
}
}
}
return v;
}
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
switch (link) {
#if rp_dynres

View File

@ -23,10 +23,6 @@ class DynamicResolutionScale {
var overTime = Math.min(scaleRangeMs, frameTimeAvg - startScaleMs);
var scale = 1.0 - (overTime / scaleRangeMs) * (1.0 - maxScale);
#if rp_fsr1
{ scale = scale * 0.5; }
#end
var w = Std.int(iron.App.w() * scale);
var h = Std.int(iron.App.h() * scale);
path.setCurrentViewport(w, h);

View File

@ -22,99 +22,99 @@ class Inc {
#end
#if (rp_voxels != "Off")
static var voxel_sh0:kha.compute.Shader = null;
static var voxel_sh1:kha.compute.Shader = null;
static var voxel_ta0:kha.compute.TextureUnit;
static var voxel_tb0:kha.compute.TextureUnit;
static var voxel_ca0:kha.compute.ConstantLocation;
static var voxel_cb0:kha.compute.ConstantLocation;
static var voxel_ta1:kha.compute.TextureUnit;
static var voxel_tb1:kha.compute.TextureUnit;
static var voxel_tc1:kha.compute.TextureUnit;
static var voxel_sh0:kha.graphics4.ComputeShader = null;
static var voxel_sh1:kha.graphics4.ComputeShader = null;
static var voxel_ta0:kha.graphics4.TextureUnit;
static var voxel_tb0:kha.graphics4.TextureUnit;
static var voxel_ca0:kha.graphics4.ConstantLocation;
static var voxel_cb0:kha.graphics4.ConstantLocation;
static var voxel_ta1:kha.graphics4.TextureUnit;
static var voxel_tb1:kha.graphics4.TextureUnit;
static var voxel_tc1:kha.graphics4.TextureUnit;
static var m = iron.math.Mat4.identity();
static var voxel_ca1:kha.compute.ConstantLocation;
static var voxel_cb1:kha.compute.ConstantLocation;
static var voxel_ca1:kha.graphics4.ConstantLocation;
static var voxel_cb1:kha.graphics4.ConstantLocation;
#if (rp_voxels == "Voxel GI")
static var voxel_td1:kha.compute.TextureUnit;
static var voxel_te1:kha.compute.TextureUnit;
static var voxel_tf1:kha.compute.TextureUnit;
static var voxel_cc1:kha.compute.ConstantLocation;
static var voxel_td1:kha.graphics4.TextureUnit;
static var voxel_te1:kha.graphics4.TextureUnit;
static var voxel_tf1:kha.graphics4.TextureUnit;
static var voxel_cc1:kha.graphics4.ConstantLocation;
#else
#if lnx_voxelgi_shadows
static var voxel_te1:kha.compute.TextureUnit;
static var voxel_te1:kha.graphics4.TextureUnit;
#end
#end
#if (lnx_voxelgi_shadows || rp_voxels == "Voxel GI")
static var voxel_sh2:kha.compute.Shader = null;
static var voxel_ta2:kha.compute.TextureUnit;
static var voxel_tb2:kha.compute.TextureUnit;
static var voxel_ca2:kha.compute.ConstantLocation;
static var voxel_cb2:kha.compute.ConstantLocation;
static var voxel_cc2:kha.compute.ConstantLocation;
static var voxel_sh2:kha.graphics4.ComputeShader = null;
static var voxel_ta2:kha.graphics4.TextureUnit;
static var voxel_tb2:kha.graphics4.TextureUnit;
static var voxel_ca2:kha.graphics4.ConstantLocation;
static var voxel_cb2:kha.graphics4.ConstantLocation;
static var voxel_cc2:kha.graphics4.ConstantLocation;
#end
static var voxel_sh3:kha.compute.Shader = null;
static var voxel_ta3:kha.compute.TextureUnit;
static var voxel_tb3:kha.compute.TextureUnit;
static var voxel_tc3:kha.compute.TextureUnit;
static var voxel_td3:kha.compute.TextureUnit;
static var voxel_te3:kha.compute.TextureUnit;
static var voxel_tf3:kha.compute.TextureUnit;
static var voxel_sh3:kha.graphics4.ComputeShader = null;
static var voxel_ta3:kha.graphics4.TextureUnit;
static var voxel_tb3:kha.graphics4.TextureUnit;
static var voxel_tc3:kha.graphics4.TextureUnit;
static var voxel_td3:kha.graphics4.TextureUnit;
static var voxel_te3:kha.graphics4.TextureUnit;
static var voxel_tf3:kha.graphics4.TextureUnit;
#if lnx_brdf
static var voxel_tg3:kha.compute.TextureUnit;
static var voxel_tg3:kha.graphics4.TextureUnit;
#end
#if lnx_radiance
static var voxel_th3:kha.compute.TextureUnit;
static var voxel_th3:kha.graphics4.TextureUnit;
#end
static var voxel_ca3:kha.compute.ConstantLocation;
static var voxel_cb3:kha.compute.ConstantLocation;
static var voxel_cc3:kha.compute.ConstantLocation;
static var voxel_cd3:kha.compute.ConstantLocation;
static var voxel_ce3:kha.compute.ConstantLocation;
static var voxel_ca3:kha.graphics4.ConstantLocation;
static var voxel_cb3:kha.graphics4.ConstantLocation;
static var voxel_cc3:kha.graphics4.ConstantLocation;
static var voxel_cd3:kha.graphics4.ConstantLocation;
static var voxel_ce3:kha.graphics4.ConstantLocation;
#if lnx_irradiance
static var voxel_cf3:kha.compute.ConstantLocation;
static var voxel_cf3:kha.graphics4.ConstantLocation;
#end
#if lnx_radiance
static var voxel_cg3:kha.compute.ConstantLocation;
static var voxel_cg3:kha.graphics4.ConstantLocation;
#end
#if lnx_envcol
static var voxel_ch3:kha.compute.ConstantLocation;
static var voxel_ch3:kha.graphics4.ConstantLocation;
#end
#if (rp_voxels == "Voxel GI")
static var voxel_sh4:kha.compute.Shader = null;
static var voxel_ta4:kha.compute.TextureUnit;
static var voxel_tb4:kha.compute.TextureUnit;
static var voxel_tc4:kha.compute.TextureUnit;
static var voxel_td4:kha.compute.TextureUnit;
static var voxel_te4:kha.compute.TextureUnit;
static var voxel_tf4:kha.compute.TextureUnit;
static var voxel_ca4:kha.compute.ConstantLocation;
static var voxel_cb4:kha.compute.ConstantLocation;
static var voxel_cc4:kha.compute.ConstantLocation;
static var voxel_cd4:kha.compute.ConstantLocation;
static var voxel_sh4:kha.graphics4.ComputeShader = null;
static var voxel_ta4:kha.graphics4.TextureUnit;
static var voxel_tb4:kha.graphics4.TextureUnit;
static var voxel_tc4:kha.graphics4.TextureUnit;
static var voxel_td4:kha.graphics4.TextureUnit;
static var voxel_te4:kha.graphics4.TextureUnit;
static var voxel_tf4:kha.graphics4.TextureUnit;
static var voxel_ca4:kha.graphics4.ConstantLocation;
static var voxel_cb4:kha.graphics4.ConstantLocation;
static var voxel_cc4:kha.graphics4.ConstantLocation;
static var voxel_cd4:kha.graphics4.ConstantLocation;
static var voxel_sh5:kha.compute.Shader = null;
static var voxel_ta5:kha.compute.TextureUnit;
static var voxel_te5:kha.compute.TextureUnit;
static var voxel_tf5:kha.compute.TextureUnit;
static var voxel_tg5:kha.compute.TextureUnit;
static var voxel_ca5:kha.compute.ConstantLocation;
static var voxel_cb5:kha.compute.ConstantLocation;
static var voxel_cc5:kha.compute.ConstantLocation;
static var voxel_cd5:kha.compute.ConstantLocation;
static var voxel_ce5:kha.compute.ConstantLocation;
static var voxel_cf5:kha.compute.ConstantLocation;
static var voxel_cg5:kha.compute.ConstantLocation;
static var voxel_sh5:kha.graphics4.ComputeShader = null;
static var voxel_ta5:kha.graphics4.TextureUnit;
static var voxel_te5:kha.graphics4.TextureUnit;
static var voxel_tf5:kha.graphics4.TextureUnit;
static var voxel_tg5:kha.graphics4.TextureUnit;
static var voxel_ca5:kha.graphics4.ConstantLocation;
static var voxel_cb5:kha.graphics4.ConstantLocation;
static var voxel_cc5:kha.graphics4.ConstantLocation;
static var voxel_cd5:kha.graphics4.ConstantLocation;
static var voxel_ce5:kha.graphics4.ConstantLocation;
static var voxel_cf5:kha.graphics4.ConstantLocation;
static var voxel_cg5:kha.graphics4.ConstantLocation;
#if rp_shadowmap
static var voxel_tb5:kha.compute.TextureUnit;
static var voxel_tc5:kha.compute.TextureUnit;
static var voxel_td5:kha.compute.TextureUnit;
static var voxel_ch5:kha.compute.ConstantLocation;
static var voxel_ci5:kha.compute.ConstantLocation;
static var voxel_cj5:kha.compute.ConstantLocation;
static var voxel_ck5:kha.compute.ConstantLocation;
static var voxel_tb5:kha.graphics4.TextureUnit;
static var voxel_tc5:kha.graphics4.TextureUnit;
static var voxel_td5:kha.graphics4.TextureUnit;
static var voxel_ch5:kha.graphics4.ConstantLocation;
static var voxel_ci5:kha.graphics4.ConstantLocation;
static var voxel_cj5:kha.graphics4.ConstantLocation;
static var voxel_ck5:kha.graphics4.ConstantLocation;
#if lnx_shadowmap_atlas
static var voxel_cl5:kha.compute.ConstantLocation;
static var voxel_cm5:kha.compute.ConstantLocation;
static var voxel_cl5:kha.graphics4.ConstantLocation;
static var voxel_cm5:kha.graphics4.ConstantLocation;
#end
#end
#end //rp_voxels == "Voxel GI"
@ -852,7 +852,8 @@ class Inc {
#end
#if (rp_voxels != "Off")
public static function computeVoxelsBegin() {
// TODO: set global graphic
public static function computeVoxelsBegin(g: kha.graphics4.Graphics) {
if (voxel_sh0 == null)
{
voxel_sh0 = path.getComputeShader("voxel_offsetprev");
@ -984,16 +985,16 @@ class Inc {
#end
}
public static function computeVoxelsOffsetPrev() {
public static function computeVoxelsOffsetPrev(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
kha.compute.Compute.setShader(voxel_sh0);
g.setComputeShader(voxel_sh0);
kha.compute.Compute.setTexture(voxel_ta0, rts.get("voxelsOut").image, kha.compute.Access.Read);
kha.compute.Compute.setTexture(voxel_tb0, rts.get("voxelsOutB").image, kha.compute.Access.Write);
g.setImageTexture(voxel_ta0, rts.get("voxelsOut").image);
g.setImageTexture(voxel_tb0, rts.get("voxelsOutB").image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
@ -1009,14 +1010,14 @@ class Inc {
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca0, fa);
g.setFloats(voxel_ca0, fa);
kha.compute.Compute.setInt(voxel_cb0, iron.RenderPath.clipmapLevel);
g.setInt(voxel_cb0, iron.RenderPath.clipmapLevel);
kha.compute.Compute.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
}
public static function computeVoxelsTemporal() {
public static function computeVoxelsTemporal(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
@ -1037,19 +1038,19 @@ class Inc {
// Check again after init
if (rts.get("voxels") == null || rts.get("voxelsOutB") == null || rts.get("voxelsOut") == null) return;
kha.compute.Compute.setShader(voxel_sh1);
g.setComputeShader(voxel_sh1);
kha.compute.Compute.setTexture(voxel_ta1, rts.get("voxels").image, kha.compute.Access.Read);
kha.compute.Compute.setTexture(voxel_tb1, rts.get("voxelsOutB").image, kha.compute.Access.Read);
kha.compute.Compute.setTexture(voxel_tc1, rts.get("voxelsOut").image, kha.compute.Access.Write);
g.setImageTexture(voxel_ta1, rts.get("voxels").image);
g.setImageTexture(voxel_tb1, rts.get("voxelsOutB").image);
g.setImageTexture(voxel_tc1, rts.get("voxelsOut").image);
#if (rp_voxels == "Voxel GI")
kha.compute.Compute.setSampledTexture(voxel_td1, rts.get("voxelsOutB").image);
kha.compute.Compute.setTexture(voxel_te1, rts.get("voxelsLight").image, kha.compute.Access.Read);
kha.compute.Compute.setTexture(voxel_tf1, rts.get("voxelsSDF").image, kha.compute.Access.Write);
kha.compute.Compute.setFloat(voxel_cc1, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
g.setTexture(voxel_td1, rts.get("voxelsOutB").image);
g.setImageTexture(voxel_te1, rts.get("voxelsLight").image);
g.setImageTexture(voxel_tf1, rts.get("voxelsSDF").image);
g.setFloat(voxel_cc1, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
#else
#if lnx_voxelgi_shadows
kha.compute.Compute.setTexture(voxel_te1, rts.get("voxelsSDF").image, kha.compute.Access.Write);
g.setImageTexture(voxel_te1, rts.get("voxelsSDF").image);
#end
#end
@ -1067,17 +1068,17 @@ class Inc {
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca1, fa);
g.setFloats(voxel_ca1, fa);
kha.compute.Compute.setInt(voxel_cb1, iron.RenderPath.clipmapLevel);
g.setInt(voxel_cb1, iron.RenderPath.clipmapLevel);
kha.compute.Compute.setFloat(voxel_cc1, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
g.setFloat(voxel_cc1, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
kha.compute.Compute.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
}
#if (lnx_voxelgi_shadows || (rp_voxels == "Voxel GI"))
public static function computeVoxelsSDF() {
public static function computeVoxelsSDF(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var clipmaps = iron.RenderPath.clipmaps;
@ -1089,33 +1090,33 @@ class Inc {
var passcount = Std.int(Math.ceil(Math.log(res) / Math.log(2.0)));
for (i in 0...passcount) {
kha.compute.Compute.setShader(voxel_sh2);
g.setComputeShader(voxel_sh2);
kha.compute.Compute.setTexture(voxel_ta2, rts.get(read_sdf).image, kha.compute.Access.Read);
kha.compute.Compute.setTexture(voxel_tb2, rts.get(write_sdf).image, kha.compute.Access.Write);
g.setImageTexture(voxel_ta2, rts.get(read_sdf).image);
g.setImageTexture(voxel_tb2, rts.get(write_sdf).image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
for (j in 0...Main.voxelgiClipmapCount) {
fa[j * 10] = clipmaps[j].voxelSize;
fa[j * 10 + 1] = clipmaps[j].extents.x;
fa[j * 10 + 2] = clipmaps[j].extents.y;
fa[j * 10 + 3] = clipmaps[j].extents.z;
fa[j * 10 + 4] = clipmaps[j].center.x;
fa[j * 10 + 5] = clipmaps[j].center.y;
fa[j * 10 + 6] = clipmaps[j].center.z;
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca2, fa);
g.setFloats(voxel_ca2, fa);
kha.compute.Compute.setInt(voxel_cb2, iron.RenderPath.clipmapLevel);
g.setInt(voxel_cb2, iron.RenderPath.clipmapLevel);
var jump_size = Math.pow(2.0, passcount - i - 1);
kha.compute.Compute.setFloat(voxel_cc2, jump_size);
g.setFloat(voxel_cc2, jump_size);
kha.compute.Compute.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
if (i < passcount - 1)
{
@ -1127,29 +1128,29 @@ class Inc {
#end
#if (rp_voxels == "Voxel AO")
public static function resolveAO() {
public static function resolveAO(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
kha.compute.Compute.setShader(voxel_sh3);
g.setComputeShader(voxel_sh3);
kha.compute.Compute.setSampledTexture(voxel_ta3, rts.get("voxelsOut").image);
kha.compute.Compute.setSampledTexture(voxel_tb3, rts.get("half").image);
kha.compute.Compute.setSampledTexture(voxel_tc3, rts.get("gbuffer0").image);
kha.compute.Compute.setTexture(voxel_td3, rts.get("voxels_ao").image, kha.compute.Access.Write);
g.setTexture(voxel_ta3, rts.get("voxelsOut").image);
g.setTexture(voxel_tb3, rts.get("half").image);
g.setTexture(voxel_tc3, rts.get("gbuffer0").image);
g.setImageTexture(voxel_td3, rts.get("voxels_ao").image);
kha.compute.Compute.setSampledTexture(voxel_te3, rts.get("gbuffer1").image);
g.setTexture(voxel_te3, rts.get("gbuffer1").image);
#if rp_gbuffer2
kha.compute.Compute.setSampledTexture(voxel_tf3, rts.get("gbuffer2").image);
g.setTexture(voxel_tf3, rts.get("gbuffer2").image);
#end
#if lnx_brdf
kha.compute.Compute.setSampledTexture(voxel_tg3, iron.Scene.active.embedded.get("brdf.png"));
g.setTexture(voxel_tg3, iron.Scene.active.embedded.get("brdf.png"));
#end
#if lnx_radiance
kha.compute.Compute.setSampledTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
g.setTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
@ -1166,7 +1167,7 @@ class Inc {
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca3, fa);
g.setFloats(voxel_ca3, fa);
#if lnx_centerworld
m.setFrom(vmat(camera.V));
@ -1176,9 +1177,9 @@ class Inc {
m.multmat(camera.P);
m.getInverse(m);
kha.compute.Compute.setMatrix(voxel_cb3, m.self);
g.setMatrix(voxel_cb3, m.self);
kha.compute.Compute.setFloat3(voxel_cc3, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
g.setFloat3(voxel_cc3, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
var width = iron.App.w();
var height = iron.App.h();
@ -1193,17 +1194,17 @@ class Inc {
width = Std.int(dp * Inc.getSuperSampling());
}
}
kha.compute.Compute.setFloat2(voxel_cd3, width, height);
g.setFloat2(voxel_cd3, width, height);
kha.compute.Compute.setFloat(voxel_ce3, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
g.setFloat(voxel_ce3, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
#if lnx_irradiance
var irradiance = iron.Scene.active.world == null ?
iron.data.WorldData.getEmptyIrradiance() :
iron.Scene.active.world.probe.irradiance;
kha.compute.Compute.setFloats(voxel_cf3, irradiance);
g.setFloats(voxel_cf3, irradiance);
#end
#if lnx_radiance
kha.compute.Compute.setFloat(voxel_cg3, iron.Scene.active.world != null ? iron.Scene.active.world.probe.raw.radiance_mipmaps + 1 - 2 : 1);
g.setFloat(voxel_cg3, iron.Scene.active.world != null ? iron.Scene.active.world.probe.raw.radiance_mipmaps + 1 - 2 : 1);
#end
#if lnx_envcol
@ -1217,34 +1218,34 @@ class Inc {
z = camera.data.raw.clear_color[2];
}
kha.compute.Compute.setFloat3(voxel_ch3, x, y, z);
g.setFloat3(voxel_ch3, x, y, z);
#end
kha.compute.Compute.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
g.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
}
#else
public static function resolveDiffuse() {
public static function resolveDiffuse(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
kha.compute.Compute.setShader(voxel_sh3);
g.setComputeShader(voxel_sh3);
kha.compute.Compute.setSampledTexture(voxel_ta3, rts.get("voxelsOut").image);
kha.compute.Compute.setSampledTexture(voxel_tb3, rts.get("half").image);
kha.compute.Compute.setSampledTexture(voxel_tc3, rts.get("gbuffer0").image);
kha.compute.Compute.setTexture(voxel_td3, rts.get("voxels_diffuse").image, kha.compute.Access.Write);
kha.compute.Compute.setSampledTexture(voxel_te3, rts.get("gbuffer1").image);
g.setTexture(voxel_ta3, rts.get("voxelsOut").image);
g.setTexture(voxel_tb3, rts.get("half").image);
g.setTexture(voxel_tc3, rts.get("gbuffer0").image);
g.setImageTexture(voxel_td3, rts.get("voxels_diffuse").image);
g.setTexture(voxel_te3, rts.get("gbuffer1").image);
#if rp_gbuffer2
kha.compute.Compute.setSampledTexture(voxel_tf3, rts.get("gbuffer2").image);
g.setTexture(voxel_tf3, rts.get("gbuffer2").image);
#end
#if lnx_brdf
kha.compute.Compute.setSampledTexture(voxel_tg3, iron.Scene.active.embedded.get("brdf.png"));
g.setTexture(voxel_tg3, iron.Scene.active.embedded.get("brdf.png"));
#end
#if lnx_radiance
kha.compute.Compute.setSampledTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
g.setTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
@ -1261,7 +1262,7 @@ class Inc {
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca3, fa);
g.setFloats(voxel_ca3, fa);
#if lnx_centerworld
m.setFrom(vmat(camera.V));
@ -1271,9 +1272,9 @@ class Inc {
m.multmat(camera.P);
m.getInverse(m);
kha.compute.Compute.setMatrix(voxel_cb3, m.self);
g.setMatrix(voxel_cb3, m.self);
kha.compute.Compute.setFloat3(voxel_cc3, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
g.setFloat3(voxel_cc3, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
var width = iron.App.w();
var height = iron.App.h();
@ -1288,17 +1289,17 @@ class Inc {
width = Std.int(dp * Inc.getSuperSampling());
}
}
kha.compute.Compute.setFloat2(voxel_cd3, width, height);
g.setFloat2(voxel_cd3, width, height);
kha.compute.Compute.setFloat(voxel_ce3, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
g.setFloat(voxel_ce3, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
#if lnx_irradiance
var irradiance = iron.Scene.active.world == null ?
iron.data.WorldData.getEmptyIrradiance() :
iron.Scene.active.world.probe.irradiance;
kha.compute.Compute.setFloats(voxel_cf3, irradiance);
g.setFloats(voxel_cf3, irradiance);
#end
#if lnx_radiance
kha.compute.Compute.setFloat(voxel_cg3, iron.Scene.active.world != null ? iron.Scene.active.world.probe.raw.radiance_mipmaps + 1 - 2 : 1);
g.setFloat(voxel_cg3, iron.Scene.active.world != null ? iron.Scene.active.world.probe.raw.radiance_mipmaps + 1 - 2 : 1);
#end
#if lnx_envcol
@ -1312,29 +1313,29 @@ class Inc {
z = camera.data.raw.clear_color[2];
}
kha.compute.Compute.setFloat3(voxel_ch3, x, y, z);
g.setFloat3(voxel_ch3, x, y, z);
#end
kha.compute.Compute.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
g.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
}
#end
public static function resolveSpecular() {
public static function resolveSpecular(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
kha.compute.Compute.setShader(voxel_sh4);
g.setComputeShader(voxel_sh4);
kha.compute.Compute.setSampledTexture(voxel_ta4, rts.get("voxelsOut").image);
kha.compute.Compute.setSampledTexture(voxel_tb4, rts.get("half").image);
kha.compute.Compute.setSampledTexture(voxel_tc4, rts.get("gbuffer0").image);
kha.compute.Compute.setSampledTexture(voxel_td4, rts.get("voxelsSDF").image);
kha.compute.Compute.setTexture(voxel_te4, rts.get("voxels_specular").image, kha.compute.Access.Write);
g.setTexture(voxel_ta4, rts.get("voxelsOut").image);
g.setTexture(voxel_tb4, rts.get("half").image);
g.setTexture(voxel_tc4, rts.get("gbuffer0").image);
g.setTexture(voxel_td4, rts.get("voxelsSDF").image);
g.setImageTexture(voxel_te4, rts.get("voxels_specular").image);
#if rp_gbuffer2
kha.compute.Compute.setSampledTexture(voxel_tf4, rts.get("gbuffer2").image);
g.setTexture(voxel_tf4, rts.get("gbuffer2").image);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
@ -1351,7 +1352,7 @@ class Inc {
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca4, fa);
g.setFloats(voxel_ca4, fa);
#if lnx_centerworld
m.setFrom(vmat(camera.V));
@ -1361,9 +1362,9 @@ class Inc {
m.multmat(camera.P);
m.getInverse(m);
kha.compute.Compute.setMatrix(voxel_cb4, m.self);
g.setMatrix(voxel_cb4, m.self);
kha.compute.Compute.setFloat3(voxel_cc4, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
g.setFloat3(voxel_cc4, camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
var width = iron.App.w();
var height = iron.App.h();
@ -1378,13 +1379,13 @@ class Inc {
width = Std.int(dp * Inc.getSuperSampling());
}
}
kha.compute.Compute.setFloat2(voxel_cd4, width, height);
g.setFloat2(voxel_cd4, width, height);
kha.compute.Compute.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
g.compute(Std.int((width + 7) / 8), Std.int((height + 7) / 8), 1);
}
#if (rp_voxels == "Voxel GI")
public static function computeVoxelsLight() {
public static function computeVoxelsLight(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
@ -1398,69 +1399,69 @@ class Inc {
if (!l.visible) continue;
path.light = l;
kha.compute.Compute.setShader(voxel_sh5);
g.setComputeShader(voxel_sh5);
kha.compute.Compute.setTexture(voxel_ta5, rts.get("voxelsLight").image, kha.compute.Access.Write);
kha.compute.Compute.setTexture(voxel_te5, rts.get("voxels").image, kha.compute.Access.Read);
kha.compute.Compute.setSampledTexture(voxel_tf5, rts.get("voxelsOut").image);
kha.compute.Compute.setSampledTexture(voxel_tg5, rts.get("voxelsSDF").image);
g.setImageTexture(voxel_ta5, rts.get("voxelsLight").image);
g.setImageTexture(voxel_te5, rts.get("voxels").image);
g.setTexture(voxel_tf5, rts.get("voxelsOut").image);
g.setTexture(voxel_tg5, rts.get("voxelsSDF").image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
for (j in 0...Main.voxelgiClipmapCount) {
fa[j * 10] = clipmaps[j].voxelSize;
fa[j * 10 + 1] = clipmaps[j].extents.x;
fa[j * 10 + 2] = clipmaps[j].extents.y;
fa[j * 10 + 3] = clipmaps[j].extents.z;
fa[j * 10 + 4] = clipmaps[j].center.x;
fa[j * 10 + 5] = clipmaps[j].center.y;
fa[j * 10 + 6] = clipmaps[j].center.z;
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
}
kha.compute.Compute.setFloats(voxel_ca5, fa);
g.setFloats(voxel_ca5, fa);
kha.compute.Compute.setInt(voxel_cb5, iron.RenderPath.clipmapLevel);
g.setInt(voxel_cb5, iron.RenderPath.clipmapLevel);
#if rp_shadowmap
if (l.data.raw.type == "sun") {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
kha.compute.Compute.setSampledTexture(voxel_tb5, rts.get("shadowMapAtlas").image);
g.setTexture(voxel_tb5, rts.get("shadowMapAtlas").image);
#else
kha.compute.Compute.setSampledTexture(voxel_tb5, rts.get("shadowMapAtlasSun").image);
g.setTexture(voxel_tb5, rts.get("shadowMapAtlasSun").image);
#end
#else
kha.compute.Compute.setSampledTexture(voxel_tb5, rts.get("shadowMap").image);
g.setTexture(voxel_tb5, rts.get("shadowMap").image);
#end
kha.compute.Compute.setInt(voxel_ch5, 1); // lightShadow
g.setInt(voxel_ch5, 1); // lightShadow
}
else if (l.data.raw.type == "spot" || l.data.raw.type == "area") {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
kha.compute.Compute.setSampledTexture(voxel_tc5, rts.get("shadowMapAtlas").image);
g.setTexture(voxel_tc5, rts.get("shadowMapAtlas").image);
#else
kha.compute.Compute.setSampledTexture(voxel_tc5, rts.get("shadowMapAtlasSpot").image);
g.setTexture(voxel_tc5, rts.get("shadowMapAtlasSpot").image);
#end
#else
kha.compute.Compute.setSampledTexture(voxel_tc5, rts.get("shadowMapSpot[" + lightIndex + "]").image);
g.setTexture(voxel_tc5, rts.get("shadowMapSpot[" + lightIndex + "]").image);
#end
kha.compute.Compute.setInt(voxel_ch5, 2);
g.setInt(voxel_ch5, 2);
}
else {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
kha.compute.Compute.setSampledTexture(voxel_td5, rts.get("shadowMapAtlas").image);
g.setTexture(voxel_td5, rts.get("shadowMapAtlas").image);
#else
kha.compute.Compute.setSampledTexture(voxel_td5, rts.get("shadowMapAtlasPoint").image);
kha.compute.Compute.setInt(voxel_cl5, i);
kha.compute.Compute.setFloats(voxel_cm5, iron.object.LightObject.pointLightsData);
g.setTexture(voxel_td5, rts.get("shadowMapAtlasPoint").image);
g.setInt(voxel_cl5, i);
g.setFloats(voxel_cm5, iron.object.LightObject.pointLightsData);
#end
#else
kha.compute.Compute.setSampledCubeMap(voxel_td5, rts.get("shadowMapPoint[" + lightIndex + "]").cubeMap);
g.setCubeMap(voxel_td5, rts.get("shadowMapPoint[" + lightIndex + "]").cubeMap);
#end
kha.compute.Compute.setInt(voxel_ch5, 3);
g.setInt(voxel_ch5, 3);
}
// lightProj
@ -1472,7 +1473,7 @@ class Inc {
var c:kha.FastFloat = f2 * far * near;
var vx:kha.FastFloat = a / b;
var vy:kha.FastFloat = c / b;
kha.compute.Compute.setFloat2(voxel_ci5, vx, vy);
g.setFloat2(voxel_ci5, vx, vy);
// LVP
m.setFrom(l.VP);
m.multmat(iron.object.Uniforms.biasMat);
@ -1496,29 +1497,29 @@ class Inc {
#end
}
#end
kha.compute.Compute.setMatrix(voxel_cj5, m.self);
g.setMatrix(voxel_cj5, m.self);
// shadowsBias
kha.compute.Compute.setFloat(voxel_ck5, l.data.raw.shadows_bias);
g.setFloat(voxel_ck5, l.data.raw.shadows_bias);
#end // rp_shadowmap
// lightPos
kha.compute.Compute.setFloat3(voxel_cc5, l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
g.setFloat3(voxel_cc5, l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
// lightCol
var f = l.data.raw.strength;
kha.compute.Compute.setFloat3(voxel_cd5, l.data.raw.color[0] * f, l.data.raw.color[1] * f, l.data.raw.color[2] * f);
g.setFloat3(voxel_cd5, l.data.raw.color[0] * f, l.data.raw.color[1] * f, l.data.raw.color[2] * f);
// lightType
kha.compute.Compute.setInt(voxel_ce5, iron.data.LightData.typeToInt(l.data.raw.type));
g.setInt(voxel_ce5, iron.data.LightData.typeToInt(l.data.raw.type));
// lightDir
var v = l.look();
kha.compute.Compute.setFloat3(voxel_cf5, v.x, v.y, v.z);
g.setFloat3(voxel_cf5, v.x, v.y, v.z);
// spotData
if (l.data.raw.type == "spot") {
var vx = l.data.raw.spot_size;
var vy = vx - l.data.raw.spot_blend;
kha.compute.Compute.setFloat2(voxel_cg5, vx, vy);
g.setFloat2(voxel_cg5, vx, vy);
}
kha.compute.Compute.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
if (!iron.object.LightObject.discardLightCulled(l)) {
lightIndex++;

View File

@ -1,231 +0,0 @@
package leenkx.renderpath;
import kha.FastFloat;
import kha.arrays.Float32Array;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
import iron.data.WorldData;
import iron.math.Vec2;
import iron.math.Vec3;
import leenkx.math.Helper;
/**
Utility class to control the Nishita sky model.
**/
class Nishita {
public static var data: NishitaData = null;
/**
Recomputes the nishita lookup table after the density settings changed.
Do not call this method on every frame (it's slow)!
**/
public static function recompute(world: WorldData) {
if (world == null || world.raw.nishita_density == null) return;
if (data == null) data = new NishitaData();
var density = world.raw.nishita_density;
data.computeLUT(new Vec3(density[0], density[1], density[2]));
}
/** Sets the sky's density parameters and calls `recompute()` afterwards. **/
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3);
var density = world.raw.nishita_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
recompute(world);
}
}
/**
This class holds the precalculated result of the inner scattering integral
of the Nishita sky model. The outer integral is calculated in
[`leenkx/Shaders/std/sky.glsl`](https://github.com/leenkx3d/leenkx/blob/master/Shaders/std/sky.glsl).
@see `leenkx.renderpath.Nishita`
**/
class NishitaData {
public var lut: kha.Image;
/**
The amount of individual sample heights stored in the LUT (and the width
of the LUT image).
**/
public static var lutHeightSteps = 128;
/**
The amount of individual sun angle steps stored in the LUT (and the
height of the LUT image).
**/
public static var lutAngleSteps = 128;
/**
Amount of steps for calculating the inner scattering integral. Heigher
values are more precise but take longer to compute.
**/
public static var jSteps = 8;
/** Radius of the atmosphere in kilometers. **/
public static var radiusAtmo = 6420.0;
/**
Radius of the planet in kilometers. The default value is the earth radius as
defined in Cycles.
**/
public static var radiusPlanet = 6360.0;
/** Rayleigh scattering coefficient. **/
public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6);
/** Rayleigh scattering scale parameter. **/
public static var rayleighScale = 8e3;
/** Mie scattering coefficient. **/
public static var mieCoeff = 2e-5;
/** Mie scattering scale parameter. **/
public static var mieScale = 1.2e3;
/** Ozone scattering coefficient. **/
// The ozone absorption coefficients are taken from Cycles code.
// Because Cycles calculates 21 wavelengths, we use the coefficients
// which are closest to the RGB wavelengths (645nm, 510nm, 440nm).
// Precalculating values by simulating Blender's spec_to_xyz() function
// to include all 21 wavelengths gave unrealistic results.
public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914);
public function new() {}
/** Approximates the density of ozone for a given sample height. **/
function getOzoneDensity(height: FastFloat): FastFloat {
// Values are taken from Cycles code
if (height < 10000.0 || height >= 40000.0) {
return 0.0;
}
if (height < 25000.0) {
return (height - 10000.0) / 15000.0;
}
return -((height - 40000.0) / 15000.0);
}
/**
Ray-sphere intersection test that assumes the sphere is centered at the
origin. There is no intersection when result.x > result.y. Otherwise
this function returns the distances to the two intersection points,
which might be equal.
**/
function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Float): Vec2 {
// Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
var a = rayDirection.dot(rayDirection);
var b = 2.0 * rayDirection.dot(rayOrigin);
var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius);
var d = (b * b) - 4.0 * a * c;
// Ray does not intersect the sphere
if (d < 0.0) return new Vec2(1e5, -1e5);
return new Vec2(
(-b - Math.sqrt(d)) / (2.0 * a),
(-b + Math.sqrt(d)) / (2.0 * a)
);
}
/**
Computes the LUT texture for the given density values.
@param density 3D vector of air density, dust density, ozone density
**/
public function computeLUT(density: Vec3) {
var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4);
for (x in 0...lutHeightSteps) {
var height = (x / (lutHeightSteps - 1));
// Use quadratic height for better horizon precision
height *= height;
height *= radiusAtmo * 1000; // Denormalize height
for (y in 0...lutAngleSteps) {
var sunTheta = y / (lutAngleSteps - 1) * 2 - 1;
// Improve horizon precision
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta;
sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize
var jODepth = sampleSecondaryRay(height, sunTheta, density);
var pixelIndex = (x + y * lutHeightSteps) * 4;
imageData[pixelIndex + 0] = jODepth.x;
imageData[pixelIndex + 1] = jODepth.y;
imageData[pixelIndex + 2] = jODepth.z;
imageData[pixelIndex + 3] = 1.0; // Unused
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage);
}
/**
Calculates the integral for the secondary ray.
**/
public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 {
var radiusPlanetMeters = radiusPlanet * 1000;
// Reconstruct values from the shader
var iPos = new Vec3(0, 0, height + radiusPlanetMeters);
var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize();
var jTime: FastFloat = 0.0;
// We compute the ray-sphere intersection in km to allow larger
// atmosphere radii (radius is squared inside raySphereIntersection())
var jStepSize: FastFloat = raySphereIntersection(iPos.clone().mult(0.001), pSun, radiusAtmo).y / jSteps;
jStepSize *= 1000; // convert back to m
// Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone)
var jODepth = new Vec3();
for (i in 0...jSteps) {
// Calculate the secondary ray sample position and height
var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5));
var jHeight = jPos.length() - radiusPlanetMeters;
// Accumulate optical depth
var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x;
var optDepthMie = Math.exp(-jHeight / mieScale) * density.y;
var optDepthOzone = getOzoneDensity(jHeight) * density.z;
jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone);
jTime += jStepSize;
}
jODepth.mult(jStepSize);
// Precalculate a part of the secondary attenuation.
// For one variable (e.g. x) in the vector, the formula is as follows:
//
// attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x)))
//
// We can split that up via:
//
// attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x))
// = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x))
//
// The first factor of the resulting multiplication is calculated in the
// shader, but we can already precalculate the second one. As a side
// effect this keeps the range of the LUT values small because we don't
// store the optical depth but the attenuation.
var jAttenuation = new Vec3();
var mie = mieCoeff * jODepth.y;
jAttenuation.addf(mie, mie, mie);
jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x));
jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z));
jAttenuation.exp(jAttenuation.mult(-1));
return jAttenuation;
}
}

View File

@ -415,9 +415,8 @@ class RenderPathDeferred {
t.width = 0;
t.height = 0;
t.displayp = Inc.getDisplayp();
t.format = Inc.getHdrFormat();
t.format = "RGBA64";
t.scale = Inc.getSuperSampling();
t.depth_buffer = "main";
path.createRenderTarget(t);
}
#end
@ -676,8 +675,10 @@ class RenderPathDeferred {
if (leenkx.data.Config.raw.rp_gi != false)
{
var path = RenderPath.active;
// TODO: investigate currentTarget
var g = path.frameG;
Inc.computeVoxelsBegin();
Inc.computeVoxelsBegin(g);
if (iron.RenderPath.pre_clear == true)
{
@ -693,7 +694,7 @@ class RenderPathDeferred {
else
{
path.clearImage("voxels", 0x00000000);
Inc.computeVoxelsOffsetPrev();
Inc.computeVoxelsOffsetPrev(g);
}
path.setTarget("");
@ -713,11 +714,11 @@ class RenderPathDeferred {
path.drawMeshes("voxel");
Inc.computeVoxelsTemporal();
Inc.computeVoxelsTemporal(g);
#if (rp_voxels == "Voxel GI")
Inc.computeVoxelsLight();
Inc.computeVoxelsSDF();
Inc.computeVoxelsLight(g);
Inc.computeVoxelsSDF(g);
#end
if (iron.RenderPath.res_pre_clear == true) {
@ -777,15 +778,16 @@ class RenderPathDeferred {
#if (rp_voxels != "Off")
if (leenkx.data.Config.raw.rp_gi != false)
{
var g = path.frameG;
#if (lnx_config && (rp_voxels == "Voxel AO"))
voxelao_pass = true;
#end
#if (rp_voxels == "Voxel AO")
Inc.resolveAO();
Inc.resolveAO(g);
path.bindTarget("voxels_ao", "voxels_ao");
#else
Inc.resolveDiffuse();
Inc.resolveSpecular();
Inc.resolveDiffuse(g);
Inc.resolveSpecular(g);
path.bindTarget("voxels_diffuse", "voxels_diffuse");
path.bindTarget("voxels_specular", "voxels_specular");
#end
@ -838,18 +840,6 @@ class RenderPathDeferred {
}
#end
#if rp_water
{
path.setTarget("buf");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("tex");
path.bindTarget("_main", "gbufferD");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
}
#end
#if (!kha_opengl)
path.setDepthFrom("tex", "gbuffer0"); // Re-bind depth
#end
@ -876,6 +866,20 @@ class RenderPathDeferred {
}
#end
#if rp_water
{
path.setDepthFrom("tex", "gbuffer1");
path.setTarget("buf");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("tex");
path.bindTarget("_main", "gbufferD");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
path.setDepthFrom("tex", "gbuffer0");
}
#end
#if rp_ssr
{
if (leenkx.data.Config.raw.rp_ssr != false) {
@ -948,20 +952,34 @@ class RenderPathDeferred {
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
//#if (!kha_opengl)
//path.setDepthFrom("gbuffer0", "gbuffer1"); // Unbind depth so we can read it
//path.depthToRenderTarget.set("main", path.renderTargets.get("tex"));
//#end
//save depth
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
//#if (!kha_opengl)
//path.setDepthFrom("gbuffer0", "tex"); // Re-bind depth
//path.depthToRenderTarget.set("main", path.renderTargets.get("gbuffer0"));
//#end
//save background color
path.setTarget("refr");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("gbuffer0", ["tex", "gbuffer_refraction",
#if rp_gbuffer2 "gbuffer1", #end
#if rp_gbuffer_emission "buf", #end
]);
path.setTarget("gbuffer0", ["gbuffer1", "gbuffer_refraction"]);
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
#end
#if rp_shadowmap
{
@ -973,32 +991,21 @@ class RenderPathDeferred {
}
#end
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
#end
#if rp_ssrs
path.bindTarget("_main", "gbufferD");
#end
path.drawMeshes("refraction");
path.setTarget("buf");
path.bindTarget("tex", "tex"); // scene with refractive objects
path.bindTarget("refr", "tex1"); // background without refractive objects
path.bindTarget("_main", "gbufferD");
path.setTarget("tex");
path.bindTarget("refr", "tex");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("gbuffer0", "gbuffer0");
path.bindTarget("gbuffer1", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("tex");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
@ -1098,7 +1105,7 @@ class RenderPathDeferred {
RenderPathCreator.finalTarget = path.currentTarget;
var target = "";
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture))
#if (!rp_fsr1 && ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture)))
{
target = framebuffer;
}
@ -1160,7 +1167,15 @@ class RenderPathDeferred {
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
#if (rp_antialiasing == "TAA")
#if rp_fsr1
path.isProbe ? path.setTarget("buf") : path.setTarget("bufa");
#else
path.isProbe ? path.setTarget(framebuffer) : path.setTarget("bufa");
#end
#elseif (rp_supersampling == 4)
path.setTarget("bufa");
#elseif rp_fsr1
path.setTarget("bufa");
#else
path.setTarget(framebuffer);
#end
@ -1173,17 +1188,36 @@ class RenderPathDeferred {
#end
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
#if ((rp_supersampling == 4) || rp_fsr1)
#if (rp_antialiasing == "SMAA")
path.setTarget("buf");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#end
#end
#if (rp_antialiasing == "TAA")
{
if (!path.isProbe) { // No last frame for probe
path.setTarget(framebuffer);
// Write TAA blend to bufb to avoid read-write hazard on taa
path.setTarget("bufb");
path.bindTarget("bufa", "tex");
path.bindTarget("taa", "tex2");
path.bindTarget("gbuffer2", "sveloc");
path.drawShader("shader_datas/taa_pass/taa_pass");
// Save blended result as history for next frame
path.setTarget("taa");
path.bindTarget("bufa", "tex");
path.bindTarget("bufb", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
// Output to framebuffer
#if rp_fsr1
path.setTarget("buf");
#else
path.setTarget(framebuffer);
#end
path.bindTarget("bufb", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
@ -1194,9 +1228,9 @@ class RenderPathDeferred {
#if rp_fsr1
{
path.setTarget("bufa");
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
path.setTarget(framebuffer != "" ? framebuffer : "buf");
path.setTarget(framebuffer);
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
}
@ -1207,7 +1241,7 @@ class RenderPathDeferred {
var finalTarget = "";
path.setTarget(finalTarget);
path.bindTarget(framebuffer, "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
}
#end

View File

@ -147,7 +147,7 @@ class RenderPathForward {
t.width = 0;
t.height = 0;
t.displayp = Inc.getDisplayp();
t.format = "DEPTH24";
t.format = "R32";
t.scale = Inc.getSuperSampling();
path.createRenderTarget(t);
@ -446,6 +446,18 @@ class RenderPathForward {
path.bindTarget("voxels", "voxels");
#if rp_shadowmap
{
#if lnx_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
path.drawMeshes("voxel");
Inc.computeVoxelsTemporal();
#if (rp_voxels == "Voxel GI")
@ -473,7 +485,7 @@ class RenderPathForward {
#if (rp_ssrefr || lnx_voxelgi_refract)
{
path.setTarget("gbuffer_refraction");
path.clearTarget(0xffff00ff);
path.clearTarget(0xffffff00);
}
#end
@ -544,11 +556,21 @@ class RenderPathForward {
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
#end
//save depth
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
#end
//save background color
path.setTarget("refr");
path.bindTarget("lbuffer0", "tex");
@ -579,18 +601,17 @@ class RenderPathForward {
path.drawMeshes("refraction");
path.setTarget("bufa");
path.setTarget("lbuffer0");
path.bindTarget("lbuffer0", "tex");
path.bindTarget("refr", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("lbuffer1", "gbuffer0");
path.bindTarget("lbuffer0", "gbuffer1");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("lbuffer0");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
@ -630,53 +651,6 @@ class RenderPathForward {
}
#end
#if rp_ssrefr
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("refr");
path.bindTarget("lbuffer0", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("lbuffer0", ["lbuffer1", "gbuffer_refraction"]);
#if rp_shadowmap
{
#if lnx_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
path.drawMeshes("refraction");
path.setTarget("bufa");
path.bindTarget("lbuffer0", "tex");
path.bindTarget("refr", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("lbuffer1", "gbuffer0");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("lbuffer0");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
#if rp_bloom
{
inline Inc.drawBloom("lbuffer0", bloomDownsampler, bloomUpsampler);
@ -718,8 +692,10 @@ class RenderPathForward {
#if rp_water
{
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
#end
path.setTarget("bufa");
path.bindTarget("lbuffer0", "tex");
@ -730,8 +706,10 @@ class RenderPathForward {
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
#end
}
#end
@ -794,10 +772,20 @@ class RenderPathForward {
path.bindTarget("bufa", "edgesTex");
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
#if (rp_supersampling == 4)
path.setTarget("bufa");
#else
path.setTarget(framebuffer);
#end
path.bindTarget("buf", "colorTex");
path.bindTarget("bufb", "blendTex");
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
#if (rp_supersampling == 4)
path.setTarget("buf");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#end
}
#end
@ -805,17 +793,16 @@ class RenderPathForward {
{
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
#if (rp_supersampling == 4)
var fsrSource = "buf";
var fsrDest = "buf";
#else
// SMAA outputs to framebuffer which needs an intermediate buffer
path.setTarget("bufb");
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
var fsrSource = "bufb";
var fsrDest = "";
#end
// SMAA outputs to framebuffer which needs an intermediate buffer
path.setTarget("bufb");
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
var fsrSource = "bufb";
#if (rp_supersampling == 4)
var fsrDest = "buf";
#else
var fsrDest = "";
#end
#else
#if (rp_supersampling == 4)
var fsrSource = "buf";

View File

@ -0,0 +1,674 @@
package leenkx.renderpath;
import kha.FastFloat;
import kha.arrays.Float32Array;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
import iron.data.WorldData;
import iron.math.Vec2;
import iron.math.Vec3;
import iron.math.Vec4;
import leenkx.math.Helper;
/**
Utility class to control the sky models (single and multiple scattering).
**/
class Sky {
public static var singleScatterData: SingleScatteringData = null;
public static var multiScatterData: MultipleScatteringData = null;
/**
Recomputes the single scattering lookup table after the density settings changed.
Do not call this method on every frame (it's slow)!
**/
public static function recomputeSingleScatter(world: WorldData) {
if (world == null || world.raw.sky_density == null) return;
if (singleScatterData == null) singleScatterData = new SingleScatteringData();
var density = world.raw.sky_density;
singleScatterData.computeLUT(new Vec3(density[0], density[1], density[2]));
}
/** Sets the sky's density parameters and calls `recomputeSingleScatter()` afterwards. **/
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
recomputeSingleScatter(world);
}
public static function recomputeMultiScatter(world: WorldData) {
if (world == null) return;
var raw = world.raw;
if (raw.sky_density == null) return;
if (multiScatterData == null) multiScatterData = new MultipleScatteringData();
var density = raw.sky_density;
var sunElevation = raw.sky_sun_elevation != null ? raw.sky_sun_elevation : 0.0;
var sunRotation = raw.sky_sun_rotation != null ? raw.sky_sun_rotation : 0.0;
var sunSize = raw.sky_sun_size != null ? raw.sky_sun_size : 0.545;
var sunIntensity = raw.sky_sun_intensity != null ? raw.sky_sun_intensity : 1.0;
var altitude = raw.sky_altitude != null ? raw.sky_altitude : 0.0;
var sunDisc = raw.sky_sun_disc != null ? raw.sky_sun_disc : 1;
multiScatterData.compute(
sunElevation,
sunRotation,
sunSize,
sunIntensity,
altitude,
sunDisc != 0,
new Vec3(density[0], density[1], density[2])
);
}
public static function setMultiScatterParams(world: WorldData, sunElevation: FastFloat, sunRotation: FastFloat,
sunSize: FastFloat, sunIntensity: FastFloat, altitude: FastFloat, sunDisc: Bool,
densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
world.raw.sky_sun_elevation = sunElevation;
world.raw.sky_sun_rotation = sunRotation;
world.raw.sky_sun_size = sunSize;
world.raw.sky_sun_intensity = sunIntensity;
world.raw.sky_altitude = altitude;
world.raw.sky_sun_disc = sunDisc ? 1 : 0;
recomputeMultiScatter(world);
}
}
/**
This class holds the precalculated result of the inner scattering integral
of the single scattering sky model. The outer integral is calculated in
[`leenkx/Shaders/std/sky.glsl`](https://github.com/leenkx3d/leenkx/blob/master/Shaders/std/sky.glsl).
@see `leenkx.renderpath.Sky`
**/
class SingleScatteringData {
public var lut: kha.Image;
/**
The amount of individual sample heights stored in the LUT (and the width
of the LUT image).
**/
public static var lutHeightSteps = 128;
/**
The amount of individual sun angle steps stored in the LUT (and the
height of the LUT image).
**/
public static var lutAngleSteps = 128;
/**
Amount of steps for calculating the inner scattering integral. Heigher
values are more precise but take longer to compute.
**/
public static var jSteps = 8;
/** Radius of the atmosphere in kilometers. **/
public static var radiusAtmo = 6420.0;
/**
Radius of the planet in kilometers. The default value is the earth radius as
defined in Cycles.
**/
public static var radiusPlanet = 6360.0;
/** Rayleigh scattering coefficient. **/
public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6);
/** Rayleigh scattering scale parameter. **/
public static var rayleighScale = 8e3;
/** Mie scattering coefficient. **/
public static var mieCoeff = 2e-5;
/** Mie scattering scale parameter. **/
public static var mieScale = 1.2e3;
/** Ozone scattering coefficient. **/
// The ozone absorption coefficients are taken from Cycles code.
// Because Cycles calculates 21 wavelengths, we use the coefficients
// which are closest to the RGB wavelengths (645nm, 510nm, 440nm).
// Precalculating values by simulating Blender's spec_to_xyz() function
// to include all 21 wavelengths gave unrealistic results.
public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914);
public function new() {}
/** Approximates the density of ozone for a given sample height. **/
function getOzoneDensity(height: FastFloat): FastFloat {
// Values are taken from Cycles code
if (height < 10000.0 || height >= 40000.0) {
return 0.0;
}
if (height < 25000.0) {
return (height - 10000.0) / 15000.0;
}
return -((height - 40000.0) / 15000.0);
}
/**
Ray-sphere intersection test that assumes the sphere is centered at the
origin. There is no intersection when result.x > result.y. Otherwise
this function returns the distances to the two intersection points,
which might be equal.
**/
function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Float): Vec2 {
// Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
var a = rayDirection.dot(rayDirection);
var b = 2.0 * rayDirection.dot(rayOrigin);
var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius);
var d = (b * b) - 4.0 * a * c;
// Ray does not intersect the sphere
if (d < 0.0) return new Vec2(1e5, -1e5);
return new Vec2(
(-b - Math.sqrt(d)) / (2.0 * a),
(-b + Math.sqrt(d)) / (2.0 * a)
);
}
/**
Computes the LUT texture for the given density values.
@param density 3D vector of air density, dust density, ozone density
**/
public function computeLUT(density: Vec3) {
var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4);
for (x in 0...lutHeightSteps) {
var height = (x / (lutHeightSteps - 1));
// Use quadratic height for better horizon precision
height *= height;
height *= radiusAtmo * 1000; // Denormalize height
for (y in 0...lutAngleSteps) {
var sunTheta = y / (lutAngleSteps - 1) * 2 - 1;
// Improve horizon precision
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta;
sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize
var jODepth = sampleSecondaryRay(height, sunTheta, density);
var pixelIndex = (x + y * lutHeightSteps) * 4;
imageData[pixelIndex + 0] = jODepth.x;
imageData[pixelIndex + 1] = jODepth.y;
imageData[pixelIndex + 2] = jODepth.z;
imageData[pixelIndex + 3] = 1.0; // Unused
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage);
}
/**
Calculates the integral for the secondary ray.
**/
public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 {
var radiusPlanetMeters = radiusPlanet * 1000;
// Reconstruct values from the shader
var iPos = new Vec3(0, 0, height + radiusPlanetMeters);
var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize();
var jTime: FastFloat = 0.0;
// We compute the ray-sphere intersection in km to allow larger
// atmosphere radii (radius is squared inside raySphereIntersection())
var jStepSize: FastFloat = raySphereIntersection(iPos.clone().mult(0.001), pSun, radiusAtmo).y / jSteps;
jStepSize *= 1000; // convert back to m
// Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone)
var jODepth = new Vec3();
for (i in 0...jSteps) {
// Calculate the secondary ray sample position and height
var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5));
var jHeight = jPos.length() - radiusPlanetMeters;
// Accumulate optical depth
var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x;
var optDepthMie = Math.exp(-jHeight / mieScale) * density.y;
var optDepthOzone = getOzoneDensity(jHeight) * density.z;
jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone);
jTime += jStepSize;
}
jODepth.mult(jStepSize);
// Precalculate a part of the secondary attenuation.
// For one variable (e.g. x) in the vector, the formula is as follows:
//
// attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x)))
//
// We can split that up via:
//
// attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x))
// = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x))
//
// The first factor of the resulting multiplication is calculated in the
// shader, but we can already precalculate the second one. As a side
// effect this keeps the range of the LUT values small because we don't
// store the optical depth but the attenuation.
var jAttenuation = new Vec3();
var mie = mieCoeff * jODepth.y;
jAttenuation.addf(mie, mie, mie);
jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x));
jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z));
jAttenuation.exp(jAttenuation.mult(-1));
return jAttenuation;
}
}
/**
This class precomputes the full sky radiance LUT
Unlike the single scattering model matching Blenders implementation.
**/
class MultipleScatteringData {
public var lut: kha.Image;
public var sunBottom: Vec3;
public var sunTop: Vec3;
public var earthIntersectionAngle: FastFloat;
public static var lutWidth = 256;
public static var lutHeight = 128;
static var transmittanceResX = 256;
static var transmittanceResY = 64;
var transmittanceLUT: haxe.io.Float32Array;
static var transmittanceSteps = 64;
static var inScatteringSteps = 64;
// Atmosphere constants sky_multiple_scattering.cpp
static var EARTH_RADIUS = 6371.0;
static var ATMOSPHERE_THICKNESS = 100.0;
static var ATMOSPHERE_RADIUS = 6471.0; // EARTH_RADIUS + ATMOSPHERE_THICKNESS
static var PHASE_ISOTROPIC = 1.0 / (4.0 * Math.PI);
static var RAYLEIGH_PHASE_SCALE = (3.0 / 16.0) * (1.0 / Math.PI);
static var G = 0.8;
static var SQR_G = 0.64;
static var GROUND_ALBEDO: Array<Float> = [0.3, 0.3, 0.3, 0.3];
// Spectral data sampled at 630, 560, 490, 430 nm
static var SUN_SPECTRAL_IRRADIANCE: Array<Float> = [1.679, 1.828, 1.986, 1.307];
static var MOLECULAR_SCATTERING_COEFFICIENT_BASE: Array<Float> = [6.605e-3, 1.067e-2, 1.842e-2, 3.156e-2];
static var OZONE_ABSORPTION_CROSS_SECTION: Array<Float> = [3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27];
static var OZONE_MEAN_DOBSON = 334.5;
static var AEROSOL_ABSORPTION_CROSS_SECTION: Array<Float> = [2.8722e-24, 4.6168e-24, 7.9706e-24, 1.3578e-23];
static var AEROSOL_SCATTERING_CROSS_SECTION: Array<Float> = [1.5908e-22, 1.7711e-22, 2.0942e-22, 2.4033e-22];
static var AEROSOL_BASE_DENSITY = 1.3681e20;
static var AEROSOL_BACKGROUND_DENSITY = 2e6;
static var AEROSOL_HEIGHT_SCALE = 0.73;
static var SPECTRAL_XYZ: Array<Array<Float>> = [
[53.386917738564668023, 22.981337506691024754, 0.0],
[43.904844466369358263, 71.347795700053393866, 0.102506867965741307],
[1.6137278251608962005, 18.422960591455485011, 31.742921188390805758],
[20.762668673810577145, 2.3614213523314368527, 110.48009643252140334]
];
var airDensity: Float = 1.0;
var aerosolDensity: Float = 1.0;
var ozoneDensity: Float = 1.0;
public function new() {
sunBottom = new Vec3();
sunTop = new Vec3();
earthIntersectionAngle = 0.0;
transmittanceLUT = new haxe.io.Float32Array(transmittanceResY * transmittanceResX * 4);
}
static inline function exp4(a: Array<Float>): Array<Float> {
return [Math.exp(a[0]), Math.exp(a[1]), Math.exp(a[2]), Math.exp(a[3])];
}
static inline function scale4(a: Array<Float>, s: Float): Array<Float> {
return [a[0] * s, a[1] * s, a[2] * s, a[3] * s];
}
static inline function add4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
}
static inline function mult4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]];
}
static inline function div4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]];
}
static inline function max4(a: Array<Float>, b: Float): Array<Float> {
return [Math.max(a[0], b), Math.max(a[1], b), Math.max(a[2], b), Math.max(a[3], b)];
}
static inline function mix4(a: Array<Float>, b: Array<Float>, t: Float): Array<Float> {
return [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), a[2] + t * (b[2] - a[2]), a[3] + t * (b[3] - a[3])];
}
// Atmospheric functions
function getMolecularScatteringCoefficient(h: Float): Array<Float> {
// MOLECULAR_SCATTERING_COEFFICIENT_BASE * exp(-0.07771971 * h^1.16364243)
var f = Math.exp(-0.07771971 * Math.pow(h, 1.16364243));
return scale4(MOLECULAR_SCATTERING_COEFFICIENT_BASE, f);
}
function getMolecularAbsorptionCoefficient(h: Float): Array<Float> {
var logH = Math.log(Math.max(h, 1e-4));
var density = 3.78547397e20 * Math.exp(-(logH - 3.22261) * (logH - 3.22261) * 5.55555555 - logH);
var result: Array<Float> = [];
for (i in 0...4) {
result[i] = OZONE_ABSORPTION_CROSS_SECTION[i] * OZONE_MEAN_DOBSON * density;
}
return result;
}
function getAerosolDensity(h: Float): Float {
var division = AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY;
return AEROSOL_BASE_DENSITY * (Math.exp(-h / AEROSOL_HEIGHT_SCALE) + division);
}
function getAtmosphereCollisionCoefficients(h: Float): {
aerosolAbs: Array<Float>, aerosolSca: Array<Float>, molAbs: Array<Float>, molSca: Array<Float>
} {
var localAerosolDensity = getAerosolDensity(h) * aerosolDensity;
var aerosolAbs = scale4(AEROSOL_ABSORPTION_CROSS_SECTION, localAerosolDensity);
var aerosolSca = scale4(AEROSOL_SCATTERING_CROSS_SECTION, localAerosolDensity);
var molAbs = scale4(getMolecularAbsorptionCoefficient(h), ozoneDensity);
var molSca = scale4(getMolecularScatteringCoefficient(h), airDensity);
return { aerosolAbs: aerosolAbs, aerosolSca: aerosolSca, molAbs: molAbs, molSca: molSca };
}
function molecularPhaseFunction(cosTheta: Float): Float {
return RAYLEIGH_PHASE_SCALE * (1.0 + cosTheta * cosTheta);
}
function aerosolPhaseFunction(cosTheta: Float): Float {
var den = 1.0 + SQR_G + 2.0 * G * cosTheta;
return PHASE_ISOTROPIC * (1.0 - SQR_G) / (den * Math.sqrt(den));
}
// nearest positive intersection distance, or -1 if no intersection
function raySphereIntersection(pos: Vec3, dir: Vec3, radius: Float): Float {
var b = pos.dot(dir);
var c = pos.dot(pos) - radius * radius;
if (c > 0.0 && b > 0.0) return -1.0;
var d = b * b - c;
if (d < 0.0) return -1.0;
if (d >= b * b) return -b + Math.sqrt(d);
return -b - Math.sqrt(d);
}
function sunDirection(cosTheta: Float): Vec3 {
// Place sun at azimuth=0 in the atan(dir.x, dir.y) convention
// Blender uses (-sqrt(1-cos²θ), 0, cosθ) but our azimuth convention
// requires (0, sqrt(1-cos²θ), cosθ) for the sun to be at azimuth=0
return new Vec3(0.0, Math.sqrt(1.0 - cosTheta * cosTheta), cosTheta);
}
function spectralToXYZ(L: Array<Float>): Vec3 {
var x = 0.0, y = 0.0, z = 0.0;
for (i in 0...4) {
x += SPECTRAL_XYZ[i][0] * L[i];
y += SPECTRAL_XYZ[i][1] * L[i];
z += SPECTRAL_XYZ[i][2] * L[i];
}
return new Vec3(x, y, z);
}
function getTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var sunDir = sunDirection(cosTheta);
var distToCenter = EARTH_RADIUS + (ATMOSPHERE_RADIUS - EARTH_RADIUS) * normalizedAltitude;
var rayOrigin = new Vec3(0.0, 0.0, distToCenter);
var tD = raySphereIntersection(rayOrigin, sunDir, ATMOSPHERE_RADIUS);
var tStep = tD / transmittanceSteps;
var result: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (step in 0...transmittanceSteps) {
var t = (step + 0.5) * tStep;
var xT = rayOrigin.clone().add(sunDir.clone().mult(t));
var altitude = Math.max(xT.length() - EARTH_RADIUS, 0.0);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
result = add4(result, scale4(extinction, tStep));
}
return exp4(scale4(result, -1.0));
}
function precomputeTransmittanceLUT() {
for (y in 0...transmittanceResY) {
for (x in 0...transmittanceResX) {
var u = x / (transmittanceResX - 1);
var v = y / (transmittanceResY - 1);
var t = getTransmittance(u * 2.0 - 1.0, v);
var idx = (y * transmittanceResX + x) * 4;
transmittanceLUT[idx] = t[0];
transmittanceLUT[idx + 1] = t[1];
transmittanceLUT[idx + 2] = t[2];
transmittanceLUT[idx + 3] = t[3];
}
}
}
function lookupTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fx = u * (transmittanceResX - 1);
var fy = v * (transmittanceResY - 1);
var x1 = Std.int(fx);
var y1 = Std.int(fy);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dxF = fx - x1;
var dyF = fy - y1;
function getPixel(px: Int, py: Int): Array<Float> {
var idx = (py * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
var bottom = mix4(getPixel(x1, y1), getPixel(x2, y1), dxF);
var top = mix4(getPixel(x1, y2), getPixel(x2, y2), dxF);
return mix4(bottom, top, dyF);
}
function lookupTransmittanceAtGround(cosTheta: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var fx = u * (transmittanceResX - 1);
var x1 = Std.int(fx);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var dxF = fx - x1;
var y = 0;
function getPixel(px: Int): Array<Float> {
var idx = (y * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(x1), getPixel(x2), dxF);
}
function lookupTransmittanceToSun(normalizedAltitude: Float): Array<Float> {
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fy = v * (transmittanceResY - 1);
var y1 = Std.int(fy);
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dyF = fy - y1;
var x = transmittanceResX - 1;
function getPixel(py: Int): Array<Float> {
var idx = (py * transmittanceResX + x) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(y1), getPixel(y2), dyF);
}
// approximation
function lookupMultiscattering(cosTheta: Float, normalizedHeight: Float, d: Float): Array<Float> {
var omega = 2.0 * Math.PI * (1.0 - Math.sqrt(Math.max(0.0, 1.0 - (EARTH_RADIUS / d) * (EARTH_RADIUS / d))));
var tToGround = lookupTransmittanceAtGround(cosTheta);
var tGroundToSample = div4(lookupTransmittanceToSun(0.0), lookupTransmittanceToSun(normalizedHeight));
// 2nd order scattering from the ground
var lGround = scale4(mult4(mult4(mult4(scale4(GROUND_ALBEDO, 1.0 / Math.PI), tToGround), tGroundToSample), [cosTheta, cosTheta, cosTheta, cosTheta]), PHASE_ISOTROPIC * omega);
// Fit of Earth's multiple scattering from other points in the atmosphere
var msFactor = 1.0 / (1.0 + 5.0 * Math.exp(-17.92 * cosTheta));
var lMs = scale4([0.217, 0.347, 0.594, 1.0], 0.02 * msFactor);
return add4(lMs, lGround);
}
function getInscattering(sunDir: Vec3, rayOrigin: Vec3, rayDir: Vec3, tD: Float): Array<Float> {
var cosTheta = rayDir.clone().mult(-1.0).dot(sunDir);
var molPhase = molecularPhaseFunction(cosTheta);
var aerPhase = aerosolPhaseFunction(cosTheta);
var dt = tD / inScatteringSteps;
var lInscattering: Array<Float> = [0.0, 0.0, 0.0, 0.0];
var transmittance: Array<Float> = [1.0, 1.0, 1.0, 1.0];
for (i in 0...inScatteringSteps) {
var t = (i + 0.5) * dt;
var xT = rayOrigin.clone().add(rayDir.clone().mult(t));
var distToCenter = xT.length();
var zenithDir = xT.clone().mult(1.0 / distToCenter);
var altitude = Math.max(distToCenter - EARTH_RADIUS, 0.0);
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
var sampleCosTheta = zenithDir.dot(sunDir);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
var tToSun = lookupTransmittance(sampleCosTheta, normalizedAltitude);
var ms = lookupMultiscattering(sampleCosTheta, normalizedAltitude, distToCenter);
// S = SUN_SPECTRAL_IRRADIANCE * (molSca * (molPhase * tToSun + ms) + aerSca * (aerPhase * tToSun + ms))
var s: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (w in 0...4) {
var molTerm = coeffs.molSca[w] * (molPhase * tToSun[w] + ms[w]);
var aerTerm = coeffs.aerosolSca[w] * (aerPhase * tToSun[w] + ms[w]);
s[w] = SUN_SPECTRAL_IRRADIANCE[w] * (molTerm + aerTerm);
}
var stepTransmittance = exp4(scale4(extinction, -dt));
var cutExt = max4(extinction, 1e-7);
var sInt: Array<Float> = [];
for (w in 0...4) {
sInt[w] = (s[w] - s[w] * stepTransmittance[w]) / cutExt[w];
}
lInscattering = add4(lInscattering, mult4(transmittance, sInt));
transmittance = mult4(transmittance, stepTransmittance);
}
return lInscattering;
}
function computeEarthAngle(altitude: Float): Float {
return Math.PI / 2.0 - Math.asin(EARTH_RADIUS / (EARTH_RADIUS + altitude / 1000.0));
}
function precomputeSun(sunElevation: Float, angularDiameter: Float, altitude: Float): { bottom: Vec3, top: Vec3 } {
var halfAngular = angularDiameter / 2.0;
var solidAngle = 2.0 * Math.PI * (1.0 - Math.cos(halfAngular));
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
function getSunXYZ(elevation: Float): Vec3 {
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - elevation);
var tToSun = getTransmittance(sunZenithCosAngle, normalizedAltitude);
var spectrum: Array<Float> = [];
for (w in 0...4) {
spectrum[w] = SUN_SPECTRAL_IRRADIANCE[w] * tToSun[w] / solidAngle;
}
return spectralToXYZ(spectrum);
}
var bottom = getSunXYZ(sunElevation - halfAngular);
var top = getSunXYZ(sunElevation + halfAngular);
return { bottom: bottom, top: top };
}
public function compute(sunElevation: Float, sunRotation: Float, sunSize: Float,
sunIntensity: Float, altitude: Float, sunDisc: Bool, density: Vec3) {
airDensity = density.x;
aerosolDensity = density.y;
ozoneDensity = density.z;
precomputeTransmittanceLUT();
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - sunElevation);
var sunDir = sunDirection(sunZenithCosAngle);
var rayOrigin = new Vec3(0.0, 0.0, EARTH_RADIUS + altKm);
earthIntersectionAngle = computeEarthAngle(altitude);
var imageData = new haxe.io.Float32Array(lutWidth * lutHeight * 4);
for (y in 0...lutHeight) {
var uvY = (y + 0.5) / lutHeight;
var l = uvY * 2.0 - 1.0;
var elevation = (l < 0 ? -1.0 : 1.0) * l * l * Math.PI / 2.0;
var cosEl = Math.cos(elevation);
var sinEl = Math.sin(elevation);
for (x in 0...lutWidth) {
var uvX = (x + 0.5) / lutWidth;
var azimuth = 2.0 * Math.PI * uvX;
// atan(dir.x, dir.y) convention
var rayDir = new Vec3(
Math.sin(azimuth) * cosEl,
Math.cos(azimuth) * cosEl,
sinEl
).normalize();
var atmosDist = raySphereIntersection(rayOrigin, rayDir, ATMOSPHERE_RADIUS);
var groundDist = raySphereIntersection(rayOrigin, rayDir, EARTH_RADIUS);
var tD = (groundDist < 0.0) ? atmosDist : groundDist;
var radiance = getInscattering(sunDir, rayOrigin, rayDir, tD);
var xyz = spectralToXYZ(radiance);
var pixelIndex = (x + y * lutWidth) * 4;
imageData[pixelIndex + 0] = xyz.x;
imageData[pixelIndex + 1] = xyz.y;
imageData[pixelIndex + 2] = xyz.z;
imageData[pixelIndex + 3] = 1.0;
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutWidth, lutHeight, TextureFormat.RGBA128, Usage.StaticUsage);
if (sunDisc) {
var sunData = precomputeSun(sunElevation, sunSize, altKm);
sunBottom = sunData.bottom;
sunTop = sunData.top;
} else {
sunBottom.set(0, 0, 0);
sunTop.set(0, 0, 0);
}
}
}

View File

@ -2198,25 +2198,28 @@ class LeenkxExporter:
light_object = light_objects[0] if len(light_objects) > 0 else None
objtype = light_ref.type
color = [light_ref.color[0], light_ref.color[1], light_ref.color[2]]
if light_ref.use_temperature:
temperature_color = light_ref.temperature_color
color[0] *= temperature_color[0]
color[1] *= temperature_color[1]
color[2] *= temperature_color[2]
if bpy.app.version >= (4, 5, 0):
if light_ref.use_temperature:
temperature_color = light_ref.temperature_color
color[0] *= temperature_color[0]
color[1] *= temperature_color[1]
color[2] *= temperature_color[2]
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
if not light_ref.normalize:
area = 0.0
try:
if light_object is not None:
area = light_ref.area(matrix_world=light_object.matrix_world)
else:
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
if not light_ref.normalize:
area = 0.0
try:
if light_object is not None:
area = light_ref.area(matrix_world=light_object.matrix_world)
else:
area = light_ref.area()
except TypeError:
area = light_ref.area()
except TypeError:
area = light_ref.area()
if area > 0.0:
strength *= area
if area > 0.0:
strength *= area
else:
strength = light_ref.energy
out_light = {
'name': object_ref[1]["structName"],
@ -3817,7 +3820,13 @@ class LeenkxExporter:
out_world['sun_direction'] = list(world.lnx_envtex_sun_direction)
out_world['turbidity'] = world.lnx_envtex_turbidity
out_world['ground_albedo'] = world.lnx_envtex_ground_albedo
out_world['nishita_density'] = list(world.lnx_nishita_density)
out_world['sky_density'] = list(world.lnx_sky_density)
out_world['sky_sun_elevation'] = world.lnx_sky_sun_elevation
out_world['sky_sun_rotation'] = world.lnx_sky_sun_rotation
out_world['sky_sun_size'] = world.lnx_sky_sun_size
out_world['sky_sun_intensity'] = world.lnx_sky_sun_intensity
out_world['sky_altitude'] = world.lnx_sky_altitude
out_world['sky_sun_disc'] = world.lnx_sky_sun_disc
disable_hdr = world.lnx_envtex_name.endswith('.jpg')

View File

@ -3,7 +3,6 @@ import os
import queue
import sys
import threading
import time
import types
from typing import Dict, Tuple, Callable, Set
@ -13,6 +12,7 @@ from bpy.app.handlers import persistent
import lnx
import lnx.api
import lnx.nodes_logic
import lnx.render_engine
import lnx.make_state as state
import lnx.utils
import lnx.utils_vs
@ -25,6 +25,7 @@ if lnx.is_reload(__name__):
log = lnx.reload_module(log)
lnx_nodes = lnx.reload_module(lnx_nodes)
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
lnx.render_engine = lnx.reload_module(lnx.render_engine)
make = lnx.reload_module(make)
state = lnx.reload_module(state)
props = lnx.reload_module(props)
@ -33,10 +34,8 @@ if lnx.is_reload(__name__):
else:
lnx.enable_reload(__name__)
# Module-level storage for active threads (eliminates re-queuing overhead)
# Module-level storage for active threads
_active_threads: Dict[threading.Thread, Callable] = {}
_last_poll_time = 0.0
_consecutive_empty_polls = 0
_last_render_engine = None
@persistent
@ -169,6 +168,14 @@ def check_render_engine() -> float:
elif _last_render_engine == 'KROM_VIEWPORT':
try:
for vid, engine in list(lnx.render_engine._active_krom_engines.items()):
try:
engine._restore_overlay()
except:
pass
lnx.render_engine._active_krom_engines.clear()
lnx.render_engine._active_krom_engine = None
lnx.render_engine._active_viewport_id = None
make.stop_viewport()
except Exception as e:
log.warn(f'Failed to stop viewport: {e}')
@ -183,113 +190,31 @@ def check_render_engine() -> float:
def poll_threads() -> float:
"""
Improved thread polling with:
- No re-queuing overhead
- Batch processing of completed threads
- Adaptive timing based on activity
- Better memory management
- Simplified logic flow
"""
global _last_poll_time, _consecutive_empty_polls
current_time = time.time()
# Process all new threads from queue at once (batch processing)
new_threads_added = 0
"""Polls the thread callback queue and processes completed threads."""
# Drain queue into active threads
try:
while True:
thread, callback = make.thread_callback_queue.get(block=False)
_active_threads[thread] = callback
new_threads_added += 1
except queue.Empty:
pass
# Early return if no active threads
if not _active_threads:
_consecutive_empty_polls += 1
# Adaptive timing: longer intervals when consistently empty
if _consecutive_empty_polls > 10:
return 0.5 # Back off when no activity
return 0.25
# Reset empty poll counter when we have active threads
_consecutive_empty_polls = 0
# Find completed threads (single pass, no re-queuing)
completed_threads = []
# Join and callback all completed threads
for thread in list(_active_threads.keys()):
if not thread.is_alive():
completed_threads.append(thread)
# Batch process all completed threads
if completed_threads:
_process_completed_threads(completed_threads)
# Adaptive timing based on activity level
active_count = len(_active_threads)
if active_count == 0:
return 0.25
elif active_count <= 3:
return 0.05 # Medium frequency for low activity
else:
return 0.01 # High frequency for high activity
callback = _active_threads.pop(thread)
try:
thread.join()
callback()
except Exception as e:
bpy.app.timers.unregister(poll_threads)
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
raise e
def _process_completed_threads(completed_threads: list) -> None:
"""Process a batch of completed threads with robust error handling."""
for thread in completed_threads:
callback = _active_threads.pop(thread) # Remove from tracking
try:
thread.join() # Should be instant since thread is dead
callback()
except Exception as e:
# Robust error recovery
_handle_callback_error(e)
continue # Continue processing other threads
# Explicit cleanup for better memory management
del thread, callback
def _handle_callback_error(exception: Exception) -> None:
"""Centralized error handling with better recovery."""
try:
# Try to unregister existing timer
bpy.app.timers.unregister(poll_threads)
except ValueError:
pass # Timer wasn't registered, that's fine
# Re-register timer with slightly longer interval for stability
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
# Re-raise the original exception after ensuring timer continuity
raise exception
def cleanup_polling_system() -> None:
"""Optional cleanup function for proper shutdown."""
global _active_threads, _consecutive_empty_polls
# Wait for remaining threads to complete (with timeout)
for thread in list(_active_threads.keys()):
if thread.is_alive():
thread.join(timeout=1.0) # 1 second timeout
# Clear tracking structures
_active_threads.clear()
_consecutive_empty_polls = 0
# Unregister timer
try:
bpy.app.timers.unregister(poll_threads)
except ValueError:
pass
def get_polling_stats() -> dict:
"""Get statistics about the polling system for monitoring."""
return {
'active_threads': len(_active_threads),
'consecutive_empty_polls': _consecutive_empty_polls,
'thread_ids': [t.ident for t in _active_threads.keys()]
}
return 0.01
loaded_py_libraries: Dict[str, types.ModuleType] = {}

View File

@ -1,12 +0,0 @@
from lnx.logicnode.lnx_nodes import *
class GetHosekWilkiePropertiesNode(LnxLogicTreeNode):
"""Gets the HosekWilkie properties."""
bl_idname = 'LNGetHosekWilkiePropertiesNode'
bl_label = 'Get HosekWilkie Properties'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Turbidity')
self.add_output('LnxFloatSocket', 'Ground Albedo')
self.add_output('LnxVectorSocket', 'Sun Direction')

View File

@ -1,14 +0,0 @@
from lnx.logicnode.lnx_nodes import *
class GetNishitaPropertiesNode(LnxLogicTreeNode):
"""Gets the Nishita properties."""
bl_idname = 'LNGetNishitaPropertiesNode'
bl_label = 'Get Nishita Properties'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Air')
self.add_output('LnxFloatSocket', 'Dust')
self.add_output('LnxFloatSocket', 'Ozone')
self.add_output('LnxVectorSocket', 'Sun Direction')

View File

@ -0,0 +1,11 @@
from lnx.logicnode.lnx_nodes import *
class GetWorldColorNode(LnxLogicTreeNode):
"""Gets the background color of the active world."""
bl_idname = 'LNGetWorldColorNode'
bl_label = 'Get World Color'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxColorSocket', 'Color')

View File

@ -0,0 +1,51 @@
from lnx.logicnode.lnx_nodes import *
class GetWorldSkyNode(LnxLogicTreeNode):
"""Gets the sky properties for the selected sky model."""
bl_idname = 'LNGetWorldSkyNode'
bl_label = 'Get World Sky'
lnx_version = 1
legacy = 'Nishita ' if bpy.app.version < (5, 0, 0) else ''
def update_inputs(self, context):
while len(self.outputs) > 0:
self.outputs.remove(self.outputs[0])
if self.property0 == 'hosek':
self.add_output('LnxFloatSocket', 'Turbidity')
self.add_output('LnxFloatSocket', 'Ground Albedo')
self.add_output('LnxVectorSocket', 'Sun Direction')
elif self.property0 == 'single':
self.add_output('LnxFloatSocket', 'Air')
self.add_output('LnxFloatSocket', 'Dust')
self.add_output('LnxFloatSocket', 'Ozone')
self.add_output('LnxFloatSocket', 'Altitude')
self.add_output('LnxVectorSocket', 'Sun Direction')
elif self.property0 == 'multi':
self.add_output('LnxFloatSocket', 'Air')
self.add_output('LnxFloatSocket', 'Dust')
self.add_output('LnxFloatSocket', 'Ozone')
self.add_output('LnxFloatSocket', 'Sun Elevation')
self.add_output('LnxFloatSocket', 'Sun Rotation')
self.add_output('LnxFloatSocket', 'Sun Size')
self.add_output('LnxFloatSocket', 'Sun Intensity')
self.add_output('LnxFloatSocket', 'Altitude')
self.add_output('LnxIntSocket', 'Sun Disc')
self.add_output('LnxVectorSocket', 'Sun Direction')
property0: HaxeEnumProperty(
'property0',
items=[('hosek', 'Hosek Wilkie', 'Hosek-Wilkie / Preetham sky model'),
('single', f'{legacy}Single Scattering', 'Single scattering sky model'),
('multi', f'{legacy}Multiple Scattering', 'Multiple scattering sky model')],
name='', default='single', update=update_inputs)
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Air')
self.add_output('LnxFloatSocket', 'Dust')
self.add_output('LnxFloatSocket', 'Ozone')
self.add_output('LnxFloatSocket', 'Altitude')
self.add_output('LnxVectorSocket', 'Sun Direction')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -0,0 +1,12 @@
from lnx.logicnode.lnx_nodes import *
class GetWorldTextureNode(LnxLogicTreeNode):
"""Gets the texture properties of the active world (strength, envmap)."""
bl_idname = 'LNGetWorldTextureNode'
bl_label = 'Get World Texture'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Strength')
self.add_output('LnxStringSocket', 'Envmap')

View File

@ -1,38 +0,0 @@
from lnx.logicnode.lnx_nodes import *
class SetHosekWilkiePropertiesNode(LnxLogicTreeNode):
"""Sets the HosekWilkie properties."""
bl_idname = 'LNSetHosekWilkiePropertiesNode'
bl_label = 'Set HosekWilkie Properties'
lnx_version = 1
def remove_extra_inputs(self, context):
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Turbidity/Ground Albedo':
self.add_input('LnxFloatSocket', 'Turbidity')
self.add_input('LnxFloatSocket', 'Ground Albedo')
if self.property0 == 'Turbidity':
self.add_input('LnxFloatSocket', 'Turbidity')
if self.property0 == 'Ground Albedo':
self.add_input('LnxFloatSocket', 'Ground Albedo')
if self.property0 == 'Sun Direction':
self.add_input('LnxVectorSocket', 'Sun Direction')
property0: HaxeEnumProperty(
'property0',
items = [('Turbidity/Ground Albedo', 'Turbidity/Ground Albedo', 'Turbidity, Ground Albedo'),
('Turbidity', 'Turbidity', 'Turbidity'),
('Ground Albedo', 'Ground Albedo', 'Ground Albedo'),
('Sun Direction', 'Sun Direction', 'Sun Direction')],
name='', default='Turbidity/Ground Albedo', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Turbidity')
self.add_input('LnxFloatSocket', 'Ground_Albedo')
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -1,43 +0,0 @@
from lnx.logicnode.lnx_nodes import *
class SetNishitaPropertiesNode(LnxLogicTreeNode):
"""Sets the Nishita properties"""
bl_idname = 'LNSetNishitaPropertiesNode'
bl_label = 'Set Nishita Properties'
lnx_version = 1
def remove_extra_inputs(self, context):
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Density':
self.add_input('LnxFloatSocket', 'Air')
self.add_input('LnxFloatSocket', 'Dust')
self.add_input('LnxFloatSocket', 'Ozone')
if self.property0 == 'Air':
self.add_input('LnxFloatSocket', 'Air')
if self.property0 == 'Dust':
self.add_input('LnxFloatSocket', 'Dust')
if self.property0 == 'Ozone':
self.add_input('LnxFloatSocket', 'Ozone')
if self.property0 == 'Sun Direction':
self.add_input('LnxVectorSocket', 'Sun Direction')
property0: HaxeEnumProperty(
'property0',
items = [('Density', 'Density', 'Air, Dust, Ozone'),
('Air', 'Air', 'Air'),
('Dust', 'Dust', 'Dust'),
('Ozone', 'Ozone', 'Ozone'),
('Sun Direction', 'Sun Direction', 'Sun Direction')],
name='', default='Density', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Air')
self.add_input('LnxFloatSocket', 'Dust')
self.add_input('LnxFloatSocket', 'Ozone')
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -0,0 +1,14 @@
from lnx.logicnode.lnx_nodes import *
class SetWorldColorNode(LnxLogicTreeNode):
"""Sets the background color of the active world."""
bl_idname = 'LNSetWorldColorNode'
bl_label = 'Set World Color'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxColorSocket', 'Color', default_value=[0.0, 0.0, 0.0, 1.0])
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,54 @@
from lnx.logicnode.lnx_nodes import *
class SetWorldSkyNode(LnxLogicTreeNode):
"""Sets the sky properties for the selected sky model."""
bl_idname = 'LNSetWorldSkyNode'
bl_label = 'Set World Sky'
lnx_version = 1
legacy = 'Nishita ' if bpy.app.version < (5, 0, 0) else ''
def update_inputs(self, context):
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'hosek':
self.add_input('LnxFloatSocket', 'Turbidity', default_value=1.0)
self.add_input('LnxFloatSocket', 'Ground Albedo', default_value=0.0)
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
elif self.property0 == 'single':
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
elif self.property0 == 'multi':
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
self.add_input('LnxFloatSocket', 'Sun Elevation', default_value=0.0)
self.add_input('LnxFloatSocket', 'Sun Rotation', default_value=0.0)
self.add_input('LnxFloatSocket', 'Sun Size', default_value=0.545)
self.add_input('LnxFloatSocket', 'Sun Intensity', default_value=1.0)
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
self.add_input('LnxBoolSocket', 'Sun Disc', default_value=True)
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
property0: HaxeEnumProperty(
'property0',
items=[('hosek', 'Hosek Wilkie', 'Hosek-Wilkie / Preetham sky model'),
('single', f'{legacy}Single Scattering', 'Single scattering sky model'),
('multi', f'{legacy}Multiple Scattering', 'Multiple scattering sky model')],
name='', default='single', update=update_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Air', default_value=1.0)
self.add_input('LnxFloatSocket', 'Dust', default_value=1.0)
self.add_input('LnxFloatSocket', 'Ozone', default_value=1.0)
self.add_input('LnxFloatSocket', 'Altitude', default_value=1.0)
self.add_input('LnxVectorSocket', 'Sun Direction', default_value=[0.0, 0.0, 1.0])
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class SetWorldTextureNode(LnxLogicTreeNode):
"""Sets the texture properties of the active world (strength, envmap)."""
bl_idname = 'LNSetWorldTextureNode'
bl_label = 'Set World Texture'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Strength', default_value=1.0)
self.add_input('LnxStringSocket', 'Envmap')
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -727,7 +727,7 @@ _viewport_proc_build = None # Separate process tracker for viewport builds
def build_viewport(viewport_id, width=1920, height=1080):
"""Build project for viewport"""
global _viewport_build_in_progress, _viewport_pending_launches, profile_time
global _viewport_build_in_progress, _viewport_pending_launches, profile_time, scripts_mtime
wrd = bpy.data.worlds.get('Lnx')
if not wrd:
@ -769,6 +769,35 @@ def build_viewport(viewport_id, width=1920, height=1080):
state.is_publish = False
state.is_export = False
# Recompile detection (matching play() behavior)
if not wrd.lnx_cache_build or \
not os.path.isfile(krom_js_path) or \
assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
state.last_target = state.target
# Trait sources modified
state.mod_scripts = []
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn)
fn = fn.split('Sources/')[1]
fn = fn[:-3] #.hx
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0:
lnx.utils.fetch_trait_props()
log.clear(clear_warnings=True, clear_errors=True)
sdk_path = lnx.utils.get_sdk_path()
@ -817,7 +846,8 @@ def compile_viewport(assets_only=False):
if not wrd.lnx_verbose_output:
cmd.append("--quiet")
if assets_only:
krom_js_path = lnx.utils.build_dir() + '/debug/krom/krom.js'
if assets_only and os.path.exists(krom_js_path):
cmd.append('--nohaxe')
cmd.append('--noproject')
@ -856,7 +886,7 @@ def viewport_build_done():
def play_viewport(viewport_id, width=1920, height=1080):
"""Launch Krom in a viewport"""
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build, scripts_mtime
if not viewport_id:
log.error('No viewport_id: play_viewport requires an id')
@ -895,9 +925,38 @@ def play_viewport(viewport_id, width=1920, height=1080):
os.makedirs(sources_path)
# export data same as play() but without setting state.is_play
state.is_viewport = True
state.target = 'krom'
state.is_publish = False
state.is_export = False
if not wrd.lnx_cache_build or \
not os.path.isfile(krom_js_path) or \
assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
state.last_target = state.target
state.mod_scripts = []
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn)
fn = fn.split('Sources/')[1]
fn = fn[:-3] #.hx
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0:
lnx.utils.fetch_trait_props()
export_data(fp, sdk_path)
compile_viewport(assets_only=(not wrd.lnx_recompile))

View File

@ -370,7 +370,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
if '_EnvSky' in world.world_defs:
# Nishita sky
# Single scattering sky
if 'vec3 sunDir' in frag.uniforms:
func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n'
# Hosek
@ -413,7 +413,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
if world.lnx_darken_clouds:
func_trace_clouds += '\t// Darken clouds when the sun is low\n'
if '_EnvSky' in world.world_defs:
# Nishita sky
# Single scattering sky
if 'vec3 sunDir' in frag.uniforms:
func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n'
# Hosek

View File

@ -328,12 +328,13 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
state.world.world_defs += '_EnvSky'
if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE':
if node.sky_type == 'PREETHAM':
log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead')
return parse_sky_hosekwilkie(node, state)
elif node.sky_type == 'NISHITA':
return parse_sky_nishita(node, state)
elif node.sky_type == 'NISHITA' or node.sky_type == 'SINGLE_SCATTERING':
return parse_sky_single_scattering(node, state)
elif node.sky_type == 'MULTIPLE_SCATTERING':
return parse_sky_multiple_scattering(node, state)
else:
log.error(f'Unsupported sky model: {node.sky_type}!')
@ -397,18 +398,20 @@ def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState)
return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;'
def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
def parse_sky_single_scattering(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
curshader = state.curshader
curshader.add_include('std/sky.glsl')
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True,
curshader.add_uniform('sampler2D singleScatterLUT', link='_singleScatterLUT', included=True,
tex_addr_u='clamp', tex_addr_v='clamp')
curshader.add_uniform('vec2 nishitaDensity', link='_nishitaDensity', included=True)
curshader.add_uniform('vec2 skyDensity', link='_skyDensity', included=True)
planet_radius = 6360e3 # Earth radius used in Blender
ray_origin_z = planet_radius + node.altitude
state.world.lnx_nishita_density = [node.air_density, node.dust_density, node.ozone_density]
dust_density = node.aerosol_density if bpy.app.version >= (5, 0, 0) else node.dust_density
state.world.lnx_sky_density = [node.air_density, dust_density, node.ozone_density]
state.world.lnx_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
sun = ''
if node.sun_disc:
@ -428,7 +431,29 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v
size = math.cos(theta)
sun = f'* sun_disk(pos, sunDir, {size}, {node.sun_intensity})'
return f'nishita_atmosphere(pos, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}'
return f'single_scatter_atmosphere(pos, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}'
def parse_sky_multiple_scattering(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
curshader = state.curshader
curshader.add_include('std/sky.glsl')
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
curshader.add_uniform('sampler2D multiScatterLUT', link='_multiScatterLUT', included=True, tex_addr_u='repeat', tex_addr_v='clamp')
curshader.add_uniform('vec4 multiScatterParams', link='_multiScatterParams', included=True)
curshader.add_uniform('vec4 multiScatterSunBottom', link='_multiScatterSunBottom', included=True)
curshader.add_uniform('vec3 multiScatterSunTop', link='_multiScatterSunTop', included=True)
dust_density = node.aerosol_density if bpy.app.version >= (5, 0, 0) else node.dust_density
state.world.lnx_sky_density = [node.air_density, dust_density, node.ozone_density]
state.world.lnx_sky_sun_elevation = node.sun_elevation
state.world.lnx_sky_sun_rotation = node.sun_rotation
state.world.lnx_sky_sun_size = node.sun_size
state.world.lnx_sky_sun_intensity = node.sun_intensity if node.sun_disc else 0.0
state.world.lnx_sky_altitude = node.altitude
state.world.lnx_sky_sun_disc = 1 if node.sun_disc else 0
state.world.lnx_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
return f'multi_scatter_atmosphere(pos)'
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:

View File

@ -210,7 +210,7 @@ def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[O
# Instancing
inst = bo.lnx_instanced
if inst != 'Off' or mat.lnx_particle_flag:
if inst != 'Off' or (mat.lnx_particle_flag and lnx.utils.get_rp().lnx_particles == 'GPU'):
instancing_usage[0] = True
mat_state.uses_instancing = True

View File

@ -464,7 +464,13 @@ def init_properties():
bpy.types.World.lnx_envtex_sun_direction = FloatVectorProperty(name="Sun Direction", size=3, default=[0,0,0])
bpy.types.World.lnx_envtex_turbidity = FloatProperty(name="Turbidity", default=1.0)
bpy.types.World.lnx_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0)
bpy.types.World.lnx_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1])
bpy.types.World.lnx_sky_density = FloatVectorProperty(name="Sky Density", size=3, default=[1, 1, 1])
bpy.types.World.lnx_sky_sun_elevation = FloatProperty(name="Sky Sun Elevation", default=0.0)
bpy.types.World.lnx_sky_sun_rotation = FloatProperty(name="Sky Sun Rotation", default=0.0)
bpy.types.World.lnx_sky_sun_size = FloatProperty(name="Sky Sun Size", default=0.545)
bpy.types.World.lnx_sky_sun_intensity = FloatProperty(name="Sky Sun Intensity", default=1.0)
bpy.types.World.lnx_sky_altitude = FloatProperty(name="Sky Altitude", default=0.0)
bpy.types.World.lnx_sky_sun_disc = IntProperty(name="Sky Sun Disc", default=1)
bpy.types.Material.lnx_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
bpy.types.Material.lnx_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
bpy.types.Material.lnx_depth_write = BoolProperty(name="Write Depth", description="Allow this material to write to the depth buffer", default=True)

View File

@ -11,6 +11,8 @@ import sys
import os
import array
import atexit
from mathutils import Quaternion, Matrix, Vector
import lnx.make_state as state
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
if not HAS_GPU_STATE:
@ -24,22 +26,6 @@ try:
except ImportError:
HAS_NUMPY = False
# TODO: Fix correctly
def _build_srgb_to_linear_lut():
lut = []
for i in range(256):
srgb = i / 255.0
if srgb <= 0.04045:
linear = srgb / 12.92
else:
linear = ((srgb + 0.055) / 1.055) ** 2.4
lut.append(linear)
return lut
_srgb_to_linear_lut_list = _build_srgb_to_linear_lut()
if HAS_NUMPY:
_srgb_to_linear_lut = np.array(_srgb_to_linear_lut_list, dtype=np.float32)
if sys.platform == 'win32':
import mmap
else:
@ -50,8 +36,8 @@ else:
except ImportError:
HAS_POSIX_IPC = False
VIEWPORT_BUILD = 0x4B524F4D
VIEWPORT_VERSION = 1
VIEWPORT_BUILD = 0x52554E54
VIEWPORT_VERSION = 2
VIEWPORT_SHMEM_NAME_BASE = "KROM_VIEWPORT_FB"
def _get_viewport_shmem_name(viewport_id=None):
@ -118,6 +104,7 @@ MOUSE_BUTTON_MIDDLE = 2
_rendered_mode_pending = False
_rendered_mode_retries = 0
_msgbus_owner = object()
_make_module = None
@bpy.app.handlers.persistent
def _on_depsgraph_update(scene, depsgraph):
@ -184,9 +171,10 @@ class KromViewportEngine(bpy.types.RenderEngine):
bl_idname = 'KROM_VIEWPORT'
bl_label = 'Krom Viewport'
bl_use_preview = False
bl_use_gpu_context = True
bl_use_shading_nodes_custom = False
bl_use_eevee_viewport = True
if bpy.app.version >= (2, 90, 0):
bl_use_gpu_context = True
bl_use_eevee_viewport = True
def _ensure_initialized(self):
@ -200,6 +188,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._initialized = False
self._shader = None
self._batch = None
self._batch_dims = None
self._viewport_init_done = True
self._rendered_mode_set = False
self._last_shading_mode = None
@ -208,6 +197,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._krom_proc = None
self._krom_launch_attempted = False
self._pending_camera_sync = False
self._input_enabled_state = None
self._last_region_dims = None
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
self._engine_id = id(self)
# print(f"New engine instance created: {self._engine_id}")
@ -221,8 +213,34 @@ class KromViewportEngine(bpy.types.RenderEngine):
if current != 'RENDERED':
space.shading.type = 'RENDERED'
self._rendered_mode_set = True
# Disable viewport overlay so Krom output is unobstructed
if space and hasattr(space, 'overlay'):
if not hasattr(self, '_overlay_saved'):
self._overlay_saved = space.overlay.show_overlays
space.overlay.show_overlays = False
except Exception as e:
print(f"Failed to set rendered mode: {e}")
def _restore_overlay(self, context=None):
try:
if not hasattr(self, '_overlay_saved'):
return
spaces = []
if context and context.space_data:
spaces = [context.space_data]
else:
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for sp in area.spaces:
if sp.type == 'VIEW_3D':
spaces.append(sp)
for sp in spaces:
if hasattr(sp, 'overlay'):
sp.overlay.show_overlays = self._overlay_saved
del self._overlay_saved
except:
pass
def _handle_mode_switch(self, context):
"""Detect shading mode changes and restart Krom on Solid->Rendered switch if needed."""
@ -326,6 +344,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
except:
pass
self._krom_proc = None
self._krom_launch_attempted = False
self._initialized = False
self._cleanup_shared_memory()
def _init_shared_memory(self, context=None):
self._ensure_initialized()
@ -493,6 +514,27 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._initialized = False
def _peek_frame_id(self):
"""Quick check if a new frame is available without reading full header."""
self._ensure_initialized()
if not self._initialized:
return None
try:
if sys.platform == 'win32':
if not self._shm_ptr:
return None
frame_id = struct.unpack('<Q', ctypes.string_at(self._shm_ptr + OFFSET_FRAME_ID, 8))[0]
ready_flag = struct.unpack('<I', ctypes.string_at(self._shm_ptr + OFFSET_READY_FLAG, 4))[0]
return frame_id, ready_flag
else:
self._shm.seek(OFFSET_FRAME_ID)
frame_id = struct.unpack('<Q', self._shm.read(8))[0]
self._shm.seek(OFFSET_READY_FLAG)
ready_flag = struct.unpack('<I', self._shm.read(4))[0]
return frame_id, ready_flag
except Exception:
return None
def _read_header(self):
"""Read and parse the shared memory header."""
self._ensure_initialized()
@ -503,10 +545,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
if sys.platform == 'win32':
if not self._shm_ptr:
return None
# use memmove for safety
header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
ctypes.memmove(header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
header_data = bytes(header_buffer)
ctypes.memmove(self._header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
header_data = bytes(self._header_buffer)
else:
self._shm.seek(0)
header_data = self._shm.read(VIEWPORT_HEADER_SIZE)
@ -516,7 +556,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
ready_flag, fmt = struct.unpack('<II', header_data[24:32])
if build != VIEWPORT_BUILD:
# shared memory may have been recreated
self._handle_shmem_invalid()
return None
@ -538,21 +577,30 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Handle invalid/stale shared memory by cleaning up and allowing re-init."""
self._cleanup_shared_memory()
self._initialized = False
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try:
bgl.glDeleteTextures(1, self._gl_texture_buf)
except:
pass
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
self._texture = None
self._last_frame_id = 0
def _read_framebuffer(self):
"""Read framebuffer from shared memory."""
# Fast path: peek frame_id and ready_flag without reading full header
peek = self._peek_frame_id()
if peek is None:
return None
frame_id, ready_flag = peek
if frame_id == self._last_frame_id or ready_flag == 0:
return None
# New frame available - read full header for dimensions
header = self._read_header()
if not header:
return None
if header['frame_id'] == self._last_frame_id:
return None
if header['ready_flag'] == 0:
return None
width = header['width']
height = header['height']
@ -565,15 +613,26 @@ class KromViewportEngine(bpy.types.RenderEngine):
if sys.platform == 'win32':
if not self._shm_ptr:
return None
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
pixels = bytes(pixel_buffer)
if HAS_NUMPY:
# Single copy: shared memory -> numpy array
pixel_array = np.ctypeslib.as_array(
(ctypes.c_ubyte * pixel_size).from_address(
self._shm_ptr + VIEWPORT_HEADER_SIZE)
).copy()
else:
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
pixel_array = bytes(pixel_buffer)
ready_bytes = struct.pack('<I', 0)
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
else:
self._shm.seek(VIEWPORT_HEADER_SIZE)
pixels = self._shm.read(pixel_size)
raw = self._shm.read(pixel_size)
if HAS_NUMPY:
pixel_array = np.frombuffer(raw, dtype=np.uint8).copy()
else:
pixel_array = raw
self._shm.seek(OFFSET_READY_FLAG)
self._shm.write(struct.pack('<I', 0))
@ -581,7 +640,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._width = width
self._height = height
return pixels
return pixel_array
except Exception as e:
self._handle_shmem_invalid()
print(f"Failed to read framebuffer: {e}")
@ -658,8 +717,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._shm.seek(OFFSET_KROM_CAMERA_DIRTY)
self._shm.write(clear_data)
from mathutils import Quaternion, Matrix, Vector
rv3d = context.space_data.region_3d if context.space_data else None
if not rv3d:
return
@ -734,6 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._ensure_initialized()
if not self._initialized:
return
if self._input_enabled_state == enabled:
return
try:
data = struct.pack('<I', 1 if enabled else 0)
@ -744,6 +803,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
else:
self._shm.seek(OFFSET_INPUT_ENABLED)
self._shm.write(data)
self._input_enabled_state = enabled
except Exception as e:
pass
@ -793,7 +853,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._shader = gpu.shader.from_builtin(SHADER_IMAGE)
def _update_texture(self, pixels, width, height):
"""Create or update the GPU texture with new pixel data."""
"""Create or update the GPU texture with new pixel data.
Uses SRGB8_A8 format for GPU-side sRGB-to-linear conversion."""
self._ensure_initialized()
try:
num_pixels = width * height * 4
@ -801,28 +862,46 @@ class KromViewportEngine(bpy.types.RenderEngine):
if len(pixels) < num_pixels:
return
# clear old texture if dimensions changed
if hasattr(self, '_tex_width') and hasattr(self, '_tex_height'):
if self._tex_width != width or self._tex_height != height:
self._texture = None
# TODO: re-investigate LUT for linearization
if HAS_NUMPY:
pixel_array = np.frombuffer(pixels[:num_pixels], dtype=np.uint8).copy()
pixel_array[3::4] = 255
float_array = _srgb_to_linear_lut[pixel_array]
float_array[3::4] = 1.0
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
else:
pixel_array = array.array('B', pixels[:num_pixels])
float_pixels = []
for i, p in enumerate(pixel_array):
if i % 4 == 3: # alpha channel
float_pixels.append(1.0)
else:
float_pixels.append(_srgb_to_linear_lut_list[p])
buffer = gpu.types.Buffer('FLOAT', len(float_pixels), float_pixels)
if HAS_GPU_TEXTURE:
if HAS_NUMPY:
pixels[3::4] = 255
float_array = pixels.astype(np.float32) / 255.0
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
float_pixels = [p / 255.0 for p in pixel_array]
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_pixels)
self._texture = gpu.types.GPUTexture((width, height), format='SRGB8_A8', data=buffer)
else:
# bgl path for Blender < 3.0
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenTextures(1, self._gl_texture_buf)
tex_id = self._gl_texture_buf[0]
bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex_id)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
if HAS_NUMPY:
pixels[3::4] = 255
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, pixels.tobytes())
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0,
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
self._texture = tex_id
self._texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=buffer)
self._tex_width = width
self._tex_height = height
@ -833,9 +912,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
def render(self, depsgraph):
"""Disabled render method for final renders (F12)."""
pass
def free(self):
"""Called by Blender when the engine instance is destroyed."""
self._stop_krom_process()
self._restore_overlay()
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try:
bgl.glDeleteTextures(1, self._gl_texture_buf)
except:
pass
if self._viewport_id and self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id]
def view_update(self, context, depsgraph):
"""Called when the scene is updated."""
if state.is_exporting:
return
self._ensure_initialized()
if not self._init_shared_memory(context):
return
@ -848,6 +941,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
def view_draw(self, context, depsgraph):
"""Called to draw the viewport."""
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running, _make_module
if state.is_exporting:
return
self._ensure_initialized()
self._handle_mode_switch(context)
@ -872,10 +970,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._pending_camera_sync = False
self._sync_camera_once(context)
# Check if Krom process has died - allow relaunch
if self._krom_launch_attempted and self._viewport_id:
if _make_module is None:
import lnx.make
_make_module = lnx.make
proc = _make_module._viewport_processes.get(self._viewport_id)
if proc is None or proc.poll() is not None:
print(f"Krom process for viewport {self._viewport_id} is gone, resetting for relaunch")
self._krom_launch_attempted = False
self._initialized = False
self._cleanup_shared_memory()
if self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id]
self._set_rendered_mode(context)
# TODO: input forwarding when viewport is active
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running
_active_krom_engine = self # legacy single-engine reference
if self._viewport_id:
@ -886,23 +997,19 @@ class KromViewportEngine(bpy.types.RenderEngine):
if not _input_operator_running:
bpy.app.timers.register(_start_input_capture, first_interval=0.1)
else:
if not hasattr(self, '_input_check_counter'):
self._input_check_counter = 0
self._input_check_counter += 1
if self._input_check_counter > 60:
self._input_check_counter = 0
bpy.app.timers.register(_start_input_capture, first_interval=0.5)
region = context.region
if region and region.width > 0 and region.height > 0:
self._request_resize(region.width, region.height)
dims = (region.width, region.height)
if self._last_region_dims != dims:
self._last_region_dims = dims
self._request_resize(region.width, region.height)
self._read_krom_camera(context)
pixels = self._read_framebuffer()
if pixels:
if pixels is not None:
self._update_texture(pixels, self._width, self._height)
if not self._texture:
@ -917,26 +1024,36 @@ class KromViewportEngine(bpy.types.RenderEngine):
bgl.glDisable(bgl.GL_BLEND)
region = context.region
vertices = (
(0, 0),
(region.width, 0),
(region.width, region.height),
(0, region.height),
)
tex_coords = (
(0, 1), # Flip Y
(1, 1),
(1, 0),
(0, 0),
)
indices = ((0, 1, 2), (0, 2, 3))
dims = (region.width, region.height)
if self._batch is None or self._batch_dims != dims:
vertices = (
(0, 0),
(region.width, 0),
(region.width, region.height),
(0, region.height),
)
tex_coords = (
(0, 1),
(1, 1),
(1, 0),
(0, 0),
)
indices = ((0, 1, 2), (0, 2, 3))
self._batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
self._batch_dims = dims
self._shader.bind()
self._shader.uniform_sampler("image", self._texture)
if HAS_GPU_TEXTURE:
self._shader.uniform_sampler("image", self._texture)
else:
bgl.glActiveTexture(bgl.GL_TEXTURE0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._texture)
self._shader.uniform_int("image", [0])
batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
batch.draw(self._shader)
self._batch.draw(self._shader)
if not HAS_GPU_TEXTURE:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
if HAS_GPU_STATE:
gpu.state.blend_set('NONE')
@ -969,12 +1086,14 @@ class KromViewportEngine(bpy.types.RenderEngine):
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
batch.draw(shader)
if HAS_GPU_STATE:
gpu.state.blend_set('NONE')
else:
bgl.glDisable(bgl.GL_BLEND)
self.tag_redraw()
class KROM_OT_viewport_input(bpy.types.Operator):
"""Modal operator to capture and forward input to Krom viewport"""
@ -988,8 +1107,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
def modal(self, context, event):
global _input_operator_running
# check if we should stop
if event.type == 'ESC':
try:
if self._engine:
@ -1002,22 +1120,13 @@ class KROM_OT_viewport_input(bpy.types.Operator):
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
return {'PASS_THROUGH'}
if not context.space_data or not context.area:
# Re-derive area/region/engine from mouse position each event
result = self._find_viewport_under_mouse(event)
if result is None:
return {'PASS_THROUGH'}
if context.space_data.type == 'VIEW_3D':
if context.engine != 'KROM_VIEWPORT':
try:
if self._engine:
self._engine._set_input_enabled(False)
except ReferenceError:
pass
_input_operator_running = False
return {'CANCELLED'}
current_engine = self._get_engine_for_area(context, event)
if current_engine:
self._engine = current_engine
engine, area, region = result
self._engine = engine
try:
if not self._engine or not self._engine._initialized:
@ -1026,37 +1135,19 @@ class KROM_OT_viewport_input(bpy.types.Operator):
_input_operator_running = False
return {'CANCELLED'}
region = context.region
if not region:
return {'PASS_THROUGH'}
# Check if mouse is over a UI region (toolbar, header, panel)
mouse_x_abs = event.mouse_x
mouse_y_abs = event.mouse_y
for rgn in area.regions:
if rgn.type == 'WINDOW':
continue
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
return {'PASS_THROUGH'}
if region.type != 'WINDOW':
return {'PASS_THROUGH'}
mouse_in_region = (0 <= event.mouse_region_x <= region.width and
0 <= event.mouse_region_y <= region.height)
if not mouse_in_region:
return {'PASS_THROUGH'}
area = context.area
if area:
mouse_x_abs = event.mouse_x
mouse_y_abs = event.mouse_y
for rgn in area.regions:
# skip the main window region where we actually want input
if rgn.type == 'WINDOW':
continue
# check if mouse is over toolbar, header, UI panel, etc.
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
# mouse is over a UI region - pass through
return {'PASS_THROUGH'}
mouse_x = event.mouse_region_x
# flip Y for Krom
mouse_y = region.height - event.mouse_region_y
# Compute mouse position relative to the WINDOW region
mouse_x = mouse_x_abs - region.x
mouse_y = region.height - (mouse_y_abs - region.y)
try:
if event.type == 'MOUSEMOVE':
@ -1128,49 +1219,63 @@ class KROM_OT_viewport_input(bpy.types.Operator):
}
return key_map.get(blender_key)
def _get_engine_for_area(self, context, event=None):
"""Get the correct engine for the viewport under the mouse cursor."""
# Use mouse coordinates to find which viewport the mouse is over
if event:
mouse_x = event.mouse_x
mouse_y = event.mouse_y
def _find_viewport_under_mouse(self, event):
"""Find the area, WINDOW region, and engine under the mouse cursor.
Returns (engine, area, region) or None."""
mouse_x = event.mouse_x
mouse_y = event.mouse_y
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
if (area.x <= mouse_x < area.x + area.width and
if not (area.x <= mouse_x < area.x + area.width and
area.y <= mouse_y < area.y + area.height):
continue
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
viewport_id = hex(space.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return _active_krom_engines[viewport_id]
except:
pass
# Find the WINDOW region for this area
win_region = None
for rgn in area.regions:
if rgn.type == 'WINDOW':
win_region = rgn
break
if context.space_data:
try:
viewport_id = hex(context.space_data.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return _active_krom_engines[viewport_id]
except:
pass
if not win_region:
continue
if _active_viewport_id and _active_viewport_id in _active_krom_engines:
return _active_krom_engines[_active_viewport_id]
# Check if mouse is actually within the WINDOW region bounds
if not (win_region.x <= mouse_x < win_region.x + win_region.width and
win_region.y <= mouse_y < win_region.y + win_region.height):
continue
return _active_krom_engine
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
viewport_id = hex(space.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return (_active_krom_engines[viewport_id], area, win_region)
except:
pass
break
return None
def invoke(self, context, event):
self._engine = self._get_engine_for_area(context)
if not self._engine:
result = self._find_viewport_under_mouse(event)
if result is None:
# Fallback: use context's viewport
if context.space_data:
try:
viewport_id = hex(context.space_data.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
self._engine = _active_krom_engines[viewport_id]
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
except:
pass
return {'CANCELLED'}
self._engine = result[0]
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@ -1192,8 +1297,12 @@ def _start_input_capture():
for region in area.regions:
if region.type == 'WINDOW':
override = {'window': window, 'area': area, 'region': region}
with bpy.context.temp_override(**override):
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
if bpy.app.version >= (3, 0, 0):
with bpy.context.temp_override(**override):
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
_input_operator_running = True
else:
bpy.ops.krom.viewport_input(override, 'INVOKE_DEFAULT')
_input_operator_running = True
return None
except Exception as e:
@ -1208,7 +1317,9 @@ def get_panels():
'VIEWLAYER_PT_layer_passes',
}
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
if bpy.app.version >= (4, 2, 0):
compatible_engines.add('BLENDER_EEVEE_NEXT')
panels = []
for panel in bpy.types.Panel.__subclasses__():
@ -1262,6 +1373,12 @@ class KROM_OT_stop_viewport(bpy.types.Operator):
make.stop_viewport(viewport_id)
global _active_krom_engines
if viewport_id in _active_krom_engines:
engine = _active_krom_engines[viewport_id]
try:
engine._stop_krom_process()
engine._restore_overlay(context)
except:
pass
del _active_krom_engines[viewport_id]
if context.space_data and hasattr(context.space_data, 'shading'):
context.space_data.shading.type = 'SOLID'

View File

@ -829,11 +829,6 @@ def get_render_resolution(scene):
resx = int(render.resolution_x * scale)
resy = int(render.resolution_y * scale)
rpdat = get_rp()
if rpdat.rp_fsr1 != 'Off':
resx = int(resx * 0.5)
resy = int(resy * 0.5)
return resx, resy
def get_texture_quality_percentage() -> int:

View File

@ -1,3 +1,3 @@
// Keep this file so that the headers are included in the compilation
#include "hl/aura/math/FFT.h"
#include "hl/aura/math/fft.h"
#include "hl/aura/types/complex_array.h"

View File

@ -2,27 +2,27 @@
#include <hl.h>
//#include <aura/types/_ComplexArray/HL_ComplexArrayImpl.h>
#include <aura/types/_ComplexArray/HL_ComplexArrayImpl.h>
#include "hl/aura/aurahl.h"
#include "common_c/math/fft.h"
#include "common_c/types/complex_t.h"
//HL_PRIM void AURA_HL_FUNC(ditfft2)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, int t, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int f, int n, int step, bool inverse) {
// const aura_complex_t *times = (aura_complex_t*) time_array->self;
// aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
HL_PRIM void AURA_HL_FUNC(ditfft2)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, int t, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int f, int n, int step, bool inverse) {
const aura_complex_t *times = (aura_complex_t*) time_array->self;
aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
/// aura_ditfft2(times, t, freqs, f, n, step, inverse);
//}
aura_ditfft2(times, t, freqs, f, n, step, inverse);
}
//HL_PRIM void AURA_HL_FUNC(ditfft2_iterative)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int n, bool inverse, aura__types___ComplexArray__HL_ComplexArrayImpl exp_rotation_step_table) {
// const aura_complex_t *times = (aura_complex_t*) time_array->self;
// aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
HL_PRIM void AURA_HL_FUNC(ditfft2_iterative)(aura__types___ComplexArray__HL_ComplexArrayImpl time_array, aura__types___ComplexArray__HL_ComplexArrayImpl freq_array, int n, bool inverse, aura__types___ComplexArray__HL_ComplexArrayImpl exp_rotation_step_table) {
const aura_complex_t *times = (aura_complex_t*) time_array->self;
aura_complex_t *freqs = (aura_complex_t*) freq_array->self;
// const aura_complex_t *exp_lut = (aura_complex_t*) exp_rotation_step_table->self;
const aura_complex_t *exp_lut = (aura_complex_t*) exp_rotation_step_table->self;
// aura_ditfft2_iterative(times, freqs, n, inverse, exp_lut);
//}
aura_ditfft2_iterative(times, freqs, n, inverse, exp_lut);
}
//DEFINE_PRIM(_VOID, ditfft2, _BYTES _I32 _BYTES _I32 _I32 _I32 _BOOL)
//DEFINE_PRIM(_VOID, ditfft2_iterative, _BYTES _BYTES _I32 _BOOL _BYTES)
DEFINE_PRIM(_VOID, ditfft2, _BYTES _I32 _BYTES _I32 _I32 _I32 _BOOL)
DEFINE_PRIM(_VOID, ditfft2_iterative, _BYTES _BYTES _I32 _BOOL _BYTES)

View File

@ -53,6 +53,10 @@ class Aura {
static final hrtfs = new Map<String, HRTF>();
#if (kha_html5 || kha_debug_html5)
public static var audioContext: js.html.audio.AudioContext;
#end
public static function init(?options: AuraOptions) {
sampleRate = kha.audio2.Audio.samplesPerSecond;
assert(Critical, sampleRate != 0, "sampleRate must not be 0!");
@ -63,12 +67,16 @@ class Aura {
listener = new Listener();
BufferCache.init();
// Sample buffer to prevent allocation
final initialBufferSize = 4096;
if (!BufferCache.getBuffer(TFloat32Array, p_samplesBuffer, 1, initialBufferSize)) {
trace('CRITICAL: Failed to allocate initial sample buffer during Aura.init()!');
#if (kha_html5 || kha_debug_html5)
if (kha.SystemImpl.mobile) {
audioContext = kha.js.MobileWebAudio._context;
}
else {
audioContext = new js.html.audio.AudioContext();
}
#end
// Create a few preconfigured mix channels
masterChannel = createMixChannel("master");
createMixChannel("music").setMixChannel(masterChannel);
@ -134,16 +142,31 @@ class Aura {
}
#end
count++;
function onChannelCountInitialized() {
count++;
if (onProgress != null) {
onProgress(count, length, soundName);
}
if (onProgress != null) {
onProgress(count, length, soundName);
if (count == length) {
done();
}
}
if (count == length) {
done();
return;
}
#if (kha_html5 || kha_debug_html5)
if (kha.SystemImpl.mobile) {
// Mobile web audio channels are always decoded and
// the channel count is set by Kha afterwards
onChannelCountInitialized();
}
else {
// HACK: Kha does not set sound.channel for compressed
// sounds on non-mobile html5 targets, so do it manually
aura.channels.Html5StreamChannel.initializeChannelCount(sound, onChannelCountInitialized);
}
#else
onChannelCountInitialized();
#end
}, (error: kha.AssetError) -> { onLoadingError(error, failed, soundName); });
}
@ -320,7 +343,7 @@ class Aura {
}
#if (kha_html5 || kha_debug_html5)
final newChannel = kha.SystemImpl.mobile ? new Html5MobileStreamChannel(sound, loop) : new Html5StreamChannel(sound, loop);
final newChannel = kha.SystemImpl.mobile ? new Html5MobileStreamChannel(sound, loop, cast(mixChannelHandle.channel, MixChannel)) : new Html5StreamChannel(sound, loop, cast(mixChannelHandle.channel, MixChannel));
#else
final khaChannel: Null<kha.audio1.AudioChannel> = kha.audio2.Audio1.stream(sound, loop);
if (khaChannel == null) {

View File

@ -8,6 +8,9 @@ import aura.threading.Message;
import aura.types.AudioBuffer;
import aura.utils.Interpolator.LinearInterpolator;
import aura.utils.MathUtils;
#if (kha_html5 || kha_debug_html5)
import js.html.audio.GainNode;
#end
/**
Main-thread handle to an audio channel in the audio thread.
@ -90,6 +93,7 @@ class BaseChannelHandle {
}
if (mixChannelHandle == null) {
channel.cleanUp();
return true;
}
@ -105,6 +109,12 @@ class BaseChannelHandle {
final success = @:privateAccess mixChannelHandle.addInputChannel(this);
if (success) {
parentHandle = mixChannelHandle;
#if (kha_html5 || kha_debug_html5)
if (channel is MixChannel) {
channel.gain.disconnect();
channel.gain.connect(@:privateAccess parentHandle.getMixChannel().gain);
}
#end
} else {
parentHandle = null;
}
@ -164,6 +174,10 @@ abstract class BaseChannel {
var paused: Bool = false;
var finished: Bool = true;
#if (kha_html5 || kha_debug_html5)
public var gain: GainNode;
#end
abstract function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz): Void;
abstract function play(retrigger: Bool): Void;
@ -218,6 +232,8 @@ abstract class BaseChannel {
}
}
function cleanUp() {}
function parseMessage(message: Message) {
switch (message.id) {
case ChannelMessageID.Play: play(cast message.data);

View File

@ -10,14 +10,21 @@ import js.html.audio.ChannelMergerNode;
import js.html.audio.GainNode;
import js.html.audio.MediaElementAudioSourceNode;
import js.html.URL;
import js.lib.ArrayBuffer;
import kha.SystemImpl;
import kha.js.MobileWebAudio;
import kha.js.MobileWebAudioChannel;
import aura.Aura;
import aura.format.audio.OggVorbisReader;
import aura.threading.Message;
import aura.types.AudioBuffer;
using StringTools;
using aura.format.BytesExtension;
/**
Channel dedicated for streaming playback on html5.
@ -36,16 +43,16 @@ import aura.types.AudioBuffer;
class Html5StreamChannel extends BaseChannel {
static final virtualChannels: Array<Html5StreamChannel> = [];
final audioContext: AudioContext;
final audioElement: AudioElement;
final source: MediaElementAudioSourceNode;
var audioContext: AudioContext;
var audioElement: AudioElement;
var source: MediaElementAudioSourceNode;
final gain: GainNode;
final leftGain: GainNode;
final rightGain: GainNode;
final attenuationGain: GainNode;
final splitter: ChannelSplitterNode;
final merger: ChannelMergerNode;
var masterGain: GainNode;
var leftGain: GainNode;
var rightGain: GainNode;
var attenuationGain: GainNode;
var splitter: ChannelSplitterNode;
var merger: ChannelMergerNode;
var virtualPosition: Float;
var lastUpdateTime: Float;
@ -53,13 +60,13 @@ class Html5StreamChannel extends BaseChannel {
var dopplerRatio: Float = 1.0;
var pitch: Float = 1.0;
public function new(sound: kha.Sound, loop: Bool) {
audioContext = new AudioContext();
public function new(sound: kha.Sound, loop: Bool, parentChannel: MixChannel) {
audioContext = Aura.audioContext;
audioElement = Browser.document.createAudioElement();
source = audioContext.createMediaElementSource(audioElement);
final mimeType = #if kha_debug_html5 "audio/ogg" #else "audio/mp4" #end;
final soundData: js.lib.ArrayBuffer = sound.compressedData.getData();
final mimeType = sound.compressedData.isByteMagic(0, "OggS") ? "audio/ogg" : "audio/mp4";
final soundData: ArrayBuffer = sound.compressedData.getData();
final blob = new js.html.Blob([soundData], {type: mimeType});
// TODO: if removing channels, use revokeObjectUrl() ?
@ -67,36 +74,37 @@ class Html5StreamChannel extends BaseChannel {
audioElement.src = URL.createObjectURL(blob);
audioElement.loop = loop;
untyped audioElement.preservesPitch = false;
audioElement.addEventListener("ended", () -> {
stop();
});
splitter = audioContext.createChannelSplitter(2);
leftGain = audioContext.createGain();
rightGain = audioContext.createGain();
attenuationGain = audioContext.createGain();
merger = audioContext.createChannelMerger(2);
gain = audioContext.createGain();
masterGain = audioContext.createGain();
source.connect(splitter);
// The sound data needs to be decoded because `sounds.channels` returns `0`.
audioContext.decodeAudioData(soundData, function (buffer) {
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
switch (buffer.numberOfChannels) {
case 1:
splitter.connect(leftGain, 0);
splitter.connect(rightGain, 0);
case 2:
splitter.connect(leftGain, 0);
splitter.connect(rightGain, 1);
default:
}
});
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
switch (sound.channels) {
case 1:
splitter.connect(leftGain, 0);
splitter.connect(rightGain, 0);
case 2:
splitter.connect(leftGain, 0);
splitter.connect(rightGain, 1);
default:
throw 'Unsupported channel count: ${sound.channels}';
}
leftGain.connect(merger, 0, 0);
rightGain.connect(merger, 0, 1);
merger.connect(attenuationGain);
attenuationGain.connect(gain);
attenuationGain.connect(masterGain);
gain.connect(audioContext.destination);
masterGain.connect(parentChannel.gain);
if (isVirtual()) {
virtualChannels.push(this);
@ -177,20 +185,45 @@ class Html5StreamChannel extends BaseChannel {
finished = true;
}
/**
Clean up Web Audio nodes. Called automatically when `BaseChannelHandle.setMixChannel(null)` is used.
**/
override function cleanUp() {
source.disconnect();
splitter.disconnect();
leftGain.disconnect();
rightGain.disconnect();
merger.disconnect();
attenuationGain.disconnect();
masterGain.disconnect();
audioElement.pause();
audioElement.src = "";
URL.revokeObjectURL(audioElement.src);
source = null;
splitter = null;
leftGain = null;
rightGain = null;
merger = null;
attenuationGain = null;
masterGain = null;
audioElement = null;
}
function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz) {}
override function parseMessage(message: Message) {
switch (message.id) {
// Because we're using a HTML implementation here, we cannot use the
// LinearInterpolator parameters
case ChannelMessageID.PVolume: attenuationGain.gain.value = cast message.data;
case ChannelMessageID.PVolume: masterGain.gain.value = cast message.data;
case ChannelMessageID.PPitch:
pitch = cast message.data;
updatePlaybackRate();
case ChannelMessageID.PDopplerRatio:
dopplerRatio = cast message.data;
updatePlaybackRate();
case ChannelMessageID.PDstAttenuation: gain.gain.value = cast message.data;
case ChannelMessageID.PDstAttenuation: attenuationGain.gain.value = cast message.data;
case ChannelMessageID.PVolumeLeft: leftGain.gain.value = cast message.data;
case ChannelMessageID.PVolumeRight: rightGain.gain.value = cast message.data;
@ -217,24 +250,23 @@ class Html5StreamChannel extends BaseChannel {
https://github.com/Kode/Kha/commit/12494b1112b64e4286b6a2fafc0f08462c1e7971
**/
class Html5MobileStreamChannel extends BaseChannel {
final audioContext: AudioContext;
final khaChannel: kha.js.MobileWebAudioChannel;
var audioContext: AudioContext;
var khaChannel: kha.js.MobileWebAudioChannel;
var parentChannel: MixChannel;
final leftGain: GainNode;
final rightGain: GainNode;
final attenuationGain: GainNode;
final splitter: ChannelSplitterNode;
final merger: ChannelMergerNode;
var leftGain: GainNode;
var rightGain: GainNode;
var attenuationGain: GainNode;
var splitter: ChannelSplitterNode;
var merger: ChannelMergerNode;
var dopplerRatio: Float = 1.0;
var pitch: Float = 1.0;
public function new(sound: kha.Sound, loop: Bool) {
audioContext = MobileWebAudio._context;
public function new(sound: kha.Sound, loop: Bool, pc: MixChannel) {
audioContext = Aura.audioContext;
khaChannel = new kha.js.MobileWebAudioChannel(cast sound, loop);
@:privateAccess khaChannel.gain.disconnect(audioContext.destination);
@:privateAccess khaChannel.source.disconnect(@:privateAccess khaChannel.gain);
parentChannel = pc;
splitter = audioContext.createChannelSplitter(2);
leftGain = audioContext.createGain();
@ -242,8 +274,6 @@ class Html5MobileStreamChannel extends BaseChannel {
merger = audioContext.createChannelMerger(2);
attenuationGain = audioContext.createGain();
@:privateAccess khaChannel.source.connect(splitter);
// TODO: add more cases for Quad and 5.1 ? - https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#audio_channels
switch (sound.channels) {
case 1:
@ -253,6 +283,7 @@ class Html5MobileStreamChannel extends BaseChannel {
splitter.connect(leftGain, 0);
splitter.connect(rightGain, 1);
default:
throw 'Unsupported channel count: ${sound.channels}';
}
leftGain.connect(merger, 0, 0);
@ -260,14 +291,19 @@ class Html5MobileStreamChannel extends BaseChannel {
merger.connect(attenuationGain);
attenuationGain.connect(@:privateAccess khaChannel.gain);
@:privateAccess khaChannel.gain.connect(audioContext.destination);
reconnectKhaChannelNodes();
}
public function play(retrigger: Bool) {
if (retrigger) {
khaChannel.position = 0;
}
@:privateAccess khaChannel.source.onended = null;
khaChannel.play();
// `MobileWebAudioChannel` recreates a 'source' when `khaChannel.play()` is called
// Reconnect 'source' and 'gain' to the proper nodes
reconnectKhaChannelNodes();
paused = false;
finished = false;
@ -283,6 +319,30 @@ class Html5MobileStreamChannel extends BaseChannel {
finished = true;
}
/**
Clean up Web Audio nodes. Called automatically when `BaseChannelHandle.setMixChannel(null)` is used.
**/
override function cleanUp() {
@:privateAccess khaChannel.source.onended = null;
@:privateAccess khaChannel.source.disconnect();
splitter.disconnect();
leftGain.disconnect();
rightGain.disconnect();
merger.disconnect();
attenuationGain.disconnect();
@:privateAccess khaChannel.gain.disconnect();
khaChannel.stop();
@:privateAccess khaChannel.gain = null;
@:privateAccess khaChannel.source = null;
splitter = null;
leftGain = null;
rightGain = null;
merger = null;
attenuationGain = null;
khaChannel = null;
}
function nextSamples(requestedSamples: AudioBuffer, sampleRate: Hertz) {}
override function parseMessage(message: Message) {
@ -311,6 +371,49 @@ class Html5MobileStreamChannel extends BaseChannel {
}
catch (e) {}
}
function reconnectKhaChannelNodes() {
@:privateAccess khaChannel.gain.disconnect();
@:privateAccess khaChannel.source.disconnect();
@:privateAccess khaChannel.source.connect(splitter);
@:privateAccess khaChannel.source.onended = stop;
@:privateAccess khaChannel.gain.connect(parentChannel.gain);
}
}
function initializeChannelCount(sound: kha.Sound, done: Void->Void) {
/*
Peek into the file to detect the file format, Kha sadly does not expose
this information.
Using `Reflect.field(kha.Assets.sounds, soundName + "Description").files`
(i.e. the data in files.json generated by Khamake) would introduce a
dependency on Kha internals: A `kha.Sound` can be backed by multiple
exported files and Kha's `LoaderImpl` decides on the order in which
those files are tried to be loaded.
*/
final isOgg = sound.compressedData.isByteMagic(0, "OggS");
if (isOgg) {
final oggReader = new OggVorbisReader(sound.compressedData);
sound.channels = oggReader.getNumChannels();
done();
}
else {
/*
In case of other formats, try to let the JS runtime decode the
entire sound data.
HACK: decodeAudioData() detaches the array buffer but requires a
non-detached buffer, so a clone is made to ensure that the array
buffer of sound.compressedData is never detached and can still be
used by other code.
*/
final soundDataClone: ArrayBuffer = sound.compressedData.getData().slice(0);
kha.audio2.Audio._context.decodeAudioData(soundDataClone, function (buffer) {
sound.channels = buffer.numberOfChannels;
done();
});
}
}
#end

View File

@ -6,6 +6,11 @@ import haxe.ds.Vector;
import sys.thread.Mutex;
#end
#if (kha_html5 || kha_debug_html5)
import aura.Aura;
import js.html.audio.AudioContext;
#end
import aura.channels.BaseChannel.BaseChannelHandle;
import aura.threading.BufferCache;
import aura.threading.Message;
@ -89,7 +94,17 @@ class MixChannel extends BaseChannel {
**/
var inputChannelsCopy: Vector<BaseChannel>;
#if (kha_html5 || kha_debug_html5)
var audioContext: AudioContext;
#end
public function new() {
#if (kha_html5 || kha_debug_html5)
audioContext = Aura.audioContext;
gain = audioContext.createGain();
gain.connect(audioContext.destination);
#end
inputChannels = new Vector<BaseChannel>(channelSize);
// Make sure super.isPlayable() is true until we find better semantics
@ -296,4 +311,18 @@ class MixChannel extends BaseChannel {
}
}
}
#if (kha_html5 || kha_debug_html5)
//TODO: add the rest of the messages for effects or create a separate `Html5MixChannel` class?
override function parseMessage(message: Message) {
switch (message.id) {
// Because we're using a HTML implementation here, we cannot use the
// LinearInterpolator parameters
case ChannelMessageID.PVolume: gain.gain.value = cast message.data;
default:
super.parseMessage(message);
}
}
#end
}

View File

@ -44,8 +44,12 @@ class StreamChannel extends BaseChannel {
}
final khaBuffer = p_khaBuffer.get();
khaChannel.nextSamples(khaBuffer, requestedSamples.channelLength, sampleRate);
khaChannel.nextSamples(khaBuffer, requestedSamples.numChannels * requestedSamples.channelLength, sampleRate);
requestedSamples.deinterleaveFromFloat32Array(khaBuffer, requestedSamples.numChannels);
if (khaChannel.finished) {
finished = true;
}
}
override function parseMessage(message: Message) {

View File

@ -43,7 +43,7 @@ abstract class Panner extends DSP {
public function new(handle: BaseChannelHandle) {
this.inUse = true; // Don't allow using panners with addInsert()
this.handle = handle;
this.handle.channel.panner = this;
handle.channel.panner = this;
}
public inline function setHandle(handle: BaseChannelHandle) {
@ -52,7 +52,7 @@ abstract class Panner extends DSP {
}
reset3D();
this.handle = handle;
this.handle.channel.panner = this;
handle.channel.panner = this;
}
/**

View File

@ -61,10 +61,13 @@ class StereoPanner extends Panner {
public inline function setBalance(balance: Balance) {
this._balance = balance;
final volumeLeft = Math.sqrt(~balance);
final volumeRight = Math.sqrt(balance);
sendMessage({ id: StereoPannerMessageID.PVolumeLeft, data: volumeLeft });
sendMessage({ id: StereoPannerMessageID.PVolumeRight, data: volumeRight });
#if (kha_html5 || kha_debug_html5)
handle.channel.sendMessage({ id: ChannelMessageID.PVolumeLeft, data: volumeLeft });
handle.channel.sendMessage({ id: ChannelMessageID.PVolumeRight, data: volumeRight });

View File

@ -0,0 +1,17 @@
package aura.format;
import haxe.io.Bytes;
using StringTools;
/**
Variant of `aura.format.InputExtension.isByteMagic()` for `haxe.io.Bytes`.
**/
inline function isByteMagic(bytes: Bytes, position: Int, magicASCII: String): Bool {
var match = true;
for (i in 0...magicASCII.length) {
match = match && bytes.get(position + i) == magicASCII.fastCodeAt(i);
}
return match;
}

View File

@ -3,6 +3,8 @@ package aura.format;
import haxe.Int64;
import haxe.io.Input;
using StringTools;
inline function readInt64(inp: Input): Int64 {
final first = inp.readInt32();
final second = inp.readInt32();
@ -19,3 +21,24 @@ inline function readUInt32(inp: Input): Int64 {
return out;
}
/**
Platform- and encoding-independent way of matching the input with an ASCII
magic string. This function does not consider the input endianess, it is
assumed that the order of characters in `magicASCII` matches the byte order
in the input stream.
- `inp.readString(len, haxe.io.Encoding.UTF8)` does not work if the input
streams contains data that can be interpreted as multi-byte characters.
- `inp.readString(len, haxe.io.Encoding.RawNative)` does not yield
platform-indepent results.
**/
inline function isByteMagic(inp: Input, magicASCII: String): Bool {
var match = true;
for (i in 0...magicASCII.length) {
match = match && inp.readByte() == magicASCII.fastCodeAt(i);
}
return match;
}

View File

@ -0,0 +1,86 @@
/**
Ogg layout:
https://en.wikipedia.org/wiki/Ogg#Page_structure
Vorbis layout:
https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2
https://wiki.xiph.org/OggVorbis
**/
package aura.format.audio;
import haxe.io.Bytes;
import haxe.io.BytesInput;
using aura.format.InputExtension;
class OggVorbisReader {
static inline final OGG_PAGE_HEADER_TYPE_BEG_OF_STREAM = 2;
static inline final VORBIS_PACKET_TYPE_IDENTIFICATION = 1;
final inp: BytesInput;
final firstSegmentPosition = 0;
final segmentTable: Bytes;
public inline function new(bytes: Bytes) {
this.inp = new BytesInput(bytes);
inp.bigEndian = false;
if (!inp.isByteMagic("OggS")) {
throw "Cannot read .ogg file, file does not start with 'OggS' magic";
}
inp.position += 1; // Skip version
final oggHeaderType = inp.readByte();
if (oggHeaderType != OGG_PAGE_HEADER_TYPE_BEG_OF_STREAM) {
throw "Cannot read .ogg file, first header type was expected to be 'Beginning Of Stream'";
}
inp.position += 8; // Skip granule position
inp.position += 4; // Skip bitstream serial number
final pageSequenceNumber = inp.readUInt32();
if (pageSequenceNumber != 0) {
throw "Cannot read .ogg file, first page sequence number was expected to be 0";
}
inp.position += 4; // Skip checksum (for now)
final numPageSegments = inp.readByte();
if (numPageSegments == 0) {
throw "Cannot read .ogg file, first page has no segments";
}
segmentTable = Bytes.alloc(numPageSegments);
inp.readFullBytes(segmentTable, 0, numPageSegments);
firstSegmentPosition = inp.position;
final packetType = inp.readByte();
if (packetType != VORBIS_PACKET_TYPE_IDENTIFICATION) {
throw "Cannot read .ogg file, Vorbis identification header expected";
}
if (!inp.isByteMagic("vorbis")) {
throw "Cannot read .ogg file, only Ogg Vorbis files are supported";
}
// See https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2, version must be 0
final version = inp.readUInt32();
if (version != 0) {
throw "Cannot read .ogg file, Vorbis version field expected to be 0";
}
}
public function getNumChannels(): Int {
inp.position = firstSegmentPosition + 1 + 6 + 4; // Skip packet type + vorbis identifier + version
final numChannels = inp.readByte();
assert(Critical, numChannels > 0);
return numChannels;
}
}

View File

@ -30,7 +30,7 @@ abstract Vec3(FastVector3) from FastVector3 to FastVector3 {
return new FastVector4(this.x, this.y, this.z);
}
#if (AURA_WITH_IRON || leenkx)
#if (AURA_WITH_IRON || armory)
@:from
public static inline function fromIronVec3(v: iron.math.Vec3): Vec3{
return new FastVector3(v.x, v.y, v.z);

View File

@ -25,7 +25,7 @@ class ChannelMessageID extends MessageID {
final PPitch;
final PDopplerRatio;
final PDstAttenuation;
#if (kha_html5 || kha_debug_html5)
final PVolumeLeft;
final PVolumeRight;

View File

@ -7,12 +7,13 @@ const targetsHL = ["windows-hl", "linux-hl", "macos-hl", "osx-hl", "android-hl",
const targetsCPP = ["windows", "linux", "macos", "osx"];
const targetsHTML5 = ["html5", "debug-html5"];
function addBackends(project) {
project.localLibraryPath = "Backends";
async function addBackends(project) {
//project.localLibraryPath = "Backends";
const isHL = targetsHL.indexOf(Project.platform) >= 0;
if (isHL) {
await project.addProject("Backends/hl");
project.addDefine("AURA_BACKEND_HL");
console.log("[Aura] Using HL/C backend");
}
@ -31,7 +32,7 @@ async function main() {
project.addSources('Sources');
if (process.argv.indexOf("--aura-no-backend") == -1) {
addBackends(project);
await addBackends(project);
}
else {
project.addDefine("AURA_NO_BACKEND");