Compare commits
22 Commits
e2b63c9a1a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c30554504 | |||
| 37c8779d12 | |||
| 6b704ff469 | |||
| 2a3bff5a18 | |||
| 91482071b8 | |||
| 38151eb233 | |||
| 78ea055aea | |||
| db2482dbe2 | |||
| 5ae6a9e698 | |||
| 6db83e559b | |||
| e7ec872747 | |||
| 31226a3871 | |||
| 9be3240d6c | |||
| 96134404a2 | |||
| 3aac63d255 | |||
| a8d6095204 | |||
| 960095d0d5 | |||
| 2b221338ae | |||
| 7ef0d59cfc | |||
| da1d893342 | |||
| 7b5a72036a | |||
| 694b226345 |
@ -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 {}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB |
@ -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);
|
||||
|
||||
BIN
Krom/Krom.app/Contents/MacOS/Krom_x64
Executable file
BIN
Krom/Krom.app/Contents/Resources/AppIcon.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
Krom/Krom.exe
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
15
leenkx/Sources/leenkx/logicnode/GetWorldColorNode.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
50
leenkx/Sources/leenkx/logicnode/GetWorldSkyNode.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
leenkx/Sources/leenkx/logicnode/GetWorldTextureNode.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
23
leenkx/Sources/leenkx/logicnode/SetWorldColorNode.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
63
leenkx/Sources/leenkx/logicnode/SetWorldSkyNode.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
25
leenkx/Sources/leenkx/logicnode/SetWorldTextureNode.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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++;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
674
leenkx/Sources/leenkx/renderpath/Sky.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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')
|
||||
|
||||
|
||||
@ -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] = {}
|
||||
|
||||
@ -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')
|
||||
@ -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')
|
||||
|
||||
11
leenkx/blender/lnx/logicnode/world/LN_get_world_color.py
Normal 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')
|
||||
51
leenkx/blender/lnx/logicnode/world/LN_get_world_sky.py
Normal 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')
|
||||
12
leenkx/blender/lnx/logicnode/world/LN_get_world_texture.py
Normal 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')
|
||||
@ -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')
|
||||
@ -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')
|
||||
14
leenkx/blender/lnx/logicnode/world/LN_set_world_color.py
Normal 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')
|
||||
54
leenkx/blender/lnx/logicnode/world/LN_set_world_sky.py
Normal 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')
|
||||
15
leenkx/blender/lnx/logicnode/world/LN_set_world_texture.py
Normal 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')
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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 });
|
||||
|
||||
17
lib/aura/Sources/aura/format/BytesExtension.hx
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
86
lib/aura/Sources/aura/format/audio/OggVorbisReader.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -25,7 +25,7 @@ class ChannelMessageID extends MessageID {
|
||||
final PPitch;
|
||||
final PDopplerRatio;
|
||||
final PDstAttenuation;
|
||||
|
||||
|
||||
#if (kha_html5 || kha_debug_html5)
|
||||
final PVolumeLeft;
|
||||
final PVolumeRight;
|
||||
|
||||
@ -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");
|
||||
|
||||