forked from LeenkxTeam/LNXSDK
Compare commits
114 Commits
5ae6a9e698
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a133c5ee94 | |||
| 4895d7cfa5 | |||
| 052ce82588 | |||
| 211cba61a6 | |||
| 4ab646015d | |||
| 9fee67d231 | |||
| 860b73a8f4 | |||
| da38631584 | |||
| 6839203dc5 | |||
| d9ac74101f | |||
| e20400654c | |||
| 058cbed20f | |||
| e0c10318e5 | |||
| 7d609b126d | |||
| 36ecaea919 | |||
| fac01a2849 | |||
| c71c1a98e4 | |||
| f6d5f142e9 | |||
| 70961b0336 | |||
| 9c50274f29 | |||
| a94b95afc8 | |||
| cee36635e0 | |||
| d0337f5dae | |||
| fca51c7e61 | |||
| 0bb4bceb57 | |||
| af9a259fbb | |||
| f1d8522f51 | |||
| 56d1b0e4a7 | |||
| 73e4a0ad30 | |||
| 67d5e6b2c7 | |||
| 635f8f6645 | |||
| b2444be7e2 | |||
| 7d00036973 | |||
| 46814444af | |||
| b4de377433 | |||
| 2abb6efe4f | |||
| d787f9a7fc | |||
| 75e95387bb | |||
| 0067459e9a | |||
| cf68067343 | |||
| 365c624d73 | |||
| 384a8a7ee8 | |||
| 4e2b59a1d6 | |||
| 6d07e70b11 | |||
| 42fdff4757 | |||
| d44fd9993a | |||
| 3fd1b395fb | |||
| 40bca62f96 | |||
| e5082835c4 | |||
| bc700a6374 | |||
| 1e500993d9 | |||
| d842744aeb | |||
| ada18b4648 | |||
| 5004efe046 | |||
| 1015e0df34 | |||
| 949b355255 | |||
| 0460b9d587 | |||
| 88cebe4f49 | |||
| c915312901 | |||
| 0f9d693bb7 | |||
| ce09e510e9 | |||
| 7319040624 | |||
| 542773bd79 | |||
| fe017dd874 | |||
| 7aeebf2008 | |||
| 0b4184ccc2 | |||
| ddc12e8607 | |||
| 6ad647ae56 | |||
| b77aca926a | |||
| c2bb20f905 | |||
| 14c6a7be03 | |||
| 85d63e8413 | |||
| 78452aaf67 | |||
| 1f72636350 | |||
| 62433ce86a | |||
| 2be36398f7 | |||
| 2675138ddc | |||
| 572665e8e6 | |||
| 0839f39dfa | |||
| 57cf4955a1 | |||
| c52ae2e4f1 | |||
| 48141f23c5 | |||
| cbbcd053fb | |||
| a57923860e | |||
| 160f6bdadf | |||
| 50ad462318 | |||
| 1239145da4 | |||
| 102941b5d2 | |||
| defff993a0 | |||
| e408151295 | |||
| 22159c9f37 | |||
| 9a2779982d | |||
| 2fc7e9880a | |||
| 7e1ac5fa5c | |||
| b2395f30fc | |||
| f4b0bf1e93 | |||
| 31a6a9d7ec | |||
| 7b2f21c499 | |||
| 17c31b4a4b | |||
| 013c8653ff | |||
| bd8b49a416 | |||
| 9d83c318b6 | |||
| cb19c9b5b4 | |||
| f4fe822ef6 | |||
| 6689fbc734 | |||
| 91b6c203e9 | |||
| 2c30554504 | |||
| 37c8779d12 | |||
| 6b704ff469 | |||
| 2a3bff5a18 | |||
| 91482071b8 | |||
| 38151eb233 | |||
| 78ea055aea | |||
| db2482dbe2 |
@ -142,6 +142,8 @@ class Image implements Canvas implements Resource {
|
||||
return 5;
|
||||
case A16:
|
||||
return 7;
|
||||
case R32UI:
|
||||
return 8;
|
||||
default:
|
||||
return 1; // Grey8
|
||||
}
|
||||
|
||||
@ -234,6 +234,8 @@ class Image implements Canvas implements Resource {
|
||||
return 5;
|
||||
case A16:
|
||||
return 7;
|
||||
case R32UI:
|
||||
return 8;
|
||||
default:
|
||||
return 1; // Grey8
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ extern class Krom {
|
||||
static function setRenderTarget(stage: kha.graphics4.TextureUnit, renderTarget: Dynamic): Void;
|
||||
static function setTextureDepth(unit: kha.graphics4.TextureUnit, texture: Dynamic): Void;
|
||||
static function setImageTexture(stage: kha.graphics4.TextureUnit, texture: Dynamic): Void;
|
||||
static function setImageRenderTarget(stage: kha.graphics4.TextureUnit, renderTarget: Dynamic): Void;
|
||||
static function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: Int, vAddressing: Int, minificationFilter: Int,
|
||||
magnificationFilter: Int, mipmapFilter: Int): Void;
|
||||
static function setTexture3DParameters(texunit: kha.graphics4.TextureUnit, uAddressing: Int, vAddressing: Int, wAddressing: Int, minificationFilter: Int,
|
||||
@ -114,6 +115,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;
|
||||
@ -128,6 +130,11 @@ extern class Krom {
|
||||
static function readStorage(name: String): haxe.io.BytesData;
|
||||
|
||||
static function fileSaveBytes(path: String, bytes: haxe.io.BytesData): Void;
|
||||
static function fileWriteBytes(path: String, bytes: haxe.io.BytesData, ?offset: Float, ?byteLength: Int): Void;
|
||||
static function fileReadBytes(path: String, ?offset: Float, ?length: Int): haxe.io.BytesData;
|
||||
static function fileSize(path: String): Float;
|
||||
static function fileExists(path: String): Bool;
|
||||
static function deleteFile(path: String): Void;
|
||||
static function sysCommand(cmd: String, ?args: Array<String>): Int;
|
||||
static function savePath(): String;
|
||||
static function getArgCount(): Int;
|
||||
@ -158,4 +165,45 @@ extern class Krom {
|
||||
static function getConstantLocationCompute(shader: Dynamic, name: String): Dynamic;
|
||||
static function getTextureUnitCompute(shader: Dynamic, name: String): Dynamic;
|
||||
static function compute(x: Int, y: Int, z: Int): Void;
|
||||
static function webviewCreate(options: Dynamic): Int;
|
||||
static function webviewLoadHTML(id: Int, html: String): Void;
|
||||
static function webviewLoadURL(id: Int, url: String): Void;
|
||||
static function webviewEvalJS(id: Int, js: String): Void;
|
||||
static function webviewEvalJSAsync(id: Int, js: String, callback: String->Void): Void;
|
||||
static function webviewShow(id: Int): Void;
|
||||
static function webviewHide(id: Int): Void;
|
||||
static function webviewDestroy(id: Int): Void;
|
||||
static function webviewResize(id: Int, width: Int, height: Int): Void;
|
||||
static function webviewMove(id: Int, x: Int, y: Int): Void;
|
||||
static function webviewSetBounds(id: Int, x: Int, y: Int, width: Int, height: Int): Void;
|
||||
static function webviewGetX(id: Int): Int;
|
||||
static function webviewGetY(id: Int): Int;
|
||||
static function webviewGetWidth(id: Int): Int;
|
||||
static function webviewGetHeight(id: Int): Int;
|
||||
static function webviewSetTransparent(id: Int, transparent: Bool): Void;
|
||||
static function webviewSetClickThrough(id: Int, enabled: Bool): Void;
|
||||
static function webviewSetTitle(id: Int, title: String): Void;
|
||||
static function webviewSend(id: Int, message: String): Void;
|
||||
static function webviewSetOnMessage(id: Int, callback: String->Void): Void;
|
||||
static function webviewSetOnLoad(id: Int, callback: Void->Void): Void;
|
||||
static function webviewSetOnError(id: Int, callback: String->Void): Void;
|
||||
static function webviewSetOnClose(id: Int, callback: Void->Void): Void;
|
||||
static function webviewCount(): Int;
|
||||
static function webviewIsValid(id: Int): Bool;
|
||||
static function webviewSetActiveDOM(id: Int): Void;
|
||||
static function webviewGetActiveDOM(): Int;
|
||||
static function webviewGoBack(id: Int): Void;
|
||||
static function webviewGoForward(id: Int): Void;
|
||||
static function webviewReload(id: Int): Void;
|
||||
static function webviewCanGoBack(id: Int): Bool;
|
||||
static function webviewCanGoForward(id: Int): Bool;
|
||||
static function webviewGetURL(id: Int): String;
|
||||
static function webviewGetPageTitle(id: Int): String;
|
||||
static function webviewMinimize(id: Int): Void;
|
||||
static function webviewMaximize(id: Int): Void;
|
||||
static function webviewRestore(id: Int): Void;
|
||||
static function webviewSetFullscreen(id: Int, fullscreen: Bool): Void;
|
||||
static function webviewIsFullscreen(id: Int): Bool;
|
||||
static function webviewEnableDevTools(id: Int, enabled: Bool): Void;
|
||||
static function webviewSetContextMenu(id: Int, enabled: Bool): Void;
|
||||
}
|
||||
|
||||
@ -75,6 +75,8 @@ class Image implements Canvas implements Resource {
|
||||
return 5;
|
||||
case A16:
|
||||
return 7;
|
||||
case R32UI:
|
||||
return 8;
|
||||
default:
|
||||
return 1; // Grey8
|
||||
}
|
||||
@ -200,6 +202,7 @@ class Image implements Canvas implements Resource {
|
||||
case RGBA64: 8;
|
||||
case A32: 4;
|
||||
case A16: 2;
|
||||
case R32UI: 4;
|
||||
default: 4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ class Compute {
|
||||
public static function setSampledDepthTexture(unit: TextureUnit, texture: Image) {
|
||||
if (texture == null)
|
||||
return;
|
||||
Krom.setSampledDepthTextureCompute(unit, texture);
|
||||
Krom.setSampledDepthTextureCompute(unit, texture.renderTarget_);
|
||||
}
|
||||
|
||||
public static function setSampledCubeMap(unit: TextureUnit, cubeMap: CubeMap) {
|
||||
@ -118,7 +118,7 @@ class Compute {
|
||||
public static function setSampledDepthCubeMap(unit: TextureUnit, cubeMap: CubeMap) {
|
||||
if (cubeMap == null)
|
||||
return;
|
||||
Krom.setSampledDepthTextureCompute(unit, cubeMap);
|
||||
Krom.setSampledDepthTextureCompute(unit, cubeMap.renderTarget_);
|
||||
}
|
||||
|
||||
public static function setTextureParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
|
||||
|
||||
@ -9,14 +9,7 @@ class ShaderStorageBuffer {
|
||||
public function new(indexCount: Int, type: VertexData) {
|
||||
myCount = indexCount;
|
||||
data = new Array<Int>();
|
||||
data[myCount - 1] = 0;
|
||||
init(indexCount, type);
|
||||
}
|
||||
|
||||
function init(indexCount: Int, type: VertexData) {
|
||||
myCount = indexCount;
|
||||
data = new Array<Int>();
|
||||
data[myCount - 1] = 0;
|
||||
if (myCount > 0) data[myCount - 1] = 0;
|
||||
}
|
||||
|
||||
public function delete(): Void {}
|
||||
|
||||
@ -122,7 +122,12 @@ class Graphics implements kha.graphics4.Graphics {
|
||||
public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
|
||||
if (texture == null)
|
||||
return;
|
||||
Krom.setImageTexture(unit, texture.texture_);
|
||||
if (texture.texture_ != null) {
|
||||
Krom.setImageTexture(unit, texture.texture_);
|
||||
}
|
||||
else if (texture.renderTarget_ != null) {
|
||||
Krom.setImageRenderTarget(unit, texture.renderTarget_);
|
||||
}
|
||||
}
|
||||
|
||||
public function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,8 +821,19 @@ int kinc_g4_max_bound_textures(void) {
|
||||
return units;
|
||||
}
|
||||
|
||||
static int getUnitStage(kinc_g4_texture_unit_t unit) {
|
||||
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
|
||||
if (unit.stages[i] >= 0) {
|
||||
return unit.stages[i];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t unit, kinc_g4_texture_direction_t dir, kinc_g4_texture_addressing_t addressing) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
int stage = getUnitStage(unit);
|
||||
if (stage < 0) return;
|
||||
glActiveTexture(GL_TEXTURE0 + stage);
|
||||
GLenum texDir;
|
||||
switch (dir) {
|
||||
case KINC_G4_TEXTURE_DIRECTION_U:
|
||||
@ -841,39 +852,39 @@ static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t u
|
||||
case KINC_G4_TEXTURE_ADDRESSING_CLAMP:
|
||||
glTexParameteri(target, texDir, GL_CLAMP_TO_EDGE);
|
||||
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
|
||||
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
|
||||
texModesU[stage] = GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
else {
|
||||
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
|
||||
texModesV[stage] = GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
break;
|
||||
case KINC_G4_TEXTURE_ADDRESSING_REPEAT:
|
||||
glTexParameteri(target, texDir, GL_REPEAT);
|
||||
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
|
||||
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
|
||||
texModesU[stage] = GL_REPEAT;
|
||||
}
|
||||
else {
|
||||
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
|
||||
texModesV[stage] = GL_REPEAT;
|
||||
}
|
||||
break;
|
||||
case KINC_G4_TEXTURE_ADDRESSING_BORDER:
|
||||
// unsupported
|
||||
glTexParameteri(target, texDir, GL_CLAMP_TO_EDGE);
|
||||
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
|
||||
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
|
||||
texModesU[stage] = GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
else {
|
||||
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
|
||||
texModesV[stage] = GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
break;
|
||||
case KINC_G4_TEXTURE_ADDRESSING_MIRROR:
|
||||
// unsupported
|
||||
glTexParameteri(target, texDir, GL_REPEAT);
|
||||
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
|
||||
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
|
||||
texModesU[stage] = GL_REPEAT;
|
||||
}
|
||||
else {
|
||||
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
|
||||
texModesV[stage] = GL_REPEAT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -881,11 +892,15 @@ static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t u
|
||||
}
|
||||
|
||||
int Kinc_G4_Internal_TextureAddressingU(kinc_g4_texture_unit_t unit) {
|
||||
return texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]];
|
||||
int stage = getUnitStage(unit);
|
||||
if (stage < 0) return GL_CLAMP_TO_EDGE;
|
||||
return texModesU[stage];
|
||||
}
|
||||
|
||||
int Kinc_G4_Internal_TextureAddressingV(kinc_g4_texture_unit_t unit) {
|
||||
return texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]];
|
||||
int stage = getUnitStage(unit);
|
||||
if (stage < 0) return GL_CLAMP_TO_EDGE;
|
||||
return texModesV[stage];
|
||||
}
|
||||
|
||||
void kinc_g4_set_texture_addressing(kinc_g4_texture_unit_t unit, kinc_g4_texture_direction_t dir, kinc_g4_texture_addressing_t addressing) {
|
||||
@ -899,7 +914,9 @@ void kinc_g4_set_texture3d_addressing(kinc_g4_texture_unit_t unit, kinc_g4_textu
|
||||
}
|
||||
|
||||
static void setTextureMagnificationFilterInternal(GLenum target, kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
|
||||
glActiveTexture(GL_TEXTURE0 + texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
int stage = getUnitStage(texunit);
|
||||
if (stage < 0) return;
|
||||
glActiveTexture(GL_TEXTURE0 + stage);
|
||||
glCheckErrors();
|
||||
switch (filter) {
|
||||
case KINC_G4_TEXTURE_FILTER_POINT:
|
||||
@ -964,26 +981,34 @@ static void setMinMipFilters(GLenum target, int unit) {
|
||||
}
|
||||
|
||||
void kinc_g4_set_texture_minification_filter(kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
|
||||
minFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
|
||||
setMinMipFilters(GL_TEXTURE_2D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
int stage = getUnitStage(texunit);
|
||||
if (stage < 0) return;
|
||||
minFilters[stage] = filter;
|
||||
setMinMipFilters(GL_TEXTURE_2D, stage);
|
||||
}
|
||||
|
||||
void kinc_g4_set_texture3d_minification_filter(kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
|
||||
minFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
|
||||
int stage = getUnitStage(texunit);
|
||||
if (stage < 0) return;
|
||||
minFilters[stage] = filter;
|
||||
#ifndef KINC_OPENGL_ES
|
||||
setMinMipFilters(GL_TEXTURE_3D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
setMinMipFilters(GL_TEXTURE_3D, stage);
|
||||
#endif
|
||||
}
|
||||
|
||||
void kinc_g4_set_texture_mipmap_filter(kinc_g4_texture_unit_t texunit, kinc_g4_mipmap_filter_t filter) {
|
||||
mipFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
|
||||
setMinMipFilters(GL_TEXTURE_2D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
int stage = getUnitStage(texunit);
|
||||
if (stage < 0) return;
|
||||
mipFilters[stage] = filter;
|
||||
setMinMipFilters(GL_TEXTURE_2D, stage);
|
||||
}
|
||||
|
||||
void kinc_g4_set_texture3d_mipmap_filter(kinc_g4_texture_unit_t texunit, kinc_g4_mipmap_filter_t filter) {
|
||||
mipFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
|
||||
int stage = getUnitStage(texunit);
|
||||
if (stage < 0) return;
|
||||
mipFilters[stage] = filter;
|
||||
#ifndef KINC_OPENGL_ES
|
||||
setMinMipFilters(GL_TEXTURE_3D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
setMinMipFilters(GL_TEXTURE_3D, stage);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@ -190,6 +190,17 @@ void kinc_g4_set_compute_shader(kinc_g4_compute_shader *shader) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void kinc_g4_set_image_render_target(kinc_g4_texture_unit_t unit, kinc_g4_render_target_t *render_target) {
|
||||
#if defined(KINC_WINDOWS) || (defined(KINC_LINUX) && defined(GL_VERSION_4_4))
|
||||
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
|
||||
if (unit.stages[i] >= 0) {
|
||||
glBindImageTexture(unit.stages[i], render_target->impl._texture, 0, GL_FALSE, 0, GL_READ_WRITE, convertInternalRTFormat((kinc_g4_render_target_format_t)render_target->impl.format));
|
||||
}
|
||||
}
|
||||
glCheckErrors();
|
||||
#endif
|
||||
}
|
||||
|
||||
void kinc_g4_compute(int x, int y, int z) {
|
||||
#ifdef HAS_COMPUTE
|
||||
glDispatchCompute(x, y, z);
|
||||
|
||||
@ -360,17 +360,23 @@ void kinc_g4_render_target_destroy(kinc_g4_render_target_t *renderTarget) {
|
||||
}
|
||||
|
||||
void kinc_g4_render_target_use_color_as_texture(kinc_g4_render_target_t *renderTarget, kinc_g4_texture_unit_t unit) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
glCheckErrors();
|
||||
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._texture);
|
||||
glCheckErrors();
|
||||
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
|
||||
if (unit.stages[i] >= 0) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit.stages[i]);
|
||||
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._texture);
|
||||
glCheckErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void kinc_g4_render_target_use_depth_as_texture(kinc_g4_render_target_t *renderTarget, kinc_g4_texture_unit_t unit) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
|
||||
glCheckErrors();
|
||||
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._depthTexture);
|
||||
glCheckErrors();
|
||||
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
|
||||
if (unit.stages[i] >= 0) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit.stages[i]);
|
||||
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._depthTexture);
|
||||
glCheckErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void kinc_g4_render_target_set_depth_stencil_from(kinc_g4_render_target_t *renderTarget, kinc_g4_render_target_t *source) {
|
||||
|
||||
@ -99,6 +99,8 @@ static int convertFormat(kinc_image_format_t format) {
|
||||
case KINC_IMAGE_FORMAT_A16:
|
||||
case KINC_IMAGE_FORMAT_GREY8:
|
||||
return GL_RED;
|
||||
case KINC_IMAGE_FORMAT_R32UI:
|
||||
return GL_RED_INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,6 +134,8 @@ static int convertInternalFormat(kinc_image_format_t format) {
|
||||
#else
|
||||
return GL_R8;
|
||||
#endif
|
||||
case KINC_IMAGE_FORMAT_R32UI:
|
||||
return GL_R32UI;
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +149,8 @@ static int convertType(kinc_image_format_t format) {
|
||||
case KINC_IMAGE_FORMAT_RGBA32:
|
||||
default:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
case KINC_IMAGE_FORMAT_R32UI:
|
||||
return GL_UNSIGNED_INT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,7 +489,7 @@ void kinc_g4_texture_init3d(kinc_g4_texture_t *texture, int width, int height, i
|
||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glCheckErrors();
|
||||
|
||||
glTexImage3D(GL_TEXTURE_3D, 0, convertInternalFormat(format), width, height, depth, 0, convertFormat(format), GL_UNSIGNED_BYTE, NULL);
|
||||
glTexImage3D(GL_TEXTURE_3D, 0, convertInternalFormat(format), width, height, depth, 0, convertFormat(format), convertType(format), NULL);
|
||||
glCheckErrors();
|
||||
#endif
|
||||
}
|
||||
@ -523,8 +529,8 @@ void Kinc_G4_Internal_TextureSet(kinc_g4_texture_t *texture, kinc_g4_texture_uni
|
||||
#else
|
||||
glBindTexture(target, texture->impl.texture);
|
||||
glCheckErrors();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, Kinc_G4_Internal_TextureAddressingU(unit));
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, Kinc_G4_Internal_TextureAddressingV(unit));
|
||||
glTexParameteri(target, GL_TEXTURE_WRAP_S, Kinc_G4_Internal_TextureAddressingU(unit));
|
||||
glTexParameteri(target, GL_TEXTURE_WRAP_T, Kinc_G4_Internal_TextureAddressingV(unit));
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -532,7 +538,7 @@ void Kinc_G4_Internal_TextureImageSet(kinc_g4_texture_t *texture, kinc_g4_textur
|
||||
#if defined(KINC_WINDOWS) || (defined(KINC_LINUX) && defined(GL_VERSION_4_4))
|
||||
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
|
||||
if (unit.stages[i] >= 0) {
|
||||
glBindImageTexture(unit.stages[i], texture->impl.texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, convertInternalFormat(texture->format));
|
||||
glBindImageTexture(unit.stages[i], texture->impl.texture, 0, GL_FALSE, 0, GL_READ_WRITE, convertInternalFormat(texture->format));
|
||||
}
|
||||
}
|
||||
glCheckErrors();
|
||||
|
||||
@ -392,6 +392,11 @@ void kinc_window_hide(int window_index) {
|
||||
UpdateWindow(windows[window_index].handle);
|
||||
}
|
||||
|
||||
void kinc_window_set_foreground(int window_index) {
|
||||
SetForegroundWindow(windows[window_index].handle);
|
||||
SetFocus(windows[window_index].handle);
|
||||
}
|
||||
|
||||
void kinc_window_set_title(int window_index, const char *title) {
|
||||
wchar_t buffer[1024];
|
||||
MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 1024);
|
||||
|
||||
@ -376,6 +376,8 @@ KINC_FUNC void kinc_g4_set_texture(kinc_g4_texture_unit_t unit, struct kinc_g4_t
|
||||
/// <param name="texture">The texture to assign to the unit</param>
|
||||
KINC_FUNC void kinc_g4_set_image_texture(kinc_g4_texture_unit_t unit, struct kinc_g4_texture *texture);
|
||||
|
||||
KINC_FUNC void kinc_g4_set_image_render_target(kinc_g4_texture_unit_t unit, struct kinc_g4_render_target *render_target);
|
||||
|
||||
KINC_FUNC bool kinc_g4_init_occlusion_query(unsigned *occlusionQuery);
|
||||
|
||||
KINC_FUNC void kinc_g4_delete_occlusion_query(unsigned occlusionQuery);
|
||||
|
||||
@ -30,7 +30,8 @@ typedef enum kinc_image_format {
|
||||
KINC_IMAGE_FORMAT_RGBA64,
|
||||
KINC_IMAGE_FORMAT_A32,
|
||||
KINC_IMAGE_FORMAT_BGRA32,
|
||||
KINC_IMAGE_FORMAT_A16
|
||||
KINC_IMAGE_FORMAT_A16,
|
||||
KINC_IMAGE_FORMAT_R32UI
|
||||
} kinc_image_format_t;
|
||||
|
||||
typedef struct kinc_image {
|
||||
@ -608,6 +609,8 @@ int kinc_image_format_sizeof(kinc_image_format_t format) {
|
||||
return 1;
|
||||
case KINC_IMAGE_FORMAT_RGB24:
|
||||
return 3;
|
||||
case KINC_IMAGE_FORMAT_R32UI:
|
||||
return 4;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -139,6 +139,11 @@ KINC_FUNC void kinc_window_show(int window);
|
||||
/// </summary>
|
||||
KINC_FUNC void kinc_window_hide(int window);
|
||||
|
||||
/// <summary>
|
||||
/// Brings a window to the foreground and sets focus to it.
|
||||
/// </summary>
|
||||
KINC_FUNC void kinc_window_set_foreground(int window);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the title of a window.
|
||||
/// </summary>
|
||||
|
||||
Binary file not shown.
@ -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);
|
||||
|
||||
7
Kha/Sources/kha/compute/Access.hx
Normal file
7
Kha/Sources/kha/compute/Access.hx
Normal file
@ -0,0 +1,7 @@
|
||||
package kha.compute;
|
||||
|
||||
enum abstract Access(Int) to Int {
|
||||
var Read = 0;
|
||||
var Write = 1;
|
||||
var ReadWrite = 2;
|
||||
}
|
||||
41
Kha/Sources/kha/compute/Compute.hx
Normal file
41
Kha/Sources/kha/compute/Compute.hx
Normal file
@ -0,0 +1,41 @@
|
||||
package kha.compute;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.Image;
|
||||
import kha.FastFloat;
|
||||
import kha.math.FastMatrix3;
|
||||
import kha.math.FastMatrix4;
|
||||
import kha.math.FastVector2;
|
||||
import kha.math.FastVector3;
|
||||
import kha.math.FastVector4;
|
||||
import kha.graphics4.CubeMap;
|
||||
import kha.graphics4.TextureAddressing;
|
||||
import kha.graphics4.TextureFilter;
|
||||
import kha.graphics4.MipMapFilter;
|
||||
|
||||
extern class Compute {
|
||||
public static function setBool(location: ConstantLocation, value: Bool): Void;
|
||||
public static function setInt(location: ConstantLocation, value: Int): Void;
|
||||
public static function setFloat(location: ConstantLocation, value: FastFloat): Void;
|
||||
public static function setFloat2(location: ConstantLocation, value1: FastFloat, value2: FastFloat): Void;
|
||||
public static function setFloat3(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void;
|
||||
public static function setFloat4(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void;
|
||||
public static function setFloats(location: ConstantLocation, values: Float32Array): Void;
|
||||
public static function setVector2(location: ConstantLocation, value: FastVector2): Void;
|
||||
public static function setVector3(location: ConstantLocation, value: FastVector3): Void;
|
||||
public static function setVector4(location: ConstantLocation, value: FastVector4): Void;
|
||||
public static function setMatrix(location: ConstantLocation, value: FastMatrix4): Void;
|
||||
public static function setMatrix3(location: ConstantLocation, value: FastMatrix3): Void;
|
||||
public static function setBuffer(buffer: ShaderStorageBuffer, index: Int): Void;
|
||||
public static function setTexture(unit: TextureUnit, texture: Image, access: Access): Void;
|
||||
public static function setSampledTexture(unit: TextureUnit, texture: Image): Void;
|
||||
public static function setSampledDepthTexture(unit: TextureUnit, texture: Image): Void;
|
||||
public static function setSampledCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
|
||||
public static function setSampledDepthCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
|
||||
public static function setTextureParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
|
||||
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
|
||||
public static function setTexture3DParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
|
||||
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
|
||||
public static function setShader(shader: Shader): Void;
|
||||
public static function compute(x: Int, y: Int, z: Int): Void;
|
||||
}
|
||||
3
Kha/Sources/kha/compute/ConstantLocation.hx
Normal file
3
Kha/Sources/kha/compute/ConstantLocation.hx
Normal file
@ -0,0 +1,3 @@
|
||||
package kha.compute;
|
||||
|
||||
interface ConstantLocation {}
|
||||
10
Kha/Sources/kha/compute/Shader.hx
Normal file
10
Kha/Sources/kha/compute/Shader.hx
Normal file
@ -0,0 +1,10 @@
|
||||
package kha.compute;
|
||||
|
||||
import kha.Blob;
|
||||
|
||||
extern class Shader {
|
||||
public function new(sources: Array<Blob>, files: Array<String>);
|
||||
public function delete(): Void;
|
||||
public function getConstantLocation(name: String): ConstantLocation;
|
||||
public function getTextureUnit(name: String): TextureUnit;
|
||||
}
|
||||
12
Kha/Sources/kha/compute/ShaderStorageBuffer.hx
Normal file
12
Kha/Sources/kha/compute/ShaderStorageBuffer.hx
Normal file
@ -0,0 +1,12 @@
|
||||
package kha.compute;
|
||||
|
||||
import kha.graphics4.VertexData;
|
||||
|
||||
extern class ShaderStorageBuffer {
|
||||
public function new(indexCount: Int, type: VertexData);
|
||||
public function delete(): Void;
|
||||
public function lock(): Array<Int>;
|
||||
public function unlock(): Void;
|
||||
public function set(): Void;
|
||||
public function count(): Int;
|
||||
}
|
||||
3
Kha/Sources/kha/compute/TextureUnit.hx
Normal file
3
Kha/Sources/kha/compute/TextureUnit.hx
Normal file
@ -0,0 +1,3 @@
|
||||
package kha.compute;
|
||||
|
||||
interface TextureUnit {}
|
||||
@ -8,4 +8,5 @@ enum abstract TextureFormat(Int) to Int {
|
||||
var RGBA64 = 4; // Half floats
|
||||
var A32 = 5; // Float
|
||||
var A16 = 6; // Half float
|
||||
var R32UI = 7; // Unsigned 32-bit integer
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
Krom/Krom.exe
BIN
Krom/Krom.exe
Binary file not shown.
Binary file not shown.
@ -1,7 +0,0 @@
|
||||
# The zlib/libpng License
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
This notice may not be removed or altered from any source distribution.
|
||||
@ -1,7 +0,0 @@
|
||||
* 2024-12-02: Submodules have been moved in to this repository (previously contained in https://github.com/leenkx3d/lnxsdk).
|
||||
* 2019-06-30: Return value of `PhysicsWorld.rayCast()` changed, see https://github.com/leenkx3d/leenkx/commit/dfb7609a28cebf3a520e6a25a8563d01c32f2b01.
|
||||
* 2019-04-06: Use voxelao instead of voxelgi, gi will be reworked into raytracing.
|
||||
* 2019-01-13: If you are using Leenkx Updater, get Leenkx 0.6beta from https://leenkx.itch.io/leenkx3d first (or clone the sdk from https://github.com/leenkx3d/lnxsdk).
|
||||
* 2018-08-28: `LampObject` and `LampData` is now `LightObject` and `LightData`
|
||||
* 2018-06-01: 'Not Equal' has been removed from the Gate logic node. Use 'Equal' and 'False' output socket instead.
|
||||
* 2017-11-20: Use `leenkx.trait.physics.*` instead of `leenkx.trait.internal.*` to access physics traits.
|
||||
93
leenkx.py
93
leenkx.py
@ -6,12 +6,13 @@ bl_info = {
|
||||
"location": "Properties -> Render -> Leenkx Player",
|
||||
"description": "Full Stack SDK",
|
||||
"author": "Leenkx.com",
|
||||
"version": (2026, 5, 0),
|
||||
"blender": (4, 5, 0),
|
||||
"version": (2026, 10, 6),
|
||||
"blender": (5, 2, 0),
|
||||
"doc_url": "https://leenkx.com/",
|
||||
"tracker_url": "https://leenkx.com/support"
|
||||
}
|
||||
from enum import IntEnum
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
@ -32,6 +33,41 @@ from bpy.app.handlers import persistent
|
||||
from bpy.props import *
|
||||
from bpy.types import Operator, AddonPreferences
|
||||
|
||||
LEENKX_PREFS_ENV = 'LEENKX_ADDON_PREFS'
|
||||
|
||||
persist_props = [
|
||||
'apply_theme', 'show_advanced', 'sdk_path', 'tabs',
|
||||
'code_editor', 'ide_bin', 'ui_scale', 'viewport_controls',
|
||||
'khamake_threads', 'khamake_threads_use_auto', 'compilation_server',
|
||||
'renderdoc_path', 'ffmpeg_path', 'save_on_build', 'open_build_directory',
|
||||
'cmft_use_opencl', 'legacy_shaders', 'relative_paths',
|
||||
'debug_console_auto', 'debug_console_visible_sc',
|
||||
'debug_console_scale_in_sc', 'debug_console_scale_out_sc',
|
||||
'android_sdk_root_path', 'android_open_build_apk_directory',
|
||||
'android_apk_copy_path', 'android_apk_copy_open_directory',
|
||||
'html5_copy_path', 'html5_server_port',
|
||||
'html5_server_log', 'profile_exporter', 'khamake_debug',
|
||||
'haxe_times', 'use_leenkx_py_symlink', 'update_submodules',
|
||||
]
|
||||
|
||||
|
||||
def save_prefs():
|
||||
prefs = LeenkxAddonPreferences.get_prefs()
|
||||
data = {k: getattr(prefs, k) for k in persist_props}
|
||||
os.environ[LEENKX_PREFS_ENV] = json.dumps(data)
|
||||
|
||||
|
||||
def restore_prefs():
|
||||
raw = os.environ.get(LEENKX_PREFS_ENV)
|
||||
if not raw:
|
||||
return
|
||||
data = json.loads(raw)
|
||||
prefs = LeenkxAddonPreferences.get_prefs()
|
||||
prefs.skip_update = True
|
||||
for k, v in data.items():
|
||||
if hasattr(prefs, k):
|
||||
setattr(prefs, k, v)
|
||||
|
||||
|
||||
if bpy.app.version < (2, 90, 0):
|
||||
ListType = List
|
||||
@ -116,7 +152,9 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
return
|
||||
self.skip_update = True
|
||||
self.sdk_path = bpy.path.reduce_dirs([bpy.path.abspath(self.sdk_path)])[0] + '/'
|
||||
save_prefs()
|
||||
restart_leenkx(context)
|
||||
update_theme(context)
|
||||
|
||||
def ide_bin_update(self, context):
|
||||
if self.skip_update:
|
||||
@ -311,6 +349,11 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
" development. Warning: this will invalidate the installation if the SDK is removed"),
|
||||
update=lambda self, context: update_leenkx_py(get_sdk_path(context)),
|
||||
)
|
||||
apply_theme: BoolProperty(
|
||||
name="Apply Leenkx Theme", default=True,
|
||||
description="Automatically apply the Leenkx Blender theme on addon registration",
|
||||
update=lambda self, context: update_theme(context),
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
self.skip_update = False
|
||||
@ -318,7 +361,7 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
layout.label(text="Welcome to Leenkx!")
|
||||
|
||||
# Compare version Blender and Leenkx (major, minor)
|
||||
if bpy.app.version[:2] not in [(4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
|
||||
if bpy.app.version[:2] not in [(5, 2), (4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
|
||||
box = layout.box().column()
|
||||
box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version")
|
||||
|
||||
@ -450,6 +493,7 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
|
||||
col = box.column(align=True)
|
||||
col.prop(self, "use_leenkx_py_symlink")
|
||||
col.prop(self, "apply_theme")
|
||||
|
||||
@staticmethod
|
||||
def get_prefs() -> 'LeenkxAddonPreferences':
|
||||
@ -458,9 +502,10 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
|
||||
|
||||
def get_fp():
|
||||
if bpy.data.filepath == '':
|
||||
filepath = getattr(bpy.data, 'filepath', '')
|
||||
if filepath == '':
|
||||
return ''
|
||||
s = bpy.data.filepath.split(os.path.sep)
|
||||
s = filepath.split(os.path.sep)
|
||||
s.pop()
|
||||
return os.path.sep.join(s)
|
||||
|
||||
@ -934,13 +979,47 @@ def on_load_post(context):
|
||||
bpy.app.timers.register(lambda: restart_leenkx(bpy.context), first_interval=0.1)
|
||||
|
||||
|
||||
def remove_leenkx_theme():
|
||||
if "leenkx.theme" in sys.modules:
|
||||
sys.modules["leenkx.theme"].unregister()
|
||||
del sys.modules["leenkx.theme"]
|
||||
|
||||
|
||||
def apply_leenkx_theme(sdk_path: str):
|
||||
remove_leenkx_theme()
|
||||
import importlib.util
|
||||
theme_path = os.path.join(sdk_path, "leenkx", "blender", "theme", "theme.py")
|
||||
if os.path.exists(theme_path):
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"leenkx.theme", theme_path)
|
||||
theme = importlib.util.module_from_spec(spec)
|
||||
sys.modules["leenkx.theme"] = theme
|
||||
spec.loader.exec_module(theme)
|
||||
theme.register(sdk_path)
|
||||
else:
|
||||
print("Leenkx theme: theme.py not found at", theme_path)
|
||||
|
||||
|
||||
def update_theme(context):
|
||||
prefs = LeenkxAddonPreferences.get_prefs()
|
||||
if prefs.apply_theme:
|
||||
sdk_path = get_sdk_path(context)
|
||||
if sdk_path != "":
|
||||
apply_leenkx_theme(sdk_path)
|
||||
else:
|
||||
remove_leenkx_theme()
|
||||
|
||||
|
||||
def on_register_post():
|
||||
detect_sdk_path()
|
||||
save_prefs()
|
||||
restart_leenkx(bpy.context)
|
||||
update_theme(bpy.context)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(LeenkxAddonPreferences)
|
||||
restore_prefs()
|
||||
bpy.utils.register_class(LnxAddonPrintVersionInfoButton)
|
||||
bpy.utils.register_class(LnxAddonInstallButton)
|
||||
bpy.utils.register_class(LnxAddonUpdateButton)
|
||||
@ -953,7 +1032,11 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if bpy.app.timers.is_registered(on_register_post):
|
||||
bpy.app.timers.unregister(on_register_post)
|
||||
remove_leenkx_theme()
|
||||
stop_leenkx()
|
||||
save_prefs()
|
||||
bpy.utils.unregister_class(LeenkxAddonPreferences)
|
||||
bpy.utils.unregister_class(LnxAddonInstallButton)
|
||||
bpy.utils.unregister_class(LnxAddonPrintVersionInfoButton)
|
||||
|
||||
17
leenkx/Shaders/add_pass/add_pass.json
Normal file
17
leenkx/Shaders/add_pass/add_pass.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"contexts": [
|
||||
{
|
||||
"name": "add_pass",
|
||||
"depth_write": false,
|
||||
"compare_mode": "always",
|
||||
"cull_mode": "none",
|
||||
"blend_source": "source_alpha",
|
||||
"blend_destination": "inverse_source_alpha",
|
||||
"blend_operation": "add",
|
||||
"links": [],
|
||||
"texture_params": [],
|
||||
"vertex_shader": "../include/pass.vert.glsl",
|
||||
"fragment_shader": "../include/pass_copy.frag.glsl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -48,6 +48,10 @@ void main() {
|
||||
}
|
||||
|
||||
if (currentMipLevel == 0) {
|
||||
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
}
|
||||
|
||||
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.2
|
||||
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D gbuffer0; // Roughness
|
||||
uniform sampler2D gbufferD; // Depth
|
||||
|
||||
uniform vec2 dirInv;
|
||||
|
||||
@ -14,19 +15,46 @@ out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
float roughness = textureLod(gbuffer0, texCoord, 0.0).b;
|
||||
// if (roughness == 0.0) { // Always blur for now, non blured output can produce noise
|
||||
// fragColor.rgb = textureLod(tex, texCoord).rgb;
|
||||
// return;
|
||||
// }
|
||||
if (roughness >= 0.8) { // No reflections
|
||||
if (roughness >= 0.8) {
|
||||
fragColor.rgb = textureLod(tex, texCoord, 0.0).rgb;
|
||||
return;
|
||||
}
|
||||
if (roughness < 0.01) {
|
||||
fragColor.rgb = textureLod(tex, texCoord, 0.0).rgb;
|
||||
return;
|
||||
}
|
||||
|
||||
fragColor.rgb = textureLod(tex, texCoord + dirInv * 2.5, 0.0).rgb;
|
||||
fragColor.rgb += textureLod(tex, texCoord + dirInv * 1.5, 0.0).rgb;
|
||||
fragColor.rgb += textureLod(tex, texCoord, 0.0).rgb;
|
||||
fragColor.rgb += textureLod(tex, texCoord - dirInv * 1.5, 0.0).rgb;
|
||||
fragColor.rgb += textureLod(tex, texCoord - dirInv * 2.5, 0.0).rgb;
|
||||
fragColor.rgb /= vec3(5.0);
|
||||
float blurRadius = 1.0 + roughness * 4.0;
|
||||
vec3 center = textureLod(tex, texCoord, 0.0).rgb;
|
||||
|
||||
float centerDepth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
|
||||
float w0 = 1.0 / (1.0 + roughness * 2.0);
|
||||
float w1 = 1.0 / (1.0 + roughness);
|
||||
float w2 = 1.0 / (1.0 + roughness * 0.5);
|
||||
float totalW = w0;
|
||||
|
||||
fragColor.rgb = center * w0;
|
||||
|
||||
vec2 offsets[4];
|
||||
offsets[0] = dirInv * blurRadius * 2.5;
|
||||
offsets[1] = dirInv * blurRadius * 1.5;
|
||||
offsets[2] = -dirInv * blurRadius * 1.5;
|
||||
offsets[3] = -dirInv * blurRadius * 2.5;
|
||||
float weights[4];
|
||||
weights[0] = w2;
|
||||
weights[1] = w1;
|
||||
weights[2] = w1;
|
||||
weights[3] = w2;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec2 sampleTC = texCoord + offsets[i];
|
||||
float sampleDepth = textureLod(gbufferD, sampleTC, 0.0).r;
|
||||
float depthWeight = exp(-abs(centerDepth - sampleDepth) * 100.0);
|
||||
float w = weights[i] * depthWeight;
|
||||
fragColor.rgb += textureLod(tex, sampleTC, 0.0).rgb * w;
|
||||
totalW += w;
|
||||
}
|
||||
|
||||
fragColor.rgb /= vec3(totalW);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#endif
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
#ifdef _CDepth
|
||||
uniform sampler2D gbufferD;
|
||||
#endif
|
||||
@ -67,6 +68,8 @@ uniform vec3 PPComp14;
|
||||
uniform vec4 PPComp15;
|
||||
uniform vec4 PPComp16;
|
||||
uniform vec4 PPComp18;
|
||||
uniform vec4 PPComp19;
|
||||
uniform vec4 PPComp20;
|
||||
#endif
|
||||
|
||||
// #ifdef _CPos
|
||||
@ -230,6 +233,45 @@ vec3 lensflare(vec2 uv, vec2 pos) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _CDistort
|
||||
float distortHash(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float distortValueNoise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
|
||||
float a = distortHash(i);
|
||||
float b = distortHash(i + vec2(1.0, 0.0));
|
||||
float c = distortHash(i + vec2(0.0, 1.0));
|
||||
float d = distortHash(i + vec2(1.0, 1.0));
|
||||
|
||||
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
||||
}
|
||||
|
||||
vec2 distortSmoothNoise(vec2 p) {
|
||||
return vec2(
|
||||
distortValueNoise(p),
|
||||
distortValueNoise(p + vec2(5.2, 1.3))
|
||||
);
|
||||
}
|
||||
|
||||
vec2 distortUV(vec2 uv, vec2 nUV, float t, float strength) {
|
||||
float intensity = 0.01 * strength;
|
||||
float scale = 4.0;
|
||||
float speed = 0.25;
|
||||
|
||||
nUV.x += t * speed;
|
||||
nUV.y += t * speed;
|
||||
vec2 noise = distortSmoothNoise(nUV * scale);
|
||||
|
||||
uv += (-1.0 + noise * 2.0) * intensity;
|
||||
return uv;
|
||||
}
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
vec2 texCo = texCoord;
|
||||
#ifdef _DynRes
|
||||
@ -252,22 +294,25 @@ void main() {
|
||||
|
||||
#ifdef _CFishEye
|
||||
#ifdef _CPostprocess
|
||||
const float fishEyeStrength = -(PPComp2.y);
|
||||
float fishEyeStrength = PPComp2.y;
|
||||
#else
|
||||
const float fishEyeStrength = -0.01;
|
||||
float fishEyeStrength = compoFisheyeStrength;
|
||||
#endif
|
||||
const vec2 m = vec2(0.5, 0.5);
|
||||
vec2 d = texCo - m;
|
||||
float r = sqrt(dot(d, d));
|
||||
float power = (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
|
||||
float bind;
|
||||
if (power > 0.0) { bind = sqrt(dot(m, m)); }
|
||||
else { bind = m.x; }
|
||||
if (power > 0.0) {
|
||||
texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power);
|
||||
}
|
||||
else {
|
||||
texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
|
||||
|
||||
if (abs(fishEyeStrength) > 0.0001) {
|
||||
const vec2 m = vec2(0.5, 0.5);
|
||||
vec2 d = texCo - m;
|
||||
float r = sqrt(dot(d, d));
|
||||
float power = - (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
|
||||
float bind;
|
||||
if (power > 0.0) { bind = sqrt(dot(m, m)); }
|
||||
else { bind = m.x; }
|
||||
if (power > 0.0) {
|
||||
texCo = m + normalize(d) * tan(r * power) * bind / tan(bind * power);
|
||||
}
|
||||
else {
|
||||
texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -277,9 +322,28 @@ void main() {
|
||||
#else
|
||||
float strengthDistort = compoDistortStrength;
|
||||
#endif
|
||||
float uX = time * strengthDistort;
|
||||
texCo.y = texCo.y + (sin(texCo.x*4.0+uX*2.0)*0.01);
|
||||
texCo.x = texCo.x + (cos(texCo.y*4.0+uX*2.0)*0.01);
|
||||
|
||||
vec2 nUV = texCo;
|
||||
|
||||
texCo = distortUV(texCo, nUV, time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.1, nUV.y + 0.1), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.2, nUV.y + 0.2), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.3, nUV.y + 0.3), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.4, nUV.y + 0.4), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.5, nUV.y + 0.5), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.6, nUV.y + 0.6), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.7, nUV.y + 0.7), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.8, nUV.y + 0.8), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.9, nUV.y + 0.9), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.15, nUV.y + 0.15), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.25, nUV.y + 0.25), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.35, nUV.y + 0.35), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.45, nUV.y + 0.45), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.55, nUV.y + 0.55), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.65, nUV.y + 0.65), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.75, nUV.y + 0.75), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.85, nUV.y + 0.85), time, strengthDistort);
|
||||
texCo = distortUV(texCo, vec2(nUV.x + 0.95, nUV.y + 0.95), time, strengthDistort);
|
||||
#endif
|
||||
|
||||
#ifdef _CDepth
|
||||
@ -343,6 +407,7 @@ void main() {
|
||||
float compoDistance = PPComp3.x;
|
||||
float compoLength = PPComp3.y;
|
||||
float compoStop = PPComp3.z;
|
||||
vec2 focus = vec2(PPComp19.x, PPComp19.y);
|
||||
|
||||
if (PPComp2.z == 1){
|
||||
compoAutoFocus = true;
|
||||
@ -350,9 +415,9 @@ void main() {
|
||||
compoAutoFocus = false;
|
||||
}
|
||||
|
||||
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop);
|
||||
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop, focus, PPComp19.z);
|
||||
#else
|
||||
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop);
|
||||
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop, vec2(0.5, 0.5), 1.0);
|
||||
#endif
|
||||
#else
|
||||
fragColor = textureLod(tex, texCo, 0.0);
|
||||
@ -383,7 +448,9 @@ void main() {
|
||||
vec3 colavg = (col1 + col2 + col3 + col4) * 0.25;
|
||||
|
||||
float edgeMagnitude = length(fragColor.rgb - colavg);
|
||||
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0));
|
||||
float luma = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
float sharpenMask = 1.0 - smoothstep(0.5, 0.8, luma);
|
||||
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0) * sharpenMask);
|
||||
#endif
|
||||
|
||||
#ifdef _CFog
|
||||
@ -433,14 +500,15 @@ void main() {
|
||||
|
||||
#ifdef _CExposure
|
||||
#ifdef _CPostprocess
|
||||
fragColor.rgb+=fragColor.rgb*PPComp8.x;
|
||||
fragColor.rgb *= pow(2.0, PPComp8.x);
|
||||
#else
|
||||
fragColor.rgb+= fragColor.rgb*compoExposureStrength;
|
||||
fragColor.rgb *= pow(2.0, compoExposureStrength);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _CPostprocess
|
||||
fragColor.rgb *= ComputeEV(0.0);
|
||||
fragColor.rgb *= pow(2.0, PPComp20.x); // exposure and gamma
|
||||
#endif
|
||||
|
||||
#ifdef _AutoExposure
|
||||
@ -516,14 +584,16 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
|
||||
fragColor.rgb = tonemapAgXFull(fragColor.rgb);
|
||||
} //else { fragColor.rgb = vec3(0,1,0); //ERROR}
|
||||
#endif
|
||||
|
||||
#ifdef _CGamma
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / PPComp20.y));
|
||||
#endif
|
||||
#else
|
||||
#ifdef _CToneFilmic
|
||||
fragColor.rgb = tonemapFilmic(fragColor.rgb); // With gamma
|
||||
#endif
|
||||
#ifdef _CToneFilmic2
|
||||
fragColor.rgb = acesFilm(fragColor.rgb);
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 1.0));
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));
|
||||
#endif
|
||||
#ifdef _CToneReinhard
|
||||
fragColor.rgb = tonemapReinhard(fragColor.rgb);
|
||||
@ -549,6 +619,10 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _CGamma
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / compoGammaStrength));
|
||||
#endif
|
||||
|
||||
#ifdef _CBW
|
||||
// fragColor.rgb = vec3(clamp(dot(fragColor.rgb, fragColor.rgb), 0.0, 1.0));
|
||||
fragColor.rgb = vec3((fragColor.r * 0.3 + fragColor.g * 0.59 + fragColor.b * 0.11) / 3.0) * 2.5;
|
||||
|
||||
@ -245,6 +245,11 @@
|
||||
"name": "PPComp18",
|
||||
"link": "_PPComp18",
|
||||
"ifdef": ["_CPostprocess"]
|
||||
},
|
||||
{
|
||||
"name": "PPComp19",
|
||||
"link": "_PPComp19",
|
||||
"ifdef": ["_CPostprocess"]
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
|
||||
@ -17,10 +17,12 @@ in vec3 wnormal;
|
||||
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
|
||||
| GBUF_IDX_1 | || base color (RGB) | occlusion/specular |
|
||||
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
|
||||
| GBUF_IDX_2 | _gbuffer2 || velocity (XY) | ignore radiance | unused |
|
||||
| GBUF_IDX_2 | _gbuffer2 || velocity (XY) | ignore radiance | tangent angle |
|
||||
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
|
||||
| GBUF_IDX_EMISSION | _EmissionShaded || emission color (RGB) | unused |
|
||||
+-------------------+-----------------++--------------+--------------+-----------------+--------------------+
|
||||
| GBUF_IDX_REFRACTION | _SSRefraction || packed IOR | transmittance | surfaceDepth | unused |
|
||||
| | _VoxelRefract || (0-1 range) | | | |
|
||||
|
||||
The indices as well as the GBUF_SIZE define are defined in "compiled.inc".
|
||||
*/
|
||||
@ -52,6 +54,10 @@ void main() {
|
||||
#endif
|
||||
|
||||
#ifdef _SSRefraction
|
||||
fragColor[GBUF_IDX_REFRACTION] = vec4(ior, opacity, 0.0, 0.0);
|
||||
fragColor[GBUF_IDX_REFRACTION] = vec4(packIOR(ior), opacity, 0.0, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _Anisotropy
|
||||
fragColor[GBUF_IDX_2].a = -1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -7,9 +7,13 @@ out vec4 fragColor[GBUF_SIZE];
|
||||
|
||||
void main() {
|
||||
fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0);
|
||||
#if GBUF_SIZE > 1
|
||||
fragColor[GBUF_IDX_1] = vec4(color, 1.0);
|
||||
#else
|
||||
fragColor[GBUF_IDX_0] = vec4(color, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
fragColor[GBUF_IDX_EMISSION] = vec4(0.0);
|
||||
fragColor[GBUF_IDX_EMISSION] = vec4(color, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -8,12 +8,10 @@
|
||||
#ifdef _Irr
|
||||
#include "std/shirr.glsl"
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
#include "std/sss.glsl"
|
||||
#endif
|
||||
#ifdef _SSRS
|
||||
#include "std/ssrs.glsl"
|
||||
#endif
|
||||
#include "std/brdf.glsl"
|
||||
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
@ -25,6 +23,9 @@ uniform sampler2D gbuffer1;
|
||||
#ifdef _EmissionShaded
|
||||
uniform sampler2D gbufferEmission;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
uniform sampler2D gbufferCoatNormal;
|
||||
#endif
|
||||
|
||||
#ifdef _VoxelGI
|
||||
uniform sampler2D voxels_diffuse;
|
||||
@ -91,7 +92,7 @@ uniform mat4 invVP;
|
||||
#ifdef _SinglePoint
|
||||
//!uniform sampler2DShadow shadowMapSpot[1];
|
||||
//!uniform sampler2D shadowMapSpotTransparent[1];
|
||||
//!uniform mat4 LWVPSpot[1];
|
||||
//!uniform mat4 LWVPSpotArray[1];
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
//!uniform sampler2DShadow shadowMapSpot[4];
|
||||
@ -136,7 +137,7 @@ uniform vec2 cameraPlane;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapSpotTransparent[1];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpot[1];
|
||||
//!uniform mat4 LWVPSpotArray[1];
|
||||
#else
|
||||
//!uniform samplerCubeShadow shadowMapPoint[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
@ -199,6 +200,7 @@ uniform vec3 sunCol;
|
||||
uniform sampler2D shadowMapAtlasSunTransparent;
|
||||
#endif
|
||||
#endif
|
||||
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
|
||||
#else
|
||||
uniform sampler2DShadow shadowMap;
|
||||
#ifdef _ShadowMapTransparent
|
||||
@ -235,6 +237,9 @@ uniform float time;
|
||||
#endif
|
||||
|
||||
#include "std/light.glsl"
|
||||
#ifdef _SSS
|
||||
#include "std/sss.glsl"
|
||||
#endif
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec3 viewRay;
|
||||
@ -254,12 +259,40 @@ void main() {
|
||||
float metallic;
|
||||
uint matid;
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
#ifdef _ExtBRDF
|
||||
matid = min(matid, uint(MAX_MATERIALS - 1));
|
||||
|
||||
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
|
||||
vec4 matp0 = vec4(0.0), matp1 = vec4(0.0), matp2 = vec4(0.0), matp3 = vec4(0.0);
|
||||
vec4 matp4 = vec4(0.0), matp5 = vec4(0.0), matp6 = vec4(0.0), matp7 = vec4(0.0);
|
||||
// TODO: coatIOR=1.5, ior=1.45, thinWall=1.0 move to python make files
|
||||
matp1.z = 1.5;
|
||||
matp3.x = 1.45;
|
||||
matp3.y = 1.0;
|
||||
if (matid >= 3u) {
|
||||
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
|
||||
}
|
||||
#ifdef _ClearCoat
|
||||
vec3 coatTintCol = vec3(matp1.w, matp2.x, matp2.y);
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
vec3 sheenTintCol = vec3(matp5.z, matp5.w, matp6.x);
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
vec3 sssColorVal = vec3(matp4.w, matp5.x, matp5.y);
|
||||
vec3 sssRadiusBase = vec3(matp4.x, matp4.y, matp4.z);
|
||||
float sssRadiusScalar = max(max(matp4.x, matp4.y), matp4.z) * matp7.x;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
vec2 occspec = unpackFloat2(g1.a);
|
||||
// re-investigate clamp basecolor to prevent extreme values causing glitches
|
||||
vec3 basecolor = min(g1.rgb, vec3(2.0));
|
||||
vec3 albedo = surfaceAlbedo(basecolor, metallic);
|
||||
vec3 f0 = surfaceF0(basecolor, metallic);
|
||||
#ifdef _ExtBRDF
|
||||
f0 = mix(f0, basecolor, vec3(matp6.y, matp6.z, matp6.w));
|
||||
#endif
|
||||
|
||||
#ifdef _VRStereo
|
||||
bool isLeftEye = texCoord.x < 0.5;
|
||||
@ -279,10 +312,26 @@ void main() {
|
||||
#endif
|
||||
float dotNV = max(dot(n, v), 0.0);
|
||||
|
||||
#ifdef _ClearCoat
|
||||
vec4 gCoat = textureLod(gbufferCoatNormal, texCoord, 0.0);
|
||||
vec3 nCoat;
|
||||
nCoat.z = 1.0 - abs(gCoat.x) - abs(gCoat.y);
|
||||
nCoat.xy = nCoat.z >= 0.0 ? gCoat.xy : octahedronWrap(gCoat.xy);
|
||||
nCoat = normalize(nCoat);
|
||||
#endif
|
||||
|
||||
#ifdef _gbuffer2
|
||||
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _Anisotropy
|
||||
#ifdef _gbuffer2
|
||||
vec3 wTangent = decodeTangent(g2.a, n);
|
||||
#else
|
||||
vec3 wTangent = vec3(0.0);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _MicroShadowing
|
||||
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
|
||||
@ -295,6 +344,44 @@ void main() {
|
||||
vec3 F = f0;
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
float iblSheenWeight = 1.0;
|
||||
float iblCoatWeight = 1.0;
|
||||
float iblLayerWeight = 1.0;
|
||||
vec3 coatTintAbsorb = vec3(1.0);
|
||||
|
||||
#ifdef _Sheen
|
||||
float sheenAlb = sheenIBLAlbedo(matp0.z, matp0.w, dotNV);
|
||||
iblSheenWeight = max(1.0 - sheenAlb *
|
||||
max(max(sheenTintCol.r, sheenTintCol.g), sheenTintCol.b), 0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
float dotNVCoat = max(dot(nCoat, v), 0.0);
|
||||
float coatF = coatIBLFresnel(matp1.x, matp1.z, dotNVCoat);
|
||||
iblCoatWeight = max(1.0 - coatF, 0.0);
|
||||
coatTintAbsorb = mix(vec3(1.0), clamp(coatTintCol, 0.0, 1.0),
|
||||
clamp(1.0 / max(dotNVCoat, 0.3) * 0.2, 0.0, 1.0));
|
||||
#endif
|
||||
|
||||
iblLayerWeight = iblSheenWeight * iblCoatWeight;
|
||||
|
||||
brdf_sheenWeight = iblSheenWeight;
|
||||
brdf_coatWeight = iblCoatWeight;
|
||||
brdf_coatTintAbsorb = coatTintAbsorb;
|
||||
#ifdef _Sheen
|
||||
brdf_sheenAlbedo = sheenAlb;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
brdf_coatF0 = (matp1.z - 1.0) / (matp1.z + 1.0);
|
||||
brdf_coatF0 = brdf_coatF0 * brdf_coatF0;
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
brdf_transmissionF0 = (matp3.x - 1.0) / (matp3.x + 1.0);
|
||||
brdf_transmissionF0 = brdf_transmissionF0 * brdf_transmissionF0;
|
||||
#endif
|
||||
#endif // _ExtBRDF
|
||||
|
||||
#ifndef _VoxelAOvar
|
||||
#ifndef _VoxelGI
|
||||
// Envmap
|
||||
@ -302,9 +389,7 @@ void main() {
|
||||
vec3 envl = shIrradiance(n, shirr);
|
||||
|
||||
#ifdef _gbuffer2
|
||||
if (g2.b < 0.5) {
|
||||
envl = envl;
|
||||
} else {
|
||||
if (g2.b >= 0.5) {
|
||||
envl = vec3(0.0);
|
||||
}
|
||||
#endif
|
||||
@ -317,20 +402,25 @@ void main() {
|
||||
#endif
|
||||
|
||||
#ifdef _Rad
|
||||
#ifdef _Anisotropy
|
||||
vec3 reflectionWorld = anisotropicIBLDirection(n, v, wTangent,
|
||||
matp0.x, roughness);
|
||||
#else
|
||||
vec3 reflectionWorld = reflect(-v, n);
|
||||
#endif
|
||||
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
|
||||
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
|
||||
prefilteredColor = min(prefilteredColor, vec3(20.0));
|
||||
#endif
|
||||
|
||||
#ifdef _EnvLDR
|
||||
envl.rgb = pow(envl.rgb, vec3(2.2));
|
||||
envl.rgb = srgbToLinear(envl.rgb);
|
||||
#ifdef _Rad
|
||||
prefilteredColor = pow(prefilteredColor, vec3(2.2));
|
||||
prefilteredColor = srgbToLinear(prefilteredColor);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= albedo;
|
||||
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
|
||||
|
||||
#ifdef _Brdf
|
||||
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
|
||||
@ -344,6 +434,68 @@ void main() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
envl.rgb *= iblLayerWeight;
|
||||
|
||||
|
||||
#ifdef _Transmission
|
||||
float transF = transmissionIBLFresnel(matp3.x, dotNV);
|
||||
float transmittance = 1.0 - transF;
|
||||
#ifdef _Rad
|
||||
if (matp2.z > 0.0 && transmittance > 0.0) {
|
||||
vec3 refrDir;
|
||||
if (matp3.y > 0.5) {
|
||||
refrDir = reflect(-v, n);
|
||||
} else {
|
||||
refrDir = transmissionIBLDirection(n, v, matp3.x);
|
||||
}
|
||||
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
|
||||
vec3 transColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(refrDir), transLod).rgb;
|
||||
transColor = min(transColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
transColor = srgbToLinear(transColor);
|
||||
#endif
|
||||
envl.rgb += albedo * matp2.z * transmittance * transColor * dotNV
|
||||
* iblLayerWeight;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
envl.rgb *= coatTintAbsorb;
|
||||
#ifdef _Rad
|
||||
if (coatF > 0.0) {
|
||||
float coatLod = getMipFromRoughness(matp1.y, envmapNumMipmaps);
|
||||
vec3 coatRefl = reflect(-v, nCoat);
|
||||
vec3 coatColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(coatRefl), coatLod).rgb;
|
||||
coatColor = min(coatColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
coatColor = srgbToLinear(coatColor);
|
||||
#endif
|
||||
envl.rgb += coatColor * coatF * iblSheenWeight;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _Sheen
|
||||
#ifdef _Rad
|
||||
if (sheenAlb > 0.0) {
|
||||
float sheenLod = getMipFromRoughness(matp0.w, envmapNumMipmaps);
|
||||
vec3 sheenRefl = reflect(-v, n);
|
||||
vec3 sheenColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(sheenRefl), sheenLod).rgb;
|
||||
sheenColor = min(sheenColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
sheenColor = srgbToLinear(sheenColor);
|
||||
#endif
|
||||
envl.rgb += sheenColor * sheenTintCol * sheenAlb;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // _ExtBRDF
|
||||
|
||||
envl.rgb *= envmapStrength * occspec.x;
|
||||
|
||||
fragColor.rgb = envl;
|
||||
@ -352,11 +504,30 @@ void main() {
|
||||
|
||||
#ifdef _VoxelGI
|
||||
fragColor.rgb = textureLod(voxels_diffuse, texCoord, 0.0).rgb * voxelgiDiff;
|
||||
if(roughness < 1.0 && occspec.y > 0.0)
|
||||
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * occspec.y * voxelgiRefl;
|
||||
if(roughness < 1.0) {
|
||||
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * F * voxelgiRefl * occspec.y;
|
||||
}
|
||||
#ifdef _Rad
|
||||
vec3 iblReflection = reflect(-v, n);
|
||||
float iblLod = getMipFromRoughness(roughness, envmapNumMipmaps);
|
||||
vec3 iblPrefiltered = textureLod(senvmapRadiance, envMapEquirect(iblReflection), iblLod).rgb;
|
||||
iblPrefiltered = min(iblPrefiltered, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
iblPrefiltered = srgbToLinear(iblPrefiltered);
|
||||
#endif
|
||||
#ifdef _ExtBRDF
|
||||
iblPrefiltered *= iblLayerWeight;
|
||||
iblPrefiltered *= coatTintAbsorb;
|
||||
#endif
|
||||
fragColor.rgb += iblPrefiltered * F * envmapStrength * occspec.x;
|
||||
#else
|
||||
#ifdef _EnvCol
|
||||
fragColor.rgb += backgroundCol * F * envmapStrength * occspec.x;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef _VoxelAOvar
|
||||
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb * voxelgiOcc;
|
||||
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -379,7 +550,7 @@ void main() {
|
||||
|
||||
#ifdef _SSGI
|
||||
vec3 ssgiColor = textureLod(ssgitex, texCoord, 0.0).rgb;
|
||||
fragColor.rgb += ssgiColor * albedo;
|
||||
fragColor.rgb += ssgiColor * basecolor;
|
||||
#endif
|
||||
|
||||
#ifdef _EmissionShadeless
|
||||
@ -406,10 +577,42 @@ void main() {
|
||||
float sdotVH = max(0.0, dot(v, sh));
|
||||
float sdotNL = max(0.0, dot(n, sunDir));
|
||||
vec3 svisibility = vec3(1.0);
|
||||
vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
|
||||
#ifdef _Anisotropy
|
||||
vec3 sdirect;
|
||||
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
|
||||
vec3 sbitangent = normalize(cross(n, wTangent));
|
||||
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
anisotropicBRDF(f0, roughness, matp0.x, matp0.y,
|
||||
wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y;
|
||||
} else {
|
||||
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
|
||||
}
|
||||
#else
|
||||
vec3 sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
float sunLayerWeight;
|
||||
sdirect = applyExtBRDFLayers(sdirect, albedo, f0, roughness,
|
||||
sdotNL, dotNV, sdotNH, sdotVH, n, sunDir, v, sh
|
||||
#ifdef _ClearCoat
|
||||
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, matp0.z, matp0.w, sheenTintCol
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, matp2.z, matp2.w, matp3.x, matp3.y
|
||||
#endif
|
||||
, sunLayerWeight);
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSunArray[0];
|
||||
#endif
|
||||
#ifdef _CSM
|
||||
svisibility = shadowTestCascade(
|
||||
#ifdef _ShadowMapAtlas
|
||||
@ -494,23 +697,17 @@ void main() {
|
||||
|
||||
fragColor.rgb += sdirect * sunCol * svisibility;
|
||||
|
||||
// #ifdef _Hair // Aniso
|
||||
// if (matid == 2) {
|
||||
// const float shinyParallel = roughness;
|
||||
// const float shinyPerpendicular = 0.1;
|
||||
// const vec3 v = vec3(0.99146, 0.11664, 0.05832);
|
||||
// vec3 T = abs(dot(n, v)) > 0.99999 ? cross(n, vec3(0.0, 1.0, 0.0)) : cross(n, v);
|
||||
// fragColor.rgb = orenNayarDiffuseBRDF(albedo, roughness, dotNV, dotNL, dotVH) + wardSpecular(n, h, dotNL, dotNV, dotNH, T, shinyParallel, shinyPerpendicular) * spec;
|
||||
// }
|
||||
// #endif
|
||||
|
||||
#ifdef _SSS
|
||||
if (matid == 2) {
|
||||
#ifdef _ExtBRDF
|
||||
if (matid >= 3u && matp3.z > 0.0) {
|
||||
#ifdef _CSM
|
||||
int casi, casindex;
|
||||
mat4 LWVP = getCascadeMat(distance(eye, p), casi, casindex);
|
||||
#endif
|
||||
fragColor.rgb += fragColor.rgb * SSSSTransmittance(
|
||||
vec3 sssColor = sssColorVal;
|
||||
float sssRadius = sssRadiusScalar;
|
||||
float sssStrength = matp3.z;
|
||||
vec3 sssResult = SSSSTransmittance(
|
||||
LWVP, p, n, sunDir, lightPlane.y,
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
@ -521,12 +718,26 @@ void main() {
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
);//TODO implement transparent shadowmaps into the SSSSTransmittance()
|
||||
, sssColor, sssRadius
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _CSM
|
||||
, tileBoundsSunArray[casi]
|
||||
#else
|
||||
, tileBoundsSunArray[0]
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
fragColor.rgb += sunCol * sssStrength * sssResult;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // _Sun
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint
|
||||
|
||||
#ifdef _VRStereo
|
||||
@ -555,12 +766,53 @@ void main() {
|
||||
#ifdef _SSRS
|
||||
, gbufferD, invVP, eye
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, matp0.z, matp0.w, sheenTintCol
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, matp0.x, matp0.y, wTangent
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, matp3.z, sssColorVal, sssRadiusBase * matp7.x, matp3.w
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, matp2.z, matp2.w, matp3.x, matp3.y
|
||||
#endif
|
||||
);
|
||||
|
||||
#ifdef _Spot
|
||||
#ifdef _SSS
|
||||
#ifdef _ShadowMap
|
||||
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance()
|
||||
#ifdef _ExtBRDF
|
||||
if (matid >= 3u && matp3.z > 0.0) {
|
||||
vec3 sssColorSpot = sssColorVal;
|
||||
float sssRadiusSpot = sssRadiusScalar;
|
||||
float sssStrengthSpot = matp3.z;
|
||||
fragColor.rgb += pointCol * sssStrengthSpot * SSSSTransmittance(LWVPSpotArray[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0], sssColorSpot, sssRadiusSpot
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4(0.0, 0.0, 1.0, 1.0)
|
||||
#endif
|
||||
);//TODO implement transparent shadowmaps into the SSSSTransmittance()
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef _Spot
|
||||
#ifdef _SSS
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _ExtBRDF
|
||||
if (matid >= 3u && matp3.z > 0.0) {
|
||||
vec3 sssColorPoint = sssColorVal;
|
||||
float sssRadiusPoint = sssRadiusScalar;
|
||||
float sssStrengthPoint = matp3.z;
|
||||
fragColor.rgb += pointCol * sssStrengthPoint * SSSSTransmittanceCube(shadowMapPoint[0], lightPos, p, n, normalize(lightPos - p), lightPlane.y, lightProj, sssColorPoint, sssRadiusPoint);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@ -618,7 +870,80 @@ void main() {
|
||||
#ifdef _SSRS
|
||||
, gbufferD, invVP, eye
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, matp0.z, matp0.w, sheenTintCol
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, matp0.x, matp0.y, wTangent
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, matp3.z, sssColorVal, sssRadiusBase * matp7.x, matp3.w
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, matp2.z, matp2.w, matp3.x, matp3.y
|
||||
#endif
|
||||
);
|
||||
|
||||
#ifdef _SSS
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _ExtBRDF
|
||||
if (matid >= 3u && matp3.z > 0.0) {
|
||||
vec3 sssColorCL = sssColorVal;
|
||||
float sssRadiusCL = sssRadiusScalar;
|
||||
float sssStrengthCL = matp3.z;
|
||||
vec3 cLightPos = lightsArray[li * 3].xyz;
|
||||
vec3 cLightCol = lightsArray[li * 3 + 1].xyz;
|
||||
vec3 cLightDir = normalize(cLightPos - p);
|
||||
#ifdef _Spot
|
||||
bool isSpotLight = lightsArray[li * 3 + 2].y != 0.0;
|
||||
if (isSpotLight) {
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlasSpot, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
|
||||
#else
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlas, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
|
||||
#endif
|
||||
#else
|
||||
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL);
|
||||
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL);
|
||||
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL);
|
||||
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlasPoint, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
|
||||
#else
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlas, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
#else
|
||||
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[0], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[1], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[2], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[3], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlasPoint, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
|
||||
#else
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCubeAtlas(shadowMapAtlas, cLightPos, p, n, cLightDir, lightPlane.y, lightProj, li, sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
#else
|
||||
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[0], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[1], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[2], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittanceCube(shadowMapPoint[3], cLightPos, p, n, cLightDir, lightPlane.y, lightProj, sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif // _Clusters
|
||||
|
||||
|
||||
@ -138,6 +138,16 @@
|
||||
"link": "_cascadeData",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
|
||||
},
|
||||
{
|
||||
"name": "tileBoundsSunArray",
|
||||
"link": "_tileBoundsSunArray",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_ShadowMapAtlas"]
|
||||
},
|
||||
{
|
||||
"name": "tileBoundsSpotArray",
|
||||
"link": "_tileBoundsSpotArray",
|
||||
"ifdef": ["_Clusters", "_Spot", "_ShadowMap", "_ShadowMapAtlas"]
|
||||
},
|
||||
{
|
||||
"name": "lightPlane",
|
||||
"link": "_lightPlane",
|
||||
@ -277,8 +287,15 @@
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot3",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "materialParams",
|
||||
"link": "_materialParams",
|
||||
"type": "floats",
|
||||
"ifdef": ["_ExtBRDF"]
|
||||
}
|
||||
],
|
||||
"texture_units": [],
|
||||
"vertex_shader": "../include/pass_viewray.vert.glsl",
|
||||
"fragment_shader": "deferred_light.frag.glsl",
|
||||
"color_attachments": ["RGBA64"]
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "compiled.inc"
|
||||
#include "std/gbuffer.glsl"
|
||||
#include "std/math.glsl"
|
||||
#include "std/brdf.glsl"
|
||||
#ifdef _Clusters
|
||||
#include "std/clusters.glsl"
|
||||
#endif
|
||||
@ -13,6 +14,12 @@
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbuffer1;
|
||||
#ifdef _gbuffer2
|
||||
uniform sampler2D gbuffer2;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
uniform sampler2D gbufferCoatNormal;
|
||||
#endif
|
||||
|
||||
uniform float envmapStrength;
|
||||
#ifdef _Irr
|
||||
@ -49,9 +56,15 @@ uniform vec2 cameraPlane;
|
||||
#ifdef _SinglePoint
|
||||
#ifdef _Spot
|
||||
//!uniform sampler2DShadow shadowMapSpot[1];
|
||||
//!uniform mat4 LWVPSpot[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapSpotTransparent[1];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[1];
|
||||
#else
|
||||
//!uniform samplerCubeShadow shadowMapPoint[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform samplerCube shadowMapPointTransparent[1];
|
||||
#endif
|
||||
//!uniform vec2 lightProj;
|
||||
#endif
|
||||
#endif
|
||||
@ -59,26 +72,41 @@ uniform vec2 cameraPlane;
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlas;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlasPoint;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasPointTransparent;
|
||||
#endif
|
||||
//!uniform vec4 pointLightDataArray[4];
|
||||
#endif
|
||||
//!uniform vec4 pointLightDataArray[maxLightsCluster * 6];
|
||||
#else
|
||||
//!uniform samplerCubeShadow shadowMapPoint[4];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform samplerCube shadowMapPointTransparent[4];
|
||||
#endif
|
||||
#endif
|
||||
//!uniform vec2 lightProj;
|
||||
#ifdef _Spot
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlasSpot;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasSpotTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
//!uniform sampler2DShadow shadowMapSpot[4];
|
||||
//!uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[4];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@ -90,9 +118,16 @@ uniform vec3 sunCol;
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSun;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasSunTransparent;
|
||||
#endif
|
||||
#endif
|
||||
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
|
||||
#else
|
||||
uniform sampler2DShadow shadowMap;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapTransparent;
|
||||
#endif
|
||||
#endif
|
||||
uniform float shadowsBias;
|
||||
#ifdef _CSM
|
||||
@ -132,17 +167,62 @@ void main() {
|
||||
float metallic;
|
||||
uint matid;
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
#ifdef _ExtBRDF
|
||||
matid = min(matid, uint(MAX_MATERIALS - 1));
|
||||
|
||||
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
|
||||
vec4 matp0 = vec4(0.0), matp1 = vec4(0.0), matp2 = vec4(0.0), matp3 = vec4(0.0);
|
||||
vec4 matp4 = vec4(0.0), matp5 = vec4(0.0), matp6 = vec4(0.0), matp7 = vec4(0.0);
|
||||
// TODO: coatIOR=1.5, ior=1.45, thinWall=1.0 move to python make files
|
||||
matp1.z = 1.5;
|
||||
matp3.x = 1.45;
|
||||
matp3.y = 1.0;
|
||||
if (matid >= 3u) {
|
||||
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
|
||||
}
|
||||
#ifdef _ClearCoat
|
||||
vec3 coatTintCol = vec3(matp1.w, matp2.x, matp2.y);
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
vec3 sheenTintCol = vec3(matp5.z, matp5.w, matp6.x);
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
vec3 sssColorVal = vec3(matp4.w, matp5.x, matp5.y);
|
||||
vec3 sssRadiusScaled = vec3(matp4.x, matp4.y, matp4.z) * matp7.x;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
|
||||
vec2 occspec = unpackFloat2(g1.a);
|
||||
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor
|
||||
vec3 f0 = surfaceF0(g1.rgb, metallic);
|
||||
vec3 basecolor = min(g1.rgb, vec3(2.0));
|
||||
vec3 albedo = surfaceAlbedo(basecolor, metallic);
|
||||
vec3 f0 = surfaceF0(basecolor, metallic);
|
||||
#ifdef _ExtBRDF
|
||||
f0 = mix(f0, basecolor, vec3(matp6.y, matp6.z, matp6.w));
|
||||
#endif
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
|
||||
vec3 v = normalize(eye - p);
|
||||
float dotNV = max(dot(n, v), 0.0);
|
||||
|
||||
#ifdef _ClearCoat
|
||||
vec4 gCoat = textureLod(gbufferCoatNormal, texCoord, 0.0);
|
||||
vec3 nCoat;
|
||||
nCoat.z = 1.0 - abs(gCoat.x) - abs(gCoat.y);
|
||||
nCoat.xy = nCoat.z >= 0.0 ? gCoat.xy : octahedronWrap(gCoat.xy);
|
||||
nCoat = normalize(nCoat);
|
||||
#endif
|
||||
|
||||
#ifdef _Anisotropy
|
||||
#ifdef _gbuffer2
|
||||
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
|
||||
vec3 wTangent = decodeTangent(g2.a, n);
|
||||
#else
|
||||
vec3 wTangent = vec3(0.0);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _Brdf
|
||||
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
|
||||
#endif
|
||||
@ -154,32 +234,144 @@ void main() {
|
||||
envl /= PI;
|
||||
#endif
|
||||
#else
|
||||
vec3 envl = vec3(1.0);
|
||||
vec3 envl = vec3(0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _Rad
|
||||
#ifdef _Anisotropy
|
||||
vec3 reflectionWorld = anisotropicIBLDirection(n, v, wTangent,
|
||||
matp0.x, roughness);
|
||||
#else
|
||||
vec3 reflectionWorld = reflect(-v, n);
|
||||
#endif
|
||||
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
|
||||
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef _EnvLDR
|
||||
envl.rgb = pow(envl.rgb, vec3(2.2));
|
||||
envl.rgb = srgbToLinear(envl.rgb);
|
||||
#ifdef _Rad
|
||||
prefilteredColor = pow(prefilteredColor, vec3(2.2));
|
||||
prefilteredColor = srgbToLinear(prefilteredColor);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= albedo;
|
||||
#ifdef _Brdf
|
||||
vec3 F = f0 * envBRDF.x + envBRDF.y;
|
||||
#else
|
||||
vec3 F = f0;
|
||||
#endif
|
||||
|
||||
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
|
||||
|
||||
#ifdef _Brdf
|
||||
envl.rgb *= 1.0 - F;
|
||||
#endif
|
||||
|
||||
#ifdef _Rad // Indirect specular
|
||||
envl.rgb += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5 * occspec.y;
|
||||
envl.rgb += prefilteredColor * F;
|
||||
#else
|
||||
#ifdef _EnvCol
|
||||
envl.rgb += backgroundCol * surfaceF0(g1.rgb, metallic); // f0
|
||||
envl.rgb += backgroundCol * F;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
float iblSheenWeight = 1.0;
|
||||
float iblCoatWeight = 1.0;
|
||||
vec3 coatTintAbsorb = vec3(1.0);
|
||||
|
||||
#ifdef _Sheen
|
||||
float sheenAlb = sheenIBLAlbedo(matp0.z, matp0.w, dotNV);
|
||||
iblSheenWeight = max(1.0 - sheenAlb *
|
||||
max(max(sheenTintCol.r, sheenTintCol.g), sheenTintCol.b), 0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
float dotNVCoat = max(dot(nCoat, v), 0.0);
|
||||
float coatF = coatIBLFresnel(matp1.x, matp1.z, dotNVCoat);
|
||||
iblCoatWeight = max(1.0 - coatF, 0.0);
|
||||
if (matp1.x > 0.0) {
|
||||
coatTintAbsorb = mix(vec3(1.0), clamp(coatTintCol, 0.0, 1.0),
|
||||
clamp(1.0 / max(dotNVCoat, 0.3) * 0.2, 0.0, 1.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
float iblLayerWeight = iblSheenWeight * iblCoatWeight;
|
||||
envl.rgb *= iblLayerWeight;
|
||||
|
||||
brdf_sheenWeight = iblSheenWeight;
|
||||
brdf_coatWeight = iblCoatWeight;
|
||||
brdf_coatTintAbsorb = coatTintAbsorb;
|
||||
#ifdef _Sheen
|
||||
brdf_sheenAlbedo = sheenAlb;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
brdf_coatF0 = (matp1.z - 1.0) / (matp1.z + 1.0);
|
||||
brdf_coatF0 = brdf_coatF0 * brdf_coatF0;
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
brdf_transmissionF0 = (matp3.x - 1.0) / (matp3.x + 1.0);
|
||||
brdf_transmissionF0 = brdf_transmissionF0 * brdf_transmissionF0;
|
||||
#endif
|
||||
|
||||
#ifdef _Transmission
|
||||
float transF = transmissionIBLFresnel(matp3.x, dotNV);
|
||||
float transmittance = 1.0 - transF;
|
||||
#ifdef _Rad
|
||||
if (matp2.z > 0.0 && transmittance > 0.0) {
|
||||
vec3 refrDir;
|
||||
if (matp3.y > 0.5) {
|
||||
refrDir = reflect(-v, n);
|
||||
} else {
|
||||
refrDir = transmissionIBLDirection(n, v, matp3.x);
|
||||
}
|
||||
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
|
||||
vec3 transColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(refrDir), transLod).rgb;
|
||||
transColor = min(transColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
transColor = srgbToLinear(transColor);
|
||||
#endif
|
||||
envl.rgb += albedo * matp2.z * transmittance * transColor * dotNV
|
||||
* iblLayerWeight;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
envl.rgb *= coatTintAbsorb;
|
||||
#ifdef _Rad
|
||||
if (coatF > 0.0) {
|
||||
float coatLod = getMipFromRoughness(matp1.y, envmapNumMipmaps);
|
||||
vec3 coatRefl = reflect(-v, nCoat);
|
||||
vec3 coatColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(coatRefl), coatLod).rgb;
|
||||
coatColor = min(coatColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
coatColor = srgbToLinear(coatColor);
|
||||
#endif
|
||||
envl.rgb += coatColor * coatF * iblSheenWeight;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _Sheen
|
||||
#ifdef _Rad
|
||||
if (sheenAlb > 0.0) {
|
||||
float sheenLod = getMipFromRoughness(matp0.w, envmapNumMipmaps);
|
||||
vec3 sheenRefl = reflect(-v, n);
|
||||
vec3 sheenColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(sheenRefl), sheenLod).rgb;
|
||||
sheenColor = min(sheenColor, vec3(20.0));
|
||||
#ifdef _EnvLDR
|
||||
sheenColor = srgbToLinear(sheenColor);
|
||||
#endif
|
||||
envl.rgb += sheenColor * sheenTintCol * sheenAlb;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // _ExtBRDF
|
||||
|
||||
envl.rgb *= envmapStrength * occspec.x;
|
||||
fragColor.rgb = envl;
|
||||
|
||||
@ -188,37 +380,107 @@ void main() {
|
||||
float sdotNH = max(0.0, dot(n, sh));
|
||||
float sdotVH = max(0.0, dot(v, sh));
|
||||
float sdotNL = max(0.0, dot(n, sunDir));
|
||||
float svisibility = 1.0;
|
||||
vec3 sdirect = lambertDiffuseBRDF(albedo, sdotNL) +
|
||||
vec3 svisibility = vec3(1.0);
|
||||
#ifdef _Anisotropy
|
||||
vec3 sdirect;
|
||||
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
|
||||
vec3 sbitangent = normalize(cross(n, wTangent));
|
||||
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
anisotropicBRDF(f0, roughness, matp0.x, matp0.y,
|
||||
wTangent, sbitangent, n, sunDir, v, sdotNL, dotNV) * occspec.y;
|
||||
} else {
|
||||
sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
|
||||
}
|
||||
#else
|
||||
vec3 sdirect = diffuseBRDF(albedo, roughness, f0, sdotNL, dotNV, sdotVH) +
|
||||
specularBRDF(f0, roughness, sdotNL, sdotNH, dotNV, sdotVH) * occspec.y;
|
||||
#endif
|
||||
|
||||
float sunSheenWeight = brdf_sheenWeight;
|
||||
float sunCoatWeight = brdf_coatWeight;
|
||||
|
||||
#ifdef _Sheen
|
||||
vec3 sunSheen = sheenBRDF(matp0.z, matp0.w, sheenTintCol, sdotNL, sdotNH, dotNV);
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
vec3 sunCoat = clearcoatBRDF(matp1.x, matp1.y, matp1.z, nCoat, sunDir, v, sh);
|
||||
#endif
|
||||
|
||||
float sunLayerWeight = sunSheenWeight * sunCoatWeight;
|
||||
sdirect *= sunLayerWeight;
|
||||
#ifdef _Transmission
|
||||
sdirect += transmissionBRDF(albedo, matp2.z, matp2.w, matp3.x, matp3.y, sdotNL, dotNV, sdotVH) * sunLayerWeight;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
sdirect *= brdf_coatTintAbsorb;
|
||||
sdirect += sunCoat * sunSheenWeight;
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
sdirect += sunSheen;
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSunArray[0];
|
||||
#endif
|
||||
#ifdef _CSM
|
||||
svisibility = shadowTestCascade(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
shadowMap
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 2, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 10, shadowsBias
|
||||
);
|
||||
#else
|
||||
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
|
||||
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 2, 1.0);
|
||||
if (lPos.w > 0.0) svisibility = shadowTest(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
shadowMap
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
@ -226,15 +488,37 @@ void main() {
|
||||
fragColor.rgb += sdirect * svisibility * sunCol;
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint
|
||||
fragColor.rgb += sampleLight(
|
||||
p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0
|
||||
#ifdef _ShadowMap
|
||||
, 0, pointBias, true
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test!
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, matp0.z, matp0.w, sheenTintCol
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, matp0.x, matp0.y, wTangent
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, matp3.z, sssColorVal, sssRadiusScaled, matp3.w
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, matp2.z, matp2.w, matp3.x, matp3.y
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
|
||||
@ -268,16 +552,41 @@ void main() {
|
||||
#ifdef _ShadowMap
|
||||
// light index, shadow bias, cast_shadows
|
||||
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, lightsArray[li * 3 + 2].y != 0.0
|
||||
, lightsArray[li * 3 + 2].y // spot size (cutoff)
|
||||
, lightsArraySpot[li].w // spot blend (exponent)
|
||||
, lightsArraySpot[li].xyz // spotDir
|
||||
, lightsArraySpot[li * 2].w // spot blend (exponent)
|
||||
, lightsArraySpot[li * 2].xyz // spotDir
|
||||
, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale
|
||||
, lightsArraySpot[li * 2 + 1].xyz // right
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, matp1.x, matp1.y, matp1.z, coatTintCol, nCoat
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, matp0.z, matp0.w, sheenTintCol
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, matp0.x, matp0.y, wTangent
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, matp3.z, sssColorVal, sssRadiusScaled, matp3.w
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, matp2.z, matp2.w, matp3.x, matp3.y
|
||||
#endif
|
||||
);
|
||||
}
|
||||
#endif // _Clusters
|
||||
|
||||
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
|
||||
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
}
|
||||
|
||||
fragColor.a = 1.0; // Mark as opaque
|
||||
}
|
||||
|
||||
@ -97,6 +97,16 @@
|
||||
"link": "_cascadeData",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
|
||||
},
|
||||
{
|
||||
"name": "tileBoundsSunArray",
|
||||
"link": "_tileBoundsSunArray",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_ShadowMapAtlas"]
|
||||
},
|
||||
{
|
||||
"name": "tileBoundsSpotArray",
|
||||
"link": "_tileBoundsSpotArray",
|
||||
"ifdef": ["_Clusters", "_Spot", "_ShadowMap", "_ShadowMapAtlas"]
|
||||
},
|
||||
{
|
||||
"name": "eyeLookRight",
|
||||
"link": "_eyeLookRight",
|
||||
@ -112,11 +122,6 @@
|
||||
"link": "_inverseViewProjectionMatrixRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVP",
|
||||
"link": "_viewProjectionMatrix",
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"name": "smSizeUniform",
|
||||
"link": "_shadowMapSize",
|
||||
@ -127,14 +132,6 @@
|
||||
"link": "_lightPlane",
|
||||
"ifdef": ["_SSS"]
|
||||
},
|
||||
{
|
||||
"name": "VP",
|
||||
"link": "_viewProjectionMatrix",
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"ifdef": ["_SMSizeUniform"]
|
||||
},
|
||||
{
|
||||
"name": "lightProj",
|
||||
"link": "_lightPlaneProj",
|
||||
@ -214,6 +211,12 @@
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot3",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "materialParams",
|
||||
"link": "_materialParams",
|
||||
"type": "floats",
|
||||
"ifdef": ["_ExtBRDF"]
|
||||
}
|
||||
],
|
||||
"vertex_shader": "../include/pass_viewray.vert.glsl",
|
||||
|
||||
8
leenkx/Shaders/render_draw/render_line.frag.glsl
Normal file
8
leenkx/Shaders/render_draw/render_line.frag.glsl
Normal file
@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
|
||||
in vec4 color;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(color);
|
||||
}
|
||||
12
leenkx/Shaders/render_draw/render_line.vert.glsl
Normal file
12
leenkx/Shaders/render_draw/render_line.vert.glsl
Normal file
@ -0,0 +1,12 @@
|
||||
#version 450
|
||||
|
||||
in vec3 pos;
|
||||
in vec4 col;
|
||||
|
||||
uniform mat4 ViewProjection;
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = col;
|
||||
gl_Position = ViewProjection * vec4(pos, 1.0);
|
||||
}
|
||||
15
leenkx/Shaders/render_draw/render_line_deferred.frag.glsl
Normal file
15
leenkx/Shaders/render_draw/render_line_deferred.frag.glsl
Normal file
@ -0,0 +1,15 @@
|
||||
#version 450
|
||||
|
||||
#include "compiled.inc"
|
||||
|
||||
in vec4 color;
|
||||
out vec4 fragColor[GBUF_SIZE];
|
||||
|
||||
void main() {
|
||||
fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0);
|
||||
fragColor[GBUF_IDX_1] = vec4(color);
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
fragColor[GBUF_IDX_EMISSION] = vec4(color);
|
||||
#endif
|
||||
}
|
||||
@ -28,10 +28,12 @@ in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
const float GOLDEN_ANGLE = 2.39996323;
|
||||
const int RAY_STEPS = 12;
|
||||
const int RAY_STEPS = 6;
|
||||
const int BINARY_STEPS = 3;
|
||||
|
||||
vec2 getProjectedCoord(const vec3 viewPos) {
|
||||
vec4 projectedCoord = P * vec4(viewPos, 1.0);
|
||||
if (projectedCoord.w <= 0.0) return vec2(-1e5);
|
||||
projectedCoord.xy /= projectedCoord.w;
|
||||
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
|
||||
#ifdef _InvY
|
||||
@ -40,16 +42,12 @@ vec2 getProjectedCoord(const vec3 viewPos) {
|
||||
return projectedCoord.xy;
|
||||
}
|
||||
|
||||
vec3 cosineSampleHemisphere(vec3 n, vec2 rand) {
|
||||
float phi = PI * 2.0 * rand.x;
|
||||
float cosTheta = sqrt(1.0 - rand.y);
|
||||
float sinTheta = sqrt(rand.y);
|
||||
float linearZ(const float depth) {
|
||||
return -P[3].z / (depth + P[2].z);
|
||||
}
|
||||
|
||||
vec3 h = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
||||
|
||||
vec3 tangent, bitangent;
|
||||
void buildTBN(vec3 n, out vec3 tangent, out vec3 bitangent) {
|
||||
vec3 absN = abs(n);
|
||||
|
||||
if (absN.x <= absN.y && absN.x <= absN.z) {
|
||||
tangent = normalize(cross(n, vec3(1.0, 0.0, 0.0)));
|
||||
} else if (absN.y <= absN.z) {
|
||||
@ -58,46 +56,70 @@ vec3 cosineSampleHemisphere(vec3 n, vec2 rand) {
|
||||
tangent = normalize(cross(n, vec3(0.0, 0.0, 1.0)));
|
||||
}
|
||||
bitangent = cross(n, tangent);
|
||||
}
|
||||
|
||||
vec3 sampleHemisphere(vec3 n, vec3 tangent, vec3 bitangent, float phi, float cosTheta) {
|
||||
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
|
||||
vec3 h = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
||||
return normalize(tangent * h.x + bitangent * h.y + n * h.z);
|
||||
}
|
||||
|
||||
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist) {
|
||||
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist, float jitter) {
|
||||
float stepSize = maxDist / float(RAY_STEPS);
|
||||
vec3 pos = origin + dir * minDist;
|
||||
float rayDist = minDist + stepSize * (jitter - 1.0);
|
||||
vec3 pos = origin + dir * rayDist;
|
||||
|
||||
float prevDepthDiff = 0.0;
|
||||
float hadValidPrev = 0.0;
|
||||
bool hasPrev = false;
|
||||
|
||||
for (int i = 1; i <= RAY_STEPS; i++) {
|
||||
for (int i = 0; i < RAY_STEPS; i++) {
|
||||
pos += dir * stepSize;
|
||||
rayDist += stepSize;
|
||||
vec2 uv = getProjectedCoord(pos);
|
||||
if (uv.x < -100.0) return vec3(-1.0);
|
||||
uv = clamp(uv, vec2(0.001), vec2(0.999));
|
||||
|
||||
vec2 sampleUV = clamp(uv, vec2(0.001), vec2(0.999));
|
||||
|
||||
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r * 2.0 - 1.0;
|
||||
float sampleDepth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
|
||||
if (sampleDepth == 1.0) {
|
||||
hadValidPrev = 0.0;
|
||||
hasPrev = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
vec3 sampleViewPos = getPosView2(invP, sampleDepth, sampleUV);
|
||||
float depthDiff = pos.z - sampleViewPos.z;
|
||||
float rayDist = length(pos - origin);
|
||||
float thickness = 0.15 + rayDist * 0.25;
|
||||
float depthDiff = pos.z - linearZ(sampleDepth);
|
||||
float thickness = maxDist * 0.075 + rayDist * 0.125;
|
||||
|
||||
float crossed = hadValidPrev * step(0.0, prevDepthDiff) * step(depthDiff, 0.0);
|
||||
float withinThickness = step(abs(depthDiff), thickness);
|
||||
bool crossed = hasPrev && (prevDepthDiff > 0.0) && (depthDiff <= 0.0);
|
||||
bool withinThickness = (depthDiff <= 0.0) && (-depthDiff < thickness);
|
||||
|
||||
if (crossed > 0.5 || withinThickness > 0.5) {
|
||||
float distWeight = 1.0 - (rayDist / maxDist);
|
||||
distWeight = max(0.0, distWeight * distWeight);
|
||||
if (crossed || withinThickness) {
|
||||
vec3 bPos = pos;
|
||||
vec3 bDir = dir * stepSize;
|
||||
for (int j = 0; j < BINARY_STEPS; j++) {
|
||||
bDir *= 0.5;
|
||||
bPos -= bDir;
|
||||
vec2 bUV = getProjectedCoord(bPos);
|
||||
bUV = clamp(bUV, vec2(0.001), vec2(0.999));
|
||||
float bDepth = textureLod(gbufferD, bUV, 0.0).r * 2.0 - 1.0;
|
||||
if (bDepth == 1.0) {
|
||||
bPos += bDir;
|
||||
continue;
|
||||
}
|
||||
if (bPos.z - linearZ(bDepth) > 0.0) bPos += bDir;
|
||||
}
|
||||
|
||||
return vec3(sampleUV, distWeight);
|
||||
vec2 bestUV = getProjectedCoord(bPos);
|
||||
if (bestUV.x < -100.0) return vec3(-1.0);
|
||||
bestUV = clamp(bestUV, vec2(0.001), vec2(0.999));
|
||||
|
||||
float hitDist = length(bPos - origin);
|
||||
float distWeight = max(0.0, 1.0 - (hitDist / maxDist));
|
||||
distWeight *= distWeight;
|
||||
|
||||
return vec3(bestUV, distWeight);
|
||||
}
|
||||
|
||||
prevDepthDiff = depthDiff;
|
||||
hadValidPrev = 1.0;
|
||||
hasPrev = true;
|
||||
}
|
||||
|
||||
return vec3(-1.0);
|
||||
@ -111,77 +133,77 @@ void main() {
|
||||
}
|
||||
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
vec2 enc = g0.rg;
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
|
||||
n = normalize(n);
|
||||
vec3 n = getNor(g0.rg);
|
||||
vec3 basecolor = textureLod(gbuffer1, texCoord, 0.0).rgb;
|
||||
|
||||
vec3 viewNormal = V3 * n;
|
||||
vec3 viewPos = getPosView2(invP, depth, texCoord);
|
||||
|
||||
#ifdef _CPostprocess
|
||||
float radius = PPComp12.y;
|
||||
float strength = PPComp12.x;
|
||||
float radius = PPComp12.y * 2.0;
|
||||
float strength = PPComp12.x * 0.5;
|
||||
#else
|
||||
float radius = ssgiRadius;
|
||||
float strength = ssgiStrength;
|
||||
float radius = ssgiRadius * 2.0;
|
||||
float strength = ssgiStrength * 0.5;
|
||||
#endif
|
||||
|
||||
float noise = fract(52.9829189 * fract(0.06711056 * texCoord.x * 1000.0 + 0.00583715 * texCoord.y * 1000.0));
|
||||
radius = min(radius, max(-viewPos.z / P[1].y, 0.05));
|
||||
|
||||
float noise = fract(52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y));
|
||||
|
||||
vec3 gi = vec3(0.0);
|
||||
int validSamples = 0;
|
||||
float missWeight = 0.0;
|
||||
|
||||
// min distance to avoid self shadowing artiffacts
|
||||
float minDist = radius * 0.05;
|
||||
|
||||
vec3 tangent, bitangent;
|
||||
buildTBN(viewNormal, tangent, bitangent);
|
||||
|
||||
for (int i = 0; i < ssgiSamples; i++) {
|
||||
float fi = float(i) + noise;
|
||||
vec2 rand = vec2(
|
||||
fract(fi * 0.7548776662 + noise),
|
||||
fract(fi * 0.5698402909 + noise * 1.5)
|
||||
);
|
||||
float phi = float(i) * GOLDEN_ANGLE + noise * PI2;
|
||||
float cosTheta = sqrt(max(0.0, 1.0 - (float(i) + 0.5) / float(ssgiSamples)));
|
||||
float jitter = fract(noise + float(i) * 0.618034);
|
||||
|
||||
vec3 rayDir = cosineSampleHemisphere(viewNormal, rand);
|
||||
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist);
|
||||
vec3 rayDir = sampleHemisphere(viewNormal, tangent, bitangent, phi, cosTheta);
|
||||
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist, jitter);
|
||||
|
||||
if (hitResult.x < 0.0) continue;
|
||||
if (hitResult.x < 0.0) {
|
||||
missWeight += 1.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
vec2 hitUV = hitResult.xy;
|
||||
float distWeight = hitResult.z;
|
||||
|
||||
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 1.0).rgb;
|
||||
vec3 hitN = getNor(textureLod(gbuffer0, hitUV, 0.0).rg);
|
||||
float emitterCos = max(0.0, dot(V3 * hitN, -rayDir));
|
||||
if (emitterCos <= 0.0) {
|
||||
missWeight += 1.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 0.0).rgb;
|
||||
|
||||
#ifdef _Sun
|
||||
vec4 hitG0 = textureLod(gbuffer0, hitUV, 0.0);
|
||||
vec2 hitEnc = hitG0.rg;
|
||||
vec3 hitN;
|
||||
hitN.z = 1.0 - abs(hitEnc.x) - abs(hitEnc.y);
|
||||
hitN.xy = hitN.z >= 0.0 ? hitEnc.xy : octahedronWrap(hitEnc.xy);
|
||||
hitN = normalize(hitN);
|
||||
float hitNdotL = max(0.0, dot(hitN, sunDir));
|
||||
vec3 hitRadiance = hitAlbedo * sunCol * hitNdotL;
|
||||
vec3 hitRadiance = hitAlbedo * (sunCol * hitNdotL + vec3(0.4));
|
||||
#else
|
||||
vec3 hitRadiance = hitAlbedo * 0.5;
|
||||
vec3 hitRadiance = hitAlbedo * 0.4;
|
||||
#endif
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
hitRadiance += textureLod(gbufferEmission, hitUV, 0.0).rgb;
|
||||
#endif
|
||||
|
||||
gi += hitRadiance * distWeight;
|
||||
validSamples++;
|
||||
gi += hitRadiance * (distWeight * emitterCos);
|
||||
}
|
||||
|
||||
if (validSamples > 0) {
|
||||
gi /= float(validSamples);
|
||||
}
|
||||
|
||||
gi *= strength;
|
||||
gi += basecolor * 0.1 * missWeight;
|
||||
gi *= 2.0 * strength / float(ssgiSamples);
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
|
||||
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
|
||||
#endif
|
||||
|
||||
fragColor = vec4(min(gi, vec3(2.0)), 1.0);
|
||||
|
||||
@ -11,6 +11,7 @@ uniform sampler2D gbuffer1; // basecol, spec
|
||||
uniform mat4 P;
|
||||
uniform mat3 V3;
|
||||
uniform vec2 cameraProj;
|
||||
uniform vec2 screenSize;
|
||||
|
||||
#ifdef _CPostprocess
|
||||
uniform vec3 PPComp9;
|
||||
@ -24,8 +25,8 @@ out vec4 fragColor;
|
||||
vec3 hitCoord;
|
||||
float depth;
|
||||
|
||||
const int numBinarySearchSteps = 7;
|
||||
const int maxSteps = int(ceil(1.0 / ssrRayStep) * ssrSearchDist);
|
||||
const int numBinarySearchSteps = 8;
|
||||
const int maxSteps = 50;
|
||||
|
||||
vec2 getProjectedCoord(const vec3 hit) {
|
||||
vec4 projectedCoord = P * vec4(hit, 1.0);
|
||||
@ -38,44 +39,58 @@ vec2 getProjectedCoord(const vec3 hit) {
|
||||
}
|
||||
|
||||
float getDeltaDepth(const vec3 hit) {
|
||||
depth = textureLod(gbufferD, getProjectedCoord(hit), 0.0).r * 2.0 - 1.0;
|
||||
vec2 tc = getProjectedCoord(hit);
|
||||
if (tc.x < 0.0 || tc.x > 1.0 || tc.y < 0.0 || tc.y > 1.0)
|
||||
return -1.0;
|
||||
depth = textureLod(gbufferD, tc, 0.0).r * 2.0 - 1.0;
|
||||
vec3 viewPos = getPosView(viewRay, depth, cameraProj);
|
||||
return viewPos.z - hit.z;
|
||||
}
|
||||
|
||||
vec4 binarySearch(vec3 dir) {
|
||||
vec4 binarySearch(vec3 dir, float stepSize) {
|
||||
float ddepth;
|
||||
for (int i = 0; i < numBinarySearchSteps; i++) {
|
||||
dir *= 0.5;
|
||||
hitCoord -= dir;
|
||||
stepSize *= 0.5;
|
||||
hitCoord -= dir * stepSize;
|
||||
ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth < 0.0) hitCoord += dir;
|
||||
if (ddepth < 0.0) hitCoord += dir * stepSize;
|
||||
}
|
||||
// Ugly discard of hits too far away
|
||||
#ifdef _CPostprocess
|
||||
if (abs(ddepth) > PPComp9.z / 500) return vec4(0.0);
|
||||
float maxDist = PPComp9.z;
|
||||
#else
|
||||
if (abs(ddepth) > ssrSearchDist / 500) return vec4(0.0);
|
||||
float maxDist = ssrSearchDist;
|
||||
#endif
|
||||
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
|
||||
if (abs(ddepth) > maxDist * 0.005) return vec4(0.0);
|
||||
vec2 hitTC = getProjectedCoord(hitCoord);
|
||||
if (hitTC.x < 0.0 || hitTC.x > 1.0 || hitTC.y < 0.0 || hitTC.y > 1.0)
|
||||
return vec4(0.0);
|
||||
return vec4(hitTC, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec4 rayCast(vec3 dir) {
|
||||
#ifdef _CPostprocess
|
||||
dir *= PPComp9.x;
|
||||
float baseStep = PPComp9.x;
|
||||
float maxDist = PPComp9.z;
|
||||
#else
|
||||
dir *= ssrRayStep;
|
||||
float baseStep = ssrRayStep;
|
||||
float maxDist = ssrSearchDist;
|
||||
#endif
|
||||
float stepSize = baseStep * max(1.0, -viewRay.z * 0.1);
|
||||
vec3 startPos = hitCoord;
|
||||
for (int i = 0; i < maxSteps; i++) {
|
||||
hitCoord += dir;
|
||||
if (getDeltaDepth(hitCoord) > 0.0) return binarySearch(dir);
|
||||
hitCoord += dir * stepSize;
|
||||
float dist = length(hitCoord - startPos);
|
||||
if (dist > maxDist) break;
|
||||
float ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth > 0.0) return binarySearch(dir, stepSize);
|
||||
stepSize *= 1.03;
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
float roughness = unpackFloat(g0.b).y;
|
||||
float roughness = g0.b;
|
||||
if (roughness == 1.0) { fragColor.rgb = vec3(0.0); return; }
|
||||
|
||||
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
|
||||
@ -92,30 +107,54 @@ void main() {
|
||||
|
||||
vec3 viewNormal = V3 * n;
|
||||
vec3 viewPos = getPosView(viewRay, d, cameraProj);
|
||||
vec3 reflected = reflect(viewPos, viewNormal);
|
||||
float NdotV = clamp(dot(viewNormal, -normalize(viewPos)), 0.0, 1.0);
|
||||
vec3 reflected = reflect(normalize(viewPos), viewNormal);
|
||||
hitCoord = viewPos;
|
||||
|
||||
#ifdef _CPostprocess
|
||||
vec3 dir = reflected * (1.0 - rand(texCoord) * PPComp10.y * roughness) * 2.0;
|
||||
#else
|
||||
vec3 dir = reflected * (1.0 - rand(texCoord) * ssrJitter * roughness) * 2.0;
|
||||
#endif
|
||||
vec3 dir = reflected;
|
||||
|
||||
// * max(ssrMinRayStep, -viewPos.z)
|
||||
vec4 coords = rayCast(dir);
|
||||
|
||||
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
|
||||
float screenEdgeFactor = clamp(1.0 - (deltaCoords.x + deltaCoords.y), 0.0, 1.0);
|
||||
if (coords.w <= 0.0) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
|
||||
float screenEdgeFactor = smoothstep(0.5, 0.15, deltaCoords.x)
|
||||
* smoothstep(0.5, 0.15, deltaCoords.y);
|
||||
screenEdgeFactor = max(screenEdgeFactor, 0.15);
|
||||
|
||||
float hitDepth = textureLod(gbufferD, coords.xy, 0.0).r * 2.0 - 1.0;
|
||||
vec3 hitViewPos = getPosView(viewRay, hitDepth, cameraProj);
|
||||
vec3 hitDir = normalize(hitViewPos - viewPos);
|
||||
float hitNdotV = clamp(dot(viewNormal, -hitDir), 0.0, 1.0);
|
||||
float hitBackFace = smoothstep(-0.15, 0.3, hitNdotV);
|
||||
|
||||
float reflectivity = 1.0 - roughness;
|
||||
#ifdef _CPostprocess
|
||||
float intensity = pow(reflectivity, PPComp10.x) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * clamp((PPComp9.z - length(viewPos - hitCoord)) * (1.0 / PPComp9.z), 0.0, 1.0) * coords.w;
|
||||
float falloffExp = PPComp10.x;
|
||||
float maxDist = PPComp9.z;
|
||||
#else
|
||||
float intensity = pow(reflectivity, ssrFalloffExp) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * clamp((ssrSearchDist - length(viewPos - hitCoord)) * (1.0 / ssrSearchDist), 0.0, 1.0) * coords.w;
|
||||
float falloffExp = ssrFalloffExp;
|
||||
float maxDist = ssrSearchDist;
|
||||
#endif
|
||||
|
||||
float distAttenuation = 1.0 - clamp(length(viewPos - hitCoord) / maxDist, 0.0, 1.0);
|
||||
distAttenuation = pow(distAttenuation, 1.5);
|
||||
|
||||
float fresnel = pow(1.0 - NdotV, 5.0);
|
||||
fresnel = mix(0.04, 1.0, fresnel);
|
||||
|
||||
float intensity = pow(reflectivity, falloffExp) * screenEdgeFactor
|
||||
* smoothstep(0.0, 0.1, -reflected.z)
|
||||
* distAttenuation
|
||||
* hitBackFace
|
||||
* coords.w;
|
||||
|
||||
intensity = clamp(intensity, 0.0, 1.0);
|
||||
|
||||
vec3 reflCol = textureLod(tex, coords.xy, 0.0).rgb;
|
||||
reflCol = clamp(reflCol, 0.0, 1.0);
|
||||
fragColor.rgb = reflCol * intensity * 0.5;
|
||||
fragColor.rgb = reflCol * intensity * mix(0.5, 1.0, fresnel);
|
||||
}
|
||||
|
||||
@ -22,6 +22,10 @@
|
||||
"name": "cameraProj",
|
||||
"link": "_cameraPlaneProj"
|
||||
},
|
||||
{
|
||||
"name": "screenSize",
|
||||
"link": "_screenSize"
|
||||
},
|
||||
{
|
||||
"name": "PPComp9",
|
||||
"link": "_PPComp9",
|
||||
|
||||
@ -12,6 +12,7 @@ uniform sampler2D tex1;
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbufferD1;
|
||||
uniform sampler2D gbuffer1;
|
||||
|
||||
uniform sampler2D gbuffer_refraction; // ior\opacity
|
||||
uniform mat4 P;
|
||||
@ -26,7 +27,7 @@ vec3 hitCoord;
|
||||
float depth;
|
||||
|
||||
const int numBinarySearchSteps = 7;
|
||||
const int maxSteps = int(ceil(1.0 / ss_refractionRayStep) * ss_refractionSearchDist);
|
||||
const int maxSteps = 50;
|
||||
|
||||
vec2 getProjectedCoord(const vec3 hit) {
|
||||
vec4 projectedCoord = P * vec4(hit, 1.0);
|
||||
@ -39,45 +40,60 @@ vec2 getProjectedCoord(const vec3 hit) {
|
||||
}
|
||||
|
||||
float getDeltaDepth(const vec3 hit) {
|
||||
depth = textureLod(gbufferD1, getProjectedCoord(hit), 0.0).r * 2.0 - 1.0;
|
||||
vec2 tc = getProjectedCoord(hit);
|
||||
if (tc.x < 0.0 || tc.x > 1.0 || tc.y < 0.0 || tc.y > 1.0)
|
||||
return -1.0;
|
||||
depth = textureLod(gbufferD1, tc, 0.0).r * 2.0 - 1.0;
|
||||
vec3 viewPos = getPosView(viewRay, depth, cameraProj);
|
||||
return viewPos.z - hit.z;
|
||||
}
|
||||
|
||||
vec4 binarySearch(vec3 dir) {
|
||||
vec4 binarySearch(vec3 dir, float stepSize) {
|
||||
float ddepth;
|
||||
for (int i = 0; i < numBinarySearchSteps; i++) {
|
||||
dir *= 0.5;
|
||||
hitCoord -= dir;
|
||||
stepSize *= 0.5;
|
||||
hitCoord -= dir * stepSize;
|
||||
ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth < 0.0) hitCoord += dir;
|
||||
if (ddepth < 0.0) hitCoord += dir * stepSize;
|
||||
}
|
||||
if (abs(ddepth) > ss_refractionSearchDist) return vec4(0.0);
|
||||
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
|
||||
if (abs(ddepth) > ss_refractionSearchDist * 0.005) return vec4(0.0);
|
||||
vec2 hitTC = getProjectedCoord(hitCoord);
|
||||
if (hitTC.x < 0.0 || hitTC.x > 1.0 || hitTC.y < 0.0 || hitTC.y > 1.0)
|
||||
return vec4(0.0);
|
||||
return vec4(hitTC, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec4 rayCast(vec3 dir) {
|
||||
float ddepth;
|
||||
dir *= ss_refractionRayStep;
|
||||
float stepSize = ss_refractionRayStep * max(1.0, -viewRay.z * 0.1);
|
||||
vec3 startPos = hitCoord;
|
||||
for (int i = 0; i < maxSteps; i++) {
|
||||
hitCoord += dir;
|
||||
ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth > 0.0) return binarySearch(dir);
|
||||
hitCoord += dir * stepSize;
|
||||
float dist = length(hitCoord - startPos);
|
||||
if (dist > ss_refractionSearchDist) break;
|
||||
float ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth > 0.0) return binarySearch(dir, stepSize);
|
||||
stepSize *= 1.03;
|
||||
}
|
||||
return vec4(texCoord, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||
float ior = gr.x;
|
||||
float ior = unpackIOR(gr.x);
|
||||
float transmittance = gr.y;
|
||||
float surfaceDepth = gr.z;
|
||||
float d = surfaceDepth * 2.0 - 1.0;
|
||||
|
||||
vec4 sceneSample = textureLod(tex, texCoord, 0.0);
|
||||
if (surfaceDepth == 0.0 || transmittance == 0.0 || ior == 1.0) {
|
||||
if (surfaceDepth == 0.0 || surfaceDepth == 1.0) {
|
||||
fragColor = sceneSample;
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0);
|
||||
if (transmittance == 0.0 || ior == 1.0) {
|
||||
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
|
||||
fragColor.rgb = g1.rgb + background * transmittance;
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
@ -96,18 +112,18 @@ void main() {
|
||||
vec3 refracted = refract(incident, viewNormal, 1.0 / ior);
|
||||
if (length(refracted) < 0.001) {
|
||||
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
|
||||
fragColor.rgb = g1.rgb + background * transmittance;
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
hitCoord = viewPos;
|
||||
|
||||
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
|
||||
vec3 dir = normalize(refracted);
|
||||
vec4 coords = rayCast(dir);
|
||||
|
||||
vec2 screenEdge = smoothstep(0.0, 0.1, coords.xy) * smoothstep(0.0, 0.1, 1.0 - coords.xy);
|
||||
float screenEdgeFactor = screenEdge.x * screenEdge.y;
|
||||
vec2 screenEdge = smoothstep(0.0, 0.05, coords.xy) * smoothstep(0.0, 0.05, 1.0 - coords.xy);
|
||||
float screenEdgeFactor = max(screenEdge.x * screenEdge.y, 0.05);
|
||||
float refractivity = 1.0 - roughness;
|
||||
|
||||
float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * coords.w;
|
||||
@ -118,6 +134,6 @@ void main() {
|
||||
|
||||
vec3 behindColor = mix(straightBackground, refractedBackground, intensity);
|
||||
|
||||
fragColor.rgb = sceneSample.rgb + behindColor * (1.0 - sceneSample.a);
|
||||
fragColor.rgb = g1.rgb + behindColor * transmittance;
|
||||
fragColor.a = 1.0;
|
||||
}
|
||||
|
||||
@ -40,100 +40,148 @@
|
||||
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbuffer1;
|
||||
uniform sampler2D tex;
|
||||
|
||||
uniform vec2 dir;
|
||||
uniform vec2 cameraProj;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
//!uniform vec4 materialParams[MAX_MATERIALS * 8];
|
||||
#endif
|
||||
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
const vec3 SKIN_SSS_RADIUS = vec3(4.8, 2.4, 1.5);
|
||||
const float SSS_DISTANCE_SCALE = 0.001;
|
||||
// TODO: finish the SSS
|
||||
const float SSS_SCALE = 0.05;
|
||||
const float DEPTH_THRESHOLD = 0.05;
|
||||
|
||||
// Temp hash func -
|
||||
float hash13(vec3 p3) {
|
||||
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
vec4 SSSSBlur() {
|
||||
const int SSSS_N_SAMPLES = 15;
|
||||
vec4 SSSSBlur(vec3 sssRadius, float sssWeight) {
|
||||
const int SSSS_N_SAMPLES = 11;
|
||||
vec4 kernel[SSSS_N_SAMPLES];
|
||||
|
||||
kernel[0] = vec4(0.233, 0.455, 0.649, 0.0); // Center sample
|
||||
kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37mm
|
||||
kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97mm
|
||||
kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93mm
|
||||
kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87mm
|
||||
kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53mm (red only)
|
||||
kernel[6] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[7] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[8] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37mm
|
||||
kernel[9] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97mm
|
||||
kernel[10] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93mm
|
||||
kernel[11] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87mm
|
||||
kernel[12] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53mm (red only)
|
||||
kernel[13] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[14] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37
|
||||
kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97
|
||||
kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93
|
||||
kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87
|
||||
kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53 (red only)
|
||||
kernel[6] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37
|
||||
kernel[7] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97
|
||||
kernel[8] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93
|
||||
kernel[9] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87
|
||||
kernel[10] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53 (red only)
|
||||
|
||||
vec4 colorM = textureLod(tex, texCoord, 0.0);
|
||||
vec2 texSize = vec2(textureSize(tex, 0));
|
||||
ivec2 texelCoord = ivec2(texCoord * texSize);
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
vec4 colorM = texelFetch(tex, texelCoord, 0);
|
||||
vec3 albedo = texelFetch(gbuffer1, texelCoord, 0).rgb;
|
||||
|
||||
vec3 irradianceM = colorM.rgb / max(albedo, vec3(0.00001));
|
||||
|
||||
float depth = texelFetch(gbufferD, texelCoord, 0).r;
|
||||
float depthM = cameraProj.y / (depth - cameraProj.x);
|
||||
|
||||
float distanceScale = 1.0 / max(depthM, 0.1);
|
||||
|
||||
vec2 finalStep = sssWidth * distanceScale * dir * SSS_DISTANCE_SCALE;
|
||||
|
||||
float blurWidth = max(max(sssRadius.r, sssRadius.g), sssRadius.b);
|
||||
float projScale = dot(dir, vec2(projectionMatrix[0][0], projectionMatrix[1][1]));
|
||||
vec2 finalStep = blurWidth * (1.0 / depthM) * dir * projScale * SSS_SCALE;
|
||||
|
||||
vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
|
||||
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15;
|
||||
|
||||
finalStep *= (1.0 + jitterOffset);
|
||||
vec3 colorBlurred = vec3(0.0);
|
||||
vec3 weightSum = vec3(0.0);
|
||||
colorBlurred += colorM.rgb * kernel[0].rgb;
|
||||
weightSum += kernel[0].rgb;
|
||||
|
||||
vec3 colorBlurred = irradianceM * kernel[0].rgb;
|
||||
vec3 weightSum = kernel[0].rgb;
|
||||
|
||||
for (int i = 1; i < SSSS_N_SAMPLES; i++) {
|
||||
float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
|
||||
vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
|
||||
vec4 color = textureLod(tex, offset, 0.0);
|
||||
const float DEPTH_THRESHOLD = 0.05;
|
||||
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
|
||||
float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
|
||||
|
||||
float depthDiff = abs(depthM - sampleDepthM);
|
||||
float depthWeight = exp(-depthDiff * 10.0);
|
||||
vec3 irradiance = irradianceM;
|
||||
float s = 0.0;
|
||||
if (all(greaterThanEqual(offset, vec2(0.0))) && all(lessThan(offset, vec2(1.0)))) {
|
||||
ivec2 sampleTexel = ivec2(offset * texSize);
|
||||
|
||||
if (depthDiff > DEPTH_THRESHOLD) {
|
||||
color.rgb = mix(colorM.rgb, color.rgb, depthWeight);
|
||||
float sampleDepth = texelFetch(gbufferD, sampleTexel, 0).r;
|
||||
float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
|
||||
float depthDiff = abs(depthM - sampleDepthM);
|
||||
|
||||
if (depthDiff < 1.0) {
|
||||
vec4 sampleG0 = texelFetch(gbuffer0, sampleTexel, 0);
|
||||
float sampleMetallic;
|
||||
uint sampleMatid;
|
||||
unpackFloatInt16(sampleG0.a, sampleMetallic, sampleMatid);
|
||||
bool sampleIsSSS = false;
|
||||
#ifdef _ExtBRDF
|
||||
if (sampleMatid >= 3u && sampleMatid < uint(MAX_MATERIALS)) {
|
||||
if (materialParams[sampleMatid * 8u + 3u].z > 0.0) sampleIsSSS = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sampleIsSSS) {
|
||||
vec3 sampleColor = texelFetch(tex, sampleTexel, 0).rgb;
|
||||
vec3 sampleAlbedo = texelFetch(gbuffer1, sampleTexel, 0).rgb;
|
||||
irradiance = sampleColor / max(sampleAlbedo, vec3(0.00001));
|
||||
}
|
||||
|
||||
if (depthDiff <= DEPTH_THRESHOLD) {
|
||||
s = 1.0;
|
||||
} else {
|
||||
s = exp(-depthDiff * 10.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
colorBlurred += color.rgb * kernel[i].rgb;
|
||||
colorBlurred += kernel[i].rgb * mix(irradianceM, irradiance, s);
|
||||
weightSum += kernel[i].rgb;
|
||||
}
|
||||
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
|
||||
|
||||
vec3 normalizedIrradiance = colorBlurred / max(weightSum, vec3(0.00001));
|
||||
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
|
||||
normalizedColor = max(normalizedColor + vec3(dither), vec3(0.0));
|
||||
return vec4(normalizedColor, colorM.a);
|
||||
normalizedIrradiance = max(normalizedIrradiance + vec3(dither), vec3(0.0));
|
||||
vec3 blurredColor = normalizedIrradiance * albedo;
|
||||
vec3 result = mix(colorM.rgb, blurredColor, sssWeight);
|
||||
return vec4(result, colorM.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
vec2 texSize0 = vec2(textureSize(gbuffer0, 0));
|
||||
ivec2 texelCoord0 = ivec2(texCoord * texSize0);
|
||||
vec4 g0 = texelFetch(gbuffer0, texelCoord0, 0);
|
||||
float metallic;
|
||||
uint matid;
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
|
||||
if (matid == 2u) {
|
||||
vec4 originalColor = textureLod(tex, texCoord, 0.0);
|
||||
vec4 blurredColor = SSSSBlur();
|
||||
vec4 sssContribution = blurredColor - originalColor;
|
||||
vec4 combined = originalColor + max(vec4(0.0), sssContribution) * 0.8;
|
||||
fragColor = max(vec4(0.0), min(combined, vec4(10.0)));
|
||||
bool applySSS = false;
|
||||
vec3 sssRadius = vec3(1.0);
|
||||
float sssWeight = 1.0;
|
||||
vec4 matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7;
|
||||
#ifdef _ExtBRDF
|
||||
if (matid >= 3u && matid < uint(MAX_MATERIALS)) {
|
||||
getMaterialParams(matid, matp0, matp1, matp2, matp3, matp4, matp5, matp6, matp7);
|
||||
// matp3.z = subsurface, matp4.xyz = subsurfaceRadiusRGB, matp7.x = subsurfaceScale
|
||||
if (matp3.z > 0.0) {
|
||||
applySSS = true;
|
||||
sssRadius = matp4.xyz * matp7.x;
|
||||
sssWeight = matp3.z;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (applySSS) {
|
||||
fragColor = SSSSBlur(sssRadius, sssWeight);
|
||||
} else {
|
||||
fragColor = textureLod(tex, texCoord, 0.0);
|
||||
vec2 texSizeMain = vec2(textureSize(tex, 0));
|
||||
ivec2 texelCoordMain = ivec2(texCoord * texSizeMain);
|
||||
fragColor = texelFetch(tex, texelCoordMain, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,16 @@
|
||||
{
|
||||
"name": "cameraProj",
|
||||
"link": "_cameraPlaneProj"
|
||||
},
|
||||
{
|
||||
"name": "projectionMatrix",
|
||||
"link": "_projectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "materialParams",
|
||||
"link": "_materialParams",
|
||||
"type": "floats",
|
||||
"ifdef": ["_ExtBRDF"]
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
@ -32,6 +42,16 @@
|
||||
{
|
||||
"name": "cameraProj",
|
||||
"link": "_cameraPlaneProj"
|
||||
},
|
||||
{
|
||||
"name": "projectionMatrix",
|
||||
"link": "_projectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "materialParams",
|
||||
"link": "_materialParams",
|
||||
"type": "floats",
|
||||
"ifdef": ["_ExtBRDF"]
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
|
||||
@ -1,10 +1,30 @@
|
||||
#ifndef _BRDF_GLSL_
|
||||
#define _BRDF_GLSL_
|
||||
|
||||
#ifndef PI
|
||||
#define PI 3.1415926535
|
||||
#endif
|
||||
#ifndef INV_PI
|
||||
#define INV_PI 0.3183098861
|
||||
#endif
|
||||
#ifndef INV_TWO_PI
|
||||
#define INV_TWO_PI 0.1591549430
|
||||
#endif
|
||||
#ifndef SCHLICK_A
|
||||
#define SCHLICK_A -5.55473
|
||||
#endif
|
||||
#ifndef SCHLICK_B
|
||||
#define SCHLICK_B -6.98316
|
||||
#endif
|
||||
#ifndef SRGB_GAMMA
|
||||
#define SRGB_GAMMA 2.2
|
||||
#endif
|
||||
#define srgbToLinear(x) pow(x, vec3(SRGB_GAMMA))
|
||||
|
||||
// http://xlgames-inc.github.io/posts/improvedibl/
|
||||
// http://blog.selfshadow.com/publications/s2013-shading-course/
|
||||
vec3 f_schlick(const vec3 f0, const float vh) {
|
||||
return f0 + (1.0 - f0) * exp2((-5.55473 * vh - 6.98316) * vh);
|
||||
return f0 + (1.0 - f0) * exp2((SCHLICK_A * vh + SCHLICK_B) * vh);
|
||||
}
|
||||
|
||||
float v_smithschlick(const float nl, const float nv, const float a) {
|
||||
@ -31,7 +51,7 @@ float d_ggx(const float nh, const float a) {
|
||||
float a2 = a * a;
|
||||
float denom = nh * nh * (a2 - 1.0) + 1.0;
|
||||
denom = max(denom * denom, 0.00006103515625 /* 2^-14 = smallest possible half float value, prevent div by zero */);
|
||||
return a2 * (1.0 / 3.1415926535) / denom;
|
||||
return a2 * INV_PI / denom;
|
||||
}
|
||||
|
||||
vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) {
|
||||
@ -44,11 +64,10 @@ vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const fl
|
||||
// http://filmicworlds.com/blog/optimizing-ggx-shaders-with-dotlh/
|
||||
vec3 specularBRDFb(const vec3 f0, const float roughness, const float dotNL, const float dotNH, const float dotLH) {
|
||||
// D
|
||||
const float pi = 3.1415926535;
|
||||
float alpha = roughness * roughness;
|
||||
float alphaSqr = alpha * alpha;
|
||||
float denom = dotNH * dotNH * (alphaSqr - 1.0) + 1.0;
|
||||
float D = alphaSqr / (pi * denom * denom);
|
||||
float D = alphaSqr / (PI * denom * denom);
|
||||
// F
|
||||
const float F_a = 1.0;
|
||||
float F_b = pow(1.0 - dotLH, 5.0);
|
||||
@ -65,21 +84,201 @@ vec3 specularBRDFb(const vec3 f0, const float roughness, const float dotNL, cons
|
||||
return specular / 4.0; // TODO: get rid of / 4.0
|
||||
}
|
||||
|
||||
vec3 orenNayarDiffuseBRDF(const vec3 albedo, const float roughness, const float nv, const float nl, const float vh) {
|
||||
float a = roughness * roughness;
|
||||
float s = a;
|
||||
float s2 = s * s;
|
||||
float vl = 2.0 * vh * vh - 1.0; // Double angle identity
|
||||
float Cosri = vl - nv * nl;
|
||||
float C1 = 1.0 - 0.5 * s2 / (s2 + 0.33);
|
||||
float test = 1.0;
|
||||
if (Cosri >= 0.0) test = (1.0 / (max(nl, nv)));
|
||||
float C2 = 0.45 * s2 / (s2 + 0.09) * Cosri * test;
|
||||
return albedo * max(0.0, nl) * (C1 + C2) * (1.0 + roughness * 0.5);
|
||||
vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) {
|
||||
return albedo * INV_PI * nl;
|
||||
}
|
||||
|
||||
vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) {
|
||||
return albedo * (1.0 / 3.1415926535) * nl;
|
||||
#ifdef _BurleyDiffuse
|
||||
vec3 burleyDiffuseBRDF(const vec3 albedo, const float roughness,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
float nl = clamp(dotNL, 0.0, 1.0);
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float energyBias = mix(0.0, 0.5, roughness);
|
||||
float energyFactor = mix(1.0, 1.0 / 1.51, roughness);
|
||||
float fd90 = energyBias + 2.0 * roughness * dotVH * dotVH;
|
||||
float lightScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nl, 5.0);
|
||||
float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0);
|
||||
return albedo * INV_PI * lightScatter * viewScatter * energyFactor * nl;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _EONDiffuse
|
||||
const float constant1_FON = 0.5 - 2.0 / (3.0 * PI);
|
||||
const float constant2_FON = 2.0 / 3.0 - 28.0 / (15.0 * PI);
|
||||
|
||||
float E_FON_approx(float mu, float r) {
|
||||
float mucomp = 1.0 - mu;
|
||||
const float g1 = 0.0571085289;
|
||||
const float g2 = 0.491881867;
|
||||
const float g3 = -0.332181442;
|
||||
const float g4 = 0.0714429953;
|
||||
float GoverPi = mucomp * (g1 + mucomp * (g2 + mucomp * (g3 + mucomp * g4)));
|
||||
return (1.0 + r * GoverPi) / (1.0 + constant1_FON * r);
|
||||
}
|
||||
|
||||
vec3 eonDiffuseBRDF(const vec3 albedo, const float roughness,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
float r = roughness;
|
||||
float mu_i = clamp(dotNL, 0.0, 1.0);
|
||||
float mu_o = clamp(dotNV, 0.0, 1.0);
|
||||
if (mu_i < 1.0e-7 || mu_o < 1.0e-7) return vec3(0.0);
|
||||
float dotLV = 2.0 * dotVH * dotVH - 1.0;
|
||||
float s = dotLV - mu_i * mu_o;
|
||||
float sovertF = s > 0.0 ? s / max(mu_i, mu_o) : s;
|
||||
float AF = 1.0 / (1.0 + constant1_FON * r);
|
||||
vec3 f_ss = (albedo * INV_PI) * AF * (1.0 + r * sovertF);
|
||||
float EFo = E_FON_approx(mu_o, r);
|
||||
float EFi = E_FON_approx(mu_i, r);
|
||||
float avgEF = AF * (1.0 + constant2_FON * r);
|
||||
vec3 rho_ms = (albedo * albedo) * avgEF
|
||||
/ max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7));
|
||||
const float eps = 1.0e-7;
|
||||
vec3 f_ms = (rho_ms * INV_PI)
|
||||
* max(eps, 1.0 - EFo)
|
||||
* max(eps, 1.0 - EFi)
|
||||
/ max(eps, 1.0 - avgEF);
|
||||
return (f_ss + f_ms) * mu_i;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _GotandaDiffuse
|
||||
vec3 gotandaDiffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
float nl = clamp(dotNL, 0.0, 1.0);
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float dotLV = 2.0 * dotVH * dotVH - 1.0;
|
||||
float Cosri = dotLV - nv * nl;
|
||||
float a2_13 = a2 + 1.36053;
|
||||
float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13)
|
||||
* (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13)
|
||||
* ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a)
|
||||
* pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0);
|
||||
float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nl, 5.0))
|
||||
+ min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nl - 1.0)) * nl;
|
||||
float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv)))
|
||||
* (1.0 - pow(1.0 - nl,
|
||||
(1.0 - 0.3726732 * nv * nv)
|
||||
/ (0.188566 + 0.38841 * nv)));
|
||||
float Bp = Cosri < 0.0 ? 1.4 * nv * nl * Cosri : Cosri;
|
||||
vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp);
|
||||
return max(albedo * INV_PI * Lr, vec3(0.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ChanDiffuse
|
||||
vec3 chanDiffuseBRDF(const vec3 albedo, const float roughness,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
float nl = clamp(dotNL, 0.0, 1.0);
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float vh = clamp(dotVH, 0.0, 1.0);
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0);
|
||||
float dotNH = clamp((nl + nv) / max(2.0 * vh, 1e-5), 0.0, 1.0);
|
||||
float F0 = vh + pow(1.0 - vh, 5.0);
|
||||
float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0);
|
||||
float FdL = 1.0 - 0.75 * pow(1.0 - nl, 5.0);
|
||||
float Fd = mix(F0, FdV * FdL, clamp(2.2 * g - 0.5, 0.0, 1.0));
|
||||
float Fb = ((34.5 * g - 59.0) * g + 24.5) * vh
|
||||
* exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(dotNH));
|
||||
float Lobe = clamp(Fd + Fb, 0.0, 1.0);
|
||||
return albedo * INV_PI * Lobe * nl;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec3 diffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
#ifdef _BurleyDiffuse
|
||||
return burleyDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
|
||||
#elif defined(_EONDiffuse)
|
||||
return eonDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
|
||||
#elif defined(_GotandaDiffuse)
|
||||
return gotandaDiffuseBRDF(albedo, roughness, f0, dotNL, dotNV, dotVH);
|
||||
#elif defined(_ChanDiffuse)
|
||||
return chanDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH);
|
||||
#else
|
||||
return lambertDiffuseBRDF(albedo, dotNL);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 lambertDiffuseIBL(const vec3 albedo) {
|
||||
return albedo;
|
||||
}
|
||||
|
||||
#ifdef _BurleyDiffuse
|
||||
vec3 burleyDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float energyBias = mix(0.0, 0.5, roughness);
|
||||
float energyFactor = mix(1.0, 1.0 / 1.51, roughness);
|
||||
float fd90 = energyBias + 2.0 * roughness * nv * nv;
|
||||
float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0);
|
||||
return albedo * viewScatter * energyFactor;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _EONDiffuse
|
||||
vec3 eonDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
|
||||
float r = roughness;
|
||||
float AF = 1.0 / (1.0 + constant1_FON * r);
|
||||
float EF = E_FON_approx(clamp(dotNV, 0.0, 1.0), r);
|
||||
float avgEF = AF * (1.0 + constant2_FON * r);
|
||||
vec3 rho_ms = (albedo * albedo) * avgEF
|
||||
/ max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7));
|
||||
return max(albedo * EF + rho_ms * (1.0 - EF), vec3(0.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _GotandaDiffuse
|
||||
vec3 gotandaDiffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) {
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float a2_13 = a2 + 1.36053;
|
||||
float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13)
|
||||
* (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13)
|
||||
* ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a)
|
||||
* pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0);
|
||||
float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nv, 5.0))
|
||||
+ min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nv - 1.0)) * nv;
|
||||
float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv)))
|
||||
* (1.0 - pow(1.0 - nv,
|
||||
(1.0 - 0.3726732 * nv * nv)
|
||||
/ (0.188566 + 0.38841 * nv)));
|
||||
float Cosri = 1.0 - nv * nv;
|
||||
float Bp = Cosri;
|
||||
vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp);
|
||||
return max(albedo * Lr, vec3(0.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ChanDiffuse
|
||||
vec3 chanDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) {
|
||||
float nv = clamp(dotNV, 0.0, 1.0);
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0);
|
||||
float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0);
|
||||
float Fd = mix(1.0, FdV * FdV, clamp(2.2 * g - 0.5, 0.0, 1.0));
|
||||
float Fb = ((34.5 * g - 59.0) * g + 24.5)
|
||||
* exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(nv));
|
||||
return albedo * clamp(Fd + Fb, 0.0, 1.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
vec3 diffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) {
|
||||
#ifdef _BurleyDiffuse
|
||||
return burleyDiffuseIBL(albedo, roughness, dotNV);
|
||||
#elif defined(_EONDiffuse)
|
||||
return eonDiffuseIBL(albedo, roughness, dotNV);
|
||||
#elif defined(_GotandaDiffuse)
|
||||
return gotandaDiffuseIBL(albedo, roughness, f0, dotNV);
|
||||
#elif defined(_ChanDiffuse)
|
||||
return chanDiffuseIBL(albedo, roughness, dotNV);
|
||||
#else
|
||||
return lambertDiffuseIBL(albedo);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) {
|
||||
@ -95,24 +294,6 @@ float getMipFromRoughness(const float roughness, const float numMipmaps) {
|
||||
return roughness * numMipmaps;
|
||||
}
|
||||
|
||||
float wardSpecular(vec3 N, vec3 H, float dotNL, float dotNV, float dotNH, vec3 fiberDirection, float shinyParallel, float shinyPerpendicular) {
|
||||
if(dotNL < 0.0 || dotNV < 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
// fiberDirection - parse from rotation
|
||||
// shinyParallel - roughness
|
||||
// shinyPerpendicular - anisotropy
|
||||
|
||||
vec3 fiberParallel = normalize(fiberDirection);
|
||||
vec3 fiberPerpendicular = normalize(cross(N, fiberDirection));
|
||||
float dotXH = dot(fiberParallel, H);
|
||||
float dotYH = dot(fiberPerpendicular, H);
|
||||
const float PI = 3.1415926535;
|
||||
float coeff = sqrt(dotNL/dotNV) / (4.0 * PI * shinyParallel * shinyPerpendicular);
|
||||
float theta = (pow(dotXH/shinyParallel, 2.0) + pow(dotYH/shinyPerpendicular, 2.0)) / (1.0 + dotNH);
|
||||
return clamp(coeff * exp(-2.0 * theta), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
|
||||
// vec3 EnvBRDFApprox(vec3 SpecularColor, float Roughness, float NoV) {
|
||||
// const vec4 c0 = { -1, -0.0275, -0.572, 0.022 };
|
||||
@ -138,4 +319,207 @@ float D_Approx(const float Roughness, const float RoL) {
|
||||
return rcp_a2 * exp2( c * RoL - c );
|
||||
}
|
||||
|
||||
#ifdef _ClearCoat
|
||||
float brdf_coatF0;
|
||||
vec3 clearcoatBRDF(const float clearcoat, const float clearcoat_rough,
|
||||
const float coat_ior, const vec3 coatN, const vec3 l, const vec3 v, const vec3 h) {
|
||||
if (clearcoat <= 0.0) return vec3(0.0);
|
||||
float cdotNL = max(0.0, dot(coatN, l));
|
||||
float cdotNH = max(0.0, dot(coatN, h));
|
||||
float cdotNV = max(0.0, dot(coatN, v));
|
||||
float cdotVH = max(0.0, dot(v, h));
|
||||
float a = clearcoat_rough * clearcoat_rough;
|
||||
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotVH + SCHLICK_B) * cdotVH);
|
||||
float D = d_ggx(cdotNH, a);
|
||||
float G = g2_approx(cdotNL, cdotNV, a);
|
||||
return vec3(clearcoat * D * G * F / max(4.0 * cdotNV, 1e-5));
|
||||
}
|
||||
|
||||
float coatAttenuation(const float clearcoat,
|
||||
const float coat_ior, const vec3 coatN, const vec3 v) {
|
||||
if (clearcoat <= 0.0) return 1.0;
|
||||
float cdotNV = max(0.0, dot(coatN, v));
|
||||
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotNV + SCHLICK_B) * cdotNV);
|
||||
return max(1.0 - F * clearcoat, 0.0);
|
||||
}
|
||||
|
||||
vec3 coatTintAttenuation(const float clearcoat, const vec3 coat_tint, const vec3 coatN, const vec3 v) {
|
||||
if (clearcoat <= 0.0) return vec3(1.0);
|
||||
float cdotNV = max(0.0, dot(coatN, v));
|
||||
float absorption = 1.0 / max(cdotNV, 0.3);
|
||||
return mix(vec3(1.0), clamp(coat_tint, 0.0, 1.0), clamp(absorption * 0.2, 0.0, 1.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Sheen
|
||||
float brdf_sheenAlbedo;
|
||||
// based on Blender sheen model/Frostbite PBR
|
||||
vec3 sheenBRDF(const float sheen, const float sheen_rough,
|
||||
const vec3 sheen_tint, const float dotNL, const float dotNH, const float dotNV) {
|
||||
if (sheen <= 0.0) return vec3(0.0);
|
||||
float rough = clamp(sheen_rough, 1e-3, 1.0);
|
||||
float a = rough * rough;
|
||||
float sinNH2 = 1.0 - dotNH * dotNH;
|
||||
float a2 = a * a;
|
||||
float denom = 1.0 + a2 * sinNH2;
|
||||
float D = (2.0 + a2) * sinNH2 * INV_TWO_PI / (denom * denom);
|
||||
float V = 1.0 / (4.0 * dotNL * dotNV + 1e-5);
|
||||
return sheen_tint * sheen * D * V * dotNL * brdf_sheenAlbedo;
|
||||
}
|
||||
|
||||
float sheenAttenuation(const float sheen, const float sheen_rough,
|
||||
const vec3 sheen_tint, const float dotNV) {
|
||||
if (sheen <= 0.0) return 1.0;
|
||||
float maxComp = sheen * max(max(sheen_tint.r, sheen_tint.g), sheen_tint.b) * brdf_sheenAlbedo;
|
||||
return max(1.0 - maxComp, 0.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Anisotropy
|
||||
// anisotropic GGX Burley 2012
|
||||
vec3 anisotropicBRDF(const vec3 f0, const float roughness, const float anisotropy,
|
||||
const float aniso_rot, const vec3 tangent, const vec3 bitangent,
|
||||
const vec3 n, const vec3 l, const vec3 v,
|
||||
const float dotNL, const float dotNV) {
|
||||
if (abs(anisotropy) <= 0.001) return vec3(0.0);
|
||||
float rot = aniso_rot * PI * 2.0;
|
||||
float cr = cos(rot);
|
||||
float sr = sin(rot);
|
||||
vec3 t = normalize(tangent * cr + bitangent * sr);
|
||||
vec3 b = normalize(bitangent * cr - tangent * sr);
|
||||
float aniso_abs = abs(anisotropy);
|
||||
float at = max(roughness * (1.0 + aniso_abs), 1e-5);
|
||||
float ab = max(roughness * (1.0 - aniso_abs), 1e-5);
|
||||
if (anisotropy < 0.0) { vec3 tmp = t; t = b; b = tmp; }
|
||||
float at2 = at * at;
|
||||
float ab2 = ab * ab;
|
||||
vec3 h = normalize(l + v);
|
||||
float dotTH = dot(t, h);
|
||||
float dotBH = dot(b, h);
|
||||
float dotTV = dot(t, v);
|
||||
float dotBV = dot(b, v);
|
||||
float dotTL = dot(t, l);
|
||||
float dotBL = dot(b, l);
|
||||
float denom = max(dotTH * dotTH / at2 + dotBH * dotBH / ab2, 1e-7);
|
||||
float D = INV_PI / (at * ab * denom * denom);
|
||||
float V = 1.0 / max(dotNL * (dotTL / at + dotBL / ab) * (dotTV / at + dotBV / ab), 1e-5);
|
||||
float dotVH = max(dot(v, h), 0.0);
|
||||
vec3 F = f_schlick(f0, dotVH);
|
||||
return D * V * F / max(4.0 * dotNV, 1e-5);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Transmission
|
||||
float brdf_transmissionF0;
|
||||
// Blenders microfacet glass/refraction model
|
||||
vec3 transmissionBRDF(const vec3 albedo, const float transmission,
|
||||
const float trans_rough, const float ior, const float thin_wall,
|
||||
const float dotNL, const float dotNV, const float dotVH) {
|
||||
if (transmission <= 0.0) return vec3(0.0);
|
||||
float F = brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotVH + SCHLICK_B) * dotVH);
|
||||
float transmittance = 1.0 - F;
|
||||
if (thin_wall > 0.5) {
|
||||
return albedo * transmission * transmittance * dotNL;
|
||||
}
|
||||
float a = trans_rough * trans_rough;
|
||||
float rough_atten = min(mix(1.0, 1.0 / max(dotNV, 0.1), a), 4.0);
|
||||
return albedo * transmission * transmittance * rough_atten * dotNL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
float brdf_sheenWeight = 1.0;
|
||||
float brdf_coatWeight = 1.0;
|
||||
vec3 brdf_coatTintAbsorb = vec3(1.0);
|
||||
|
||||
vec3 applyExtBRDFLayers(
|
||||
const vec3 direct,
|
||||
const vec3 albedo,
|
||||
const vec3 f0,
|
||||
const float roughness,
|
||||
const float dotNL, const float dotNV, const float dotNH, const float dotVH,
|
||||
const vec3 n, const vec3 l, const vec3 v, const vec3 h,
|
||||
#ifdef _ClearCoat
|
||||
const float clearcoat, const float clearcoatRough, const float coatIOR,
|
||||
const vec3 coatTint, const vec3 coatN,
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
const float sheen, const float sheenRough, const vec3 sheenTint,
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
const float transmission, const float transRough, const float ior, const float thinWall,
|
||||
#endif
|
||||
out float layerWeight
|
||||
) {
|
||||
float sheenWeight = brdf_sheenWeight;
|
||||
float coatWeight = brdf_coatWeight;
|
||||
#ifdef _Sheen
|
||||
vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV);
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, coatN, l, v, h);
|
||||
#endif
|
||||
layerWeight = sheenWeight * coatWeight;
|
||||
vec3 result = direct * layerWeight;
|
||||
#ifdef _Transmission
|
||||
result += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight;
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
result *= brdf_coatTintAbsorb;
|
||||
result += coatContrib * sheenWeight;
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
result += sheenContrib;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ClearCoat
|
||||
float coatIBLFresnel(const float clearcoat, const float coat_ior,
|
||||
const float dotNV_coat) {
|
||||
if (clearcoat <= 0.0) return 0.0;
|
||||
float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * dotNV_coat + SCHLICK_B) * dotNV_coat);
|
||||
return F * clearcoat;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Sheen
|
||||
float sheenIBLAlbedo(const float sheen, const float sheen_rough,
|
||||
const float dotNV) {
|
||||
if (sheen <= 0.0) return 0.0;
|
||||
float rough = clamp(sheen_rough, 1e-3, 1.0);
|
||||
return sheen * (1.0 - 0.5 * rough) * mix(1.0, dotNV, 0.5);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Anisotropy
|
||||
vec3 anisotropicIBLDirection(const vec3 n, const vec3 v, const vec3 tangent,
|
||||
const float anisotropy, const float roughness) {
|
||||
if (abs(anisotropy) <= 0.001 || dot(tangent, tangent) < 0.001)
|
||||
return reflect(-v, n);
|
||||
vec3 bitangent = normalize(cross(n, tangent));
|
||||
vec3 r = reflect(-v, n);
|
||||
float aniso_abs = abs(anisotropy);
|
||||
vec3 stretchDir = anisotropy > 0.0 ? tangent : bitangent;
|
||||
float stretchAmt = aniso_abs * roughness;
|
||||
return normalize(r + stretchDir * stretchAmt * dot(r, stretchDir) * 0.5);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _Transmission
|
||||
float transmissionIBLFresnel(const float ior, const float dotNV) {
|
||||
return brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotNV + SCHLICK_B) * dotNV);
|
||||
}
|
||||
|
||||
vec3 transmissionIBLDirection(const vec3 n, const vec3 v, const float ior) {
|
||||
float eta = 1.0 / ior;
|
||||
vec3 refrDir = refract(-v, n, eta);
|
||||
if (dot(refrDir, refrDir) < 0.001) {
|
||||
refrDir = reflect(-v, n);
|
||||
}
|
||||
return refrDir;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@ -34,17 +34,19 @@ THE SOFTWARE.
|
||||
// https://research.nvidia.com/sites/default/files/publications/GIVoxels-pg2011-authors.pdf
|
||||
|
||||
const float MAX_DISTANCE = voxelgiRange;
|
||||
const int MAX_CONE_STEPS = 32;
|
||||
|
||||
#ifdef _VoxelGI
|
||||
uniform sampler3D dummy;
|
||||
|
||||
vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCount * 10], const float clipmap_index, const float step_dist, const int precomputed_direction, const vec3 face_offset, const vec3 direction_weight) {
|
||||
vec4 col = vec4(0.0);
|
||||
vec3 tc = (P - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
|
||||
int base = int(clipmap_index * 10);
|
||||
float voxelSize = float(clipmaps[base]);
|
||||
vec3 tc = (P - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
|
||||
vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
tc = tc * 0.5 + 0.5;
|
||||
tc = clamp(tc, half_texel, 1.0 - half_texel);
|
||||
tc.x = (tc.x + precomputed_direction) / (6 + DIFFUSE_CONE_COUNT);
|
||||
tc.x = (tc.x + precomputed_direction) / (6 + diffuseConeCount);
|
||||
tc.y = (tc.y + clipmap_index) / voxelgiClipmapCount;
|
||||
|
||||
if (precomputed_direction == 0) {
|
||||
@ -55,7 +57,7 @@ vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCo
|
||||
else
|
||||
col = textureLod(voxels, tc, 0);
|
||||
|
||||
col *= step_dist / float(clipmaps[int(clipmap_index * 10)]);
|
||||
col *= step_dist / voxelSize;
|
||||
|
||||
return col;
|
||||
}
|
||||
@ -64,11 +66,13 @@ vec4 sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCo
|
||||
#ifdef _VoxelAOvar
|
||||
float sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapCount * 10], const float clipmap_index, const float step_dist, const int precomputed_direction, const vec3 face_offset, const vec3 direction_weight) {
|
||||
float opac = 0.0;
|
||||
vec3 tc = (P - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
|
||||
int base = int(clipmap_index * 10);
|
||||
float voxelSize = float(clipmaps[base]);
|
||||
vec3 tc = (P - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
|
||||
vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
tc = tc * 0.5 + 0.5;
|
||||
tc = clamp(tc, half_texel, 1.0 - half_texel);
|
||||
tc.x = (tc.x + precomputed_direction) / (6 + DIFFUSE_CONE_COUNT);
|
||||
tc.x = (tc.x + precomputed_direction) / (6 + diffuseConeCount);
|
||||
tc.y = (tc.y + clipmap_index) / voxelgiClipmapCount;
|
||||
|
||||
if (precomputed_direction == 0) {
|
||||
@ -79,7 +83,7 @@ float sampleVoxel(sampler3D voxels, vec3 P, const float clipmaps[voxelgiClipmapC
|
||||
else
|
||||
opac = textureLod(voxels, tc, 0).r;
|
||||
|
||||
opac *= step_dist / float(clipmaps[int(clipmap_index * 10)]);
|
||||
opac *= step_dist / voxelSize;
|
||||
|
||||
return opac;
|
||||
}
|
||||
@ -92,7 +96,7 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
||||
float dist = voxelSize0;
|
||||
float step_dist = dist;
|
||||
vec3 samplePos;
|
||||
vec3 start_pos = origin + n * voxelSize0;
|
||||
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
|
||||
int clipmap_index0 = 0;
|
||||
|
||||
vec3 aniso_direction = -dir;
|
||||
@ -100,20 +104,24 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
||||
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||
) / (6 + DIFFUSE_CONE_COUNT);
|
||||
) / (6 + diffuseConeCount);
|
||||
vec3 direction_weight = abs(dir);
|
||||
|
||||
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
||||
|
||||
while (sampleCol.a < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
|
||||
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
int steps = 0;
|
||||
while (sampleCol.a < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
|
||||
vec4 mipSample = vec4(0.0);
|
||||
float diam = max(voxelSize0, dist * coneCoefficient);
|
||||
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
|
||||
float clipmap_index = floor(lod);
|
||||
float clipmap_blend = fract(lod);
|
||||
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
|
||||
vec3 p0 = start_pos + dir * dist;
|
||||
|
||||
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
|
||||
int base = int(clipmap_index * 10);
|
||||
float voxelSize = float(clipmaps[base]);
|
||||
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
|
||||
samplePos = samplePos * 0.5 + 0.5;
|
||||
|
||||
if (any(notEqual(samplePos, clamp(samplePos, 0.0, 1.0)))) {
|
||||
@ -121,19 +129,26 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge fade: blend toward coarser clipmap near boundaries
|
||||
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
|
||||
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
|
||||
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
|
||||
float totalBlend = max(clipmap_blend, edgeBlend);
|
||||
|
||||
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
|
||||
|
||||
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
vec4 mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
|
||||
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
|
||||
int baseNext = int((clipmap_index + 1.0) * 10);
|
||||
float voxelSizeCoarse = float(clipmaps[baseNext]);
|
||||
mipSampleNext *= voxelSizeCoarse / voxelSize;
|
||||
mipSample = mix(mipSample, mipSampleNext, totalBlend);
|
||||
}
|
||||
|
||||
sampleCol += (1.0 - sampleCol.a) * mipSample;
|
||||
|
||||
float stepSizeCurrent = step_size;
|
||||
if (use_sdf) {
|
||||
// half texel correction is applied to avoid sampling over current clipmap:
|
||||
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
vec3 tc0 = clamp(samplePos, half_texel, 1 - half_texel);
|
||||
tc0.y = (tc0.y + clipmap_index) / voxelgiClipmapCount; // remap into clipmap
|
||||
float sdf = textureLod(voxelsSDF, tc0, 0).r;
|
||||
@ -141,6 +156,7 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
||||
}
|
||||
step_dist = diam * stepSizeCurrent;
|
||||
dist += step_dist;
|
||||
steps++;
|
||||
}
|
||||
return sampleCol;
|
||||
}
|
||||
@ -148,18 +164,17 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
||||
vec4 traceDiffuse(const vec3 origin, const vec3 normal, const sampler3D voxels, const float clipmaps[voxelgiClipmapCount * 10]) {
|
||||
float sum = 0.0;
|
||||
vec4 amount = vec4(0.0);
|
||||
mat3 TBN = makeTangentBasis(normal);
|
||||
for (int i = 0; i < DIFFUSE_CONE_COUNT; ++i) {
|
||||
vec3 coneDir = TBN * DIFFUSE_CONE_DIRECTIONS[i];
|
||||
for (int i = 0; i < diffuseConeCount; ++i) {
|
||||
vec3 coneDir = diffuseConeDirections[i];
|
||||
const float cosTheta = dot(normal, coneDir);
|
||||
if (cosTheta <= 0)
|
||||
continue;
|
||||
int precomputed_direction = 6 + i;
|
||||
amount += traceCone(voxels, dummy, origin, normal, coneDir, precomputed_direction, false, DIFFUSE_CONE_APERTURE, 1.0, clipmaps) * cosTheta;
|
||||
amount += traceCone(voxels, voxels, origin, normal, coneDir, precomputed_direction, false, diffuseConeAperture, 1.0, clipmaps) * cosTheta;
|
||||
sum += cosTheta;
|
||||
}
|
||||
|
||||
amount /= sum;
|
||||
amount /= max(sum, 0.0001);
|
||||
amount.rgb = max(amount.rgb, vec3(0.0));
|
||||
amount.a = clamp(amount.a, 0.0, 1.0);
|
||||
|
||||
@ -186,7 +201,7 @@ vec4 traceRefraction(const vec3 origin, const vec3 normal, sampler3D voxels, sam
|
||||
amount.rgb = max(vec3(0.0), amount.rgb);
|
||||
amount.a = clamp(amount.a, 0.0, 1.0);
|
||||
|
||||
return amount * voxelgiOcc;
|
||||
return amount * voxelgiOcc * voxelgiRefr;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -197,7 +212,7 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
||||
float dist = voxelSize0;
|
||||
float step_dist = dist;
|
||||
vec3 samplePos;
|
||||
vec3 start_pos = origin + n * voxelSize0;
|
||||
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
|
||||
int clipmap_index0 = 0;
|
||||
|
||||
vec3 aniso_direction = -dir;
|
||||
@ -205,20 +220,23 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
||||
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||
) / (6 + DIFFUSE_CONE_COUNT);
|
||||
) / (6 + diffuseConeCount);
|
||||
vec3 direction_weight = abs(dir);
|
||||
|
||||
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
||||
|
||||
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
|
||||
int steps = 0;
|
||||
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
|
||||
float mipSample = 0.0;
|
||||
float diam = max(voxelSize0, dist * coneCoefficient);
|
||||
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
|
||||
float clipmap_index = floor(lod);
|
||||
float clipmap_blend = fract(lod);
|
||||
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
|
||||
vec3 p0 = start_pos + dir * dist;
|
||||
|
||||
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution.x);
|
||||
int base = int(clipmap_index * 10);
|
||||
float voxelSize = float(clipmaps[base]);
|
||||
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
|
||||
samplePos = samplePos * 0.5 + 0.5;
|
||||
|
||||
if ((any(notEqual(clamp(samplePos, 0.0, 1.0), samplePos)))) {
|
||||
@ -226,17 +244,27 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge fade: blend toward coarser clipmap near boundaries
|
||||
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
|
||||
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
|
||||
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
|
||||
float totalBlend = max(clipmap_blend, edgeBlend);
|
||||
|
||||
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
|
||||
|
||||
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
|
||||
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
|
||||
int baseNext = int((clipmap_index + 1.0) * 10);
|
||||
float voxelSizeCoarse = float(clipmaps[baseNext]);
|
||||
mipSampleNext *= voxelSizeCoarse / voxelSize;
|
||||
mipSample = mix(mipSample, mipSampleNext, totalBlend);
|
||||
}
|
||||
|
||||
sampleCol += (1.0 - sampleCol) * mipSample;
|
||||
|
||||
step_dist = diam * step_size;
|
||||
dist += step_dist;
|
||||
steps++;
|
||||
}
|
||||
return sampleCol;
|
||||
}
|
||||
@ -245,18 +273,18 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
||||
float traceAO(const vec3 origin, const vec3 normal, const sampler3D voxels, const float clipmaps[voxelgiClipmapCount * 10]) {
|
||||
float sum = 0.0;
|
||||
float amount = 0.0;
|
||||
for (int i = 0; i < DIFFUSE_CONE_COUNT; i++) {
|
||||
vec3 coneDir = DIFFUSE_CONE_DIRECTIONS[i];
|
||||
for (int i = 0; i < diffuseConeCount; i++) {
|
||||
vec3 coneDir = diffuseConeDirections[i];
|
||||
int precomputed_direction = 6 + i;
|
||||
const float cosTheta = dot(normal, coneDir);
|
||||
if (cosTheta <= 0)
|
||||
continue;
|
||||
amount += traceConeAO(voxels, origin, normal, coneDir, precomputed_direction, DIFFUSE_CONE_APERTURE, 1.0, clipmaps) * cosTheta;
|
||||
amount += traceConeAO(voxels, origin, normal, coneDir, precomputed_direction, diffuseConeAperture, 1.0, clipmaps) * cosTheta;
|
||||
sum += cosTheta;
|
||||
}
|
||||
amount /= sum;
|
||||
amount /= max(sum, 0.0001);
|
||||
amount = clamp(amount, 0.0, 1.0);
|
||||
return amount * voxelgiOcc;
|
||||
return amount;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -267,7 +295,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
||||
float dist = voxelSize0;
|
||||
float step_dist = dist;
|
||||
vec3 samplePos;
|
||||
vec3 start_pos = origin + n * voxelSize0;
|
||||
vec3 start_pos = origin + n * voxelSize0 * voxelgiOffset;
|
||||
int clipmap_index0 = 0;
|
||||
|
||||
vec3 aniso_direction = -dir;
|
||||
@ -275,19 +303,23 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
||||
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||
) / (6 + DIFFUSE_CONE_COUNT);
|
||||
) / (6 + diffuseConeCount);
|
||||
vec3 direction_weight = abs(dir);
|
||||
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
||||
|
||||
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount) {
|
||||
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
int steps = 0;
|
||||
while (sampleCol < 1.0 && dist < MAX_DISTANCE && clipmap_index0 < voxelgiClipmapCount && steps < MAX_CONE_STEPS) {
|
||||
float mipSample = 0.0;
|
||||
float diam = max(voxelSize0, dist * coneCoefficient);
|
||||
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
|
||||
float clipmap_index = floor(lod);
|
||||
float clipmap_blend = fract(lod);
|
||||
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
|
||||
vec3 p0 = start_pos + dir * dist;
|
||||
|
||||
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
|
||||
int base = int(clipmap_index * 10);
|
||||
float voxelSize = float(clipmaps[base]);
|
||||
samplePos = (p0 - vec3(clipmaps[base + 4], clipmaps[base + 5], clipmaps[base + 6])) / (voxelSize * voxelgiResolution);
|
||||
samplePos = samplePos * 0.5 + 0.5;
|
||||
|
||||
if ((any(notEqual(samplePos, clamp(samplePos, 0.0, 1.0))))) {
|
||||
@ -295,27 +327,34 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge fade: blend toward coarser clipmap near boundaries
|
||||
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
|
||||
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
|
||||
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
|
||||
float totalBlend = max(clipmap_blend, edgeBlend);
|
||||
|
||||
#ifdef _VoxelAOvar
|
||||
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, 0, face_offset, direction_weight);
|
||||
#else
|
||||
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, 0, face_offset, direction_weight).a;
|
||||
#endif
|
||||
|
||||
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
if(totalBlend > 0.05 && clipmap_index < voxelgiClipmapCount - 1) {
|
||||
int baseNext = int((clipmap_index + 1.0) * 10);
|
||||
float voxelSizeCoarse = float(clipmaps[baseNext]);
|
||||
float scaleRatio = voxelSizeCoarse / voxelSize;
|
||||
#ifdef _VoxelAOvar
|
||||
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight);
|
||||
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight) * scaleRatio;
|
||||
#else
|
||||
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight).a;
|
||||
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight).a * scaleRatio;
|
||||
#endif
|
||||
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
|
||||
mipSample = mix(mipSample, mipSampleNext, totalBlend);
|
||||
}
|
||||
|
||||
sampleCol += (1.0 - sampleCol) * mipSample;
|
||||
|
||||
float stepSizeCurrent = step_size;
|
||||
|
||||
// half texel correction is applied to avoid sampling over current clipmap:
|
||||
const vec3 half_texel = vec3(0.5) / voxelgiResolution;
|
||||
vec3 tc0 = clamp(samplePos, half_texel, 1 - half_texel);
|
||||
tc0.y = (tc0.y + clipmap_index) / voxelgiClipmapCount; // remap into clipmap
|
||||
float sdf = textureLod(voxelsSDF, tc0, 0.0).r;
|
||||
@ -323,6 +362,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
||||
|
||||
step_dist = diam * stepSizeCurrent;
|
||||
dist += step_dist;
|
||||
steps++;
|
||||
}
|
||||
return sampleCol;
|
||||
}
|
||||
@ -330,7 +370,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
||||
|
||||
float traceShadow(const vec3 origin, const vec3 normal, const sampler3D voxels, const sampler3D voxelsSDF, const vec3 dir, const float clipmaps[voxelgiClipmapCount * 10], const vec2 pixel, const vec2 velocity) {
|
||||
vec3 P = origin + dir * (BayerMatrix8[int(pixel.x + velocity.x) % 8][int(pixel.y + velocity.y) % 8] - 0.5) * voxelgiStep;
|
||||
float amount = traceConeShadow(voxels, voxelsSDF, P, normal, dir, SHADOW_CONE_APERTURE, voxelgiStep, clipmaps);
|
||||
float amount = traceConeShadow(voxels, voxelsSDF, P, normal, dir, voxelgiAperture, voxelgiStep, clipmaps);
|
||||
amount = clamp(amount, 0.0, 1.0);
|
||||
return amount * voxelgiOcc;
|
||||
}
|
||||
|
||||
@ -20,46 +20,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const int DIFFUSE_CONE_COUNT = 16;
|
||||
const float diffuseConeAperture = radians(39.0);
|
||||
|
||||
const float SHADOW_CONE_APERTURE = radians(15.0);
|
||||
|
||||
const float DIFFUSE_CONE_APERTURE = 0.872665;
|
||||
|
||||
mat3 makeTangentBasis(const vec3 normal) {
|
||||
// Create a tangent basis from normal vector
|
||||
vec3 tangent;
|
||||
vec3 bitangent;
|
||||
|
||||
// Compute tangent (Frisvad's method)
|
||||
if (abs(normal.z) < 0.999) {
|
||||
tangent = normalize(cross(vec3(0, 1, 0), normal));
|
||||
} else {
|
||||
tangent = normalize(cross(normal, vec3(1, 0, 0)));
|
||||
}
|
||||
bitangent = cross(normal, tangent);
|
||||
|
||||
return mat3(tangent, bitangent, normal);
|
||||
}
|
||||
|
||||
// 16 optimized cone directions for hemisphere sampling (Z-up, normalized)
|
||||
const vec3 DIFFUSE_CONE_DIRECTIONS[16] = vec3[](
|
||||
vec3(0.707107, 0.000000, 0.707107), // Front
|
||||
vec3(-0.707107, 0.000000, 0.707107), // Back
|
||||
vec3(0.000000, 0.707107, 0.707107), // Right
|
||||
vec3(0.000000, -0.707107, 0.707107), // Left
|
||||
vec3(0.500000, 0.500000, 0.707107), // Front-right
|
||||
vec3(-0.500000, 0.500000, 0.707107), // Back-right
|
||||
vec3(0.500000, -0.500000, 0.707107), // Front-left
|
||||
vec3(-0.500000, -0.500000, 0.707107),// Back-left
|
||||
vec3(0.353553, 0.000000, 0.935414), // Narrow front
|
||||
vec3(-0.353553, 0.000000, 0.935414), // Narrow back
|
||||
vec3(0.000000, 0.353553, 0.935414), // Narrow right
|
||||
vec3(0.000000, -0.353553, 0.935414), // Narrow left
|
||||
vec3(0.270598, 0.270598, 0.923880), // Narrow front-right
|
||||
vec3(-0.270598, 0.270598, 0.923880), // Narrow back-right
|
||||
vec3(0.270598, -0.270598, 0.923880), // Narrow front-left
|
||||
vec3(-0.270598, -0.270598, 0.923880) // Narrow back-left
|
||||
const vec3 diffuseConeDirections[16] = vec3[](
|
||||
vec3( 0.3480, 0.0000, 0.9375),
|
||||
vec3(-0.4299, 0.3938, 0.8125),
|
||||
vec3( 0.0635, -0.7234, 0.6875),
|
||||
vec3( 0.5031, 0.6561, 0.5625),
|
||||
vec3(-0.8855, -0.1566, 0.4375),
|
||||
vec3( 0.8015, -0.5098, 0.3125),
|
||||
vec3(-0.2550, 0.9486, 0.1875),
|
||||
vec3(-0.4600, -0.8857, 0.0625),
|
||||
vec3( 0.9375, 0.3424, -0.0625),
|
||||
vec3(-0.9080, 0.3748, -0.1875),
|
||||
vec3( 0.4026, -0.8604, -0.3125),
|
||||
vec3( 0.2691, 0.8580, -0.4375),
|
||||
vec3(-0.7154, -0.4146, -0.5625),
|
||||
vec3( 0.7092, -0.1559, -0.6875),
|
||||
vec3(-0.3353, 0.4769, -0.8125),
|
||||
vec3(-0.0447, -0.3451, -0.9375)
|
||||
);
|
||||
|
||||
// TO DO - Disabled momentarily instead of changing formulas
|
||||
|
||||
@ -10,9 +10,9 @@
|
||||
|
||||
const int samples = 8; // Samples on the first ring
|
||||
const int rings = 6; // Ring count
|
||||
const vec2 focus = vec2(0.5, 0.5);
|
||||
//const vec2 focus = vec2(0.5, 0.5);
|
||||
const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm)
|
||||
const float maxblur = 1.0;
|
||||
//const float maxblur = 1.0;
|
||||
const float threshold = 0.5; // Highlight threshold
|
||||
const float gain = 2.0; // Highlight gain
|
||||
const float bias = 0.5; // Bokeh edge bias
|
||||
@ -41,7 +41,9 @@ vec3 dof(
|
||||
const bool autoFocus,
|
||||
const float DOFDistance,
|
||||
const float DOFLength,
|
||||
const float DOFFStop) {
|
||||
const float DOFFStop,
|
||||
const vec2 focus,
|
||||
const float maxblur) {
|
||||
|
||||
float depth = linearize(gdepth, cameraProj);
|
||||
float fDepth = 0.0;
|
||||
@ -85,7 +87,6 @@ vec3 dof(
|
||||
float pw = (cos(float(j) * step) * float(i));
|
||||
float ph = (sin(float(j) * step) * float(i));
|
||||
float p = 1.0;
|
||||
// if (pentagon) p = penta(vec2(pw, ph));
|
||||
blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
|
||||
s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p;
|
||||
}
|
||||
|
||||
@ -170,4 +170,88 @@ void unpackFloatInt16(float val, out float f, out uint i) {
|
||||
f = (bitsValue & ~(0xF << numBitFloat)) / maxValFloat;
|
||||
}
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
// extended material parameters by material slot ID returns vec4s (28 floats) of extended BRDF parameters
|
||||
void getMaterialParams(uint matid, out vec4 p0, out vec4 p1, out vec4 p2, out vec4 p3,
|
||||
out vec4 p4, out vec4 p5, out vec4 p6, out vec4 p7) {
|
||||
uint base = matid * 8u;
|
||||
#if defined(_Anisotropy) || defined(_Sheen)
|
||||
p0 = materialParams[base];
|
||||
#else
|
||||
p0 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_ClearCoat)
|
||||
p1 = materialParams[base + 1u];
|
||||
#else
|
||||
p1 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_ClearCoat) || defined(_Transmission)
|
||||
p2 = materialParams[base + 2u];
|
||||
#else
|
||||
p2 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_Transmission) || defined(_SSS)
|
||||
p3 = materialParams[base + 3u];
|
||||
#else
|
||||
p3 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_SSS)
|
||||
p4 = materialParams[base + 4u];
|
||||
#else
|
||||
p4 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_Sheen) || defined(_SSS)
|
||||
p5 = materialParams[base + 5u];
|
||||
#else
|
||||
p5 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_ExtBRDF)
|
||||
p6 = materialParams[base + 6u];
|
||||
#else
|
||||
p6 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(_SSS)
|
||||
p7 = materialParams[base + 7u];
|
||||
#else
|
||||
p7 = vec4(0.0);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
float packIOR(float ior) {
|
||||
return clamp((ior - 1.0) / 1.5, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float unpackIOR(float packed) {
|
||||
return packed * 1.5 + 1.0;
|
||||
}
|
||||
|
||||
#ifndef PI
|
||||
#define PI 3.1415926535
|
||||
#endif
|
||||
#ifndef PI2
|
||||
#define PI2 6.2831853071
|
||||
#endif
|
||||
|
||||
float encodeTangent(vec3 tangent, vec3 normal) {
|
||||
if (length(tangent) < 0.5) return -1.0;
|
||||
vec3 t = normalize(tangent);
|
||||
vec3 n = normalize(normal);
|
||||
vec3 ref = abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 r = normalize(ref - n * dot(ref, n));
|
||||
vec3 b = cross(n, r);
|
||||
float angle = atan(dot(t, b), dot(t, r));
|
||||
return (angle / (2.0 * PI) + 0.5);
|
||||
}
|
||||
|
||||
vec3 decodeTangent(float enc, vec3 normal) {
|
||||
if (enc < 0.0) return vec3(1.0, 0.0, 0.0);
|
||||
vec3 n = normalize(normal);
|
||||
vec3 ref = abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 r = normalize(ref - n * dot(ref, n));
|
||||
vec3 b = cross(n, r);
|
||||
float angle = (enc - 0.5) * 2.0 * PI;
|
||||
return normalize(r * cos(angle) + b * sin(angle));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,9 @@ uniform sampler2D texIES;
|
||||
|
||||
float iesAttenuation(vec3 l) {
|
||||
|
||||
const float PI = 3.1415926535;
|
||||
#ifndef PI
|
||||
#define PI 3.1415926535
|
||||
#endif
|
||||
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
|
||||
// Sample direction into light space
|
||||
// vec3 iesSampleDirection = mul(light.worldToLight , -L);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,31 +14,54 @@
|
||||
#ifdef _SinglePoint
|
||||
#ifdef _Spot
|
||||
uniform sampler2DShadow shadowMapSpot[1];
|
||||
uniform mat4 LWVPSpot[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapSpotTransparent[1];
|
||||
#endif
|
||||
uniform mat4 LWVPSpotArray[1];
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform samplerCube shadowMapPointTransparent[1];
|
||||
#endif
|
||||
uniform vec2 lightProj;
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlas;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#ifndef _SinglePoint
|
||||
uniform vec2 lightProj;
|
||||
#endif
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasPoint;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasPointTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[4];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform samplerCube shadowMapPointTransparent[4];
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSpot;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasSpotTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
@ -49,23 +72,71 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
const vec3 albedo, const float rough, const float spec, const vec3 f0
|
||||
#ifdef _ShadowMap
|
||||
, int index, float bias, bool receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, bool transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, float clearcoat, float clearcoatRough, float coatIOR, vec3 coatTint, vec3 coatN
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, float sheen, float sheenRough, vec3 sheenTint
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, float anisotropy, float anisoRot, vec3 tangent
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, float subsurface, vec3 sssColor, vec3 sssRadius, float sssAnisotropy
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, float transmission, float transRough, float ior, float thinWall
|
||||
#endif
|
||||
) {
|
||||
vec3 ld = lp - p;
|
||||
vec3 l = normalize(ld);
|
||||
float dist = length(ld);
|
||||
vec3 l = ld / dist;
|
||||
vec3 h = normalize(v + l);
|
||||
float dotNH = max(0.0, dot(n, h));
|
||||
float dotVH = max(0.0, dot(v, h));
|
||||
float dotNL = max(0.0, dot(n, l));
|
||||
|
||||
vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
|
||||
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
|
||||
#ifdef _Anisotropy
|
||||
vec3 direct;
|
||||
if (abs(anisotropy) > 0.001 && dot(tangent, tangent) > 0.001) {
|
||||
vec3 bitangent = normalize(cross(n, tangent));
|
||||
direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
|
||||
anisotropicBRDF(f0, rough, anisotropy, anisoRot,
|
||||
tangent, bitangent, n, l, v, dotNL, dotNV) * spec;
|
||||
} else {
|
||||
direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
|
||||
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
|
||||
}
|
||||
#else
|
||||
vec3 direct = diffuseBRDF(albedo, rough, f0, dotNL, dotNV, dotVH) +
|
||||
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
|
||||
#endif
|
||||
|
||||
#ifdef _ExtBRDF
|
||||
float layerWeight;
|
||||
direct = applyExtBRDFLayers(direct, albedo, f0, rough, dotNL, dotNV, dotNH, dotVH, n, l, v, h
|
||||
#ifdef _ClearCoat
|
||||
, clearcoat, clearcoatRough, coatIOR, coatTint, coatN
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, sheen, sheenRough, sheenTint
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, transmission, transRough, ior, thinWall
|
||||
#endif
|
||||
, layerWeight
|
||||
);
|
||||
#endif
|
||||
|
||||
direct *= lightCol;
|
||||
direct *= attenuate(distance(p, lp));
|
||||
direct *= attenuate(dist);
|
||||
|
||||
#ifdef _Spot
|
||||
if (isSpot) {
|
||||
@ -74,25 +145,77 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= shadowTest(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
tileBounds = tileBoundsSpotArray[index];
|
||||
direct *= shadowTest(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias);
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -105,25 +228,76 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= PCFFakeCube(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
);
|
||||
direct *= PCFFakeCube(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n);
|
||||
else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n);
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[1],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[2],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -132,4 +306,47 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
return direct;
|
||||
}
|
||||
|
||||
// Backward-compatible overload for generated shaders that don't pass extended BRDF params
|
||||
#ifdef _ExtBRDF
|
||||
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,
|
||||
const vec3 albedo, const float rough, const float spec, const vec3 f0
|
||||
#ifdef _ShadowMap
|
||||
, int index, float bias, bool receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, bool transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
|
||||
#endif
|
||||
) {
|
||||
return sampleLight(p, n, v, dotNV, lp, lightCol, albedo, rough, spec, f0
|
||||
#ifdef _ShadowMap
|
||||
, index, bias, receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, isSpot, spotSize, spotBlend, spotDir, scale, right
|
||||
#endif
|
||||
#ifdef _ClearCoat
|
||||
, 0.0, 0.0, 1.5, vec3(1.0), n
|
||||
#endif
|
||||
#ifdef _Sheen
|
||||
, 0.0, 0.0, vec3(1.0)
|
||||
#endif
|
||||
#ifdef _Anisotropy
|
||||
, 0.0, 0.0, vec3(0.0)
|
||||
#endif
|
||||
#ifdef _SSS
|
||||
, 0.0, vec3(0.0), vec3(0.0), 0.0
|
||||
#endif
|
||||
#ifdef _Transmission
|
||||
, 0.0, 0.0, 1.45, 1.0
|
||||
#endif
|
||||
);
|
||||
}
|
||||
#endif // _ExtBRDF
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,41 +1,81 @@
|
||||
/*
|
||||
https://github.com/JonasFolletete/glsl-triplanar-mapping
|
||||
vec4 boxProjection(sampler2D image, vec3 normal, vec3 coord, float blend) {
|
||||
vec3 n = normalize(normal);
|
||||
vec3 N = abs(n);
|
||||
vec4 color1, color2, color3;
|
||||
|
||||
MIT License
|
||||
vec2 uv = coord.yz;
|
||||
if (n.x < 0.0) {
|
||||
uv.x = 1.0 - uv.x;
|
||||
}
|
||||
color1 = texture(image, uv);
|
||||
|
||||
Copyright (c) 2018 Jonas Folletête
|
||||
uv = coord.xz;
|
||||
if (n.y > 0.0) {
|
||||
uv.x = 1.0 - uv.x;
|
||||
}
|
||||
color2 = texture(image, uv);
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
uv = vec2(coord.y, 1.0 - coord.x);
|
||||
if (n.z > 0.0) {
|
||||
uv.x = 1.0 - uv.x;
|
||||
}
|
||||
color3 = texture(image, uv);
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
N /= max(dot(N, vec3(1.0)), 1e-8);
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
float limit = 0.5 + 0.5 * clamp(blend, 0.0, 1.0);
|
||||
vec3 weight;
|
||||
weight = N.xyz / (N.xyx + N.yzz);
|
||||
weight = clamp((weight - 0.5 * (1.0 - clamp(blend, 0.0, 1.0))) / max(1e-8, clamp(blend, 0.0, 1.0)), 0.0, 1.0);
|
||||
|
||||
vec3 blendNormal(vec3 normal) {
|
||||
vec3 blending = abs(normal);
|
||||
blending = normalize(max(blending, 0.00001));
|
||||
blending /= vec3(blending.x + blending.y + blending.z);
|
||||
return blending;
|
||||
if (N.z < (1.0 - limit) * (N.y + N.x)) {
|
||||
weight.z = 0.0;
|
||||
weight.y = 1.0 - weight.x;
|
||||
}
|
||||
else if (N.x < (1.0 - limit) * (N.y + N.z)) {
|
||||
weight.x = 0.0;
|
||||
weight.z = 1.0 - weight.y;
|
||||
}
|
||||
else if (N.y < (1.0 - limit) * (N.x + N.z)) {
|
||||
weight.y = 0.0;
|
||||
weight.x = 1.0 - weight.z;
|
||||
}
|
||||
else {
|
||||
weight = ((2.0 - limit) * N + (limit - 1.0)) / max(1e-8, clamp(blend, 0.0, 1.0));
|
||||
}
|
||||
|
||||
return color1 * weight.x + color2 * weight.y + color3 * weight.z;
|
||||
}
|
||||
|
||||
vec3 triplanarMapping (sampler2D ImageTexture, vec3 normal, vec3 position) {
|
||||
vec3 normalBlend = blendNormal(normal);
|
||||
vec3 xColor = texture(ImageTexture, position.yz).rgb;
|
||||
vec3 yColor = texture(ImageTexture, position.xz).rgb;
|
||||
vec3 zColor = texture(ImageTexture, position.xy).rgb;
|
||||
|
||||
return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
|
||||
vec2 sphericalMapping(vec3 coord) {
|
||||
vec3 vin = coord * 2.0 - vec3(1.0);
|
||||
float len = length(vin);
|
||||
float v, u;
|
||||
if (len > 0.0) {
|
||||
if (vin.x == 0.0 && vin.y == 0.0) {
|
||||
u = 0.0;
|
||||
}
|
||||
else {
|
||||
u = (1.0 - atan(vin.x, vin.y) / PI) * 0.5;
|
||||
}
|
||||
v = acos(clamp(vin.z / len, -1.0, 1.0)) / PI;
|
||||
}
|
||||
else {
|
||||
v = u = 0.0;
|
||||
}
|
||||
return vec2(u, v);
|
||||
}
|
||||
|
||||
vec2 tubeMapping(vec3 coord) {
|
||||
vec3 vin = coord * 2.0 - vec3(1.0);
|
||||
float u, v;
|
||||
v = - (vin.z + 1.0) * 0.5;
|
||||
float len = sqrt(vin.x * vin.x + vin.y * vin.y);
|
||||
if (len > 0.0) {
|
||||
u = (1.0 - (atan(vin.x / len, vin.y / len) / PI)) * 0.5;
|
||||
}
|
||||
else {
|
||||
v = u = 0.0;
|
||||
}
|
||||
return vec2(u, v);
|
||||
}
|
||||
|
||||
@ -8,13 +8,25 @@ float hash(const vec2 p) {
|
||||
}
|
||||
|
||||
vec2 envMapEquirect(const vec3 normal) {
|
||||
const float PI = 3.1415926535;
|
||||
const float PI2 = PI * 2.0;
|
||||
#ifndef PI
|
||||
#define PI 3.1415926535
|
||||
#endif
|
||||
#ifndef PI2
|
||||
#define PI2 6.2831853071
|
||||
#endif
|
||||
float phi = acos(normal.z);
|
||||
float theta = atan(-normal.y, normal.x) + PI;
|
||||
return vec2(theta / PI2, phi / PI);
|
||||
}
|
||||
|
||||
vec2 envMapMirror(const vec3 co) {
|
||||
vec3 nco = normalize(co);
|
||||
nco.y -= 1.0;
|
||||
float div = 2.0 * sqrt(max(-0.5 * nco.y, 0.0));
|
||||
nco /= max(1e-8, div);
|
||||
return 0.5 * nco.xz + 0.5;
|
||||
}
|
||||
|
||||
float rand(const vec2 co) { // Unreliable
|
||||
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
@ -4,6 +4,36 @@ uniform vec2 morphScaleOffset;
|
||||
uniform vec2 morphDataDim;
|
||||
uniform vec4 morphWeights[8];
|
||||
|
||||
void getMorphedVertex(vec2 uvCoord, inout vec3 A, vec4 imorph) {
|
||||
vec3 morph = texture(morphDataPos, uvCoord).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += imorph.x * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += imorph.y * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += imorph.z * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += imorph.w * morph;
|
||||
}
|
||||
|
||||
void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor, vec4 imorph) {
|
||||
vec3 norm = oldNor + imorph.x * (texture(morphDataNor, uvCoord).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + imorph.y * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + imorph.z * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + imorph.w * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
morphNor = normalize(morphNor);
|
||||
}
|
||||
|
||||
void getMorphedVertex(vec2 uvCoord, inout vec3 A){
|
||||
vec3 totalDelta = vec3(0.0);
|
||||
for(int i = 0; i<8; i++ )
|
||||
|
||||
@ -22,6 +22,14 @@ uniform vec2 smSizeUniform;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
|
||||
#if defined(_Clusters) && defined(_Spot) && defined(_ShadowMap)
|
||||
uniform vec4 tileBoundsSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
vec4 tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
// PCF that clamps samples to tile boundaries to prevent bleeding
|
||||
vec3 PCFTileAware(sampler2DShadow shadowMap,
|
||||
@ -67,9 +75,15 @@ vec3 PCFTileAware(sampler2DShadow shadowMap,
|
||||
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
|
||||
if (shadowmap_transparent.a < compare)
|
||||
result *= shadowmap_transparent.rgb;
|
||||
vec3 transResult = vec3(0.0);
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
vec4 smt = texture(shadowMapTransparent,
|
||||
clamp(uv + vec2(x, y) / smSize, tileMin, tileMax));
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
}
|
||||
}
|
||||
result *= transResult / 9.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -134,9 +148,15 @@ vec3 PCF(sampler2DShadow shadowMap,
|
||||
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
|
||||
if (shadowmap_transparent.a < compare)
|
||||
result *= shadowmap_transparent.rgb;
|
||||
vec3 transResult = vec3(0.0);
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
vec4 smt = texture(shadowMapTransparent,
|
||||
uv + vec2(x, y) / smSize);
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
}
|
||||
}
|
||||
result *= transResult / 9.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -179,9 +199,19 @@ vec3 PCFCube(samplerCubeShadow shadowMapCube,
|
||||
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec4 shadowmap_transparent = texture(shadowMapCubeTransparent, ml);
|
||||
if (shadowmap_transparent.a < compare)
|
||||
result *= shadowmap_transparent.rgb;
|
||||
vec3 transResult = vec3(0.0);
|
||||
vec4 smt = texture(shadowMapCubeTransparent, ml);
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
for (int x = -1; x <= 1; x += 2) {
|
||||
for (int y = -1; y <= 1; y += 2) {
|
||||
for (int z = -1; z <= 1; z += 2) {
|
||||
smt = texture(shadowMapCubeTransparent,
|
||||
ml + vec3(x, y, z) * s);
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
result *= transResult / 9.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -291,13 +321,13 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
const vec2 smSize = smSizeUniform; // TODO: incorrect...
|
||||
const float compare = lpToDepth(lp, lightProj) - bias * 1.5;
|
||||
ml = ml + n * bias * 20;
|
||||
int faceIndex = 0;
|
||||
const int lightIndex = index * 6;
|
||||
const vec2 uv = sampleCube(ml, faceIndex);
|
||||
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
||||
const vec2 smSize = smSizeUniform; // TODO: incorrect...
|
||||
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
|
||||
#ifdef _FlipY
|
||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||
@ -377,9 +407,15 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec4 shadowmap_transparent = texture(shadowMapTransparent, uvtiled);
|
||||
if (shadowmap_transparent.a < compare)
|
||||
result *= shadowmap_transparent.rgb;
|
||||
vec3 transResult = vec3(0.0);
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
vec4 smt = texture(shadowMapTransparent,
|
||||
clamp(uvtiled + vec2(x, y) / smSize, 0.0, 1.0));
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
}
|
||||
}
|
||||
result *= transResult / 9.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -387,10 +423,6 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform vec4 tileBounds;
|
||||
#endif
|
||||
|
||||
vec3 shadowTest(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
@ -405,9 +437,9 @@ vec3 shadowTest(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapAtlas
|
||||
// use tile PCF
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSizeAtlas = smSizeUniform;
|
||||
vec2 smSizeAtlas = smSizeUniform * (tileBounds.zw - tileBounds.xy);
|
||||
#else
|
||||
const vec2 smSizeAtlas = shadowmapSize;
|
||||
vec2 smSizeAtlas = shadowmapSize * (tileBounds.zw - tileBounds.xy);
|
||||
#endif
|
||||
return PCFTileAware(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
@ -455,7 +487,7 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
|
||||
float(d > casData[c * 4].y),
|
||||
float(d > casData[c * 4].z),
|
||||
float(d > casData[c * 4].w));
|
||||
casi = int(min(dot(ci, comp), c));
|
||||
casi = int(min(dot(ci, comp), float(c - 1)));
|
||||
// Get cascade mat
|
||||
casIndex = casi * 4;
|
||||
return mat4(
|
||||
@ -479,8 +511,12 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSize = smSizeUniform;
|
||||
#else
|
||||
#ifdef _ShadowMapAtlas
|
||||
vec2 smSize = shadowmapSize * (tileBoundsSunArray[0].zw - tileBoundsSunArray[0].xy);
|
||||
#else
|
||||
const vec2 smSize = shadowmapSize * vec2(shadowmapCascades, 1.0);
|
||||
#endif
|
||||
#endif
|
||||
const int c = shadowmapCascades;
|
||||
float d = distance(eye, p);
|
||||
int casi;
|
||||
@ -489,16 +525,35 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
vec4 lPos = LWVP * vec4(p, 1.0);
|
||||
lPos.xyz /= lPos.w;
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSunArray[casi];
|
||||
#endif
|
||||
|
||||
vec3 visibility = vec3(1.0);
|
||||
if (lPos.w > 0.0) visibility = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
if (lPos.w > 0.0) {
|
||||
#ifdef _ShadowMapAtlas
|
||||
visibility = PCFTileAware(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
visibility = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Blend cascade
|
||||
// https://github.com/TheRealMJP/Shadows
|
||||
@ -518,15 +573,33 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
lPos2.xyz /= lPos2.w;
|
||||
vec3 visibility2 = vec3(1.0);
|
||||
// use lPos2 coordinates for second cascade, not lPos
|
||||
if (lPos2.w > 0.0) visibility2 = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSunArray[casi + 1];
|
||||
#endif
|
||||
if (lPos2.w > 0.0) {
|
||||
#ifdef _ShadowMapAtlas
|
||||
visibility2 = PCFTileAware(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
visibility2 = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
float lerpAmt = smoothstep(0.0, blendThres, splitDist);
|
||||
return mix(visibility2, visibility, lerpAmt);
|
||||
|
||||
@ -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,43 +22,43 @@
|
||||
|
||||
#include "std/math.glsl"
|
||||
|
||||
uniform sampler2D nishitaLUT;
|
||||
uniform vec2 nishitaDensity;
|
||||
uniform sampler2D singleScatterLUT;
|
||||
uniform vec2 skyDensity;
|
||||
|
||||
#ifndef PI
|
||||
#define PI 3.141592
|
||||
#define PI 3.1415926535
|
||||
#endif
|
||||
#ifndef HALF_PI
|
||||
#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,83 @@ 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 half_diameter = max(angular_diameter * 0.5, 0.0005);
|
||||
float dist = distance(dir, sun_dir) / half_diameter;
|
||||
float edge = smoothstep(1.0, 0.95, dist);
|
||||
if (edge <= 0.0) return vec3(0.0);
|
||||
|
||||
float horizon = multiScatterSunBottom.w;
|
||||
float dir_elev = asin(clamp(dir.z, -1.0, 1.0));
|
||||
float horizon_fade = smoothstep(horizon - half_diameter * 0.1, horizon + half_diameter * 0.1, dir_elev);
|
||||
edge *= horizon_fade;
|
||||
if (edge <= 0.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 t = clamp((dir_elev - (sun_elev - half_diameter)) / (2.0 * half_diameter), 0.0, 1.0);
|
||||
vec3 sun_color = mix(multiScatterSunBottom.rgb, multiScatterSunTop, t) * intensity * limb_darkening;
|
||||
|
||||
return xyz_to_rgb(sun_color) * edge;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -1,15 +1,25 @@
|
||||
|
||||
// Separable SSS Transmittance Function, ref to sss_pass
|
||||
vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sampler2DShadow shadowMap) {
|
||||
const float translucency = 1.0;
|
||||
vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sampler2DShadow shadowMap, vec3 sssColor, float sssRadius
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4 tileBounds
|
||||
#endif
|
||||
) {
|
||||
const float translucency = 0.85;
|
||||
vec4 shrinkedPos = vec4(p - 0.005 * n, 1.0);
|
||||
vec4 shadowPos = LWVP * shrinkedPos;
|
||||
float scale = 8.25 * (1.0 - translucency) / (sssWidth / 10.0);
|
||||
float d1 = texture(shadowMap, vec3(shadowPos.xy / shadowPos.w, shadowPos.z)).r; // 'd1' has a range of 0..1
|
||||
float d2 = shadowPos.z; // 'd2' has a range of 0..'lightFarPlane'
|
||||
d1 *= lightFar; // So we scale 'd1' accordingly:
|
||||
float d = scale * abs(d1 - d2);
|
||||
vec2 shadowUV = shadowPos.xy / shadowPos.w;
|
||||
#ifdef _ShadowMapAtlas
|
||||
shadowUV = clamp(shadowUV, tileBounds.xy, tileBounds.zw);
|
||||
#endif
|
||||
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
|
||||
float d1 = texture(shadowMap, vec3(shadowUV, shadowPos.z)).r;
|
||||
float d2 = shadowPos.z;
|
||||
d1 *= lightFar;
|
||||
d2 *= lightFar;
|
||||
float d = scale * abs(d1 - d2) * 1000.0;
|
||||
|
||||
if (d > 10.0) return vec3(0.0);
|
||||
float dd = -d * d;
|
||||
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
|
||||
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
|
||||
@ -17,10 +27,70 @@ vec3 SSSSTransmittance(mat4 LWVP, vec3 p, vec3 n, vec3 l, float lightFar, sample
|
||||
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
|
||||
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
|
||||
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
|
||||
return profile * clamp(0.3 + dot(l, -n), 0.0, 1.0);
|
||||
profile *= mix(vec3(1.0), sssColor, 0.8);
|
||||
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 SSSSTransmittanceCube(float translucency, vec4 shadowPos, vec3 n, vec3 l, float lightFar) {
|
||||
// TODO
|
||||
return vec3(0.0);
|
||||
#ifdef _ShadowMapAtlas
|
||||
vec3 SSSSTransmittanceCubeAtlas(sampler2DShadow shadowMap, vec3 lightPos, vec3 p, vec3 n, vec3 l, float lightFar, vec2 lightProj, int index, vec3 sssColor, float sssRadius) {
|
||||
const float translucency = 0.85;
|
||||
vec3 shrinkedPos = p - 0.005 * n;
|
||||
vec3 ld = normalize(shrinkedPos - lightPos);
|
||||
#ifdef _InvY
|
||||
ld.y = -ld.y;
|
||||
#endif
|
||||
float d2 = lpToDepth(ld, lightProj);
|
||||
int faceIndex = 0;
|
||||
int lightIndex = index * 6;
|
||||
vec2 uv = sampleCube(ld, faceIndex);
|
||||
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex];
|
||||
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
|
||||
#ifdef _FlipY
|
||||
uvtiled.y = 1.0 - uvtiled.y;
|
||||
#endif
|
||||
float d1 = texture(shadowMap, vec3(uvtiled, d2)).r;
|
||||
d1 *= lightFar;
|
||||
d2 *= lightFar;
|
||||
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
|
||||
// d1/d2 are in meters, sssRadius is in mm, exponential constants are in mm^2
|
||||
float d = scale * abs(d1 - d2) * 1000.0; // Convert distance to mm
|
||||
|
||||
if (d > 10.0) return vec3(0.0);
|
||||
float dd = -d * d;
|
||||
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
|
||||
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
|
||||
vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
|
||||
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
|
||||
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
|
||||
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
|
||||
profile *= mix(vec3(1.0), sssColor, 0.8);
|
||||
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
vec3 SSSSTransmittanceCube(samplerCubeShadow shadowMapCube, vec3 lightPos, vec3 p, vec3 n, vec3 l, float lightFar, vec2 lightProj, vec3 sssColor, float sssRadius) {
|
||||
const float translucency = 0.85;
|
||||
vec3 shrinkedPos = p - 0.005 * n;
|
||||
vec3 ld = normalize(shrinkedPos - lightPos);
|
||||
#ifdef _InvY
|
||||
ld.y = -ld.y;
|
||||
#endif
|
||||
float d2 = lpToDepth(ld, lightProj);
|
||||
float d1 = texture(shadowMapCube, vec4(ld, d2)).r;
|
||||
d1 *= lightFar;
|
||||
d2 *= lightFar;
|
||||
float scale = 2.5 * (1.0 - translucency) / max(sssRadius, 0.001);
|
||||
// d1/d2 are in meters, sssRadius is in mm, exponential constants are in mm^2
|
||||
float d = scale * abs(d1 - d2) * 1000.0; // Convert distance to mm
|
||||
|
||||
if (d > 10.0) return vec3(0.0);
|
||||
float dd = -d * d;
|
||||
vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
|
||||
vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
|
||||
vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
|
||||
vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
|
||||
vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
|
||||
vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
|
||||
profile *= mix(vec3(1.0), sssColor, 0.8);
|
||||
return profile * clamp(0.5 + dot(l, -n), 0.0, 1.0);
|
||||
}
|
||||
|
||||
@ -1,136 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout (local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
|
||||
|
||||
#include "compiled.inc"
|
||||
#include "std/math.glsl"
|
||||
#include "std/gbuffer.glsl"
|
||||
#include "std/imageatomic.glsl"
|
||||
#ifdef _VoxelShadow
|
||||
#include "std/conetrace.glsl"
|
||||
#endif
|
||||
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 lightColor;
|
||||
uniform int lightType;
|
||||
uniform vec3 lightDir;
|
||||
uniform vec2 spotData;
|
||||
#ifdef _ShadowMap
|
||||
uniform int lightShadow;
|
||||
uniform vec2 lightProj;
|
||||
uniform float shadowsBias;
|
||||
uniform mat4 LVP;
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform int index;
|
||||
uniform vec4 pointLightDataArray[maxLightsCluster * 6];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform float clipmaps[voxelgiClipmapCount * 10];
|
||||
uniform int clipmapLevel;
|
||||
|
||||
uniform layout(r32ui) uimage3D voxelsLight;
|
||||
|
||||
#ifdef _ShadowMap
|
||||
uniform sampler2DShadow shadowMap;
|
||||
uniform sampler2D shadowMapTransparent;
|
||||
uniform sampler2DShadow shadowMapSpot;
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform sampler2DShadow shadowMapPoint;
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168
|
||||
// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/
|
||||
vec2 sampleCube(vec3 dir, out int faceIndex) {
|
||||
vec3 dirAbs = abs(dir);
|
||||
float ma;
|
||||
vec2 uv;
|
||||
if(dirAbs.z >= dirAbs.x && dirAbs.z >= dirAbs.y) {
|
||||
faceIndex = dir.z < 0.0 ? 5 : 4;
|
||||
ma = 0.5 / dirAbs.z;
|
||||
uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y);
|
||||
}
|
||||
else if(dirAbs.y >= dirAbs.x) {
|
||||
faceIndex = dir.y < 0.0 ? 3 : 2;
|
||||
ma = 0.5 / dirAbs.y;
|
||||
uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z);
|
||||
}
|
||||
else {
|
||||
faceIndex = dir.x < 0.0 ? 1 : 0;
|
||||
ma = 0.5 / dirAbs.x;
|
||||
uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
|
||||
}
|
||||
// downscale uv a little to hide seams
|
||||
// transform coordinates from clip space to texture space
|
||||
#ifndef _FlipY
|
||||
return uv * 0.9976 * ma + 0.5;
|
||||
#else
|
||||
#ifdef HLSL
|
||||
return uv * 0.9976 * ma + 0.5;
|
||||
#else
|
||||
return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
float lpToDepth(vec3 lp, const vec2 lightProj) {
|
||||
lp = abs(lp);
|
||||
float zcomp = max(lp.x, max(lp.y, lp.z));
|
||||
zcomp = lightProj.x - lightProj.y / zcomp;
|
||||
return zcomp * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
void main() {
|
||||
int res = voxelgiResolution.x;
|
||||
ivec3 dst = ivec3(gl_GlobalInvocationID.xyz);
|
||||
|
||||
vec3 wposition = (gl_GlobalInvocationID.xyz + 0.5) / voxelgiResolution.x;
|
||||
wposition = wposition * 2.0 - 1.0;
|
||||
wposition *= float(clipmaps[int(clipmapLevel * 10)]);
|
||||
wposition *= voxelgiResolution.x;
|
||||
wposition += vec3(clipmaps[clipmapLevel * 10 + 4], clipmaps[clipmapLevel * 10 + 5], clipmaps[clipmapLevel * 10 + 6]);
|
||||
|
||||
float visibility;
|
||||
vec3 lp = lightPos - wposition;
|
||||
vec3 l;
|
||||
if (lightType == 0) { l = lightDir; visibility = 1.0; }
|
||||
else { l = normalize(lp); visibility = attenuate(distance(wposition, lightPos)); }
|
||||
|
||||
#ifdef _ShadowMap
|
||||
if (lightShadow == 1) {
|
||||
vec4 lightPosition = LVP * vec4(wposition, 1.0);
|
||||
vec3 lPos = lightPosition.xyz / lightPosition.w;
|
||||
visibility *= texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)).r;
|
||||
}
|
||||
else if (lightShadow == 2) {
|
||||
vec4 lightPosition = LVP * vec4(wposition, 1.0);
|
||||
vec3 lPos = lightPosition.xyz / lightPosition.w;
|
||||
visibility *= texture(shadowMapSpot, vec3(lPos.xy, lPos.z - shadowsBias)).r;
|
||||
}
|
||||
else if (lightShadow == 3) {
|
||||
#ifdef _ShadowMapAtlas
|
||||
int faceIndex = 0;
|
||||
const int lightIndex = index * 6;
|
||||
const vec2 uv = sampleCube(-l, faceIndex);
|
||||
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
||||
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
|
||||
#ifdef _FlipY
|
||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||
#endif
|
||||
visibility *= texture(shadowMapPoint, vec3(uvtiled, lpToDepth(lp, lightProj) - shadowsBias)).r;
|
||||
#else
|
||||
visibility *= texture(shadowMapPoint, vec4(-l, lpToDepth(lp, lightProj) - shadowsBias)).r;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
vec3 light = visibility * lightColor;
|
||||
imageAtomicAdd(voxelsLight, dst, uint(light.r * 255));
|
||||
imageAtomicAdd(voxelsLight, dst + ivec3(0, 0, voxelgiResolution.x), uint(light.g * 255));
|
||||
imageAtomicAdd(voxelsLight, dst + ivec3(0, 0, voxelgiResolution.x * 2), uint(light.b * 255));
|
||||
}
|
||||
@ -38,7 +38,6 @@ uniform layout(r8) image3D voxelsOut;
|
||||
#endif
|
||||
|
||||
uniform int clipmapLevel;
|
||||
uniform float voxelBlend;
|
||||
|
||||
uniform float clipmaps[voxelgiClipmapCount * 10];
|
||||
|
||||
@ -47,7 +46,7 @@ void main() {
|
||||
ivec3 src = ivec3(gl_GlobalInvocationID.xyz);
|
||||
src.y += clipmapLevel * res;
|
||||
|
||||
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
|
||||
for (int i = 0; i < 6 + diffuseConeCount; i++)
|
||||
{
|
||||
vec4 col = vec4(0.0);
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ void main() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= albedo;
|
||||
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
|
||||
|
||||
#ifdef _Brdf
|
||||
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
|
||||
@ -165,7 +165,7 @@ void main() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= envmapStrength * occspec.x;
|
||||
envl.rgb *= envmapStrength * voxelgiEnv * occspec.x;
|
||||
|
||||
vec3 occ = envl * (1.0 - traceAO(P, n, voxels, clipmaps));
|
||||
|
||||
|
||||
@ -53,13 +53,6 @@ uniform float shirr[7 * 4];
|
||||
#ifdef _Brdf
|
||||
uniform sampler2D senvmapBrdf;
|
||||
#endif
|
||||
#ifdef _Rad
|
||||
uniform sampler2D senvmapRadiance;
|
||||
uniform int envmapNumMipmaps;
|
||||
#endif
|
||||
#ifdef _EnvCol
|
||||
uniform vec3 backgroundCol;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
const vec2 pixel = gl_GlobalInvocationID.xy;
|
||||
@ -140,37 +133,20 @@ void main() {
|
||||
vec3 envl = vec3(0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _Rad
|
||||
vec3 reflectionWorld = reflect(-v, n);
|
||||
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
|
||||
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef _EnvLDR
|
||||
envl.rgb = pow(envl.rgb, vec3(2.2));
|
||||
#ifdef _Rad
|
||||
prefilteredColor = pow(prefilteredColor, vec3(2.2));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= albedo;
|
||||
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
|
||||
|
||||
#ifdef _Brdf
|
||||
envl.rgb *= 1.0 - F; //LV: We should take refracted light into account
|
||||
#endif
|
||||
|
||||
#ifdef _Rad // Indirect specular
|
||||
envl.rgb += prefilteredColor * F; //LV: Removed "1.5 * occspec.y". Specular should be weighted only by FV LUT
|
||||
#else
|
||||
#ifdef _EnvCol
|
||||
envl.rgb += backgroundCol * F; //LV: Eh, what's the point of weighting it only by F0?
|
||||
#endif
|
||||
#endif
|
||||
|
||||
envl.rgb *= envmapStrength * occspec.x;
|
||||
envl.rgb *= envmapStrength * voxelgiEnv * occspec.x;
|
||||
|
||||
vec4 trace = traceDiffuse(P, n, voxels, clipmaps);
|
||||
vec3 color = trace.rgb * albedo * (1.0 - F);
|
||||
vec3 color = trace.rgb * diffuseIBL(albedo, roughness, f0, dotNV) * (1.0 - F);
|
||||
color += envl * (1.0 - trace.a);
|
||||
|
||||
imageStore(voxels_diffuse, ivec2(pixel), vec4(color, 1.0));
|
||||
|
||||
@ -48,13 +48,15 @@ void main() {
|
||||
const vec2 pixel = gl_GlobalInvocationID.xy;
|
||||
vec2 uv = (pixel + 0.5) / postprocess_resolution;
|
||||
#ifdef _InvY
|
||||
uv.y = 1.0 - uv.y
|
||||
uv.y = 1.0 - uv.y;
|
||||
#endif
|
||||
|
||||
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
|
||||
if (depth == 0) return;
|
||||
|
||||
vec2 ior_opac = textureLod(gbuffer_refraction, uv, 0.0).xy;
|
||||
float ior = unpackIOR(ior_opac.x);
|
||||
float opacity = ior_opac.y;
|
||||
|
||||
float x = uv.x * 2 - 1;
|
||||
float y = uv.y * 2 - 1;
|
||||
@ -72,8 +74,8 @@ void main() {
|
||||
n = normalize(n);
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
if(ior_opac.y < 1.0)
|
||||
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior_opac.x, g0.b, clipmaps, pixel).rgb;
|
||||
if(opacity < 1.0)
|
||||
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior, g0.b, clipmaps, pixel).rgb;
|
||||
|
||||
imageStore(voxels_refraction, ivec2(pixel), vec4(color, 1.0));
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ void main() {
|
||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||
n = normalize(n);
|
||||
|
||||
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel);
|
||||
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel, vec2(0.0));
|
||||
|
||||
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
|
||||
}
|
||||
|
||||
@ -66,9 +66,13 @@ void main() {
|
||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||
n = normalize(n);
|
||||
|
||||
float roughness = g0.b;
|
||||
|
||||
vec3 v = normalize(eye - P);
|
||||
|
||||
vec2 velocity = -textureLod(sveloc, uv, 0.0).rg;
|
||||
|
||||
vec3 color = traceSpecular(P, n, voxels, voxelsSDF, normalize(eye - P), g0.z * g0.z, clipmaps, pixel, velocity).rgb;
|
||||
vec3 color = traceSpecular(P, n, voxels, voxelsSDF, v, roughness * roughness, clipmaps, pixel, velocity).rgb;
|
||||
|
||||
imageStore(voxels_specular, ivec2(pixel), vec4(color, 1.0));
|
||||
}
|
||||
|
||||
@ -43,7 +43,6 @@ uniform mat4 LVP;
|
||||
#endif
|
||||
uniform sampler3D voxelsSampler;
|
||||
uniform layout(r32ui) uimage3D voxels;
|
||||
uniform layout(r32ui) uimage3D voxelsLight;
|
||||
uniform layout(rgba8) image3D voxelsB;
|
||||
uniform layout(rgba8) image3D voxelsOut;
|
||||
uniform layout(r8) image3D SDF;
|
||||
@ -75,21 +74,13 @@ void main() {
|
||||
#endif
|
||||
|
||||
#ifdef _VoxelGI
|
||||
vec3 light = vec3(0.0);
|
||||
light.r = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz)).r) / 255;
|
||||
light.g = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz) + ivec3(0, 0, voxelgiResolution.x)).r) / 255;
|
||||
light.b = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz) + ivec3(0, 0, voxelgiResolution.x * 2)).r) / 255;
|
||||
light /= 3;
|
||||
vec4 aniso_colors[6];
|
||||
#else
|
||||
float aniso_colors[6];
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
|
||||
for (int i = 0; i < 6 + diffuseConeCount; i++)
|
||||
{
|
||||
#ifdef _VoxelGI
|
||||
vec4 aniso_colors[6];
|
||||
#else
|
||||
float aniso_colors[6];
|
||||
#endif
|
||||
|
||||
ivec3 src = ivec3(gl_GlobalInvocationID.xyz);
|
||||
src.x += i * res;
|
||||
ivec3 dst = src;
|
||||
@ -103,30 +94,37 @@ void main() {
|
||||
|
||||
if (i < 6) {
|
||||
#ifdef _VoxelGI
|
||||
uint count = imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 15)).r;
|
||||
if (count > 0) {
|
||||
vec4 basecol = vec4(0.0);
|
||||
basecol.r = float(imageLoad(voxels, src)) / 255;
|
||||
basecol.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x))) / 255;
|
||||
basecol.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 2))) / 255;
|
||||
basecol.a = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 3))) / 255;
|
||||
basecol /= 4;
|
||||
basecol /= count;
|
||||
vec3 emission = vec3(0.0);
|
||||
emission.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 4))) / 255;
|
||||
emission.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 5))) / 255;
|
||||
emission.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 6))) / 255;
|
||||
emission /= 3;
|
||||
emission /= count;
|
||||
vec3 N = vec3(0.0);
|
||||
N.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 7))) / 255;
|
||||
N.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 8))) / 255;
|
||||
N /= 2;
|
||||
N /= count;
|
||||
vec3 wnormal = decode_oct(N.rg * 2 - 1);
|
||||
vec3 envl = vec3(0.0);
|
||||
envl.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 9))) / 255;
|
||||
envl.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 10))) / 255;
|
||||
envl.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 11))) / 255;
|
||||
envl /= 3;
|
||||
envl /= count;
|
||||
#ifdef _HOSEK
|
||||
envl *= 100;
|
||||
#endif
|
||||
vec3 light = vec3(0.0);
|
||||
light.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 12))) / 255;
|
||||
light.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 13))) / 255;
|
||||
light.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 14))) / 255;
|
||||
light /= count;
|
||||
|
||||
//clipmap to world
|
||||
vec3 wposition = (gl_GlobalInvocationID.xyz + 0.5) / voxelgiResolution.x;
|
||||
@ -138,8 +136,9 @@ void main() {
|
||||
radiance = basecol;
|
||||
vec4 trace = traceDiffuse(wposition, wnormal, voxelsSampler, clipmaps);
|
||||
vec3 indirect = trace.rgb + envl.rgb * (1.0 - trace.a);
|
||||
radiance.rgb *= light.rgb + indirect.rgb;
|
||||
radiance.rgb *= light.rgb * INV_PI + indirect.rgb;
|
||||
radiance.rgb += emission.rgb;
|
||||
}
|
||||
|
||||
#else
|
||||
opac = float(imageLoad(voxels, src)) / 255;
|
||||
@ -196,7 +195,7 @@ void main() {
|
||||
}
|
||||
else {
|
||||
// precompute cone sampling:
|
||||
vec3 coneDirection = DIFFUSE_CONE_DIRECTIONS[i - 6];
|
||||
vec3 coneDirection = diffuseConeDirections[i - 6];
|
||||
vec3 aniso_direction = -coneDirection;
|
||||
uvec3 face_offsets = uvec3(
|
||||
aniso_direction.x > 0 ? 0 : 1,
|
||||
|
||||
@ -88,53 +88,99 @@ 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
|
||||
float geomZ = p.z; // undisplaced depth for foam/fog tests
|
||||
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
|
||||
@ -147,8 +193,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 +204,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 +222,36 @@ 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);
|
||||
// Depth fog - blend toward waterColor with depth, faded at horizon
|
||||
float depthFog = clamp(-(geomZ - waterLevel) * waterDensity, 0.0, 0.9);
|
||||
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
|
||||
// Alpha fades smoothly at horizon instead of hard cut
|
||||
// Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
|
||||
// appears where the ray z reaches -3.5 / domeRadius
|
||||
float farPlane = cameraProj.y / (1.0 - cameraProj.x);
|
||||
float horizonFade = clamp((-3.5 / (farPlane * 0.95) - vray.z) * 20.0, 0.0, 1.0);
|
||||
fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - 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(geomZ - 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ld",
|
||||
"link": "_lightDirection"
|
||||
"link": "_sunDirection"
|
||||
},
|
||||
{
|
||||
"name": "invVP",
|
||||
|
||||
@ -237,8 +237,24 @@ class App {
|
||||
traitRenders.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
traitRenders2D.push(f);
|
||||
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
|
||||
if (index < 0 || index >= traitRenders2D.length) {
|
||||
traitRenders2D.push(f);
|
||||
} else {
|
||||
traitRenders2D.insert(index, f);
|
||||
}
|
||||
}
|
||||
|
||||
public static function moveRender2D(f: kha.graphics2.Graphics->Void, newIndex: Int) {
|
||||
var oldIndex = traitRenders2D.indexOf(f);
|
||||
if (oldIndex != -1) {
|
||||
traitRenders2D.splice(oldIndex, 1);
|
||||
if (newIndex >= traitRenders2D.length) {
|
||||
traitRenders2D.push(f);
|
||||
} else {
|
||||
traitRenders2D.insert(newIndex, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function removeRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
|
||||
@ -304,10 +304,13 @@ class RenderPath {
|
||||
currentD = 1;
|
||||
currentFace = -1;
|
||||
meshesSorted = false;
|
||||
sun = null;
|
||||
|
||||
for (l in Scene.active.lights) {
|
||||
if (l.visible) l.buildMatrix(Scene.active.camera);
|
||||
if (l.data.raw.type == "sun") sun = l;
|
||||
if (l.data.raw.type == "sun") {
|
||||
if (sun == null || (!sun.data.raw.cast_shadow && l.data.raw.cast_shadow)) sun = l;
|
||||
}
|
||||
else point = l;
|
||||
}
|
||||
light = Scene.active.lights[0];
|
||||
@ -500,7 +503,7 @@ class RenderPath {
|
||||
}
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
var isShadows = context == "shadowmap";
|
||||
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (isShadows) {
|
||||
// Disabled shadow casting for this light
|
||||
if (light == null || !light.data.raw.cast_shadow || !light.visible || light.data.raw.strength == 0) return;
|
||||
@ -528,13 +531,11 @@ class RenderPath {
|
||||
|
||||
if (!drawn) submitDraw(context);
|
||||
|
||||
#if lnx_debug
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(context);
|
||||
if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length);
|
||||
}
|
||||
#end
|
||||
|
||||
end();
|
||||
}
|
||||
@ -594,7 +595,6 @@ class RenderPath {
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
static var contextEvents: Map<String, Array<Graphics->Int->Int->Void>> = null;
|
||||
public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
|
||||
if (contextEvents == null) contextEvents = new Map();
|
||||
@ -605,7 +605,13 @@ class RenderPath {
|
||||
}
|
||||
ar.push(onContext);
|
||||
}
|
||||
#end
|
||||
|
||||
public static function removeNotifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(name);
|
||||
if (ar != null) ar.remove(onContext);
|
||||
}
|
||||
}
|
||||
|
||||
#if rp_decals
|
||||
public function drawDecals(context: String) {
|
||||
@ -902,7 +908,9 @@ class RenderPath {
|
||||
#end
|
||||
|
||||
Data.getShader(shaderPath[0], shaderPath[1], function(res: ShaderData) {
|
||||
cc.context = res.getContext(shaderPath[2]);
|
||||
if (res != null) {
|
||||
cc.context = res.getContext(shaderPath[2]);
|
||||
}
|
||||
loading--;
|
||||
});
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import iron.object.SpeakerObject;
|
||||
import iron.object.DecalObject;
|
||||
import iron.object.ProbeObject;
|
||||
import iron.object.Tilesheet;
|
||||
import iron.object.CurveObject;
|
||||
import iron.data.CameraData;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.LightData;
|
||||
@ -64,6 +65,7 @@ class Scene {
|
||||
#end
|
||||
public var empties: Array<Object>;
|
||||
public var animations: Array<Animation>;
|
||||
public var tilesheets: Array<Tilesheet>;
|
||||
#if lnx_skin
|
||||
public var armatures: Array<Armature>;
|
||||
#end
|
||||
@ -71,6 +73,13 @@ class Scene {
|
||||
|
||||
public var embedded: Map<String, kha.Image>;
|
||||
|
||||
#if (rp_renderer == "Deferred")
|
||||
public static inline var MAX_MATERIALS = 16;
|
||||
public static inline var FLOATS_PER_MATERIAL_PARAM = 32; // 8 vec4s per material
|
||||
public var materialParamsBuffer: kha.arrays.Float32Array;
|
||||
public var materialParamsDirty: Bool = true;
|
||||
#end
|
||||
|
||||
public var ready: Bool; // Async in progress
|
||||
|
||||
public var traitInits: Array<Void->Void> = [];
|
||||
@ -103,10 +112,14 @@ class Scene {
|
||||
#end
|
||||
empties = [];
|
||||
animations = [];
|
||||
tilesheets = [];
|
||||
#if lnx_skin
|
||||
armatures = [];
|
||||
#end
|
||||
embedded = new Map();
|
||||
#if (rp_renderer == "Deferred")
|
||||
materialParamsBuffer = new kha.arrays.Float32Array(MAX_MATERIALS * FLOATS_PER_MATERIAL_PARAM);
|
||||
#end
|
||||
root = new Object();
|
||||
root.name = "Root";
|
||||
traitInits = [];
|
||||
@ -125,6 +138,14 @@ class Scene {
|
||||
|
||||
// Startup scene
|
||||
active.addScene(format.name, null, function(sceneObject: Object) {
|
||||
|
||||
if (format.properties != null) {
|
||||
sceneObject.properties = new Map();
|
||||
for (p in format.properties) {
|
||||
sceneObject.properties.set(p.name, cleanValue(p.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Create traits bottom-up (children first, then parents)
|
||||
createTraitsBottomUp(sceneObject);
|
||||
|
||||
@ -204,6 +225,89 @@ class Scene {
|
||||
root.remove();
|
||||
}
|
||||
|
||||
#if (rp_renderer == "Deferred")
|
||||
public function markMaterialParamsDirty() {
|
||||
materialParamsDirty = true;
|
||||
}
|
||||
|
||||
public function updateMaterialParams() {
|
||||
if (!materialParamsDirty) return;
|
||||
materialParamsDirty = false;
|
||||
var buf = materialParamsBuffer;
|
||||
for (m in meshes) {
|
||||
if (m.materials == null) continue;
|
||||
for (mat in m.materials) {
|
||||
if (mat == null) continue;
|
||||
if (mat.contexts == null) continue;
|
||||
var slot = -1;
|
||||
var bc = null;
|
||||
for (ctx in mat.contexts) {
|
||||
if (ctx == null || ctx.raw == null) continue;
|
||||
if (ctx.raw.name == "mesh" && ctx.raw.bind_constants != null) {
|
||||
bc = ctx.raw.bind_constants;
|
||||
for (c in bc) {
|
||||
if (c != null && c.name == "materialID" && c.intValue != null) {
|
||||
slot = c.intValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot < 0 || slot >= MAX_MATERIALS) continue;
|
||||
var base = slot * FLOATS_PER_MATERIAL_PARAM;
|
||||
if (base + FLOATS_PER_MATERIAL_PARAM - 1 >= buf.length) continue;
|
||||
if (bc == null) continue;
|
||||
for (i in 0...FLOATS_PER_MATERIAL_PARAM) buf[base + i] = 0.0;
|
||||
buf[base + 6] = 1.5; // coatIOR
|
||||
buf[base + 12] = 1.45; // ior
|
||||
buf[base + 13] = 1.0; // thinWall
|
||||
for (c in bc) {
|
||||
if (c == null || c.name == null || c.floatValue == null) continue;
|
||||
switch (c.name) {
|
||||
// matp0: vec4(anisotropy, anisoRot, sheen, sheenRough)
|
||||
case "anisotropy": buf[base + 0] = c.floatValue;
|
||||
case "anisoRot": buf[base + 1] = c.floatValue;
|
||||
case "sheen": buf[base + 2] = c.floatValue;
|
||||
case "sheenRough": buf[base + 3] = c.floatValue;
|
||||
// matp1: vec4(clearcoat, clearcoatRough, coatIOR, coatTintR)
|
||||
case "clearcoat": buf[base + 4] = c.floatValue;
|
||||
case "clearcoatRough": buf[base + 5] = c.floatValue;
|
||||
case "coatIOR": buf[base + 6] = c.floatValue;
|
||||
case "coatTintR": buf[base + 7] = c.floatValue;
|
||||
// matp2: vec4(coatTintG, coatTintB, transmission, transRough)
|
||||
case "coatTintG": buf[base + 8] = c.floatValue;
|
||||
case "coatTintB": buf[base + 9] = c.floatValue;
|
||||
case "transmission": buf[base + 10] = c.floatValue;
|
||||
case "transmissionRough": buf[base + 11] = c.floatValue;
|
||||
// matp3: vec4(ior, thinWall, subsurface, subsurfaceAnisotropy)
|
||||
case "ior": buf[base + 12] = c.floatValue;
|
||||
case "thinWall": buf[base + 13] = c.floatValue;
|
||||
case "subsurface": buf[base + 14] = c.floatValue;
|
||||
case "subsurfaceAnisotropy": buf[base + 15] = c.floatValue;
|
||||
// matp4: vec4(subsurfaceRadiusR, subsurfaceRadiusG, subsurfaceRadiusB, subsurfaceColorR)
|
||||
case "subsurfaceRadiusR": buf[base + 16] = c.floatValue;
|
||||
case "subsurfaceRadiusG": buf[base + 17] = c.floatValue;
|
||||
case "subsurfaceRadiusB": buf[base + 18] = c.floatValue;
|
||||
case "subsurfaceColorR": buf[base + 19] = c.floatValue;
|
||||
// matp5: vec4(subsurfaceColorG, subsurfaceColorB, sheenTintR, sheenTintG)
|
||||
case "subsurfaceColorG": buf[base + 20] = c.floatValue;
|
||||
case "subsurfaceColorB": buf[base + 21] = c.floatValue;
|
||||
case "sheenTintR": buf[base + 22] = c.floatValue;
|
||||
case "sheenTintG": buf[base + 23] = c.floatValue;
|
||||
// matp6: vec4(sheenTintB, specularTintR, specularTintG, specularTintB)
|
||||
case "sheenTintB": buf[base + 24] = c.floatValue;
|
||||
case "specularTintR": buf[base + 25] = c.floatValue;
|
||||
case "specularTintG": buf[base + 26] = c.floatValue;
|
||||
case "specularTintB": buf[base + 27] = c.floatValue;
|
||||
// matp7: vec4(subsurfaceScale, 0, 0, 0)
|
||||
case "subsurfaceScale": buf[base + 28] = c.floatValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
static var framePassed = true;
|
||||
public static function setActive(sceneName: String, done: Object->Void = null) {
|
||||
if (!framePassed) return;
|
||||
@ -249,6 +353,7 @@ class Scene {
|
||||
if (terrainStream != null) terrainStream.update(active.camera);
|
||||
#end
|
||||
for (anim in animations) anim.update(Time.delta);
|
||||
for (tilesheet in tilesheets) tilesheet.update();
|
||||
for (e in empties) if (e != null && e.parent != null) e.transform.update();
|
||||
}
|
||||
|
||||
@ -348,9 +453,23 @@ class Scene {
|
||||
return g;
|
||||
}
|
||||
|
||||
public function removeFromGroups(object: Object) {
|
||||
if (groups == null) return;
|
||||
for (name in groups.keys()) getGroup(name).remove(object);
|
||||
}
|
||||
|
||||
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
|
||||
var object = new MeshObject(data, materials);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
#if (rp_renderer == "Deferred")
|
||||
markMaterialParamsDirty();
|
||||
#end
|
||||
return object;
|
||||
}
|
||||
|
||||
public function addCurveObject(data: TCurveData, parent: Object = null): CurveObject {
|
||||
var object = new CurveObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
@ -613,6 +732,10 @@ class Scene {
|
||||
else done(ro);
|
||||
});
|
||||
}
|
||||
else if (o.type == "curve_object") {
|
||||
var object = addCurveObject(Data.getCurveRawByName(format.curve_datas, o.data_ref), parent);
|
||||
returnObject(object, o, done);
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
|
||||
@ -793,11 +916,13 @@ class Scene {
|
||||
}
|
||||
else { #end // lnx_skin
|
||||
#if lnx_stream
|
||||
streamMeshObject(
|
||||
if ((o.particle_refs == null || o.particle_refs.length == 0) && o.is_particle == null && parent != null)
|
||||
streamMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
else
|
||||
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#else
|
||||
returnMeshObject(
|
||||
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#end
|
||||
object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#if lnx_skin
|
||||
}
|
||||
#end
|
||||
@ -872,20 +997,24 @@ class Scene {
|
||||
#end
|
||||
if (o.properties != null) {
|
||||
object.properties = new Map();
|
||||
for (p in o.properties) object.properties.set(p.name, p.value);
|
||||
for (p in o.properties) {
|
||||
object.properties.set(p.name, cleanValue(p.value));
|
||||
}
|
||||
}
|
||||
|
||||
if (o.vertex_groups != null) {
|
||||
object.vertex_groups = new Map();
|
||||
for (p in o.vertex_groups){
|
||||
var verts = [];
|
||||
for(i in 0...Std.int(p.value.length/3)){
|
||||
var x = Std.parseFloat(p.value[i*3]);
|
||||
var y = Std.parseFloat(p.value[i*3+1]);
|
||||
var z = Std.parseFloat(p.value[i*3+2]);
|
||||
verts.push(new iron.math.Vec4(x, y, z, 1));
|
||||
cast(object, MeshObject).vertexGroups = new Map();
|
||||
for (p in o.vertex_groups) {
|
||||
var verts:Array<iron.math.Vec4> = [];
|
||||
|
||||
var data:kha.arrays.Float32Array = cast p.value;
|
||||
|
||||
if (data != null) {
|
||||
for (i in 0...Std.int(data.length / 3)) {
|
||||
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
|
||||
}
|
||||
}
|
||||
object.vertex_groups.set(p.name, verts);
|
||||
cast(object, MeshObject).vertexGroups.set(p.name, verts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1049,4 +1178,16 @@ class Scene {
|
||||
public function notifyOnRemove(f: Void->Void) {
|
||||
traitRemoves.push(f);
|
||||
}
|
||||
|
||||
static function cleanValue(val: Dynamic): Dynamic {
|
||||
if (val == null) return null;
|
||||
if (untyped val.buffer != null) {
|
||||
var data: kha.arrays.Float32Array = cast val;
|
||||
return [for (i in 0...data.length) data[i]];
|
||||
}
|
||||
if (Std.isOfType(val, Array)) {
|
||||
return [for (item in (cast val: Array<Dynamic>)) cleanValue(item)];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,10 +125,10 @@ class Trait {
|
||||
/**
|
||||
Add 2D render handler.
|
||||
**/
|
||||
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
|
||||
if (_render2D == null) _render2D = [];
|
||||
_render2D.push(f);
|
||||
App.notifyOnRender2D(f);
|
||||
App.notifyOnRender2D(f, index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -311,7 +311,7 @@ class Data {
|
||||
|
||||
loadingSceneRaws.set(file, [done]);
|
||||
|
||||
// If no extension specified, set to .arm
|
||||
// If no extension specified, set to .lnx
|
||||
var compressed = file.endsWith(".lz4");
|
||||
var isJson = file.endsWith(".json");
|
||||
var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx";
|
||||
@ -405,6 +405,13 @@ class Data {
|
||||
}
|
||||
#end
|
||||
|
||||
public static function getCurveRawByName(datas: Array<TCurveData>, name: String): TCurveData {
|
||||
if (datas == null || datas.length == 0) return null;
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Raw assets
|
||||
public static function getBlob(file: String, done: kha.Blob->Void) {
|
||||
var cached = cachedBlobs.get(file); // Is already cached
|
||||
|
||||
@ -40,6 +40,8 @@ class Geometry {
|
||||
public var instancedVB: VertexBuffer = null;
|
||||
public var instanced = false;
|
||||
public var instanceCount = 0;
|
||||
public var instanceElements: Array<{name: String, data: String}> = [];
|
||||
public var instanceStride: Int = 0;
|
||||
|
||||
public var positions: TVertexArray;
|
||||
public var normals: TVertexArray;
|
||||
@ -55,6 +57,8 @@ class Geometry {
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
public var skeletonTransformsI: Array<Mat4> = null;
|
||||
public var actions: Map<String, Array<iron.data.SceneFormat.TObj>> = new Map();
|
||||
public var mats: Map<String, Array<Mat4>> = new Map();
|
||||
#end
|
||||
|
||||
public function new(data: MeshData, indices: Array<Uint32Array>, materialIndices: Array<Int>, usage: Usage = null) {
|
||||
@ -128,11 +132,39 @@ class Geometry {
|
||||
structure.add("iscl", kha.graphics4.VertexData.Float3);
|
||||
}
|
||||
|
||||
if (instanceElements != null && instanceElements.length > 0) {
|
||||
for (elem in instanceElements) {
|
||||
if (StringTools.startsWith(elem.name, "i") && elem.name != "ipos" && elem.name != "irot" && elem.name != "iscl") {
|
||||
var vdata = VertexData.Float1;
|
||||
var dataStr: String = Reflect.field(elem, "data");
|
||||
switch (dataStr) {
|
||||
case "float1": vdata = VertexData.Float1;
|
||||
case "float2": vdata = VertexData.Float2;
|
||||
case "float3": vdata = VertexData.Float3;
|
||||
case "float4": vdata = VertexData.Float4;
|
||||
}
|
||||
structure.add(elem.name, vdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.instanceStride = Std.int(structure.byteSize() / 4);
|
||||
instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4));
|
||||
instancedVB = new VertexBuffer(instanceCount, structure, usage, 1);
|
||||
var vertices = instancedVB.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]);
|
||||
instancedVB.unlock();
|
||||
|
||||
}
|
||||
|
||||
public function updateInstanced(data: Float32Array) {
|
||||
if (instancedVB == null) return;
|
||||
|
||||
var vertices = instancedVB.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 4)) {
|
||||
vertices.setFloat32(i * 4, data[i]);
|
||||
}
|
||||
instancedVB.unlock();
|
||||
}
|
||||
|
||||
public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) {
|
||||
|
||||
@ -40,6 +40,8 @@ typedef TSceneFormat = {
|
||||
@:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2
|
||||
@:optional public var terrain_datas: Array<TTerrainData>;
|
||||
@:optional public var terrain_ref: String;
|
||||
@:optional public var properties: Array<TProperty>;
|
||||
@:optional public var curve_datas: Array<TCurveData>;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -348,7 +350,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
|
||||
@ -426,6 +434,7 @@ typedef TParticleData = {
|
||||
// Velocity
|
||||
public var object_align_factor: Float32Array;
|
||||
public var factor_random: FastFloat;
|
||||
public var normal_factor: FastFloat;
|
||||
// Rotation
|
||||
public var use_rotations: Bool;
|
||||
public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
|
||||
@ -519,7 +528,7 @@ typedef TVertex_groups = {
|
||||
@:structInit class TVertex_groups {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Dynamic;
|
||||
public var value: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -558,6 +567,21 @@ typedef TConstraint = {
|
||||
@:optional public var invert_z: Null<Bool>;
|
||||
@:optional public var use_offset: Null<Bool>;
|
||||
@:optional public var influence: Null<FastFloat>;
|
||||
@:optional public var use_min_x: Null<Bool>;
|
||||
@:optional public var use_max_x: Null<Bool>;
|
||||
@:optional public var use_min_y: Null<Bool>;
|
||||
@:optional public var use_max_y: Null<Bool>;
|
||||
@:optional public var use_min_z: Null<Bool>;
|
||||
@:optional public var use_max_z: Null<Bool>;
|
||||
@:optional public var use_limit_x: Null<Bool>;
|
||||
@:optional public var use_limit_y: Null<Bool>;
|
||||
@:optional public var use_limit_z: Null<Bool>;
|
||||
@:optional public var min_x: Null<FastFloat>;
|
||||
@:optional public var max_x: Null<FastFloat>;
|
||||
@:optional public var min_y: Null<FastFloat>;
|
||||
@:optional public var max_y: Null<FastFloat>;
|
||||
@:optional public var min_z: Null<FastFloat>;
|
||||
@:optional public var max_z: Null<FastFloat>;
|
||||
}
|
||||
|
||||
#if js
|
||||
@ -616,3 +640,48 @@ typedef TTrack = {
|
||||
public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values
|
||||
@:optional public var ref_values: Array<Array<String>>; // ref values
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TBezierPoint = {
|
||||
#else
|
||||
@:structInit class TBezierPoint {
|
||||
#end
|
||||
public var co: Float32Array;
|
||||
public var handle_left: Float32Array;
|
||||
public var handle_right: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TSpline = {
|
||||
#else
|
||||
@:structInit class TSpline {
|
||||
#end
|
||||
public var closed: Bool;
|
||||
public var resolution: Int;
|
||||
public var points: Array<TBezierPoint>;
|
||||
public var material_index: Int;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShapeKey = {
|
||||
#else
|
||||
@:structInit class TShapeKey {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Float;
|
||||
public var points: Array<TBezierPoint>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TCurveData = {
|
||||
#else
|
||||
@:structInit class TCurveData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var object: String;
|
||||
public var splines: Array<TSpline>;
|
||||
public var strength: Float;
|
||||
public var color: Float32Array;
|
||||
@:optional public var material_refs: Array<String>;
|
||||
@:optional public var shape_keys: Array<TShapeKey>;
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ class ShaderData {
|
||||
if (raw == null) {
|
||||
trace('Shader data "$name" not found!');
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
new ShaderData(raw, done, overrideContext);
|
||||
});
|
||||
@ -79,6 +80,8 @@ class ShaderContext {
|
||||
|
||||
var structure: VertexStructure;
|
||||
var instancingType = 0;
|
||||
var instanceElements: Array<{name: String, data: String}> = [];
|
||||
var instanceStride: Int = 0;
|
||||
|
||||
public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) {
|
||||
this.raw = raw;
|
||||
@ -108,6 +111,11 @@ class ShaderContext {
|
||||
if (instancingType == 3 || instancingType == 4) {
|
||||
instStruct.add("iscl", VertexData.Float3);
|
||||
}
|
||||
|
||||
for (e in instanceElements)
|
||||
instStruct.add(e.name, parseData(e.data));
|
||||
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
||||
|
||||
instStruct.instanced = true;
|
||||
pipeState.inputLayout = [structure, instStruct];
|
||||
}
|
||||
@ -268,10 +276,12 @@ class ShaderContext {
|
||||
if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "irot") { irot = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; }
|
||||
if (Reflect.field(elem, "name").startsWith("i")) { instanceElements.push(elem); continue; }
|
||||
#else
|
||||
if (elem.name == "ipos") { ipos = true; continue; }
|
||||
if (elem.name == "irot") { irot = true; continue; }
|
||||
if (elem.name == "iscl") { iscl = true; continue; }
|
||||
if (elem.name.startsWith("i")) { instanceElements.push(elem); continue; }
|
||||
#end
|
||||
structure.add(elem.name, parseData(elem.data));
|
||||
}
|
||||
|
||||
357
leenkx/Sources/iron/format/gif/Data.hx
Normal file
357
leenkx/Sources/iron/format/gif/Data.hx
Normal file
@ -0,0 +1,357 @@
|
||||
package iron.format.gif;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
/**
|
||||
* Gif data.
|
||||
*/
|
||||
typedef Data =
|
||||
{
|
||||
/**
|
||||
* Gif version. There is only 2 Gif version exists. 87a and 89a.
|
||||
* 87a have less features and does not support any extensions.
|
||||
* Unknown version is adviced to be interpreted as newest (89a) official version.
|
||||
*/
|
||||
var version:Version;
|
||||
/**
|
||||
* Information about logical screen of Gif that provides basic information about Gif.
|
||||
*/
|
||||
var logicalScreenDescriptor:LogicalScreenDescriptor;
|
||||
/**
|
||||
* Global color table used for Gif. Present only if Logical Screen Descriptor contained global color table flag.
|
||||
* Note that this color table not always present since frames can contain local color tables that overrides global color table.
|
||||
*/
|
||||
@:optional var globalColorTable:Null<ColorTable>;
|
||||
/**
|
||||
* List of Gif data blocks.
|
||||
*/
|
||||
var blocks:List<Block>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gif data block. Custom blocks are not supported.
|
||||
*/
|
||||
enum Block
|
||||
{
|
||||
/**
|
||||
* Gif frame block.
|
||||
* Note that this block does not contain link to graphic control extension of Frame even if it is present. GraphicControl extension Block commonly present right before frame Block.
|
||||
*/
|
||||
BFrame(frame:Frame);
|
||||
/**
|
||||
* Additional extension block. This Block does not supported in 87a Gif specification version.
|
||||
*/
|
||||
BExtension(extension:Extension);
|
||||
/**
|
||||
* End of File block. Represents end of Gif data.
|
||||
*/
|
||||
BEOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension block contains additional data about Gif image. This block does not supported by 87a version.
|
||||
*/
|
||||
enum Extension
|
||||
{
|
||||
/**
|
||||
* Graphic Control extension gives additional control over next frame, like frame delay, disposal method, alpha channel and other information.
|
||||
*/
|
||||
EGraphicControl(gce:GraphicControlExtension);
|
||||
/**
|
||||
* Commentary extension. Not show up as any visual, just a text in file.
|
||||
*/
|
||||
EComment(text:String);
|
||||
/**
|
||||
* Text extension. Must work as text rendering on the image, but ignored by all major Gif decoders.
|
||||
*/
|
||||
EText(pte:PlainTextExtension);
|
||||
/**
|
||||
* Application extension allow to insert additional application data into Gif. Mostly used app extension is NETSCAPE2.0 looping extension, used to set up amount of loops in frame.
|
||||
*/
|
||||
EApplicationExtension(ext:ApplicationExtension);
|
||||
|
||||
/**
|
||||
* Unknown extension.
|
||||
*/
|
||||
EUnknown(id:Int, data:Bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Application extension. Mostly used only for one reason - setting up loops count. There is exist other app extensions but they are really rare.
|
||||
*/
|
||||
enum ApplicationExtension
|
||||
{
|
||||
/**
|
||||
* NETSCAPE2.0 looping extension. Contains only amount of animation repeats.
|
||||
* Note that there is two NETSCAPE2.0 app extensions for Gif format and the type of extension is stored in first byte of data. Looping extension have ID 1.
|
||||
*/
|
||||
AENetscapeLooping(loops:Int);
|
||||
/**
|
||||
* Unknown or unsupported app extension.
|
||||
*/
|
||||
AEUnknown(name:String, version:String, data:Bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Typical color table for Gif image.
|
||||
* Can contain 2, 4, 8, 16, 32, 64, 128 or 256 colors.
|
||||
* Data stored in RGB format. Information about alpha channel provided by Graohic Control Extension.
|
||||
*/
|
||||
typedef ColorTable = Bytes;
|
||||
|
||||
/**
|
||||
* Single frame of the image.
|
||||
* Actually it's a merge of 3 consequent blocks:
|
||||
* 1. Image Descriptor.
|
||||
* Contains frame informations like position, size, existing of local color table and interlaced flag.
|
||||
* 2. [Local color table].
|
||||
* Only present if Image Descriptor contains local color table flag. Overrides global color table.
|
||||
* 3. Pixel data blocks.
|
||||
* LZW compressed pixel data.
|
||||
*/
|
||||
typedef Frame =
|
||||
{
|
||||
/**
|
||||
* X position of image on the Logical Screen
|
||||
*/
|
||||
var x:Int;
|
||||
|
||||
/**
|
||||
* Y position of image on the Logical Screen
|
||||
*/
|
||||
var y:Int;
|
||||
|
||||
/**
|
||||
* Width of image in pixels
|
||||
*/
|
||||
var width:Int;
|
||||
|
||||
/**
|
||||
* Height of image in pixels
|
||||
*/
|
||||
var height:Int;
|
||||
|
||||
/**
|
||||
* Is this image uses local color table?
|
||||
*/
|
||||
var localColorTable:Bool;
|
||||
|
||||
/**
|
||||
* Is this image written in interlace mode?
|
||||
* Note: The pixel data already deinterlaced and this flag presented only for information purpose (and for Writer when there is one).
|
||||
*/
|
||||
var interlaced:Bool;
|
||||
|
||||
/**
|
||||
* Is local color table sorted in order of decreasing priority?
|
||||
*/
|
||||
var sorted:Bool;
|
||||
|
||||
/**
|
||||
* Size of local color table
|
||||
*/
|
||||
var localColorTableSize:Int;
|
||||
|
||||
/**
|
||||
* Pixel data of frame. Stored as Indexed colors, 1 byte per pixel.
|
||||
*/
|
||||
var pixels:Bytes;
|
||||
|
||||
/**
|
||||
* Local color table used by frame. Stored as 3-byte RGB colors. If value is null, must be used global color table.
|
||||
*/
|
||||
var colorTable:ColorTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Graphic Control Extension block, used for setting up disposal method, transparency, delay and user input.
|
||||
*/
|
||||
typedef GraphicControlExtension =
|
||||
{
|
||||
/**
|
||||
* Disposal method of frame.
|
||||
*/
|
||||
var disposalMethod:DisposalMethod;
|
||||
/**
|
||||
* Is image must wait for user input, before dispose?
|
||||
* This flag may be used by user-defined program but absolutely ignored by any Gif players.
|
||||
*/
|
||||
var userInput:Bool;
|
||||
/**
|
||||
* Is image have transparency?
|
||||
*/
|
||||
var hasTransparentColor:Bool;
|
||||
/**
|
||||
* Delay, before next image appears. Delay is in centiseconds (1 centisecond = 1/100 seconds).
|
||||
* Note: Some players (like FastStone) cut fraction of elapsed time when progressing to next frame which results in small timing error.
|
||||
* Recommended to use `time -= delay` instead of `time = 0`.
|
||||
*/
|
||||
var delay:Int;
|
||||
/**
|
||||
* Index in color table that used as transparent.
|
||||
*/
|
||||
var transparentIndex:Int;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for rendering text on Gif logical screen. It does not supported by major Gif decoders.
|
||||
* Font and text size decision is left to decoder. (recommended to decide based on grid/cell size)
|
||||
* Text must be rendered with one character at cell.
|
||||
* It's recommended to replace any characters less than 0x20 and greater than 0xf7 to be rendered as Space (0x20)
|
||||
*/
|
||||
typedef PlainTextExtension =
|
||||
{
|
||||
/**
|
||||
* X position of text grid on Logical Screen.
|
||||
*/
|
||||
var textGridX:Int;
|
||||
/**
|
||||
* Y position of text grid on Logical Screen.
|
||||
*/
|
||||
var textGridY:Int;
|
||||
/**
|
||||
* Width of text grid in pixels.
|
||||
*/
|
||||
var textGridWidth:Int;
|
||||
/**
|
||||
* Height of text grid in pixels.
|
||||
*/
|
||||
var textGridHeight:Int;
|
||||
/**
|
||||
* Width of character cell in text grid.
|
||||
*/
|
||||
var charCellWidth:Int;
|
||||
/**
|
||||
* Height of character cell in text grid.
|
||||
*/
|
||||
var charCellHeight:Int;
|
||||
/**
|
||||
* Foreground/character color index.
|
||||
*/
|
||||
var textForegroundColorIndex:Int;
|
||||
/**
|
||||
* Background color index.
|
||||
*/
|
||||
var textBackgroundColorIndex:Int;
|
||||
/**
|
||||
* Text to render.
|
||||
*/
|
||||
var text:String;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical screen descriptor of GIF file.
|
||||
* Contains very basic information about Gif.
|
||||
*/
|
||||
typedef LogicalScreenDescriptor =
|
||||
{
|
||||
/**
|
||||
* Width of GIF image in pixels
|
||||
*/
|
||||
var width:Int;
|
||||
|
||||
/**
|
||||
* Height of GIF image in pixels
|
||||
*/
|
||||
var height:Int;
|
||||
|
||||
/**
|
||||
* Is this file uses global color table?
|
||||
*/
|
||||
var hasGlobalColorTable:Bool;
|
||||
|
||||
/**
|
||||
* Specification:
|
||||
* Number of bits per primary color available
|
||||
to the original image, minus 1. This value represents the size of
|
||||
the entire palette from which the colors in the graphic were
|
||||
selected, not the number of colors actually used in the graphic.
|
||||
For example, if the value in this field is 3, then the palette of
|
||||
the original image had 4 bits per primary color available to create
|
||||
the image. This value should be set to indicate the richness of
|
||||
the original palette, even if not every color from the whole
|
||||
palette is available on the source machine.
|
||||
*/
|
||||
var colorResolution:Int;
|
||||
|
||||
/**
|
||||
* Specification:
|
||||
* Indicates whether the Global Color Table is sorted.
|
||||
If the flag is set, the Global Color Table is sorted, in order of
|
||||
decreasing importance. Typically, the order would be decreasing
|
||||
frequency, with most frequent color first. This assists a decoder,
|
||||
with fewer available colors, in choosing the best subset of colors;
|
||||
the decoder may use an initial segment of the table to render the
|
||||
graphic.
|
||||
*/
|
||||
var sorted:Bool;
|
||||
|
||||
/**
|
||||
* Size of global color table.
|
||||
*/
|
||||
var globalColorTableSize:Int;
|
||||
|
||||
/**
|
||||
* Background color index in global color table
|
||||
*/
|
||||
var backgroundColorIndex:Int;
|
||||
|
||||
/**
|
||||
* Factor used to compute an approximation of the aspect ratio of the pixel in the original image.
|
||||
*/
|
||||
var pixelAspectRatio:Float;
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of Gif file.
|
||||
* The only 2 official versions is GIF87a and GIF89a.
|
||||
*/
|
||||
enum Version
|
||||
{
|
||||
/**
|
||||
* First version of Gif file format from May 1987.
|
||||
*
|
||||
* Note: The checking of unsupported blocks disabled by default to save some time. To enable supported blocks check set `yagp_strict_version_check` debug variable.
|
||||
*/
|
||||
GIF87a;
|
||||
/**
|
||||
* Second and actual version of Gif file format from July 1989.
|
||||
*/
|
||||
GIF89a;
|
||||
/**
|
||||
* Unknown version of Gif file.
|
||||
*/
|
||||
Unknown(version:String);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposal method of GIF frame.
|
||||
*/
|
||||
enum DisposalMethod
|
||||
{
|
||||
/**
|
||||
* The disposal method is unspecified. Action on demand of viewer.
|
||||
*
|
||||
* Mostly interpreted as NO_ACTION.
|
||||
*/
|
||||
UNSPECIFIED;
|
||||
/**
|
||||
* No action required.
|
||||
*/
|
||||
NO_ACTION;
|
||||
/**
|
||||
* Fill frame rectangle with background color.
|
||||
*
|
||||
* Usage note:
|
||||
* Most renderers clears to transparency instead of filling background color, when frame's transparent color index not equals to background color index.
|
||||
*/
|
||||
FILL_BACKGROUND;
|
||||
/**
|
||||
* Render previous state of gif as it before rendering disposing frame.
|
||||
*/
|
||||
RENDER_PREVIOUS;
|
||||
/**
|
||||
* Reserved disposal methods.
|
||||
*/
|
||||
UNDEFINED(index:Int);
|
||||
}
|
||||
359
leenkx/Sources/iron/format/gif/GifEncoder.hx
Normal file
359
leenkx/Sources/iron/format/gif/GifEncoder.hx
Normal file
@ -0,0 +1,359 @@
|
||||
package iron.format.gif;
|
||||
|
||||
/*
|
||||
* No copyright asserted on the source code of this class. May be used
|
||||
* for any purpose.
|
||||
*
|
||||
* Original code by Kevin Weiner, FM Software.
|
||||
* Adapted by Thomas Hourdel (https://github.com/Chman/Moments)
|
||||
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
|
||||
*/
|
||||
|
||||
import haxe.io.UInt8Array;
|
||||
import haxe.io.BytesOutput;
|
||||
|
||||
@:enum abstract GifRepeat(Int)
|
||||
from Int to Int {
|
||||
var None = 0;
|
||||
var Infinite = -1;
|
||||
}
|
||||
|
||||
@:enum abstract GifQuality(Int)
|
||||
from Int to Int {
|
||||
var Best = 1;
|
||||
var VeryHigh = 10;
|
||||
var QuiteHigh = 20;
|
||||
var High = 35;
|
||||
var Mid = 50;
|
||||
var Low = 65;
|
||||
var QuiteLow = 80;
|
||||
var VeryLow = 90;
|
||||
var Worst = 100;
|
||||
}
|
||||
|
||||
class GifEncoder {
|
||||
|
||||
var width: Int;
|
||||
var height: Int;
|
||||
var framerate: Float = 24; // used if frame.delay < 0
|
||||
var repeat: Int = -1; // -1: infinite, 0: none, >0: repeat count
|
||||
|
||||
var colorDepth: Int = 8; // Number of bit planes
|
||||
var paletteSize: Int = 7; // Color table size (bits-1)
|
||||
var sampleInterval: Int = 10; // Default sample interval for quantizer
|
||||
|
||||
//caches
|
||||
var pixels: UInt8Array;
|
||||
var indexedPixels: UInt8Array; // Converted frame indexed to palette
|
||||
var colorTab: UInt8Array; // RGB palette
|
||||
var usedEntry: Array<Bool>; // Active palette entries
|
||||
//
|
||||
var nq: NeuQuant;
|
||||
var lzwEncoder: LzwEncoder;
|
||||
//internal
|
||||
var started: Bool = false;
|
||||
var first_frame: Bool = true;
|
||||
|
||||
//:todo: error handling could be better - but throw inside of another thread on cpp is too quiet
|
||||
|
||||
/** Allows a custom print handler for error messages.
|
||||
Defaults to Sys.println on sys targets, and trace otherwise. */
|
||||
public var print: Dynamic->Void;
|
||||
|
||||
// Public API
|
||||
|
||||
/** Construct a gif encoder with options:
|
||||
|
||||
frame width/height:
|
||||
Default is 0, required
|
||||
|
||||
framerate:
|
||||
This is used if an added frame has a delay that is negative.
|
||||
|
||||
repeat:
|
||||
Default is 0 (no repeat); -1 means play indefinitely.
|
||||
Use GifRepeat for clarity
|
||||
|
||||
quality:
|
||||
Sets quality of color quantization (conversion of images to
|
||||
the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1)
|
||||
produce better colors, but slow processing significantly. Higher values will speed
|
||||
up the quantization pass at the cost of lower image quality (maximum = 100). */
|
||||
public function new(
|
||||
_frame_width:Int,
|
||||
_frame_height:Int,
|
||||
_framerate:Float,
|
||||
_repeat:Int = GifRepeat.Infinite,
|
||||
_quality:Int = 10
|
||||
) {
|
||||
|
||||
#if sys
|
||||
print = Sys.println;
|
||||
#else
|
||||
print = function(v) { trace(v); }
|
||||
#end
|
||||
|
||||
width = _frame_width;
|
||||
height = _frame_height;
|
||||
framerate = _framerate;
|
||||
repeat = _repeat;
|
||||
|
||||
sampleInterval = Std.int(clamp(_quality, 1, 100));
|
||||
usedEntry = [for (i in 0...256) false];
|
||||
|
||||
pixels = new UInt8Array(width * height * 3);
|
||||
indexedPixels = new UInt8Array(width * height);
|
||||
|
||||
nq = new NeuQuant();
|
||||
lzwEncoder = new LzwEncoder();
|
||||
|
||||
} //new
|
||||
|
||||
public function start(output:BytesOutput) : Void {
|
||||
|
||||
if(output == null) {
|
||||
print("gif: start() output must not be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
output.writeString("GIF89a");
|
||||
|
||||
write_LSD(output);
|
||||
|
||||
started = true;
|
||||
|
||||
} //start
|
||||
|
||||
public function add(output:BytesOutput, frame:GifFrame) : Void {
|
||||
|
||||
if(output == null) {
|
||||
print("gif: add() output must not be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!started) {
|
||||
print("gif: add() requires start to be called before adding frames.");
|
||||
return;
|
||||
}
|
||||
|
||||
var pixels = get_pixels(frame);
|
||||
analyze(pixels);
|
||||
|
||||
if(first_frame) {
|
||||
|
||||
write_palette(output);
|
||||
|
||||
if(repeat != GifRepeat.None) {
|
||||
write_NetscapeExt(output);
|
||||
}
|
||||
|
||||
first_frame = false;
|
||||
|
||||
} //first_frame
|
||||
|
||||
var delay = if(frame.delay < 0) {
|
||||
1.0/framerate;
|
||||
} else {
|
||||
frame.delay;
|
||||
}
|
||||
|
||||
write_GraphicControlExt(output, delay);
|
||||
write_image_desc(output, first_frame);
|
||||
|
||||
if(!first_frame) {
|
||||
write_palette(output);
|
||||
}
|
||||
|
||||
write_pixels(output);
|
||||
|
||||
} //add
|
||||
|
||||
public function commit(output:BytesOutput) : Void {
|
||||
|
||||
if(output == null) {
|
||||
print("gif: commit() output must be not null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!started) {
|
||||
print("gif: commit() called without start() being called first.");
|
||||
return;
|
||||
}
|
||||
|
||||
output.writeByte(0x3b); // Gif trailer
|
||||
output.flush();
|
||||
output.close();
|
||||
|
||||
started = false;
|
||||
first_frame = true;
|
||||
|
||||
} //commit
|
||||
|
||||
//helpers
|
||||
|
||||
function get_pixels(frame:GifFrame):UInt8Array {
|
||||
|
||||
//if not flipped we can use the data as is
|
||||
if (!frame.flippedY) return frame.data;
|
||||
|
||||
//otherwise flip it, and return the cached array
|
||||
var stride = width * 3;
|
||||
for(y in 0...height) {
|
||||
var begin = (height - 1 - y) * stride;
|
||||
pixels.view.buffer.blit(y * stride, frame.data.view.buffer, begin, stride);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
|
||||
} //get_pixels
|
||||
|
||||
function analyze(pixels:UInt8Array) {
|
||||
|
||||
// Create reduced palette
|
||||
nq.reset(pixels, pixels.length, sampleInterval);
|
||||
colorTab = nq.process();
|
||||
|
||||
// Map image pixels to new palette
|
||||
var k:Int = 0;
|
||||
for (i in 0...(width * height)) {
|
||||
var r = pixels[k++] & 0xff;
|
||||
var g = pixels[k++] & 0xff;
|
||||
var b = pixels[k++] & 0xff;
|
||||
var index = nq.map(r, g,b);
|
||||
usedEntry[index] = true;
|
||||
indexedPixels[i] = index;
|
||||
}
|
||||
|
||||
} //analyze
|
||||
|
||||
//writers
|
||||
//
|
||||
|
||||
/** Writes Logical Screen Descriptor. */
|
||||
function write_LSD(output:BytesOutput) {
|
||||
//
|
||||
|
||||
// Logical screen size
|
||||
output.writeInt16(width);
|
||||
output.writeInt16(height);
|
||||
|
||||
// Packed fields
|
||||
output.writeByte(0x80 | // 1 : global color table flag = 1 (gct used)
|
||||
0x70 | // 2-4 : color resolution = 7
|
||||
0x00 | // 5 : gct sort flag = 0
|
||||
paletteSize); // 6-8 : gct size
|
||||
|
||||
output.writeByte(0); // Background color index
|
||||
output.writeByte(0); // Pixel aspect ratio - assume 1:1
|
||||
|
||||
} //write_LSD
|
||||
|
||||
/** Writes Netscape application extension to define repeat count. */
|
||||
function write_NetscapeExt(output:BytesOutput):Void {
|
||||
|
||||
var repeats = repeat;
|
||||
if(repeats == GifRepeat.Infinite || repeats < 0) repeats = 0;
|
||||
if(repeats == GifRepeat.None) repeats = -1;
|
||||
|
||||
output.writeByte(0x21); // Extension introducer
|
||||
output.writeByte(0xff); // App extension label
|
||||
output.writeByte(11); // Block size
|
||||
output.writeString("NETSCAPE" + "2.0"); // App id + auth code
|
||||
output.writeByte(3); // Sub-block size
|
||||
output.writeByte(1); // Loop sub-block id
|
||||
output.writeInt16(repeats); // Loop count (extra iterations, 0=repeat forever)
|
||||
output.writeByte(0); // Block terminator
|
||||
|
||||
} //write_NetscapeExt
|
||||
|
||||
/** Write color table. */
|
||||
function write_palette(output:BytesOutput):Void {
|
||||
|
||||
output.write(colorTab.view.buffer);
|
||||
|
||||
var n:Int = (3 * 256) - colorTab.length;
|
||||
|
||||
for (i in 0...n) {
|
||||
output.writeByte(0);
|
||||
}
|
||||
|
||||
} //write_palette
|
||||
|
||||
/** Encodes and writes pixel data. */
|
||||
function write_pixels(output:BytesOutput):Void {
|
||||
|
||||
lzwEncoder.reset(indexedPixels, colorDepth);
|
||||
lzwEncoder.encode(output);
|
||||
|
||||
} //write_pixels
|
||||
|
||||
/** Writes Image Descriptor. */
|
||||
function write_image_desc(output:BytesOutput, first:Bool):Void {
|
||||
|
||||
output.writeByte(0x2c); // Image separator
|
||||
output.writeInt16(0); // Image position x = 0
|
||||
output.writeInt16(0); // Image position y = 0
|
||||
output.writeInt16(width); // Image width
|
||||
output.writeInt16(height); // Image height
|
||||
|
||||
//Write LCT, or GCT
|
||||
|
||||
if(first) {
|
||||
|
||||
output.writeByte(0); // No LCT - GCT is used for first (or only) frame
|
||||
|
||||
} else {
|
||||
|
||||
output.writeByte(0x80 | // 1 local color table 1=yes
|
||||
0 | // 2 interlace - 0=no
|
||||
0 | // 3 sorted - 0=no
|
||||
0 | // 4-5 reserved
|
||||
paletteSize); // 6-8 size of color table
|
||||
|
||||
} //else
|
||||
|
||||
} //write_image_desc
|
||||
|
||||
/** Writes Graphic Control Extension. Delay is in seconds, floored and converted to 1/100 of a second */
|
||||
function write_GraphicControlExt(output:BytesOutput, delay:Float):Void {
|
||||
|
||||
output.writeByte(0x21); // Extension introducer
|
||||
output.writeByte(0xf9); // GCE label
|
||||
output.writeByte(4); // data block size
|
||||
|
||||
// Packed fields
|
||||
output.writeByte(0 | // 1:3 reserved
|
||||
0 | // 4:6 disposal
|
||||
0 | // 7 user input - 0 = none
|
||||
0 ); // 8 transparency flag
|
||||
|
||||
//convert to 1/100 sec
|
||||
var delay_val = Math.floor(delay * 100);
|
||||
|
||||
output.writeInt16(delay_val); // Delay x 1/100 sec
|
||||
output.writeByte(0); // Transparent color index
|
||||
output.writeByte(0); // Block terminator
|
||||
|
||||
} //write_GraphicControlExt
|
||||
|
||||
/** Clamp a value between a and b and return the clamped version */
|
||||
static inline public function clamp(value:Float, a:Float, b:Float):Float
|
||||
{
|
||||
return ( value < a ) ? a : ( ( value > b ) ? b : value );
|
||||
}
|
||||
|
||||
} //GifEncoder
|
||||
|
||||
|
||||
typedef GifFrame = {
|
||||
|
||||
/** Delay of the frame in seconds. This value gets floored
|
||||
when encoded due to gif format requirements. If this value is negative,
|
||||
the default encoder frame rate will be used. */
|
||||
var delay: Float;
|
||||
/** Whether or not this frame should be flipped on the Y axis */
|
||||
var flippedY: Bool;
|
||||
/** Pixels data in unsigned bytes, rgb format */
|
||||
var data: UInt8Array;
|
||||
|
||||
}
|
||||
350
leenkx/Sources/iron/format/gif/LzwEncoder.hx
Normal file
350
leenkx/Sources/iron/format/gif/LzwEncoder.hx
Normal file
@ -0,0 +1,350 @@
|
||||
package iron.format.gif;
|
||||
|
||||
/*
|
||||
* No copyright asserted on the source code of this class. May be used
|
||||
* for any purpose, however, refer to the Unisys LZW patent for restrictions
|
||||
* on use of the associated LZWEncoder class :
|
||||
*
|
||||
* The Unisys patent expired on 20 June 2003 in the USA, in Europe it expired
|
||||
* on 18 June 2004, in Japan the patent expired on 20 June 2004 and in Canada
|
||||
* it expired on 7 July 2004. The U.S. IBM patent expired 11 August 2006, The
|
||||
* Software Freedom Law Center says that after 1 October 2006, there will be
|
||||
* no significant patent claims interfering with employment of the GIF format.
|
||||
*
|
||||
* Original code by Kevin Weiner, FM Software.
|
||||
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
|
||||
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
|
||||
*
|
||||
*/
|
||||
|
||||
import haxe.io.Int32Array;
|
||||
import haxe.io.UInt8Array;
|
||||
|
||||
class LzwEncoder {
|
||||
static var EOF(default, never):Int = -1;
|
||||
|
||||
var pixAry:UInt8Array;
|
||||
var initCodeSize:Int;
|
||||
var curPixel:Int;
|
||||
|
||||
// GIFCOMPR.C - GIF Image compression routines
|
||||
//
|
||||
// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
||||
// David Rowley (mgardi@watdcsu.waterloo.edu)
|
||||
|
||||
// General DEFINEs
|
||||
|
||||
static var BITS(default, never):Int = 12;
|
||||
|
||||
static var HSIZE(default, never):Int = 5003; // 80% occupancy
|
||||
|
||||
// GIF Image compression - modified 'compress'
|
||||
//
|
||||
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
||||
//
|
||||
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
||||
// Jim McKie (decvax!mcvax!jim)
|
||||
// Steve Davies (decvax!vax135!petsd!peora!srd)
|
||||
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
||||
// James A. Woods (decvax!ihnp4!ames!jaw)
|
||||
// Joe Orost (decvax!vax135!petsd!joe)
|
||||
|
||||
var n_bits:Int; // number of bits/code
|
||||
var maxbits:Int = BITS; // user settable max # bits/code
|
||||
var maxcode:Int; // maximum code, given n_bits
|
||||
var maxmaxcode:Int = 1 << BITS; // should NEVER generate this code
|
||||
|
||||
var htab:Int32Array;
|
||||
var codetab:Int32Array;
|
||||
|
||||
var hsize:Int = HSIZE; // for dynamic table sizing
|
||||
|
||||
var free_ent:Int = 0; // first unused entry
|
||||
|
||||
// block compression parameters -- after all codes are used up,
|
||||
// and compression rate changes, start over.
|
||||
var clear_flg:Bool = false;
|
||||
|
||||
// Algorithm: use open addressing double hashing (no chaining) on the
|
||||
// prefix code / next character combination. We do a variant of Knuth's
|
||||
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
|
||||
// secondary probe. Here, the modular division first probe is gives way
|
||||
// to a faster exclusive-or manipulation. Also do block compression with
|
||||
// an adaptive reset, whereby the code table is cleared when the compression
|
||||
// ratio decreases, but after the table fills. The variable-length output
|
||||
// codes are re-sized at this point, and a special CLEAR code is generated
|
||||
// for the decompressor. Late addition: construct the table according to
|
||||
// file size for noticeable speed improvement on small files. Please direct
|
||||
// questions about this implementation to ames!jaw.
|
||||
|
||||
var g_init_bits:Int;
|
||||
|
||||
var ClearCode:Int;
|
||||
var EOFCode:Int;
|
||||
|
||||
// output
|
||||
//
|
||||
// output the given code.
|
||||
// Inputs:
|
||||
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
|
||||
// that n_bits =< wordsize - 1.
|
||||
// outputs:
|
||||
// outputs code to the file.
|
||||
// Assumptions:
|
||||
// Chars are 8 bits long.
|
||||
// Algorithm:
|
||||
// Maintain a BITS character long buffer (so that 8 codes will
|
||||
// fit in it exactly). Use the VAX insv instruction to insert each
|
||||
// code in turn. When the buffer fills up empty it and start over.
|
||||
|
||||
var cur_accum:Int = 0;
|
||||
var cur_bits:Int = 0;
|
||||
|
||||
var masks:Array<Int> =
|
||||
[
|
||||
0x0000,
|
||||
0x0001,
|
||||
0x0003,
|
||||
0x0007,
|
||||
0x000F,
|
||||
0x001F,
|
||||
0x003F,
|
||||
0x007F,
|
||||
0x00FF,
|
||||
0x01FF,
|
||||
0x03FF,
|
||||
0x07FF,
|
||||
0x0FFF,
|
||||
0x1FFF,
|
||||
0x3FFF,
|
||||
0x7FFF,
|
||||
0xFFFF ];
|
||||
|
||||
// Number of characters so far in this 'packet'
|
||||
var a_count:Int;
|
||||
|
||||
// Define the storage for the packet accumulator
|
||||
var accum:UInt8Array;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
public function new()
|
||||
{
|
||||
htab = new Int32Array(HSIZE);
|
||||
codetab = new Int32Array(HSIZE);
|
||||
accum = new UInt8Array(256);
|
||||
}
|
||||
|
||||
//Reset the encoder to new pixel data and default values
|
||||
public function reset(pixels:UInt8Array, color_depth:Int) { //width and height used to be passed in though they were never used
|
||||
pixAry = pixels;
|
||||
initCodeSize = Std.int(Math.max(2, color_depth));
|
||||
|
||||
maxbits = BITS;
|
||||
maxmaxcode = 1 << BITS;
|
||||
hsize = HSIZE;
|
||||
free_ent = 0;
|
||||
clear_flg = false;
|
||||
cur_accum = 0;
|
||||
cur_bits = 0;
|
||||
}
|
||||
|
||||
// add a character to the end of the current packet, and if it is 254
|
||||
// characters, flush the packet to disk.
|
||||
function add(c:UInt, out:haxe.io.Output):Void
|
||||
{
|
||||
accum[a_count++] = c;
|
||||
if (a_count >= 254)
|
||||
flush(out);
|
||||
}
|
||||
|
||||
// Clear out the hash table
|
||||
|
||||
// table clear for block compress
|
||||
function clearTable(out:haxe.io.Output):Void
|
||||
{
|
||||
resetCodeTable(hsize);
|
||||
free_ent = ClearCode + 2;
|
||||
clear_flg = true;
|
||||
|
||||
output(ClearCode, out);
|
||||
}
|
||||
|
||||
// reset code table
|
||||
function resetCodeTable(hsize:Int):Void
|
||||
{
|
||||
for (i in 0...hsize)
|
||||
htab[i] = -1;
|
||||
}
|
||||
|
||||
function compress(init_bits:Int, out:haxe.io.Output):Void
|
||||
{
|
||||
var fcode:Int;
|
||||
var i:Int /* = 0 */;
|
||||
var c:Int;
|
||||
var ent:Int;
|
||||
var disp:Int;
|
||||
var hsize_reg:Int;
|
||||
var hshift:Int;
|
||||
|
||||
// Set up the globals: g_init_bits - initial number of bits
|
||||
g_init_bits = init_bits;
|
||||
|
||||
// Set up the necessary values
|
||||
clear_flg = false;
|
||||
n_bits = g_init_bits;
|
||||
maxcode = maxCode(n_bits);
|
||||
|
||||
ClearCode = 1 << (init_bits - 1);
|
||||
EOFCode = ClearCode + 1;
|
||||
free_ent = ClearCode + 2;
|
||||
|
||||
a_count = 0; // clear packet
|
||||
|
||||
ent = nextPixel();
|
||||
|
||||
hshift = 0;
|
||||
fcode = hsize;
|
||||
while (fcode < 65536) {
|
||||
++hshift;
|
||||
fcode *= 2;
|
||||
}
|
||||
|
||||
hshift = 8 - hshift; // set hash code range bound
|
||||
|
||||
hsize_reg = hsize;
|
||||
resetCodeTable(hsize_reg); // clear hash table
|
||||
|
||||
output(ClearCode, out);
|
||||
|
||||
while ((c = nextPixel()) != EOF)
|
||||
{
|
||||
fcode = (c << maxbits) + ent;
|
||||
i = (c << hshift) ^ ent; // xor hashing
|
||||
|
||||
if (htab[i] == fcode)
|
||||
{
|
||||
ent = codetab[i];
|
||||
continue;
|
||||
}
|
||||
else if (htab[i] >= 0) // non-empty slot
|
||||
{
|
||||
disp = hsize_reg - i; // secondary hash (after G. Knott)
|
||||
if (i == 0)
|
||||
disp = 1;
|
||||
do
|
||||
{
|
||||
if ((i -= disp) < 0)
|
||||
i += hsize_reg;
|
||||
|
||||
if (htab[i] == fcode)
|
||||
{
|
||||
ent = codetab[i];
|
||||
break;
|
||||
}
|
||||
} while (htab[i] >= 0);
|
||||
if (htab[i] == fcode) continue;
|
||||
}
|
||||
output(ent, out);
|
||||
ent = c;
|
||||
if (free_ent < maxmaxcode)
|
||||
{
|
||||
codetab[i] = free_ent++; // code -> hashtable
|
||||
htab[i] = fcode;
|
||||
}
|
||||
else
|
||||
clearTable(out);
|
||||
}
|
||||
// Put out the final code.
|
||||
output(ent, out);
|
||||
output(EOFCode, out);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
public function encode(os:haxe.io.Output):Void
|
||||
{
|
||||
os.writeByte( initCodeSize ); // write "initial code size" byte
|
||||
curPixel = 0;
|
||||
compress(initCodeSize + 1, os); // compress and write the pixel data
|
||||
os.writeByte(0); // write block terminator
|
||||
}
|
||||
|
||||
// flush the packet to disk, and reset the accumulator
|
||||
function flush(out:haxe.io.Output):Void
|
||||
{
|
||||
if (a_count > 0)
|
||||
{
|
||||
out.writeByte(a_count);
|
||||
out.writeBytes(accum.view.buffer, 0, a_count);
|
||||
a_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline function maxCode(n_bits:Int):Int
|
||||
{
|
||||
return (1 << n_bits) - 1;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Return the next pixel from the image
|
||||
//----------------------------------------------------------------------------
|
||||
function nextPixel():Int
|
||||
{
|
||||
if (curPixel == pixAry.length)
|
||||
return EOF;
|
||||
|
||||
curPixel++;
|
||||
return pixAry[curPixel - 1] & 0xff;
|
||||
}
|
||||
|
||||
function output(code:Int, out:haxe.io.Output):Void
|
||||
{
|
||||
cur_accum &= masks[cur_bits];
|
||||
|
||||
if (cur_bits > 0)
|
||||
cur_accum |= (code << cur_bits);
|
||||
else
|
||||
cur_accum = code;
|
||||
|
||||
cur_bits += n_bits;
|
||||
|
||||
while (cur_bits >= 8)
|
||||
{
|
||||
add(cur_accum & 0xff, out);
|
||||
cur_accum >>= 8;
|
||||
cur_bits -= 8;
|
||||
}
|
||||
|
||||
// If the next entry is going to be too big for the code size,
|
||||
// then increase it, if possible.
|
||||
if (free_ent > maxcode || clear_flg)
|
||||
{
|
||||
if (clear_flg)
|
||||
{
|
||||
maxcode = maxCode(n_bits = g_init_bits);
|
||||
clear_flg = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
++n_bits;
|
||||
if (n_bits == maxbits)
|
||||
maxcode = maxmaxcode;
|
||||
else
|
||||
maxcode = maxCode(n_bits);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == EOFCode)
|
||||
{
|
||||
// At EOF, write the rest of the buffer.
|
||||
while (cur_bits > 0)
|
||||
{
|
||||
add(cur_accum & 0xff, out);
|
||||
cur_accum >>= 8;
|
||||
cur_bits -= 8;
|
||||
}
|
||||
|
||||
flush(out);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
541
leenkx/Sources/iron/format/gif/NeuQuant.hx
Normal file
541
leenkx/Sources/iron/format/gif/NeuQuant.hx
Normal file
@ -0,0 +1,541 @@
|
||||
package iron.format.gif;
|
||||
|
||||
/*
|
||||
* Copyright (c) 1994 Anthony Dekker
|
||||
* Ported to Java by Kevin Weiner, FM Software
|
||||
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
|
||||
*
|
||||
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
|
||||
* See "Kohonen neural networks for optimal colour quantization"
|
||||
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
|
||||
* for a discussion of the algorithm.
|
||||
*
|
||||
* Any party obtaining a copy of these files from the author, directly or
|
||||
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
|
||||
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
|
||||
* in this software and documentation files (the "Software"), including without
|
||||
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons who receive
|
||||
* copies from any such party to do so, with the only requirement being
|
||||
* that this copyright notice remain intact.
|
||||
*
|
||||
*/
|
||||
|
||||
import haxe.io.Int32Array;
|
||||
import haxe.io.UInt8Array;
|
||||
|
||||
class NeuQuant {
|
||||
|
||||
inline static var netsize : Int = 256; // Number of colours used
|
||||
|
||||
// Four primes near 500 - assume no image has a length so large that it is divisible by all four primes
|
||||
inline static var prime1 : Int = 499;
|
||||
inline static var prime2 : Int = 491;
|
||||
inline static var prime3 : Int = 487;
|
||||
inline static var prime4 : Int = 503;
|
||||
|
||||
inline static var minpicturebytes : Int = (3 * prime4); // Minimum size for input image
|
||||
|
||||
// Network Definitions
|
||||
inline static var netbiasshift : Int = 4; // Bias for colour values
|
||||
inline static var ncycles : Int = 100; // No. of learning cycles
|
||||
|
||||
// Defs for freq and bias
|
||||
inline static var intbiasshift : Int = 16; // Bias for fractions
|
||||
inline static var intbias : Int = (1 << intbiasshift);
|
||||
inline static var gammashift : Int = 10; // Gamma = 1024
|
||||
inline static var gamma : Int = (1 << gammashift);
|
||||
inline static var betashift : Int = 10;
|
||||
inline static var beta : Int = (intbias >> betashift); // Beta = 1/1024
|
||||
inline static var betagamma : Int = (intbias << (gammashift - betashift));
|
||||
|
||||
// Defs for decreasing radius factor
|
||||
inline static var initrad : Int = (netsize >> 3); // For 256 cols, radius starts
|
||||
inline static var radiusbiasshift : Int = 6; // At 32.0 biased by 6 bits
|
||||
inline static var radiusbias : Int = (1 << radiusbiasshift);
|
||||
inline static var initradius : Int = (initrad * radiusbias); // And decreases by a
|
||||
inline static var radiusdec : Int = 30; // Factor of 1/30 each cycle
|
||||
|
||||
// Defs for decreasing alpha factor
|
||||
inline static var alphabiasshift : Int = 10; /* alpha starts at 1.0 */
|
||||
inline static var initalpha : Int = (1 << alphabiasshift);
|
||||
|
||||
// Radbias and alpharadbias used for radpower calculation
|
||||
inline static var radbiasshift : Int = 8;
|
||||
inline static var radbias : Int = (1 << radbiasshift);
|
||||
inline static var alpharadbshift : Int = (alphabiasshift + radbiasshift);
|
||||
inline static var alpharadbias : Int = (1 << alpharadbshift);
|
||||
|
||||
var alphadec:Int; // Biased by 10 bits
|
||||
|
||||
// Types and Global Variables
|
||||
|
||||
var thepicture: UInt8Array; // The input image itself
|
||||
var lengthcount: Int; // Lengthcount = H*W*3
|
||||
var samplefac: Int; // Sampling factor 1..30
|
||||
var network: Int32Array; // The network itself - [netsize][4]
|
||||
var netindex: Int32Array; // For network lookup - really 256
|
||||
var bias: Int32Array; // Bias array for learning
|
||||
var freq: Int32Array; // Frequency array for learning
|
||||
var radpower: Int32Array; // Radpower for precomputation
|
||||
var colormap_map: UInt8Array; // Cached color map array
|
||||
var colormap_index: Int32Array; // Cached color map index
|
||||
|
||||
public function new()
|
||||
{
|
||||
netindex = new Int32Array(256);
|
||||
bias = new Int32Array(netsize);
|
||||
freq = new Int32Array(netsize);
|
||||
radpower = new Int32Array(initrad);
|
||||
network = new Int32Array(netsize * 4);
|
||||
colormap_map = new UInt8Array(3 * netsize);
|
||||
colormap_index = new Int32Array(netsize);
|
||||
}
|
||||
|
||||
// Reset network in range (0,0,0) to (255,255,255) and set parameters
|
||||
public function reset(thepic:UInt8Array, len:Int, sample:Int):Void {
|
||||
thepicture = thepic;
|
||||
lengthcount = len;
|
||||
samplefac = sample;
|
||||
|
||||
for (i in 0...netsize) {
|
||||
network[i*4 + 0] = network[i*4 + 1] = network[i*4 + 2] = Std.int((i << (netbiasshift + 8)) / netsize);
|
||||
freq[i] = Std.int(intbias / netsize); // 1 / netsize
|
||||
bias[i] = 0; // allocated to zero?
|
||||
}
|
||||
}
|
||||
|
||||
public function colormap():UInt8Array
|
||||
{
|
||||
for(i in 0...netsize) {
|
||||
colormap_index[network[i * 4 + 3]] = i;
|
||||
}
|
||||
|
||||
var k:Int = 0;
|
||||
for (i in 0...netsize)
|
||||
{
|
||||
var j = colormap_index[i];
|
||||
colormap_map[k++] = network[j * 4];
|
||||
colormap_map[k++] = network[j * 4 + 1];
|
||||
colormap_map[k++] = network[j * 4 + 2];
|
||||
}
|
||||
|
||||
return colormap_map;
|
||||
}
|
||||
|
||||
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
|
||||
public function inxbuild():Void
|
||||
{
|
||||
var i:Int;
|
||||
var j:Int;
|
||||
var smallpos:Int;
|
||||
var smallval:Int;
|
||||
var previouscol:Int;
|
||||
var startpos:Int;
|
||||
|
||||
previouscol = 0;
|
||||
startpos = 0;
|
||||
|
||||
for (i in 0...netsize)
|
||||
{
|
||||
smallpos = i;
|
||||
smallval = network[i*4 + 1]; // Index on g
|
||||
|
||||
// Find smallest in i..netsize-1
|
||||
for (j in (i + 1)...netsize)
|
||||
{
|
||||
if (network[j*4 + 1] < smallval)
|
||||
{
|
||||
smallpos = j;
|
||||
smallval = network[j*4 + 1]; // Index on g
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Swap p (i) and q (smallpos) entries
|
||||
if (i != smallpos)
|
||||
{
|
||||
j = network[smallpos*4 + 0];
|
||||
network[smallpos*4 + 0] = network[i*4 + 0];
|
||||
network[i*4 + 0] = j;
|
||||
j = network[smallpos*4 + 1];
|
||||
network[smallpos*4 + 1] = network[i*4 + 1];
|
||||
network[i*4 + 1] = j;
|
||||
j = network[smallpos*4 + 2];
|
||||
network[smallpos*4 + 2] = network[i*4 + 2];
|
||||
network[i*4 + 2] = j;
|
||||
j = network[smallpos*4 + 3];
|
||||
network[smallpos*4 + 3] = network[i*4 + 3];
|
||||
network[i*4 + 3] = j;
|
||||
}
|
||||
|
||||
// Smallval entry is now in position i
|
||||
if (smallval != previouscol)
|
||||
{
|
||||
netindex[previouscol] = (startpos + i) >> 1;
|
||||
|
||||
for (j in (previouscol + 1)...smallval)
|
||||
netindex[j] = i;
|
||||
|
||||
previouscol = smallval;
|
||||
startpos = i;
|
||||
}
|
||||
}
|
||||
|
||||
var maxnetpos = netsize - 1;
|
||||
|
||||
netindex[previouscol] = (startpos + maxnetpos) >> 1;
|
||||
|
||||
for (j in (previouscol + 1)...256)
|
||||
netindex[j] = maxnetpos;
|
||||
}
|
||||
|
||||
// Main learning Loop
|
||||
public function learn():Void
|
||||
{
|
||||
var i:Int;
|
||||
var j:Int;
|
||||
var b:Int;
|
||||
var g:Int;
|
||||
var r:Int;
|
||||
var radius:Int;
|
||||
var rad:Int;
|
||||
var alpha:Int;
|
||||
var step:Int;
|
||||
var delta:Int;
|
||||
var samplepixels:Int;
|
||||
|
||||
var p:UInt8Array;
|
||||
var pix:Int;
|
||||
var lim:Int;
|
||||
|
||||
if (lengthcount < minpicturebytes)
|
||||
samplefac = 1;
|
||||
|
||||
alphadec = 30 + Std.int((samplefac - 1) / 3);
|
||||
p = thepicture;
|
||||
pix = 0;
|
||||
lim = lengthcount;
|
||||
samplepixels = Std.int(lengthcount / (3 * samplefac));
|
||||
delta = Std.int(samplepixels / ncycles);
|
||||
alpha = initalpha;
|
||||
radius = initradius;
|
||||
|
||||
rad = radius >> radiusbiasshift;
|
||||
|
||||
if (rad <= 1)
|
||||
rad = 0;
|
||||
|
||||
for (i in 0...rad)
|
||||
radpower[i] = Std.int(alpha * (((rad * rad - i * i) * radbias) / (rad * rad)));
|
||||
|
||||
if (lengthcount < minpicturebytes)
|
||||
{
|
||||
step = 3;
|
||||
}
|
||||
else if ((lengthcount % prime1) != 0)
|
||||
{
|
||||
step = 3 * prime1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((lengthcount % prime2) != 0)
|
||||
{
|
||||
step = 3 * prime2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((lengthcount % prime3) != 0)
|
||||
step = 3 * prime3;
|
||||
else
|
||||
step = 3 * prime4;
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (i < samplepixels)
|
||||
{
|
||||
b = (p[pix + 0] & 0xff) << netbiasshift;
|
||||
g = (p[pix + 1] & 0xff) << netbiasshift;
|
||||
r = (p[pix + 2] & 0xff) << netbiasshift;
|
||||
j = contest(b, g, r);
|
||||
|
||||
altersingle(alpha, j, b, g, r);
|
||||
|
||||
if (rad != 0)
|
||||
alterneigh(rad, j, b, g, r); // Alter neighbours
|
||||
|
||||
pix += step;
|
||||
|
||||
if (pix >= lim)
|
||||
pix -= lengthcount;
|
||||
|
||||
i++;
|
||||
|
||||
if (delta == 0)
|
||||
delta = 1;
|
||||
|
||||
if (i % delta == 0)
|
||||
{
|
||||
alpha -= Std.int(alpha / alphadec);
|
||||
radius -= Std.int(radius / radiusdec);
|
||||
rad = radius >> radiusbiasshift;
|
||||
|
||||
if (rad <= 1)
|
||||
rad = 0;
|
||||
|
||||
for (j in 0...rad)
|
||||
radpower[j] = Std.int(alpha * (((rad * rad - j * j) * radbias) / (rad * rad)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for BGR values 0..255 (after net is unbiased) and return colour index
|
||||
public function map(b:Int, g:Int, r:Int):Int
|
||||
{
|
||||
var i:Int;
|
||||
var j:Int;
|
||||
var dist:Int;
|
||||
var a:Int;
|
||||
var bestd:Int;
|
||||
var best:Int;
|
||||
|
||||
bestd = 1000; // Biggest possible dist is 256*3
|
||||
best = -1;
|
||||
i = netindex[g]; // Index on g
|
||||
j = i - 1; // Start at netindex[g] and work outwards
|
||||
|
||||
while ((i < netsize) || (j >= 0))
|
||||
{
|
||||
if (i < netsize)
|
||||
{
|
||||
dist = network[i*4 + 1] - g; // Inx key
|
||||
|
||||
if (dist >= bestd)
|
||||
{
|
||||
i = netsize; // Stop iter
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dist < 0)
|
||||
dist = -dist;
|
||||
|
||||
a = network[i*4 + 0] - b;
|
||||
|
||||
if (a < 0)
|
||||
a = -a;
|
||||
|
||||
dist += a;
|
||||
|
||||
if (dist < bestd)
|
||||
{
|
||||
a = network[i*4 + 2] - r;
|
||||
|
||||
if (a < 0)
|
||||
a = -a;
|
||||
|
||||
dist += a;
|
||||
|
||||
if (dist < bestd)
|
||||
{
|
||||
bestd = dist;
|
||||
best = network[i*4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (j >= 0)
|
||||
{
|
||||
dist = g - network[j*4 + 1]; // Inx key - reverse dif
|
||||
|
||||
if (dist >= bestd)
|
||||
{
|
||||
j = -1; // Stop iter
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dist < 0)
|
||||
dist = -dist;
|
||||
|
||||
a = network[j*4 + 0] - b;
|
||||
|
||||
if (a < 0)
|
||||
a = -a;
|
||||
|
||||
dist += a;
|
||||
|
||||
if (dist < bestd)
|
||||
{
|
||||
a = network[j*4 + 2] - r;
|
||||
|
||||
if (a < 0)
|
||||
a = -a;
|
||||
|
||||
dist += a;
|
||||
|
||||
if (dist < bestd)
|
||||
{
|
||||
bestd = dist;
|
||||
best = network[j*4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
public function process():UInt8Array
|
||||
{
|
||||
learn();
|
||||
unbiasnet();
|
||||
inxbuild();
|
||||
return colormap();
|
||||
}
|
||||
|
||||
// Unbias network to give byte values 0..255 and record position i to prepare for sort
|
||||
public function unbiasnet():Void
|
||||
{
|
||||
for (i in 0...netsize)
|
||||
{
|
||||
network[i*4] >>= netbiasshift;
|
||||
network[i*4 + 1] >>= netbiasshift;
|
||||
network[i*4 + 2] >>= netbiasshift;
|
||||
network[i*4 + 3] = i; // Record colour no
|
||||
}
|
||||
}
|
||||
|
||||
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
|
||||
function alterneigh(rad:Int, i:Int, b:Int, g:Int, r:Int):Void
|
||||
{
|
||||
var j:Int;
|
||||
var k:Int;
|
||||
var lo:Int;
|
||||
var hi:Int;
|
||||
var a:Int;
|
||||
var m:Int;
|
||||
|
||||
lo = i - rad;
|
||||
|
||||
if (lo < -1)
|
||||
lo = -1;
|
||||
|
||||
hi = i + rad;
|
||||
|
||||
if (hi > netsize)
|
||||
hi = netsize;
|
||||
|
||||
j = i + 1;
|
||||
k = i - 1;
|
||||
m = 1;
|
||||
|
||||
while ((j < hi) || (k > lo))
|
||||
{
|
||||
a = radpower[m++];
|
||||
|
||||
if (j < hi)
|
||||
{
|
||||
network[j * 4 + 0] -= Std.int((a * (network[j * 4 + 0] - b)) / alpharadbias);
|
||||
network[j * 4 + 1] -= Std.int((a * (network[j * 4 + 1] - g)) / alpharadbias);
|
||||
network[j * 4 + 2] -= Std.int((a * (network[j * 4 + 2] - r)) / alpharadbias);
|
||||
j++;
|
||||
}
|
||||
|
||||
if (k > lo)
|
||||
{
|
||||
network[k * 4 + 0] -= Std.int((a * (network[k * 4 + 0] - b)) / alpharadbias);
|
||||
network[k * 4 + 1] -= Std.int((a * (network[k * 4 + 1] - g)) / alpharadbias);
|
||||
network[k * 4 + 2] -= Std.int((a * (network[k * 4 + 2] - r)) / alpharadbias);
|
||||
k--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move neuron i towards biased (b,g,r) by factor alpha
|
||||
function altersingle(alpha:Int, i:Int, b:Int, g:Int, r:Int):Void
|
||||
{
|
||||
/* Alter hit neuron */
|
||||
network[i*4 + 0] -= Std.int((alpha * (network[i*4 + 0] - b)) / initalpha);
|
||||
network[i*4 + 1] -= Std.int((alpha * (network[i*4 + 1] - g)) / initalpha);
|
||||
network[i*4 + 2] -= Std.int((alpha * (network[i*4 + 2] - r)) / initalpha);
|
||||
}
|
||||
|
||||
inline function make_abs(value:Int) : Int {
|
||||
var tmp = value >> 31;
|
||||
value ^= tmp;
|
||||
value += tmp & 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Search for biased BGR values
|
||||
static inline var bestd_init = ~(1 << 31);
|
||||
function contest(b:Int, g:Int, r:Int):Int
|
||||
{
|
||||
// Finds closest neuron (min dist) and updates freq
|
||||
// Finds best neuron (min dist-bias) and returns position
|
||||
// For frequently chosen neurons, freq[i] is high and bias[i] is negative
|
||||
// bias[i] = gamma*((1/netsize)-freq[i])
|
||||
|
||||
var i:Int;
|
||||
var dist:Int;
|
||||
var a:Int;
|
||||
var biasdist:Int;
|
||||
var betafreq:Int;
|
||||
var bestpos:Int;
|
||||
var bestbiaspos:Int;
|
||||
var bestd:Int;
|
||||
var bestbiasd:Int;
|
||||
|
||||
bestd = bestd_init;
|
||||
bestbiasd = bestd;
|
||||
bestpos = -1;
|
||||
bestbiaspos = bestpos;
|
||||
|
||||
for (i in 0...netsize)
|
||||
{
|
||||
var i_n = i * 4;
|
||||
var b_i = i_n + 0;
|
||||
var g_i = i_n + 1;
|
||||
var r_i = i_n + 2;
|
||||
|
||||
var b_a = network[b_i];
|
||||
var g_a = network[g_i];
|
||||
var r_a = network[r_i];
|
||||
|
||||
b_a = make_abs(b_a - b);
|
||||
g_a = make_abs(g_a - g);
|
||||
r_a = make_abs(r_a - r);
|
||||
|
||||
dist = b_a + g_a + r_a;
|
||||
|
||||
if (dist < bestd)
|
||||
{
|
||||
bestd = dist;
|
||||
bestpos = i;
|
||||
}
|
||||
|
||||
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
|
||||
|
||||
if (biasdist < bestbiasd)
|
||||
{
|
||||
bestbiasd = biasdist;
|
||||
bestbiaspos = i;
|
||||
}
|
||||
|
||||
betafreq = (freq[i] >> betashift);
|
||||
freq[i] -= betafreq;
|
||||
bias[i] += (betafreq << gammashift);
|
||||
}
|
||||
|
||||
freq[bestpos] += beta;
|
||||
bias[bestpos] -= betagamma;
|
||||
return bestbiaspos;
|
||||
}
|
||||
|
||||
}
|
||||
346
leenkx/Sources/iron/format/gif/Reader.hx
Normal file
346
leenkx/Sources/iron/format/gif/Reader.hx
Normal file
@ -0,0 +1,346 @@
|
||||
package iron.format.gif;
|
||||
import iron.format.gif.Data;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Input;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author Yanrishatum
|
||||
*/
|
||||
class Reader
|
||||
{
|
||||
|
||||
private var i:Input;
|
||||
|
||||
public function new(i:Input)
|
||||
{
|
||||
this.i = i;
|
||||
i.bigEndian = false;
|
||||
}
|
||||
|
||||
public function read():Data
|
||||
{
|
||||
for (b in [71, 73, 70])
|
||||
{
|
||||
if (i.readByte() != b) throw "Invalid header";
|
||||
}
|
||||
|
||||
var gifVer:String = i.readString(3);
|
||||
var version:Version = Version.GIF89a;
|
||||
switch(gifVer)
|
||||
{
|
||||
case "87a": version = Version.GIF87a;
|
||||
case "89a": version = Version.GIF89a;
|
||||
default: version = Version.Unknown(gifVer);
|
||||
}
|
||||
|
||||
// Logical screen descriptor.
|
||||
var width:Int = i.readUInt16();
|
||||
var height:Int = i.readUInt16();
|
||||
var packedField:Int = i.readByte();
|
||||
var bgIndex:Int = i.readByte();
|
||||
var pixelAspectRatio:Float = i.readByte();
|
||||
if (pixelAspectRatio != 0) pixelAspectRatio = (pixelAspectRatio + 15) / 64;
|
||||
else pixelAspectRatio = 1;
|
||||
|
||||
var lsd:LogicalScreenDescriptor =
|
||||
{
|
||||
width: width,
|
||||
height: height,
|
||||
hasGlobalColorTable: (packedField & 128) == 128,
|
||||
colorResolution: (packedField & 112) >>> 4,
|
||||
sorted: (packedField & 8) == 8,
|
||||
globalColorTableSize: 2 << (packedField & 7),
|
||||
backgroundColorIndex: bgIndex,
|
||||
pixelAspectRatio: pixelAspectRatio
|
||||
}
|
||||
|
||||
var gct:ColorTable = null;
|
||||
if (lsd.hasGlobalColorTable) gct = readColorTable(lsd.globalColorTableSize);
|
||||
|
||||
var blocks:List<Block> = new List();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var b:Block = readBlock();
|
||||
blocks.add(b);
|
||||
if (b == Block.BEOF) break;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
version: version,
|
||||
logicalScreenDescriptor: lsd,
|
||||
globalColorTable: gct,
|
||||
blocks: blocks
|
||||
}
|
||||
}
|
||||
|
||||
private function readBlock():Block
|
||||
{
|
||||
var blockID:Int = i.readByte();
|
||||
switch(blockID)
|
||||
{
|
||||
case 0x2C:
|
||||
// Image
|
||||
return readImage();
|
||||
case 0x21:
|
||||
// Extension
|
||||
return readExtension();
|
||||
case 0x3B:
|
||||
return Block.BEOF;
|
||||
}
|
||||
// The behaviour of taking unknown block ID is unspecified.
|
||||
return Block.BEOF;
|
||||
}
|
||||
|
||||
private function readImage():Block
|
||||
{
|
||||
var x:Int = i.readUInt16();
|
||||
var y:Int = i.readUInt16();
|
||||
var width:Int = i.readUInt16();
|
||||
var height:Int = i.readUInt16();
|
||||
var packed:Int = i.readByte();
|
||||
var localColorTable:Bool = (packed & 128) == 128;
|
||||
var interlaced:Bool = (packed & 64) == 64;
|
||||
var sorted:Bool = (packed & 32) == 32;
|
||||
var localColorTableSize:Int = 2 << (packed & 7);
|
||||
|
||||
var lct:ColorTable = null;
|
||||
if (localColorTable) lct = readColorTable(localColorTableSize);
|
||||
|
||||
return Block.BFrame(
|
||||
{
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height,
|
||||
localColorTable: localColorTable,
|
||||
interlaced:interlaced,
|
||||
sorted:sorted,
|
||||
localColorTableSize:localColorTableSize,
|
||||
pixels:readPixels(width, height, interlaced),
|
||||
colorTable:lct
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function readPixels(width:Int, height:Int, interlaced:Bool):Bytes
|
||||
{
|
||||
var input:Input = this.i;
|
||||
|
||||
var pixelsCount:Int = width * height;
|
||||
var pixels:Bytes = Bytes.alloc(pixelsCount);
|
||||
|
||||
var minCodeSize:Int = input.readByte();
|
||||
|
||||
var blockSize:Int = input.readByte() - 1;
|
||||
var bits:Int = input.readByte();
|
||||
var bitsCount:Int = 8;
|
||||
|
||||
var clearCode:Int = 1 << minCodeSize;
|
||||
var eoiCode:Int = clearCode + 1;
|
||||
|
||||
var codeSize:Int = minCodeSize + 1;
|
||||
var codeSizeLimit:Int = 1 << codeSize;
|
||||
var codeMask = codeSizeLimit - 1;
|
||||
|
||||
|
||||
var baseDict:Array<Array<Int>> = new Array();
|
||||
for (i in 0...clearCode) baseDict[i] = [i];
|
||||
|
||||
var dict:Array<Array<Int>> = new Array();
|
||||
var dictLen:Int = clearCode + 2;
|
||||
var newRecord:Array<Int>;
|
||||
|
||||
var i:Int = 0;
|
||||
var code:Int = 0;
|
||||
var last:Int;
|
||||
|
||||
while (i < pixelsCount)
|
||||
{
|
||||
last = code;
|
||||
while (bitsCount < codeSize)
|
||||
{
|
||||
if (blockSize == 0) break;
|
||||
bits |= input.readByte() << bitsCount;
|
||||
bitsCount += 8;
|
||||
blockSize--;
|
||||
if (blockSize == 0) blockSize = input.readByte();
|
||||
}
|
||||
code = bits & codeMask;
|
||||
bits >>= codeSize;
|
||||
bitsCount -= codeSize;
|
||||
|
||||
if (code == clearCode)
|
||||
{
|
||||
dict = baseDict.copy();
|
||||
dictLen = clearCode + 2;
|
||||
codeSize = minCodeSize + 1;
|
||||
codeSizeLimit = (1 << codeSize);
|
||||
codeMask = codeSizeLimit - 1;
|
||||
continue;
|
||||
}
|
||||
if (code == eoiCode) break;
|
||||
|
||||
if (code < dictLen)
|
||||
{
|
||||
if (last != clearCode)
|
||||
{
|
||||
newRecord = dict[last].copy();
|
||||
newRecord.push(dict[code][0]);
|
||||
dict[dictLen++] = newRecord;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code != dictLen) throw 'Invalid LZW code. Excepted: $dictLen, got: $code';
|
||||
newRecord = dict[last].copy();
|
||||
newRecord.push(newRecord[0]);
|
||||
dict[dictLen++] = newRecord;
|
||||
}
|
||||
|
||||
newRecord = dict[code];
|
||||
for (item in newRecord) pixels.set(i++, item);
|
||||
|
||||
if (dictLen == codeSizeLimit && codeSize < 12)
|
||||
{
|
||||
codeSize++;
|
||||
codeSizeLimit = (1 << codeSize);
|
||||
codeMask = codeSizeLimit - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Just in case
|
||||
while (blockSize > 0)
|
||||
{
|
||||
input.readByte();
|
||||
blockSize--;
|
||||
if (blockSize == 0) blockSize = input.readByte();
|
||||
}
|
||||
|
||||
while (i < pixelsCount) pixels.set(i++, 0);
|
||||
if (interlaced)
|
||||
{
|
||||
var buffer:Bytes = Bytes.alloc(pixelsCount);
|
||||
var offset:Int = deinterlace(pixels, buffer, 8, 0, 0 , width, height); // Every 8 line with start at 0
|
||||
offset = deinterlace(pixels, buffer, 8, 4, offset, width, height); // Every 8 line with start at 4
|
||||
offset = deinterlace(pixels, buffer, 4, 2, offset, width, height); // Every 4 line with start at 2
|
||||
deinterlace(pixels, buffer, 2, 1, offset, width, height); // Every 2 line with start at 1
|
||||
pixels = buffer;
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private function deinterlace(input:Bytes, output:Bytes, step:Int, y:Int, offset:Int, width:Int, height:Int):Int
|
||||
{
|
||||
while (y < height)
|
||||
{
|
||||
output.blit(y * width, input, offset, width);
|
||||
offset += width;
|
||||
y += step;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private function readExtension():Block
|
||||
{
|
||||
var subId:Int = i.readByte();
|
||||
|
||||
switch(subId)
|
||||
{
|
||||
case 0xF9:
|
||||
// Graphics Control Extension
|
||||
if (i.readByte() != 4) throw "Incorrect Graphic Control Extension block size!";
|
||||
var packed:Int = i.readByte();
|
||||
var disposalMethod:DisposalMethod = switch ( (packed & 28) >> 2)
|
||||
{
|
||||
case 0: DisposalMethod.UNSPECIFIED;
|
||||
case 1: DisposalMethod.NO_ACTION;
|
||||
case 2: DisposalMethod.FILL_BACKGROUND;
|
||||
case 3: DisposalMethod.RENDER_PREVIOUS;
|
||||
default: DisposalMethod.UNDEFINED((packed & 28) >> 2);
|
||||
};
|
||||
var b:Block = Block.BExtension(Extension.EGraphicControl(
|
||||
{
|
||||
disposalMethod:disposalMethod,
|
||||
userInput: (packed & 2) == 2,
|
||||
hasTransparentColor: (packed & 1) == 1,
|
||||
delay: i.readUInt16(),
|
||||
transparentIndex: i.readByte()
|
||||
}));
|
||||
i.readByte(); // Terminator
|
||||
return b;
|
||||
case 0x01:
|
||||
// Text block
|
||||
// Exists only on paper, nobody ever used it.
|
||||
if (i.readByte() != 12) throw "Incorrect size of Plain Text Extension introducer block.";
|
||||
return Block.BExtension(Extension.EText(
|
||||
{
|
||||
textGridX: i.readUInt16(),
|
||||
textGridY: i.readUInt16(),
|
||||
textGridWidth: i.readUInt16(),
|
||||
textGridHeight: i.readUInt16(),
|
||||
charCellWidth: i.readByte(),
|
||||
charCellHeight: i.readByte(),
|
||||
textForegroundColorIndex: i.readByte(),
|
||||
textBackgroundColorIndex: i.readByte(),
|
||||
text: readBlocks().toString()
|
||||
}));
|
||||
case 0xFE:
|
||||
// Commentary
|
||||
return Block.BExtension(Extension.EComment(readBlocks().toString()));
|
||||
case 0xFF:
|
||||
// Application extension
|
||||
return readApplicationExtension();
|
||||
default:
|
||||
return Block.BExtension(Extension.EUnknown(subId, readBlocks()));
|
||||
}
|
||||
}
|
||||
|
||||
private function readApplicationExtension():Block
|
||||
{
|
||||
if (i.readByte() != 11) throw "Incorrect size of Application Extension introducer block.";
|
||||
var name:String = i.readString(8);
|
||||
var version:String = i.readString(3);
|
||||
var data:Bytes = readBlocks();
|
||||
if (name == "NETSCAPE" && version == "2.0" && data.get(0) == 1)
|
||||
{
|
||||
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(data.get(1) | (data.get(2) << 8))));
|
||||
}
|
||||
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AEUnknown(name, version, data)));
|
||||
}
|
||||
|
||||
private inline function readBlocks():Bytes
|
||||
{
|
||||
var buffer:BytesOutput = new BytesOutput();
|
||||
var bytes:Bytes = Bytes.alloc(255);
|
||||
var len:Int = i.readByte();
|
||||
while (len != 0)
|
||||
{
|
||||
i.readBytes(bytes, 0, len);
|
||||
buffer.writeBytes(bytes, 0, len);
|
||||
len = i.readByte();
|
||||
}
|
||||
buffer.flush();
|
||||
bytes = buffer.getBytes();
|
||||
buffer.close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private function readColorTable(size:Int):ColorTable
|
||||
{
|
||||
size *= 3;
|
||||
var output:ColorTable = ColorTable.alloc(size);
|
||||
var c:Int = 0;
|
||||
while (c < size)
|
||||
{
|
||||
output.set(c , i.readByte()); // R
|
||||
output.set(c + 1, i.readByte()); // G
|
||||
output.set(c + 2, i.readByte()); // B
|
||||
c += 3;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
390
leenkx/Sources/iron/format/gif/Tools.hx
Normal file
390
leenkx/Sources/iron/format/gif/Tools.hx
Normal file
@ -0,0 +1,390 @@
|
||||
package iron.format.gif;
|
||||
|
||||
import iron.format.gif.Data;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesData;
|
||||
|
||||
/**
|
||||
* Tools for gif data.
|
||||
* @author Yanrishatum
|
||||
*/
|
||||
class Tools
|
||||
{
|
||||
/**
|
||||
* Returns amount of frames in Gif data.
|
||||
*/
|
||||
public static function framesCount(data:Data):Int
|
||||
{
|
||||
var frames:Int = 0;
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch(block)
|
||||
{
|
||||
case Block.BFrame(_):
|
||||
frames++;
|
||||
default :
|
||||
}
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns frame at given index.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Index of frame.
|
||||
* @return Frame at given index or null, if there is no frame at that index.
|
||||
*/
|
||||
public static function frame(data:Data, frameIndex:Int):Frame
|
||||
{
|
||||
var counter:Int = 0;
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BFrame(frame):
|
||||
if (counter == frameIndex) return frame;
|
||||
counter++;
|
||||
default :
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Graphic Control extension for frame at given index.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Index of frame.
|
||||
* @return GCE extension if it is exists for given frame, null otherwise.
|
||||
*/
|
||||
public static function graphicControl(data:Data, frameIndex:Int):GraphicControlExtension
|
||||
{
|
||||
var counter:Int = 0;
|
||||
var gce:GraphicControlExtension = null;
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BFrame(frame):
|
||||
if (counter == frameIndex) return gce;
|
||||
gce = null;
|
||||
counter++;
|
||||
case Block.BExtension(Extension.EGraphicControl(g)):
|
||||
gce = g;
|
||||
default :
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
// Extracting.
|
||||
//==========================================================
|
||||
|
||||
/**
|
||||
* Extracts frame pixel data in Blue-Green-Red-Alpha pixel format.
|
||||
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Frame index.
|
||||
* @return BGRA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
|
||||
*/
|
||||
public static function extractBGRA(data:Data, frameIndex:Int):Bytes
|
||||
{
|
||||
var gce:GraphicControlExtension = null;
|
||||
var frameCaret:Int = 0;
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BExtension(ext):
|
||||
switch(ext)
|
||||
{
|
||||
case Extension.EGraphicControl(g):
|
||||
gce = g;
|
||||
default:
|
||||
}
|
||||
case Block.BFrame(frame):
|
||||
if (frameCaret == frameIndex)
|
||||
{
|
||||
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
|
||||
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
|
||||
if (ct == null) throw "Frame does not have a color table!";
|
||||
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
|
||||
var writeCaret:Int = 0;
|
||||
for (i in 0...frame.pixels.length)
|
||||
{
|
||||
var index:Int = frame.pixels.get(i) * 3;
|
||||
bytes.set(writeCaret , ct.get(index + 2)); // B
|
||||
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
|
||||
bytes.set(writeCaret + 2, ct.get(index )); // R
|
||||
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
|
||||
else bytes.set(writeCaret + 3, 0xFF); // A = FF
|
||||
|
||||
writeCaret += 4;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
frameCaret++;
|
||||
gce = null;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts frame pixel data in Red-Green-Blue-Alpha pixel format.
|
||||
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Frame index.
|
||||
* @return RGBA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
|
||||
*/
|
||||
public static function extractRGBA(data:Data, frameIndex:Int):Bytes
|
||||
{
|
||||
var gce:GraphicControlExtension = null;
|
||||
var frameCaret:Int = 0;
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BExtension(ext):
|
||||
switch(ext)
|
||||
{
|
||||
case Extension.EGraphicControl(g):
|
||||
gce = g;
|
||||
default:
|
||||
}
|
||||
case Block.BFrame(frame):
|
||||
if (frameCaret == frameIndex)
|
||||
{
|
||||
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
|
||||
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
|
||||
if (ct == null) throw "Frame does not have a color table!";
|
||||
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
|
||||
var writeCaret:Int = 0;
|
||||
for (i in 0...frame.pixels.length)
|
||||
{
|
||||
var index:Int = frame.pixels.get(i) * 3;
|
||||
bytes.set(writeCaret , ct.get(index )); // R
|
||||
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
|
||||
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
|
||||
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
|
||||
else bytes.set(writeCaret + 3, 0xFF); // A = FF
|
||||
|
||||
writeCaret += 4;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
frameCaret++;
|
||||
gce = null;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts full Gif pixel data to specified frame in Blue-Green-Red-Alpha pixel format.
|
||||
* This functions returns full representation of frame including rendering of all other frames before.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Frame index.
|
||||
* @return BGRA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
|
||||
*/
|
||||
public static function extractFullBGRA(data:Data, frameIndex:Int):Bytes
|
||||
{
|
||||
var gce:GraphicControlExtension = null;
|
||||
var frameCaret:Int = 0;
|
||||
|
||||
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
|
||||
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BExtension(ext):
|
||||
switch(ext)
|
||||
{
|
||||
case Extension.EGraphicControl(g):
|
||||
gce = g;
|
||||
default:
|
||||
}
|
||||
case Block.BFrame(frame):
|
||||
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
|
||||
if (ct == null) throw "Frame does not have a color table!";
|
||||
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
|
||||
var pixels:Bytes = frame.pixels;
|
||||
var x:Int = 0;
|
||||
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
|
||||
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
|
||||
|
||||
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
|
||||
|
||||
switch (disposalMethod)
|
||||
{
|
||||
case DisposalMethod.RENDER_PREVIOUS:
|
||||
// Do not render frame at all
|
||||
case DisposalMethod.FILL_BACKGROUND:
|
||||
for (i in 0...pixels.length)
|
||||
{
|
||||
bytes.set(writeCaret , 0); // B
|
||||
bytes.set(writeCaret + 1, 0); // G
|
||||
bytes.set(writeCaret + 2, 0); // R
|
||||
bytes.set(writeCaret + 3, 0); // A
|
||||
|
||||
if (++x == frame.width)
|
||||
{
|
||||
x = 0;
|
||||
writeCaret += lineSkip;
|
||||
}
|
||||
else writeCaret += 4;
|
||||
}
|
||||
default:
|
||||
for (i in 0...pixels.length)
|
||||
{
|
||||
var index:Int = pixels.get(i) * 3;
|
||||
if (transparentIndex != index) // Render only if pixel non-transparent
|
||||
{
|
||||
bytes.set(writeCaret , ct.get(index + 2)); // B
|
||||
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
|
||||
bytes.set(writeCaret + 2, ct.get(index )); // R
|
||||
bytes.set(writeCaret + 3, 0xFF); // A
|
||||
}
|
||||
|
||||
if (++x == frame.width)
|
||||
{
|
||||
x = 0;
|
||||
writeCaret += lineSkip;
|
||||
}
|
||||
else writeCaret += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameCaret == frameIndex) return bytes;
|
||||
frameCaret++;
|
||||
gce = null;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts full Gif pixel data to specified frame in Red-Green-Blue-Alpha pixel format.
|
||||
* This functions returns full representation of frame including rendering of all other frames before.
|
||||
* @param data Gif data.
|
||||
* @param frameIndex Frame index.
|
||||
* @return RGBA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
|
||||
*/
|
||||
public static function extractFullRGBA(data:Data, frameIndex:Int):Bytes
|
||||
{
|
||||
var gce:GraphicControlExtension = null;
|
||||
var frameCaret:Int = 0;
|
||||
|
||||
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
|
||||
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BExtension(ext):
|
||||
switch(ext)
|
||||
{
|
||||
case Extension.EGraphicControl(g):
|
||||
gce = g;
|
||||
default:
|
||||
}
|
||||
case Block.BFrame(frame):
|
||||
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
|
||||
if (ct == null) throw "Frame does not have a color table!";
|
||||
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
|
||||
var pixels:Bytes = frame.pixels;
|
||||
var x:Int = 0;
|
||||
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
|
||||
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
|
||||
|
||||
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
|
||||
|
||||
switch (disposalMethod)
|
||||
{
|
||||
case DisposalMethod.RENDER_PREVIOUS:
|
||||
// Do not render frame at all
|
||||
case DisposalMethod.FILL_BACKGROUND:
|
||||
for (i in 0...pixels.length)
|
||||
{
|
||||
bytes.set(writeCaret , 0); // R
|
||||
bytes.set(writeCaret + 1, 0); // G
|
||||
bytes.set(writeCaret + 2, 0); // B
|
||||
bytes.set(writeCaret + 3, 0); // A
|
||||
|
||||
if (++x == frame.width)
|
||||
{
|
||||
x = 0;
|
||||
writeCaret += lineSkip;
|
||||
}
|
||||
else writeCaret += 4;
|
||||
}
|
||||
default:
|
||||
for (i in 0...pixels.length)
|
||||
{
|
||||
var index:Int = pixels.get(i) * 3;
|
||||
if (transparentIndex != index) // Render only if pixel non-transparent
|
||||
{
|
||||
bytes.set(writeCaret , ct.get(index )); // R
|
||||
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
|
||||
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
|
||||
bytes.set(writeCaret + 3, 0xFF); // A
|
||||
}
|
||||
|
||||
if (++x == frame.width)
|
||||
{
|
||||
x = 0;
|
||||
writeCaret += lineSkip;
|
||||
}
|
||||
else writeCaret += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameCaret == frameIndex) return bytes;
|
||||
frameCaret++;
|
||||
gce = null;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of animation repeats stored in Gif data.
|
||||
* This is link to Netscape Looping application extension. If this extension does not present amount of loops equals to 1.
|
||||
* @param data Gif data.
|
||||
* @return Amount of animation repeats. Zero equals to infinite amount of repeats.
|
||||
*/
|
||||
public static function loopCount(data:Data):Int
|
||||
{
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch(block)
|
||||
{
|
||||
case Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(loops))): return loops;
|
||||
default :
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//==========================================================
|
||||
// In-Dev writer tools.
|
||||
//==========================================================
|
||||
|
||||
//public static function buildFrameFromTrueColor(pixels:Bytes, width:Int, height:Int):Void
|
||||
//{
|
||||
//
|
||||
//}
|
||||
|
||||
private static var LN2:Float = Math.log(2);
|
||||
@:noCompletion public static inline function log2(val:Float):Float
|
||||
{
|
||||
return Math.log(val) / LN2;
|
||||
}
|
||||
}
|
||||
525
leenkx/Sources/iron/format/gif/Writer.hx
Normal file
525
leenkx/Sources/iron/format/gif/Writer.hx
Normal file
@ -0,0 +1,525 @@
|
||||
package format.gif;
|
||||
import format.gif.Data;
|
||||
import haxe.ds.Vector;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Output;
|
||||
import haxe.io.UInt8Array;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author Yanrishatum
|
||||
*/
|
||||
class Writer
|
||||
{
|
||||
|
||||
private var o:Output;
|
||||
private var lzw:LZWEncoder;
|
||||
private var gctSize:Int;
|
||||
|
||||
public function new(o:Output)
|
||||
{
|
||||
this.o = o;
|
||||
this.lzw = new LZWEncoder();
|
||||
o.bigEndian = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write entire Data at once.
|
||||
* @param data Input gif file data
|
||||
*/
|
||||
public function write(data:Data):Void
|
||||
{
|
||||
// Header
|
||||
writeHeader(data.version);
|
||||
|
||||
// Logical screen descriptor.
|
||||
writeLogicalScreenDescriptor(data.logicalScreenDescriptor, data.globalColorTable);
|
||||
|
||||
for (block in data.blocks)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case Block.BEOF:
|
||||
writeEOF();
|
||||
return;
|
||||
case Block.BExtension(ext):
|
||||
switch (ext)
|
||||
{
|
||||
case Extension.EUnknown(id, bytes):
|
||||
writeUnknownExtension(id, bytes);
|
||||
case Extension.EComment(text):
|
||||
writeComment(text);
|
||||
case Extension.EText(textExt):
|
||||
writeText(textExt);
|
||||
case Extension.EGraphicControl(gce):
|
||||
writeGraphicControl(gce);
|
||||
case Extension.EApplicationExtension(appExt):
|
||||
writeAppExtension(appExt);
|
||||
}
|
||||
case Block.BFrame(frame):
|
||||
writeFrame(frame);
|
||||
}
|
||||
}
|
||||
writeEOF(); // If we doesn't encountered EOF block - write it.
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes header of Gif file. Must be first.
|
||||
* @param version
|
||||
*/
|
||||
public function writeHeader(version:Version):Void
|
||||
{
|
||||
o.writeString("GIF");
|
||||
switch(version)
|
||||
{
|
||||
case Version.GIF87a: o.writeString("87a");
|
||||
case Version.GIF89a: o.writeString("89a");
|
||||
case Version.Unknown(v):
|
||||
if (v.length == 3) o.writeString(v);
|
||||
else if (v.length > 3) o.writeString(v.substr(0, 3));
|
||||
else
|
||||
{
|
||||
while (v.length < 3) v += "-";
|
||||
o.writeString(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes Logical Screen Descriptor block. Must go right after header.
|
||||
* @param lsd Logical Screen Descriptor object.
|
||||
* @param globalColorTable Global color table. Required only if LSD contains hasGlobalColorTable flag.
|
||||
* Color table must be a RGB-aligned Bytes with 3 bytes per color.
|
||||
*/
|
||||
public function writeLogicalScreenDescriptor(lsd:LogicalScreenDescriptor, globalColorTable:Bytes = null):Void
|
||||
{
|
||||
o.writeUInt16(lsd.width);
|
||||
o.writeUInt16(lsd.height);
|
||||
|
||||
var packed:Int = 0;
|
||||
if (lsd.hasGlobalColorTable) packed |= 128;
|
||||
packed |= (lsd.colorResolution << 4) & 112;
|
||||
if (lsd.sorted) packed |= 8;
|
||||
packed |= Math.round(Tools.log2(lsd.globalColorTableSize) - 1) & 7;
|
||||
o.writeByte(packed);
|
||||
|
||||
o.writeByte(lsd.backgroundColorIndex);
|
||||
if (lsd.pixelAspectRatio == 1) o.writeByte(0);
|
||||
else o.writeByte(Std.int(lsd.pixelAspectRatio) * 64 - 15);
|
||||
|
||||
if (lsd.hasGlobalColorTable)
|
||||
{
|
||||
if (globalColorTable != null)
|
||||
{
|
||||
o.writeBytes(globalColorTable, 0, globalColorTable.length);
|
||||
gctSize = lsd.globalColorTableSize;
|
||||
}
|
||||
else throw "hasGlobalColorTable flag present, but there is no global color table!";
|
||||
}
|
||||
}
|
||||
|
||||
public function writeComment(text:String):Void
|
||||
{
|
||||
o.writeByte(0x21);
|
||||
o.writeByte(0xFE);
|
||||
writeStringBlocks(text);
|
||||
}
|
||||
|
||||
public function writeText(textExt:PlainTextExtension):Void
|
||||
{
|
||||
o.writeByte(0x21);
|
||||
o.writeByte(0x01);
|
||||
o.writeByte(12);
|
||||
o.writeUInt16(textExt.textGridX);
|
||||
o.writeUInt16(textExt.textGridY);
|
||||
o.writeUInt16(textExt.textGridWidth);
|
||||
o.writeUInt16(textExt.textGridHeight);
|
||||
o.writeByte(textExt.charCellWidth);
|
||||
o.writeByte(textExt.charCellHeight);
|
||||
o.writeByte(textExt.textForegroundColorIndex);
|
||||
o.writeByte(textExt.textForegroundColorIndex);
|
||||
writeStringBlocks(textExt.text);
|
||||
}
|
||||
|
||||
public function writeGraphicControl(gce:GraphicControlExtension):Void
|
||||
{
|
||||
o.writeByte(0x21);
|
||||
o.writeByte(0xF9);
|
||||
o.writeByte(4);
|
||||
var packed:Int = 0;
|
||||
|
||||
switch (gce.disposalMethod)
|
||||
{
|
||||
case DisposalMethod.UNSPECIFIED: // 0
|
||||
case DisposalMethod.NO_ACTION: packed |= 4;
|
||||
case DisposalMethod.FILL_BACKGROUND: packed |= 8;
|
||||
case DisposalMethod.RENDER_PREVIOUS: packed |= 12;
|
||||
case DisposalMethod.UNDEFINED(idx): packed |= (idx & 7) << 2;
|
||||
}
|
||||
if (gce.userInput) packed |= 2;
|
||||
if (gce.hasTransparentColor) packed |= 1;
|
||||
|
||||
o.writeByte(packed);
|
||||
o.writeUInt16(gce.delay);
|
||||
o.writeByte(gce.transparentIndex);
|
||||
o.writeByte(0); // Terminator
|
||||
}
|
||||
|
||||
public function writeAppExtension(appExt:ApplicationExtension):Void
|
||||
{
|
||||
o.writeByte(0x21);
|
||||
o.writeByte(0xFF);
|
||||
o.writeByte(11);
|
||||
switch (appExt)
|
||||
{
|
||||
case ApplicationExtension.AENetscapeLooping(loops):
|
||||
o.writeString("NETSCAPE2.0");
|
||||
o.writeByte(3);
|
||||
o.writeByte(1); // Looping
|
||||
o.writeUInt16(loops);
|
||||
o.writeByte(0);
|
||||
case ApplicationExtension.AEUnknown(name, version, bytes):
|
||||
o.writeString(name);
|
||||
o.writeString(version);
|
||||
writeBlocks(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public function writeUnknownExtension(id:Int, bytes:Bytes):Void
|
||||
{
|
||||
o.writeByte(0x21);
|
||||
o.writeByte(id);
|
||||
writeBlocks(bytes);
|
||||
}
|
||||
|
||||
public function writeFrame(frame:Frame):Void
|
||||
{
|
||||
|
||||
o.writeByte(0x2C);
|
||||
o.writeUInt16(frame.x);
|
||||
o.writeUInt16(frame.y);
|
||||
o.writeUInt16(frame.width);
|
||||
o.writeUInt16(frame.height);
|
||||
|
||||
var packed:Int = 0;
|
||||
if (frame.localColorTable) packed |= 128;
|
||||
if (frame.interlaced) packed |= 64;
|
||||
if (frame.sorted) packed |= 32;
|
||||
packed |= Math.round(Tools.log2(frame.localColorTableSize) - 1) & 7;
|
||||
o.writeByte(packed);
|
||||
if (frame.localColorTable)
|
||||
{
|
||||
if (frame.colorTable != null) o.writeBytes(frame.colorTable, 0, frame.colorTable.length);
|
||||
else throw "localColorTable flag is set, but there is no local color table!";
|
||||
}
|
||||
|
||||
lzw.encode(frame.width, frame.height, frame.pixels, frame.localColorTable ? frame.localColorTableSize : gctSize, o, frame.interlaced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes EndOfFile block.
|
||||
*/
|
||||
public function writeEOF():Void
|
||||
{
|
||||
o.writeByte(0x3B);
|
||||
}
|
||||
|
||||
private function writeStringBlocks(text:String):Void
|
||||
{
|
||||
var len:Int;
|
||||
var caret:Int = 0;
|
||||
while (caret < text.length)
|
||||
{
|
||||
len = text.length - caret;
|
||||
if (len > 0xFF) len = 0xFF;
|
||||
o.writeByte(len);
|
||||
for (i in 0...len) o.writeByte(text.charCodeAt(i + caret));
|
||||
caret += len;
|
||||
}
|
||||
o.writeByte(0);
|
||||
}
|
||||
|
||||
private function writeBlocks(bytes:Bytes):Void
|
||||
{
|
||||
var len:Int;
|
||||
var caret:Int = 0;
|
||||
while (caret < bytes.length)
|
||||
{
|
||||
len = bytes.length - caret;
|
||||
if (len > 0xFF) len = 0xFF;
|
||||
o.writeByte(len);
|
||||
o.writeBytes(bytes, caret, len);
|
||||
caret += 0xFF;
|
||||
}
|
||||
o.writeByte(0); // Terminator
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class LZWEncoder
|
||||
{
|
||||
private var EOF:Int = -1;
|
||||
private static inline var BITS:Int = 12;
|
||||
private static inline var HSIZE:Int = 5003;
|
||||
private var masks:Array<Int> = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
||||
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
|
||||
|
||||
private var out:Output;
|
||||
private var bits:Int;
|
||||
private var bitsCount:Int;
|
||||
|
||||
private var minCodeSize:Int;
|
||||
private var codeSize:Int;
|
||||
private var codeSizeLimit:Int;
|
||||
private var clearFlag:Bool;
|
||||
|
||||
private var clearCode:Int;
|
||||
private var eofCode:Int;
|
||||
|
||||
// Dict
|
||||
private var htab:Vector<Int>;
|
||||
private var codetab:Vector<Int>;
|
||||
private var freeEnt:Int;
|
||||
|
||||
// Block buffer
|
||||
private var blockBuffer:Bytes;
|
||||
private var blockBufferCaret:Int;
|
||||
|
||||
// Input data
|
||||
private var pixels:Bytes;
|
||||
private var width:Int;
|
||||
private var height:Int;
|
||||
private var remaining:Int;
|
||||
|
||||
// Non-interlaced
|
||||
private var pixelsCaret:Int;
|
||||
// Interlaced
|
||||
private var interlaced:Bool;
|
||||
private var pixelsX:Int;
|
||||
private var pixelsY:Int;
|
||||
private var interlacingStage:Int;
|
||||
private var interlacingStep:Int;
|
||||
|
||||
public function new()
|
||||
{
|
||||
blockBuffer = Bytes.alloc(256);
|
||||
}
|
||||
|
||||
public function encode(width:Int, height:Int, pixels:Bytes, colorsCount:Int, out:Output, interlaced:Bool):Void
|
||||
{
|
||||
minCodeSize = Math.round(Tools.log2(colorsCount));
|
||||
|
||||
this.pixels = pixels;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.out = out;
|
||||
|
||||
htab = new Vector(HSIZE);
|
||||
codetab = new Vector(HSIZE);
|
||||
|
||||
blockBufferCaret = 0;
|
||||
bits = 0;
|
||||
bitsCount = 0;
|
||||
|
||||
clearCode = 1 << minCodeSize;
|
||||
eofCode = clearCode + 1;
|
||||
freeEnt = clearCode + 2;
|
||||
|
||||
out.writeByte(minCodeSize);
|
||||
remaining = width * height;
|
||||
|
||||
this.interlaced = interlaced;
|
||||
if (interlaced)
|
||||
{
|
||||
pixelsX = 0;
|
||||
pixelsY = 0;
|
||||
interlacingStage = 0;
|
||||
interlacingStep = 8;
|
||||
}
|
||||
else pixelsCaret = 0;
|
||||
|
||||
compress();
|
||||
out.writeByte(0);
|
||||
}
|
||||
|
||||
private function char_out(c:Int):Void
|
||||
{
|
||||
blockBuffer.set(blockBufferCaret++, c);
|
||||
if (blockBufferCaret >= 254) flush_char();
|
||||
}
|
||||
|
||||
private function cl_block():Void
|
||||
{
|
||||
cl_hash(HSIZE);
|
||||
freeEnt = clearCode + 2;
|
||||
clearFlag = true;
|
||||
output(clearCode);
|
||||
}
|
||||
|
||||
private function cl_hash(hsize:Int):Void
|
||||
{
|
||||
for (i in 0...hsize) htab[i] = -1;
|
||||
}
|
||||
|
||||
private function compress():Void
|
||||
{
|
||||
var disp:Int;
|
||||
var i:Int;
|
||||
|
||||
clearFlag = false;
|
||||
codeSize = minCodeSize + 1;
|
||||
codeSizeLimit = MAXCODE(codeSize);
|
||||
|
||||
var ent:Int = nextPixel();
|
||||
|
||||
var hshift:Int = 0;
|
||||
var fcode:Int = HSIZE;
|
||||
while (fcode < 65536)
|
||||
{
|
||||
++hshift;
|
||||
fcode *= 2;
|
||||
}
|
||||
hshift = 8 - hshift;
|
||||
var hsize_reg:Int = HSIZE;
|
||||
cl_hash(hsize_reg);
|
||||
|
||||
output(clearCode);
|
||||
|
||||
var c:Int;
|
||||
while ((c = nextPixel()) != EOF)
|
||||
{
|
||||
fcode = (c << BITS) + ent;
|
||||
i = (c << hshift) ^ ent;
|
||||
if (htab[i] == fcode)
|
||||
{
|
||||
ent = codetab[i];
|
||||
continue;
|
||||
}
|
||||
else if (htab[i] >= 0)
|
||||
{
|
||||
disp = hsize_reg - i;
|
||||
if (i == 0) disp = 1;
|
||||
var skip:Bool = false;
|
||||
do
|
||||
{
|
||||
if ((i -= disp) < 0) i += hsize_reg;
|
||||
if (htab[i] == fcode)
|
||||
{
|
||||
ent = codetab[i];
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (htab[i] >= 0);
|
||||
if (skip) continue;
|
||||
}
|
||||
|
||||
output(ent);
|
||||
ent = c;
|
||||
if (freeEnt < (1 << BITS))
|
||||
{
|
||||
codetab[i] = freeEnt++;
|
||||
htab[i] = fcode;
|
||||
}
|
||||
else
|
||||
{
|
||||
cl_block();
|
||||
}
|
||||
}
|
||||
|
||||
output(ent);
|
||||
output(eofCode);
|
||||
}
|
||||
|
||||
private function flush_char():Void
|
||||
{
|
||||
if (blockBufferCaret > 0)
|
||||
{
|
||||
out.writeByte(blockBufferCaret);
|
||||
out.writeBytes(blockBuffer, 0, blockBufferCaret);
|
||||
blockBufferCaret = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private inline function MAXCODE(n_bits:Int):Int
|
||||
{
|
||||
return (1 << n_bits) - 1;
|
||||
}
|
||||
|
||||
private function nextPixel():Int
|
||||
{
|
||||
if (remaining == 0) return EOF;
|
||||
remaining--;
|
||||
if (interlaced)
|
||||
{
|
||||
if (++pixelsX == width)
|
||||
{
|
||||
pixelsX = 0;
|
||||
pixelsY += interlacingStep;
|
||||
if (pixelsY >= height)
|
||||
{
|
||||
switch (interlacingStage)
|
||||
{
|
||||
// first: Every 8 line with start at 0
|
||||
case 0: pixelsY = 4; // Every 8 line with start at 4
|
||||
case 1: pixelsY = 2; interlacingStep = 4; // Every 4 line with start at 2
|
||||
case 2: pixelsY = 1; interlacingStep = 2; // Every 2 line with start at 1
|
||||
default: return -1; // EOF
|
||||
}
|
||||
interlacingStage++;
|
||||
}
|
||||
}
|
||||
return pixels.get(pixelsY * width + pixelsX);
|
||||
}
|
||||
else
|
||||
{
|
||||
return pixels.get(pixelsCaret++);
|
||||
}
|
||||
}
|
||||
|
||||
private function output(code:Int):Void
|
||||
{
|
||||
bits &= masks[bitsCount];
|
||||
|
||||
if (bitsCount > 0) bits |= (code << bitsCount);
|
||||
else bits = code;
|
||||
|
||||
bitsCount += codeSize;
|
||||
|
||||
while (bitsCount >= 8)
|
||||
{
|
||||
char_out(bits & 0xFF);
|
||||
bits >>= 8;
|
||||
bitsCount -= 8;
|
||||
}
|
||||
|
||||
if (freeEnt > codeSizeLimit || clearFlag)
|
||||
{
|
||||
if (clearFlag)
|
||||
{
|
||||
codeSizeLimit = MAXCODE(codeSize = minCodeSize + 1);
|
||||
clearFlag = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
codeSize++;
|
||||
if (codeSize == BITS) codeSizeLimit = 1 << BITS;
|
||||
else codeSizeLimit = MAXCODE(codeSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == eofCode)
|
||||
{
|
||||
while (bitsCount > 0)
|
||||
{
|
||||
char_out(bits & 0xFF);
|
||||
bits >>= 8;
|
||||
bitsCount -= 8;
|
||||
}
|
||||
flush_char();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
leenkx/Sources/iron/format/jpg/Data.hx
Normal file
37
leenkx/Sources/iron/format/jpg/Data.hx
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* JPG File Format
|
||||
* Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion)
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.jpg;
|
||||
|
||||
typedef Data = {
|
||||
var width : Int;
|
||||
var height : Int;
|
||||
var quality : Float;
|
||||
var pixels : haxe.io.Bytes;
|
||||
}
|
||||
657
leenkx/Sources/iron/format/jpg/Writer.hx
Normal file
657
leenkx/Sources/iron/format/jpg/Writer.hx
Normal file
@ -0,0 +1,657 @@
|
||||
package iron.format.jpg;
|
||||
|
||||
class Writer {
|
||||
var ZigZag: Array<Int>;
|
||||
|
||||
// Static table initialization
|
||||
function initZigZag() {
|
||||
ZigZag = [
|
||||
0, 1, 5, 6,14,15,27,28,
|
||||
2, 4, 7,13,16,26,29,42,
|
||||
3, 8,12,17,25,30,41,43,
|
||||
9,11,18,24,31,40,44,53,
|
||||
10,19,23,32,39,45,52,54,
|
||||
20,22,33,38,46,51,55,60,
|
||||
21,34,37,47,50,56,59,61,
|
||||
35,36,48,49,57,58,62,63
|
||||
];
|
||||
}
|
||||
|
||||
var YTable: Array<Int>;
|
||||
var UVTable: Array<Int>;
|
||||
var fdtbl_Y: Array<Float>;
|
||||
var fdtbl_UV: Array<Float>;
|
||||
|
||||
function initQuantTables(sf: Int) {
|
||||
var YQT: Array<Int> = [
|
||||
16, 11, 10, 16, 24, 40, 51, 61,
|
||||
12, 12, 14, 19, 26, 58, 60, 55,
|
||||
14, 13, 16, 24, 40, 57, 69, 56,
|
||||
14, 17, 22, 29, 51, 87, 80, 62,
|
||||
18, 22, 37, 56, 68,109,103, 77,
|
||||
24, 35, 55, 64, 81,104,113, 92,
|
||||
49, 64, 78, 87,103,121,120,101,
|
||||
72, 92, 95, 98,112,100,103, 99
|
||||
];
|
||||
for (i in 0...64) {
|
||||
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
|
||||
if( t < 1 ) t = 1;
|
||||
else if( t > 255 ) t = 255;
|
||||
YTable[ ZigZag[i] ] = t;
|
||||
}
|
||||
var UVQT: Array<Int> = [
|
||||
17, 18, 24, 47, 99, 99, 99, 99,
|
||||
18, 21, 26, 66, 99, 99, 99, 99,
|
||||
24, 26, 56, 99, 99, 99, 99, 99,
|
||||
47, 66, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99
|
||||
];
|
||||
for( j in 0...64 ) {
|
||||
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
|
||||
if( u < 1 ) u = 1;
|
||||
else if( u > 255 ) u = 255;
|
||||
UVTable[ ZigZag[j] ] = u;
|
||||
}
|
||||
var aasf: Array<Float> = [
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379
|
||||
];
|
||||
var k = 0;
|
||||
for( row in 0...8 ) {
|
||||
for( col in 0...8 ) {
|
||||
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var std_dc_luminance_nrcodes: Array<Int>;
|
||||
var std_dc_luminance_values: haxe.io.Bytes;
|
||||
var std_ac_luminance_nrcodes: Array<Int>;
|
||||
var std_ac_luminance_values: haxe.io.Bytes;
|
||||
|
||||
function initLuminance() {
|
||||
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
|
||||
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
|
||||
std_ac_luminance_values = strIntsToBytes(
|
||||
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
|
||||
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
|
||||
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
|
||||
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
|
||||
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
|
||||
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
|
||||
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
|
||||
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
|
||||
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
|
||||
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
|
||||
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
|
||||
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
|
||||
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
|
||||
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
|
||||
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
|
||||
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
|
||||
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
|
||||
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
|
||||
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
|
||||
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
function strIntsToBytes( s: String ) {
|
||||
var len = s.length;
|
||||
var b = new haxe.io.BytesBuffer();
|
||||
var val = 0;
|
||||
var i = 0;
|
||||
for( j in 0...len ) {
|
||||
if( s.charAt( j ) == ',' ) {
|
||||
val = Std.parseInt( s.substr(i, j - i) );
|
||||
b.addByte( val );
|
||||
i = j + 1;
|
||||
}
|
||||
}
|
||||
if( i < len ) {
|
||||
val = Std.parseInt( s.substr(i) );
|
||||
b.addByte( val );
|
||||
}
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
var std_dc_chrominance_nrcodes: Array<Int>;
|
||||
var std_dc_chrominance_values: haxe.io.Bytes;
|
||||
var std_ac_chrominance_nrcodes: Array<Int>;
|
||||
var std_ac_chrominance_values: haxe.io.Bytes;
|
||||
|
||||
function initChrominance() {
|
||||
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
|
||||
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
|
||||
std_ac_chrominance_values = strIntsToBytes(
|
||||
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
|
||||
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
|
||||
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
|
||||
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
|
||||
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
|
||||
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
|
||||
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
|
||||
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
|
||||
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
|
||||
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
|
||||
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
|
||||
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
|
||||
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
|
||||
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
|
||||
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
|
||||
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
|
||||
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
|
||||
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
|
||||
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
|
||||
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
var YDC_HT: Map<Int,BitString>;
|
||||
var UVDC_HT: Map<Int,BitString>;
|
||||
var YAC_HT: Map<Int,BitString>;
|
||||
var UVAC_HT: Map<Int,BitString>;
|
||||
|
||||
// Función para crear la tabla Huffman (Helper)
|
||||
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
|
||||
var codevalue = 0;
|
||||
var pos_in_table = 0;
|
||||
var HT: Map<Int,BitString> = new Map();
|
||||
for( k in 1...17 ) {
|
||||
var end = nrcodes[k];
|
||||
for( j in 0...end ) {
|
||||
var idx: Int = std_table.get( pos_in_table );
|
||||
HT.set( idx, new BitString( k, codevalue ) );
|
||||
pos_in_table++;
|
||||
codevalue++;
|
||||
}
|
||||
codevalue *= 2;
|
||||
}
|
||||
return HT;
|
||||
}
|
||||
|
||||
function initHuffmanTbl() {
|
||||
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
|
||||
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
|
||||
|
||||
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
|
||||
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
|
||||
|
||||
// CORRECCIÓN DE TABLAS: Asegurar la existencia de EOB (0x00) y ZRL (0xF0)
|
||||
// Esto es necesario para evitar fallos si el 'computeHuffmanTbl' no incluye estos valores por algún motivo.
|
||||
if (YAC_HT.get(0x00) == null) YAC_HT.set(0x00, new BitString(4, 0x00));
|
||||
if (UVAC_HT.get(0x00) == null) UVAC_HT.set(0x00, new BitString(4, 0x00));
|
||||
if (YAC_HT.get(0xF0) == null) YAC_HT.set(0xF0, new BitString(11, 0x1E));
|
||||
if (UVAC_HT.get(0xF0) == null) UVAC_HT.set(0xF0, new BitString(11, 0x1E));
|
||||
}
|
||||
|
||||
var bitcode: Map<Int,BitString>;
|
||||
var category: Map<Int,Int>;
|
||||
|
||||
function initCategoryNumber() {
|
||||
var nrlower = 1;
|
||||
var nrupper = 2;
|
||||
var idx: Int;
|
||||
for (cat in 1...16) {
|
||||
//Positive numbers
|
||||
for( nr in nrlower...nrupper ) {
|
||||
idx = 32767 + nr;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nr ) );
|
||||
}
|
||||
//Negative numbers
|
||||
var nrneg: Int = -(nrupper - 1);
|
||||
while( nrneg <= -nrlower ) {
|
||||
idx = 32767 + nrneg;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
|
||||
nrneg++;
|
||||
}
|
||||
nrlower <<= 1;
|
||||
nrupper <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// IO functions
|
||||
var byteout: haxe.io.Output;
|
||||
var bytenew: Int;
|
||||
var bytepos: Int;
|
||||
|
||||
function writeBits(bs: BitString) {
|
||||
// Se confía en que bs no es nulo gracias al clamping y las correcciones de tablas.
|
||||
var value: Int = bs.val;
|
||||
var posval: Int = bs.len - 1;
|
||||
while( posval >= 0 ) {
|
||||
if( (value & (1 << posval)) != 0 ) {
|
||||
bytenew |= (1 << bytepos);
|
||||
}
|
||||
posval--;
|
||||
bytepos--;
|
||||
if( bytepos < 0 ) {
|
||||
if( bytenew == 0xFF ) {
|
||||
b(0xFF);
|
||||
b(0);
|
||||
}
|
||||
else {
|
||||
b(bytenew);
|
||||
}
|
||||
bytepos = 7;
|
||||
bytenew = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeWord( val: Int ) {
|
||||
b( (val >> 8) & 0xFF );
|
||||
b( val & 0xFF );
|
||||
}
|
||||
|
||||
// DCT & quantization core
|
||||
|
||||
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
|
||||
/* Pass 1: process rows. */
|
||||
var dataOff = 0;
|
||||
for (i in 0...8) {
|
||||
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
|
||||
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
|
||||
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
|
||||
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
|
||||
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
|
||||
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
|
||||
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
|
||||
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
|
||||
|
||||
/* Even part */
|
||||
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
|
||||
var tmp13: Float = tmp0 - tmp3;
|
||||
var tmp11: Float = tmp1 + tmp2;
|
||||
var tmp12: Float = tmp1 - tmp2;
|
||||
|
||||
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
|
||||
data[dataOff + 4] = tmp10 - tmp11;
|
||||
|
||||
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
|
||||
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
|
||||
data[dataOff + 6] = tmp13 - z1;
|
||||
|
||||
/* Odd part */
|
||||
tmp10 = tmp4 + tmp5; /* phase 2 */
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
|
||||
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
|
||||
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
|
||||
var z3: Float = tmp11 * 0.707106781; /* c4 */
|
||||
|
||||
var z11: Float = tmp7 + z3; /* phase 5 */
|
||||
var z13: Float = tmp7 - z3;
|
||||
|
||||
data[dataOff + 5] = z13 + z2; /* phase 6 */
|
||||
data[dataOff + 3] = z13 - z2;
|
||||
data[dataOff + 1] = z11 + z4;
|
||||
data[dataOff + 7] = z11 - z4;
|
||||
|
||||
dataOff += 8; /* advance pointer to next row */
|
||||
}
|
||||
|
||||
/* Pass 2: process columns. */
|
||||
dataOff = 0;
|
||||
for (j in 0...8) {
|
||||
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
|
||||
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
|
||||
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
|
||||
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
|
||||
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
|
||||
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
|
||||
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
|
||||
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
|
||||
|
||||
/* Even part */
|
||||
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
|
||||
var tmp13p2: Float = tmp0p2 - tmp3p2;
|
||||
var tmp11p2: Float = tmp1p2 + tmp2p2;
|
||||
var tmp12p2: Float = tmp1p2 - tmp2p2;
|
||||
|
||||
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
|
||||
data[dataOff+32] = tmp10p2 - tmp11p2;
|
||||
|
||||
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
|
||||
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
|
||||
data[dataOff+48] = tmp13p2 - z1p2;
|
||||
|
||||
/* Odd part */
|
||||
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
|
||||
tmp11p2 = tmp5p2 + tmp6p2;
|
||||
tmp12p2 = tmp6p2 + tmp7p2;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
|
||||
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
|
||||
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
|
||||
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
|
||||
|
||||
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
|
||||
var z13p2: Float = tmp7p2 - z3p2;
|
||||
|
||||
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
|
||||
data[dataOff+24] = z13p2 - z2p2;
|
||||
data[dataOff+ 8] = z11p2 + z4p2;
|
||||
data[dataOff+56] = z11p2 - z4p2;
|
||||
|
||||
dataOff++; /* advance pointer to next column */
|
||||
}
|
||||
|
||||
// Quantize/descale the coefficients
|
||||
for (k in 0...64) {
|
||||
// Apply the quantization and scaling factor & Round to nearest integer
|
||||
data[k] = Math.round(data[k] * fdtbl[k]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Chunk writing
|
||||
|
||||
inline function b(v) {
|
||||
byteout.writeByte(v);
|
||||
}
|
||||
|
||||
function writeAPP0() {
|
||||
b(0xFF); b(0xE0); //<- marker 0xFFE0
|
||||
b(0); b(16); //<- length
|
||||
b("J".code); // J
|
||||
b("F".code);
|
||||
b("I".code);
|
||||
b("F".code);
|
||||
b(0);
|
||||
b(1); // versionhi
|
||||
b(1); // versionlo
|
||||
b(0); // xyunits
|
||||
b(0); b(1); // xdensity
|
||||
b(0); b(1); // ydensity
|
||||
b(0); // thumbnwidth
|
||||
b(0); // thumbnheight
|
||||
}
|
||||
function writeDQT() {
|
||||
b(0xFF); b(0xDB); //<- marker 0xFFDB
|
||||
b(0); b(132); //<- length
|
||||
b(0);
|
||||
for( j in 0...64 )
|
||||
b(YTable[j]);
|
||||
b(1);
|
||||
for( j in 0...64 )
|
||||
b(UVTable[j]);
|
||||
}
|
||||
function writeSOF0(width: Int, height: Int) {
|
||||
b(0xFF); b(0xC0); //<- marker 0xFFC0
|
||||
b(0); b(17); //<- length, truecolor YUV JPG
|
||||
b(8); // precision
|
||||
b( (height>>8) & 0xFF );
|
||||
b( height & 0xFF );
|
||||
b( (width>>8) & 0xFF );
|
||||
b( width & 0xFF );
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0x11); // HVY
|
||||
b(0); // QTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HVU
|
||||
b(1); // QTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HVV
|
||||
b(1); // QTV
|
||||
}
|
||||
|
||||
function writeDHT() {
|
||||
b(0xFF); b(0xC4); //<- marker 0xFFC4
|
||||
b(0x01); b(0xA2); //<- length
|
||||
b(0); // HTYDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_luminance_nrcodes[j]);
|
||||
byteout.write(std_dc_luminance_values);
|
||||
|
||||
b(0x10); // HTYACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_luminance_nrcodes[j]);
|
||||
byteout.write(std_ac_luminance_values);
|
||||
|
||||
b(1); // HTUDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_chrominance_nrcodes[j]);
|
||||
byteout.write(std_dc_chrominance_values);
|
||||
|
||||
b(0x11); // HTUACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_chrominance_nrcodes[j]);
|
||||
byteout.write(std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function writeSOS() {
|
||||
b(0xFF); b(0xDA); //<- marker 0xFFDA
|
||||
b(0); b(12); //<- length
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0); // HTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HTV
|
||||
b(0); // Ss
|
||||
b(0x3F); // Se
|
||||
b(0); // Bf
|
||||
}
|
||||
|
||||
// Core processing
|
||||
var DU: Array<Float>;
|
||||
|
||||
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
|
||||
var EOB: BitString = HTAC.get( 0x00 );
|
||||
var M16zeroes: BitString = HTAC.get( 0xF0 );
|
||||
|
||||
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
|
||||
//ZigZag reorder
|
||||
for (i in 0...64) {
|
||||
DU[ ZigZag[i] ] = DU_DCT[i];
|
||||
}
|
||||
var idx: Int;
|
||||
var Diff = Std.int( DU[0] - DC );
|
||||
DC = DU[0];
|
||||
|
||||
// CORRECCIÓN DE RANGO: Clamping de la diferencia DC (previene accesos a `category` fuera de rango)
|
||||
if (Diff > 16383) Diff = 16383;
|
||||
if (Diff < -16383) Diff = -16383;
|
||||
|
||||
//Encode DC
|
||||
if( Diff == 0 ) {
|
||||
writeBits( HTDC.get(0) );
|
||||
} else {
|
||||
idx = 32767 + Diff;
|
||||
writeBits(HTDC.get( category.get( idx ) ));
|
||||
writeBits( bitcode.get( idx ) );
|
||||
}
|
||||
|
||||
//Encode ACs
|
||||
var end0pos = 63;
|
||||
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
|
||||
|
||||
//end0pos = first element in reverse order !=0
|
||||
if ( end0pos == 0 ) {
|
||||
writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
var i = 1;
|
||||
while ( i <= end0pos ) {
|
||||
var startpos = i;
|
||||
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
|
||||
|
||||
// Chequeo de seguridad si 'i' saltó más allá
|
||||
if (i > end0pos) break;
|
||||
|
||||
var nrzeroes: Int = i - startpos;
|
||||
if ( nrzeroes >= 16 ) {
|
||||
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
|
||||
nrzeroes &= 0xF;
|
||||
}
|
||||
|
||||
// CORRECCIÓN DE RANGO: Clamping del coeficiente AC
|
||||
var du_val = Std.int( DU[i] );
|
||||
if (du_val > 16383) du_val = 16383;
|
||||
if (du_val < -16383) du_val = -16383;
|
||||
|
||||
// LÓGICA DE SALTO: Si el clamping forzó el valor a 0, saltamos.
|
||||
if (du_val == 0) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = 32767 + du_val;
|
||||
|
||||
var cat = category.get( idx );
|
||||
// Si 'cat' es nulo, significa que el valor de 'du_val' está fuera del rango -16383..16383, lo cual el clamping debería haber prevenido.
|
||||
var index_ac = nrzeroes * 16 + cat;
|
||||
|
||||
writeBits( HTAC.get( index_ac ) );
|
||||
writeBits( bitcode.get( idx ) );
|
||||
i++;
|
||||
}
|
||||
if( end0pos != 63 ) writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
|
||||
var YDU: Array<Float>;
|
||||
var UDU: Array<Float>;
|
||||
var VDU: Array<Float>;
|
||||
|
||||
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
|
||||
var pos = 0;
|
||||
for( y in 0...8 ) {
|
||||
var offset = ((y + ypos) * width + xpos) << 2;
|
||||
for( x in 0...8 ) {
|
||||
offset++; // skip alpha
|
||||
var R = img.get(offset++);
|
||||
var G = img.get(offset++);
|
||||
var B = img.get(offset++);
|
||||
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
|
||||
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
|
||||
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function new( out : haxe.io.Output ) {
|
||||
//begin : lines added to initialize variables
|
||||
YTable = new Array<Int>();
|
||||
UVTable = new Array<Int>();
|
||||
fdtbl_Y = new Array<Float>();
|
||||
fdtbl_UV = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YTable.push(0); UVTable.push(0);
|
||||
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
|
||||
}
|
||||
|
||||
bitcode = new Map();
|
||||
category = new Map();
|
||||
byteout = out;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
YDC_HT = new Map();
|
||||
UVDC_HT = new Map();
|
||||
YAC_HT = new Map();
|
||||
UVAC_HT = new Map();
|
||||
|
||||
YDU = new Array<Float>();
|
||||
UDU = new Array<Float>();
|
||||
VDU = new Array<Float>();
|
||||
DU = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
|
||||
}
|
||||
initZigZag();
|
||||
initLuminance();
|
||||
initChrominance();
|
||||
//end : lines added to initialize variables
|
||||
|
||||
// Create tables
|
||||
initHuffmanTbl();
|
||||
initCategoryNumber();
|
||||
}
|
||||
|
||||
public function write( image : Data ) {
|
||||
// init quality table
|
||||
var quality = image.quality;
|
||||
if( quality <= 0 ) quality = 1;
|
||||
if( quality > 100 ) quality = 100;
|
||||
var sf =
|
||||
if( quality < 50 ) Std.int( 5000 / quality )
|
||||
else Std.int( 200 - quality * 2 );
|
||||
initQuantTables(sf);
|
||||
|
||||
// Initialize bit writer
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
var width = image.width;
|
||||
var height = image.height;
|
||||
// Add JPEG headers
|
||||
writeWord(0xFFD8); // SOI
|
||||
writeAPP0();
|
||||
writeDQT();
|
||||
writeSOF0( width, height );
|
||||
writeDHT();
|
||||
writeSOS();
|
||||
|
||||
// Encode 8x8 macroblocks
|
||||
var DCY = 0.0;
|
||||
var DCU = 0.0;
|
||||
var DCV = 0.0;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
var ypos = 0;
|
||||
while( ypos < height ) {
|
||||
var xpos = 0;
|
||||
while( xpos < width ) {
|
||||
|
||||
// CORRECCIÓN CRÍTICA DE ESTADO: Limpieza de arreglos para evitar arrastre de valores (lo que el 'trace' estaba enmascarando)
|
||||
// Se asegura que los buffers sean cero antes de llenarlos con RGB2YUV si no se llenan completamente.
|
||||
for (k in 0...64) { YDU[k] = 0.0; UDU[k] = 0.0; VDU[k] = 0.0; }
|
||||
|
||||
RGB2YUV(image.pixels, width, xpos, ypos);
|
||||
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
|
||||
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
|
||||
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
|
||||
xpos += 8;
|
||||
}
|
||||
ypos += 8;
|
||||
}
|
||||
|
||||
// Do the bit alignment of the EOI marker
|
||||
if( bytepos >= 0 ) {
|
||||
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
|
||||
writeBits(fillbits);
|
||||
}
|
||||
|
||||
writeWord(0xFFD9); //EOI
|
||||
}
|
||||
}
|
||||
|
||||
private class BitString {
|
||||
public var len: Int;
|
||||
public var val: Int;
|
||||
|
||||
public function new( l: Int, v: Int ) {
|
||||
len = l;
|
||||
val = v;
|
||||
}
|
||||
}
|
||||
651
leenkx/Sources/iron/format/jpg/WriterOriginal.hx
Normal file
651
leenkx/Sources/iron/format/jpg/WriterOriginal.hx
Normal file
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* JPG File Format
|
||||
* Copyright (C) 2007-2009 Thibault Imbert, AS3-to-Haxe by Michel Oster
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.jpg;
|
||||
|
||||
class Writer {
|
||||
var ZigZag: Array<Int>;
|
||||
|
||||
// Static table initialization
|
||||
function initZigZag() {
|
||||
ZigZag = [
|
||||
0, 1, 5, 6,14,15,27,28,
|
||||
2, 4, 7,13,16,26,29,42,
|
||||
3, 8,12,17,25,30,41,43,
|
||||
9,11,18,24,31,40,44,53,
|
||||
10,19,23,32,39,45,52,54,
|
||||
20,22,33,38,46,51,55,60,
|
||||
21,34,37,47,50,56,59,61,
|
||||
35,36,48,49,57,58,62,63
|
||||
];
|
||||
}
|
||||
|
||||
var YTable: Array<Int>; // = new Array(64);
|
||||
var UVTable: Array<Int>; // = new Array(64);
|
||||
var fdtbl_Y: Array<Float>; // = new Array(64);
|
||||
var fdtbl_UV: Array<Float>; // = new Array(64);
|
||||
|
||||
function initQuantTables(sf: Int) {
|
||||
var YQT: Array<Int> = [
|
||||
16, 11, 10, 16, 24, 40, 51, 61,
|
||||
12, 12, 14, 19, 26, 58, 60, 55,
|
||||
14, 13, 16, 24, 40, 57, 69, 56,
|
||||
14, 17, 22, 29, 51, 87, 80, 62,
|
||||
18, 22, 37, 56, 68,109,103, 77,
|
||||
24, 35, 55, 64, 81,104,113, 92,
|
||||
49, 64, 78, 87,103,121,120,101,
|
||||
72, 92, 95, 98,112,100,103, 99
|
||||
];
|
||||
for (i in 0...64) {
|
||||
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
|
||||
if( t < 1 ) t = 1;
|
||||
else if( t > 255 ) t = 255;
|
||||
YTable[ ZigZag[i] ] = t;
|
||||
}
|
||||
var UVQT: Array<Int> = [
|
||||
17, 18, 24, 47, 99, 99, 99, 99,
|
||||
18, 21, 26, 66, 99, 99, 99, 99,
|
||||
24, 26, 56, 99, 99, 99, 99, 99,
|
||||
47, 66, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99
|
||||
];
|
||||
for( j in 0...64 ) {
|
||||
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
|
||||
if( u < 1 ) u = 1;
|
||||
else if( u > 255 ) u = 255;
|
||||
UVTable[ ZigZag[j] ] = u;
|
||||
}
|
||||
var aasf: Array<Float> = [
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379
|
||||
];
|
||||
var k = 0;
|
||||
for( row in 0...8 ) {
|
||||
for( col in 0...8 ) {
|
||||
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var std_dc_luminance_nrcodes: Array<Int>;
|
||||
var std_dc_luminance_values: haxe.io.Bytes;
|
||||
var std_ac_luminance_nrcodes: Array<Int>;
|
||||
var std_ac_luminance_values: haxe.io.Bytes;
|
||||
|
||||
function initLuminance() {
|
||||
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
|
||||
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
|
||||
std_ac_luminance_values = strIntsToBytes(
|
||||
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
|
||||
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
|
||||
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
|
||||
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
|
||||
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
|
||||
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
|
||||
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
|
||||
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
|
||||
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
|
||||
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
|
||||
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
|
||||
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
|
||||
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
|
||||
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
|
||||
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
|
||||
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
|
||||
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
|
||||
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
|
||||
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
|
||||
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
function strIntsToBytes( s: String ) {
|
||||
var len = s.length;
|
||||
var b = new haxe.io.BytesBuffer();
|
||||
var val = 0;
|
||||
var i = 0;
|
||||
for( j in 0...len ) {
|
||||
if( s.charAt( j ) == ',' ) {
|
||||
val = Std.parseInt( s.substr(i, j - i) );
|
||||
b.addByte( val );
|
||||
i = j + 1;
|
||||
}
|
||||
}
|
||||
if( i < len ) {
|
||||
val = Std.parseInt( s.substr(i) );
|
||||
b.addByte( val );
|
||||
}
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
var std_dc_chrominance_nrcodes: Array<Int>;
|
||||
var std_dc_chrominance_values: haxe.io.Bytes;
|
||||
var std_ac_chrominance_nrcodes: Array<Int>;
|
||||
var std_ac_chrominance_values: haxe.io.Bytes;
|
||||
|
||||
function initChrominance() {
|
||||
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
|
||||
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
|
||||
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
|
||||
std_ac_chrominance_values = strIntsToBytes(
|
||||
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
|
||||
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
|
||||
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
|
||||
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
|
||||
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
|
||||
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
|
||||
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
|
||||
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
|
||||
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
|
||||
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
|
||||
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
|
||||
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
|
||||
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
|
||||
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
|
||||
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
|
||||
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
|
||||
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
|
||||
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
|
||||
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
|
||||
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
|
||||
'0xf9,0xfa'
|
||||
);
|
||||
}
|
||||
|
||||
var YDC_HT: Map<Int,BitString>;
|
||||
var UVDC_HT: Map<Int,BitString>;
|
||||
var YAC_HT: Map<Int,BitString>;
|
||||
var UVAC_HT: Map<Int,BitString>;
|
||||
|
||||
function initHuffmanTbl() {
|
||||
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
|
||||
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
|
||||
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
|
||||
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
|
||||
var codevalue = 0;
|
||||
var pos_in_table = 0;
|
||||
var HT: Map<Int,BitString> = new Map();
|
||||
for( k in 1...17 ) {
|
||||
var end = nrcodes[k];
|
||||
for( j in 0...end ) {
|
||||
var idx: Int = std_table.get( pos_in_table );
|
||||
HT.set( idx, new BitString( k, codevalue ) );
|
||||
pos_in_table++;
|
||||
codevalue++;
|
||||
}
|
||||
codevalue *= 2;
|
||||
}
|
||||
return HT;
|
||||
}
|
||||
|
||||
var bitcode: Map<Int,BitString>;
|
||||
var category: Map<Int,Int>;
|
||||
|
||||
function initCategoryNumber() {
|
||||
var nrlower = 1;
|
||||
var nrupper = 2;
|
||||
var idx: Int;
|
||||
for (cat in 1...16) {
|
||||
//Positive numbers
|
||||
for( nr in nrlower...nrupper ) {
|
||||
idx = 32767 + nr;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nr ) );
|
||||
}
|
||||
//Negative numbers
|
||||
var nrneg: Int = -(nrupper - 1);
|
||||
while( nrneg <= -nrlower ) {
|
||||
idx = 32767 + nrneg;
|
||||
category.set( idx, cat );
|
||||
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
|
||||
nrneg++;
|
||||
}
|
||||
nrlower <<= 1;
|
||||
nrupper <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// IO functions
|
||||
var byteout: haxe.io.Output;
|
||||
var bytenew: Int;
|
||||
var bytepos: Int;
|
||||
|
||||
function writeBits(bs: BitString) {
|
||||
var value: Int = bs.val;
|
||||
var posval: Int = bs.len - 1;
|
||||
while( posval >= 0 ) {
|
||||
//if (value & uint(1 << posval) ) {
|
||||
if( (value & (1 << posval)) != 0 ) { //<- CORRECT ?
|
||||
//bytenew |= uint(1 << bytepos);
|
||||
bytenew |= (1 << bytepos);
|
||||
}
|
||||
posval--;
|
||||
bytepos--;
|
||||
if( bytepos < 0 ) {
|
||||
if( bytenew == 0xFF ) {
|
||||
b(0xFF);
|
||||
b(0);
|
||||
}
|
||||
else {
|
||||
b(bytenew);
|
||||
}
|
||||
bytepos = 7;
|
||||
bytenew = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeWord( val: Int ) {
|
||||
b( (val >> 8) & 0xFF );
|
||||
b( val & 0xFF );
|
||||
}
|
||||
|
||||
// DCT & quantization core
|
||||
|
||||
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
|
||||
/* Pass 1: process rows. */
|
||||
var dataOff = 0;
|
||||
for (i in 0...8) {
|
||||
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
|
||||
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
|
||||
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
|
||||
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
|
||||
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
|
||||
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
|
||||
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
|
||||
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
|
||||
|
||||
/* Even part */
|
||||
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
|
||||
var tmp13: Float = tmp0 - tmp3;
|
||||
var tmp11: Float = tmp1 + tmp2;
|
||||
var tmp12: Float = tmp1 - tmp2;
|
||||
|
||||
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
|
||||
data[dataOff + 4] = tmp10 - tmp11;
|
||||
|
||||
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
|
||||
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
|
||||
data[dataOff + 6] = tmp13 - z1;
|
||||
|
||||
/* Odd part */
|
||||
tmp10 = tmp4 + tmp5; /* phase 2 */
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
|
||||
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
|
||||
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
|
||||
var z3: Float = tmp11 * 0.707106781; /* c4 */
|
||||
|
||||
var z11: Float = tmp7 + z3; /* phase 5 */
|
||||
var z13: Float = tmp7 - z3;
|
||||
|
||||
data[dataOff + 5] = z13 + z2; /* phase 6 */
|
||||
data[dataOff + 3] = z13 - z2;
|
||||
data[dataOff + 1] = z11 + z4;
|
||||
data[dataOff + 7] = z11 - z4;
|
||||
|
||||
dataOff += 8; /* advance pointer to next row */
|
||||
}
|
||||
|
||||
/* Pass 2: process columns. */
|
||||
dataOff = 0;
|
||||
for (j in 0...8) {
|
||||
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
|
||||
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
|
||||
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
|
||||
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
|
||||
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
|
||||
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
|
||||
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
|
||||
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
|
||||
|
||||
/* Even part */
|
||||
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
|
||||
var tmp13p2: Float = tmp0p2 - tmp3p2;
|
||||
var tmp11p2: Float = tmp1p2 + tmp2p2;
|
||||
var tmp12p2: Float = tmp1p2 - tmp2p2;
|
||||
|
||||
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
|
||||
data[dataOff+32] = tmp10p2 - tmp11p2;
|
||||
|
||||
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
|
||||
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
|
||||
data[dataOff+48] = tmp13p2 - z1p2;
|
||||
|
||||
/* Odd part */
|
||||
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
|
||||
tmp11p2 = tmp5p2 + tmp6p2;
|
||||
tmp12p2 = tmp6p2 + tmp7p2;
|
||||
|
||||
/* The rotator is modified from fig 4-8 to avoid extra negations. */
|
||||
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
|
||||
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
|
||||
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
|
||||
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
|
||||
|
||||
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
|
||||
var z13p2: Float = tmp7p2 - z3p2;
|
||||
|
||||
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
|
||||
data[dataOff+24] = z13p2 - z2p2;
|
||||
data[dataOff+ 8] = z11p2 + z4p2;
|
||||
data[dataOff+56] = z11p2 - z4p2;
|
||||
|
||||
dataOff++; /* advance pointer to next column */
|
||||
}
|
||||
|
||||
// Quantize/descale the coefficients
|
||||
for (k in 0...64) {
|
||||
// Apply the quantization and scaling factor & Round to nearest integer
|
||||
data[k] = Math.round(data[k] * fdtbl[k]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Chunk writing
|
||||
|
||||
inline function b(v) {
|
||||
byteout.writeByte(v);
|
||||
}
|
||||
|
||||
function writeAPP0() {
|
||||
b(0xFF); b(0xE0); //<- marker 0xFFE0
|
||||
b(0); b(16); //<- length
|
||||
b("J".code); // J
|
||||
b("F".code);
|
||||
b("I".code);
|
||||
b("F".code);
|
||||
b(0);
|
||||
b(1); // versionhi
|
||||
b(1); // versionlo
|
||||
b(0); // xyunits
|
||||
b(0); b(1); // xdensity
|
||||
b(0); b(1); // ydensity
|
||||
b(0); // thumbnwidth
|
||||
b(0); // thumbnheight
|
||||
}
|
||||
function writeDQT() {
|
||||
b(0xFF); b(0xDB); //<- marker 0xFFDB
|
||||
b(0); b(132); //<- length
|
||||
b(0);
|
||||
for( j in 0...64 )
|
||||
b(YTable[j]);
|
||||
b(1);
|
||||
for( j in 0...64 )
|
||||
b(UVTable[j]);
|
||||
}
|
||||
function writeSOF0(width: Int, height: Int) {
|
||||
b(0xFF); b(0xC0); //<- marker 0xFFC0
|
||||
b(0); b(17); //<- length, truecolor YUV JPG
|
||||
b(8); // precision
|
||||
b( (height>>8) & 0xFF );
|
||||
b( height & 0xFF );
|
||||
b( (width>>8) & 0xFF );
|
||||
b( width & 0xFF );
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0x11); // HVY
|
||||
b(0); // QTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HVU
|
||||
b(1); // QTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HVV
|
||||
b(1); // QTV
|
||||
}
|
||||
|
||||
function writeDHT() {
|
||||
b(0xFF); b(0xC4); //<- marker 0xFFC4
|
||||
b(0x01); b(0xA2); //<- length
|
||||
b(0); // HTYDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_luminance_nrcodes[j]);
|
||||
byteout.write(std_dc_luminance_values);
|
||||
|
||||
b(0x10); // HTYACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_luminance_nrcodes[j]);
|
||||
byteout.write(std_ac_luminance_values);
|
||||
|
||||
b(1); // HTUDCinfo
|
||||
for( j in 1...17 )
|
||||
b(std_dc_chrominance_nrcodes[j]);
|
||||
byteout.write(std_dc_chrominance_values);
|
||||
|
||||
b(0x11); // HTUACinfo
|
||||
for( j in 1...17 )
|
||||
b(std_ac_chrominance_nrcodes[j]);
|
||||
byteout.write(std_ac_chrominance_values);
|
||||
}
|
||||
|
||||
function writeSOS() {
|
||||
b(0xFF); b(0xDA); //<- marker 0xFFDA
|
||||
b(0); b(12); //<- length
|
||||
b(3); // nrofcomponents
|
||||
b(1); // IdY
|
||||
b(0); // HTY
|
||||
b(2); // IdU
|
||||
b(0x11); // HTU
|
||||
b(3); // IdV
|
||||
b(0x11); // HTV
|
||||
b(0); // Ss
|
||||
b(0x3F); // Se
|
||||
b(0); // Bf
|
||||
}
|
||||
|
||||
// Core processing
|
||||
var DU: Array<Float>; //<- initialized in function new JPEGEncoder()
|
||||
|
||||
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
|
||||
var EOB: BitString = HTAC.get( 0x00 );
|
||||
var M16zeroes: BitString = HTAC.get( 0xF0 );
|
||||
|
||||
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
|
||||
//ZigZag reorder
|
||||
for (i in 0...64) {
|
||||
DU[ ZigZag[i] ] = DU_DCT[i];
|
||||
}
|
||||
var idx: Int;
|
||||
var Diff = Std.int( DU[0] - DC );
|
||||
DC = DU[0];
|
||||
//Encode DC
|
||||
if( Diff == 0 ) {
|
||||
writeBits( HTDC.get(0) ); // Diff might be 0
|
||||
} else {
|
||||
idx = 32767 + Diff;
|
||||
writeBits(HTDC.get( category.get( idx ) ));
|
||||
writeBits( bitcode.get( idx ) );
|
||||
}
|
||||
|
||||
//Encode ACs
|
||||
var end0pos = 63;
|
||||
//for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) { };
|
||||
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
|
||||
|
||||
//end0pos = first element in reverse order !=0
|
||||
if ( end0pos == 0 ) {
|
||||
writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
var i = 1;
|
||||
while ( i <= end0pos ) {
|
||||
var startpos = i;
|
||||
//for (; (DU[i]==0) && (i<=end0pos); i++) { }; <- it's a 'while' loop
|
||||
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
|
||||
|
||||
var nrzeroes: Int = i - startpos;
|
||||
if ( nrzeroes >= 16 ) {
|
||||
//for (var nrmarker: Int=1; nrmarker <= nrzeroes/16; nrmarker++) {
|
||||
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
|
||||
nrzeroes &= 0xF;
|
||||
}
|
||||
idx = 32767 + Std.int( DU[i] ); //<- line added
|
||||
writeBits( HTAC.get( nrzeroes * 16 + category.get( idx ) ) );
|
||||
writeBits( bitcode.get( idx ) );
|
||||
i++;
|
||||
}
|
||||
if( end0pos != 63 ) writeBits(EOB);
|
||||
return DC;
|
||||
}
|
||||
|
||||
var YDU: Array<Float>;
|
||||
var UDU: Array<Float>;
|
||||
var VDU: Array<Float>;
|
||||
|
||||
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
|
||||
var pos = 0;
|
||||
for( y in 0...8 ) {
|
||||
var offset = ((y + ypos) * width + xpos) << 2;
|
||||
for( x in 0...8 ) {
|
||||
offset++; // skip alpha
|
||||
var R = img.get(offset++);
|
||||
var G = img.get(offset++);
|
||||
var B = img.get(offset++);
|
||||
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
|
||||
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
|
||||
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function new( out : haxe.io.Output ) {
|
||||
//begin : lines added to initialize variables
|
||||
YTable = new Array<Int>();
|
||||
UVTable = new Array<Int>();
|
||||
fdtbl_Y = new Array<Float>();
|
||||
fdtbl_UV = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YTable.push(0); UVTable.push(0);
|
||||
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
|
||||
}
|
||||
|
||||
bitcode = new Map(); //<- 65535 elements <BitString>
|
||||
category = new Map(); //<- 65535 elements <Int>
|
||||
byteout = out;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
YDC_HT = new Map();
|
||||
UVDC_HT = new Map();
|
||||
YAC_HT = new Map();
|
||||
UVAC_HT = new Map();
|
||||
|
||||
YDU = new Array<Float>(); //<- 64 elements
|
||||
UDU = new Array<Float>();
|
||||
VDU = new Array<Float>();
|
||||
DU = new Array<Float>();
|
||||
for (i in 0...64) {
|
||||
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
|
||||
}
|
||||
initZigZag();
|
||||
initLuminance();
|
||||
initChrominance();
|
||||
//end : lines added to initialize variables
|
||||
|
||||
// Create tables
|
||||
initHuffmanTbl();
|
||||
initCategoryNumber();
|
||||
}
|
||||
|
||||
public function write( image : Data ) {
|
||||
// init quality table
|
||||
var quality = image.quality;
|
||||
if( quality <= 0 ) quality = 1;
|
||||
if( quality > 100 ) quality = 100;
|
||||
var sf =
|
||||
if( quality < 50 ) Std.int( 5000 / quality )
|
||||
else Std.int( 200 - quality * 2 );
|
||||
initQuantTables(sf);
|
||||
|
||||
// Initialize bit writer
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
|
||||
var width = image.width;
|
||||
var height = image.height;
|
||||
// Add JPEG headers
|
||||
writeWord(0xFFD8); // SOI
|
||||
writeAPP0();
|
||||
writeDQT();
|
||||
writeSOF0( width, height );
|
||||
writeDHT();
|
||||
writeSOS();
|
||||
|
||||
// Encode 8x8 macroblocks
|
||||
var DCY = 0.0;
|
||||
var DCU = 0.0;
|
||||
var DCV = 0.0;
|
||||
bytenew = 0;
|
||||
bytepos = 7;
|
||||
var ypos = 0;
|
||||
while( ypos < height ) {
|
||||
var xpos = 0;
|
||||
while( xpos < width ) {
|
||||
RGB2YUV(image.pixels, width, xpos, ypos);
|
||||
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
|
||||
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
|
||||
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
|
||||
xpos += 8;
|
||||
}
|
||||
ypos += 8;
|
||||
}
|
||||
|
||||
// Do the bit alignment of the EOI marker
|
||||
if( bytepos >= 0 ) {
|
||||
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
|
||||
writeBits(fillbits);
|
||||
}
|
||||
|
||||
writeWord(0xFFD9); //EOI
|
||||
}
|
||||
}
|
||||
|
||||
private class BitString {
|
||||
public var len: Int;
|
||||
public var val: Int;
|
||||
|
||||
public function new( l: Int, v: Int ) {
|
||||
len = l;
|
||||
val = v;
|
||||
}
|
||||
}
|
||||
56
leenkx/Sources/iron/format/wav/Data.hx
Normal file
56
leenkx/Sources/iron/format/wav/Data.hx
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.wav;
|
||||
|
||||
typedef WAVE = {
|
||||
header : WAVEHeader,
|
||||
data : haxe.io.Bytes,
|
||||
cuePoints : Array<CuePoint>
|
||||
}
|
||||
|
||||
typedef WAVEHeader = {
|
||||
format : WAVEFormat,
|
||||
channels : Int,
|
||||
samplingRate : Int,
|
||||
byteRate : Int, // samplingRate * channels * bitsPerSample / 8
|
||||
blockAlign : Int, // channels * bitsPerSample / 8
|
||||
bitsPerSample : Int
|
||||
}
|
||||
|
||||
typedef CuePoint = {
|
||||
id : Int,
|
||||
sampleOffset : Int
|
||||
}
|
||||
|
||||
enum WAVEFormat {
|
||||
WF_PCM;
|
||||
}
|
||||
|
||||
|
||||
155
leenkx/Sources/iron/format/wav/Reader.hx
Normal file
155
leenkx/Sources/iron/format/wav/Reader.hx
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
package iron.format.wav;
|
||||
import iron.format.wav.Data;
|
||||
|
||||
class Reader {
|
||||
|
||||
var i : haxe.io.Input;
|
||||
var version : Int;
|
||||
|
||||
public function new(i) {
|
||||
this.i = i;
|
||||
i.bigEndian = false;
|
||||
}
|
||||
|
||||
inline function readInt() {
|
||||
#if haxe3
|
||||
return i.readInt32();
|
||||
#else
|
||||
return i.readUInt30();
|
||||
#end
|
||||
}
|
||||
|
||||
public function read() : WAVE {
|
||||
|
||||
if (i.readString(4) != "RIFF")
|
||||
throw "RIFF header expected";
|
||||
|
||||
var len = readInt();
|
||||
|
||||
if (i.readString(4) != "WAVE")
|
||||
throw "WAVE signature not found";
|
||||
|
||||
var fmt = i.readString(4);
|
||||
while(fmt != "fmt ") {
|
||||
switch( fmt ) {
|
||||
case "JUNK": //protool
|
||||
var junkLen = i.readInt32();
|
||||
i.read(junkLen);
|
||||
fmt = i.readString(4);
|
||||
case "bext":
|
||||
var bextLen = i.readInt32();
|
||||
i.read(bextLen);
|
||||
fmt = i.readString(4);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( fmt != "fmt " )
|
||||
throw "unsupported wave chunk "+fmt;
|
||||
|
||||
var fmtlen = readInt();
|
||||
var format = switch (i.readUInt16()) {
|
||||
case 1,3: WF_PCM;
|
||||
default: throw "only PCM (uncompressed) WAV files are supported";
|
||||
}
|
||||
var channels = i.readUInt16();
|
||||
var samplingRate = readInt();
|
||||
var byteRate = readInt();
|
||||
var blockAlign = i.readUInt16();
|
||||
var bitsPerSample = i.readUInt16();
|
||||
|
||||
if (fmtlen > 16)
|
||||
i.read(fmtlen - 16);
|
||||
|
||||
var nextChunk = i.readString (4);
|
||||
while (nextChunk != "data") {
|
||||
// read past other subchunks
|
||||
i.read(readInt());
|
||||
nextChunk = i.readString (4);
|
||||
}
|
||||
|
||||
// data
|
||||
if (nextChunk != "data")
|
||||
throw "expected data subchunk";
|
||||
|
||||
var datalen = readInt();
|
||||
|
||||
var data : haxe.io.Bytes;
|
||||
try {
|
||||
data = i.read(datalen);
|
||||
} catch (e : haxe.io.Eof) {
|
||||
throw "Invalid chunk data length";
|
||||
}
|
||||
|
||||
var cuePoints = new Array<CuePoint>();
|
||||
try {
|
||||
|
||||
while (true) {
|
||||
var nextChunk = i.readString (4);
|
||||
switch (nextChunk) {
|
||||
case "cue ":
|
||||
readInt();
|
||||
var nbCuePoints = readInt();
|
||||
|
||||
for (_ in 0...nbCuePoints) {
|
||||
var cueId = readInt();
|
||||
readInt();
|
||||
i.readString(4);
|
||||
readInt();
|
||||
readInt();
|
||||
var cueSampleOffset = readInt();
|
||||
cuePoints.push({ id : cueId, sampleOffset: cueSampleOffset });
|
||||
}
|
||||
default:
|
||||
var n = readInt();
|
||||
if( n < 0 ) break;
|
||||
i.read(n);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e : haxe.io.Eof) { }
|
||||
|
||||
return {
|
||||
header: {
|
||||
format: format,
|
||||
channels: channels,
|
||||
samplingRate: samplingRate,
|
||||
byteRate: byteRate,
|
||||
blockAlign: blockAlign,
|
||||
bitsPerSample: bitsPerSample
|
||||
},
|
||||
data: data,
|
||||
cuePoints: cuePoints
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
leenkx/Sources/iron/format/wav/Writer.hx
Normal file
72
leenkx/Sources/iron/format/wav/Writer.hx
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* format - Haxe File Formats
|
||||
*
|
||||
* WAVE File Format
|
||||
* Copyright (C) 2009 Robin Palotai
|
||||
*
|
||||
* Copyright (c) 2009, The Haxe Project Contributors
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
package iron.format.wav;
|
||||
import iron.format.wav.Data;
|
||||
|
||||
class Writer {
|
||||
|
||||
var o : haxe.io.Output;
|
||||
|
||||
public function new(output : haxe.io.Output) {
|
||||
o = output;
|
||||
o.bigEndian = false;
|
||||
}
|
||||
|
||||
public function write(wav : WAVE) {
|
||||
var hdr = wav.header;
|
||||
|
||||
o.writeString("RIFF");
|
||||
writeInt(36 + wav.data.length);
|
||||
o.writeString("WAVE");
|
||||
|
||||
o.writeString("fmt ");
|
||||
writeInt(16);
|
||||
o.writeUInt16(1);
|
||||
o.writeUInt16(hdr.channels);
|
||||
writeInt(hdr.samplingRate);
|
||||
writeInt(hdr.byteRate);
|
||||
o.writeUInt16(hdr.blockAlign);
|
||||
o.writeUInt16(hdr.bitsPerSample);
|
||||
|
||||
o.writeString("data");
|
||||
writeInt(wav.data.length);
|
||||
o.write(wav.data);
|
||||
}
|
||||
|
||||
inline function writeInt( v : Int ) {
|
||||
#if haxe3
|
||||
o.writeInt32(v);
|
||||
#else
|
||||
o.writeUInt30(v);
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
@ -66,32 +66,12 @@ class Quat {
|
||||
}
|
||||
|
||||
public inline function fromAxisAngle(axis: Vec4, angle: FastFloat): Quat {
|
||||
//var s: FastFloat = Math.sin(angle * 0.5);
|
||||
//x = axis.x * s;
|
||||
//y = axis.y * s;
|
||||
//z = axis.z * s;
|
||||
//w = Math.cos(angle * 0.5);
|
||||
//return normalize();
|
||||
// Normalize the axis vector first
|
||||
var axisLen = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
|
||||
if (axisLen > 0.00001) {
|
||||
var aL = 1.0 / axisLen;
|
||||
var nX = axis.x * aL;
|
||||
var nY = axis.y * aL;
|
||||
var nZ = axis.z * aL;
|
||||
var halfAngle = angle * 0.5;
|
||||
var s: FastFloat = Math.sin(halfAngle);
|
||||
x = nX * s;
|
||||
y = nY * s;
|
||||
z = nZ * s;
|
||||
w = Math.cos(halfAngle);
|
||||
} else {
|
||||
x = 0.0;
|
||||
y = 0.0;
|
||||
z = 0.0;
|
||||
w = 1.0;
|
||||
}
|
||||
return this;
|
||||
var s: FastFloat = Math.sin(angle * 0.5);
|
||||
x = axis.x * s;
|
||||
y = axis.y * s;
|
||||
z = axis.z * s;
|
||||
w = Math.cos(angle * 0.5);
|
||||
return normalize();
|
||||
}
|
||||
|
||||
public inline function toAxisAngle(axis: Vec4): FastFloat {
|
||||
@ -399,33 +379,17 @@ class Quat {
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function fromEulerOrdered(e: Vec4, order: String): Quat {
|
||||
var c1 = Math.cos(e.x / 2);
|
||||
var c2 = Math.cos(e.y / 2);
|
||||
var c3 = Math.cos(e.z / 2);
|
||||
var s1 = Math.sin(e.x / 2);
|
||||
var s2 = Math.sin(e.y / 2);
|
||||
var s3 = Math.sin(e.z / 2);
|
||||
|
||||
var mappedAngles = new Vec4();
|
||||
switch (order) {
|
||||
case "XYZ":
|
||||
mappedAngles.set(e.x, e.y, e.z);
|
||||
case "XZY":
|
||||
mappedAngles.set(e.x, e.z, e.y);
|
||||
case "YXZ":
|
||||
mappedAngles.set(e.y, e.x, e.z);
|
||||
case "YZX":
|
||||
mappedAngles.set(e.y, e.z, e.x);
|
||||
case "ZXY":
|
||||
mappedAngles.set(e.z, e.x, e.y);
|
||||
case "ZYX":
|
||||
mappedAngles.set(e.z, e.y, e.x);
|
||||
}
|
||||
var c1 = Math.cos(mappedAngles.x / 2);
|
||||
var c2 = Math.cos(mappedAngles.y / 2);
|
||||
var c3 = Math.cos(mappedAngles.z / 2);
|
||||
var s1 = Math.sin(mappedAngles.x / 2);
|
||||
var s2 = Math.sin(mappedAngles.y / 2);
|
||||
var s3 = Math.sin(mappedAngles.z / 2);
|
||||
var qx = new Quat(s1, 0, 0, c1);
|
||||
var qy = new Quat(0, s2, 0, c2);
|
||||
var qz = new Quat(0, 0, s3, c3);
|
||||
|
||||
// Original multiplication sequence (implements reverse of 'order')
|
||||
if (order.charAt(2) == 'X')
|
||||
this.setFrom(qx);
|
||||
else if (order.charAt(2) == 'Y')
|
||||
@ -445,12 +409,6 @@ class Quat {
|
||||
else
|
||||
this.mult(qz);
|
||||
|
||||
// TO DO quick fix somethings wrong..
|
||||
this.x = -this.x;
|
||||
this.y = -this.y;
|
||||
this.z = -this.z;
|
||||
this.w = -this.w;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -1,31 +1,119 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
|
||||
class Constraint {
|
||||
var raw: TConstraint;
|
||||
var target: Transform = null;
|
||||
|
||||
public function new(constr: TConstraint) {
|
||||
raw = constr;
|
||||
public function new(constraint: TConstraint) {
|
||||
raw = constraint;
|
||||
}
|
||||
|
||||
public function apply(transform: Transform) {
|
||||
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
|
||||
|
||||
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;
|
||||
|
||||
if (raw.type == "COPY_LOCATION") {
|
||||
if (raw.use_x) {
|
||||
transform.world._30 = target.loc.x;
|
||||
if (raw.use_offset) transform.world._30 += transform.loc.x;
|
||||
if (raw.use_offset) {
|
||||
if (raw.use_x) transform.world._30 += target.world._30;
|
||||
if (raw.use_y) transform.world._31 += target.world._31;
|
||||
if (raw.use_z) transform.world._32 += target.world._32;
|
||||
}
|
||||
if (raw.use_y) {
|
||||
transform.world._31 = target.loc.y;
|
||||
if (raw.use_offset) transform.world._31 += transform.loc.y;
|
||||
else {
|
||||
if (raw.use_x) transform.world._30 = target.world._30;
|
||||
if (raw.use_y) transform.world._31 = target.world._31;
|
||||
if (raw.use_z) transform.world._32 = target.world._32;
|
||||
}
|
||||
if (raw.use_z) {
|
||||
transform.world._32 = target.loc.z;
|
||||
if (raw.use_offset) transform.world._32 += transform.loc.z;
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_ROTATION") {
|
||||
var tq = target.rot;
|
||||
var mq = transform.rot;
|
||||
if (raw.use_offset) {
|
||||
mq.mult(tq);
|
||||
}
|
||||
else {
|
||||
if (raw.use_x) mq.x = tq.x;
|
||||
if (raw.use_y) mq.y = tq.y;
|
||||
if (raw.use_z) mq.z = tq.z;
|
||||
mq.w = tq.w;
|
||||
}
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
var scale = transform.scale;
|
||||
transform.world.compose(loc, mq, scale);
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_SCALE") {
|
||||
var ts = target.scale;
|
||||
if (raw.use_offset) {
|
||||
if (raw.use_x) transform.scale.x *= ts.x;
|
||||
if (raw.use_y) transform.scale.y *= ts.y;
|
||||
if (raw.use_z) transform.scale.z *= ts.z;
|
||||
}
|
||||
else {
|
||||
if (raw.use_x) transform.scale.x = ts.x;
|
||||
if (raw.use_y) transform.scale.y = ts.y;
|
||||
if (raw.use_z) transform.scale.z = ts.z;
|
||||
}
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
|
||||
else if (raw.type == "COPY_TRANSFORMS") {
|
||||
transform.world.setFrom(target.world);
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_LOCATION") {
|
||||
if (raw.use_min_x && transform.world._30 < raw.min_x) transform.world._30 = raw.min_x;
|
||||
if (raw.use_max_x && transform.world._30 > raw.max_x) transform.world._30 = raw.max_x;
|
||||
|
||||
if (raw.use_min_y && transform.world._31 < raw.min_y) transform.world._31 = raw.min_y;
|
||||
if (raw.use_max_y && transform.world._31 > raw.max_y) transform.world._31 = raw.max_y;
|
||||
|
||||
if (raw.use_min_z && transform.world._32 < raw.min_z) transform.world._32 = raw.min_z;
|
||||
if (raw.use_max_z && transform.world._32 > raw.max_z) transform.world._32 = raw.max_z;
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_ROTATION") {
|
||||
var euler = transform.rot.getEuler();
|
||||
var changed = false;
|
||||
|
||||
if (raw.use_limit_x) {
|
||||
if (euler.x < raw.min_x) { euler.x = raw.min_x; changed = true; }
|
||||
if (euler.x > raw.max_x) { euler.x = raw.max_x; changed = true; }
|
||||
}
|
||||
if (raw.use_limit_y) {
|
||||
if (euler.y < raw.min_y) { euler.y = raw.min_y; changed = true; }
|
||||
if (euler.y > raw.max_y) { euler.y = raw.max_y; changed = true; }
|
||||
}
|
||||
if (raw.use_limit_z) {
|
||||
if (euler.z < raw.min_z) { euler.z = raw.min_z; changed = true; }
|
||||
if (euler.z > raw.max_z) { euler.z = raw.max_z; changed = true; }
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
transform.rot.fromEuler(euler.x, euler.y, euler.z);
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
}
|
||||
|
||||
else if (raw.type == "LIMIT_SCALE") {
|
||||
if (raw.use_min_x && transform.scale.x < raw.min_x) transform.scale.x = raw.min_x;
|
||||
if (raw.use_max_x && transform.scale.x > raw.max_x) transform.scale.x = raw.max_x;
|
||||
|
||||
if (raw.use_min_y && transform.scale.y < raw.min_y) transform.scale.y = raw.min_y;
|
||||
if (raw.use_max_y && transform.scale.y > raw.max_y) transform.scale.y = raw.max_y;
|
||||
|
||||
if (raw.use_min_z && transform.scale.z < raw.min_z) transform.scale.z = raw.min_z;
|
||||
if (raw.use_max_z && transform.scale.z > raw.max_z) transform.scale.z = raw.max_z;
|
||||
|
||||
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
|
||||
transform.world.compose(loc, transform.rot, transform.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1339
leenkx/Sources/iron/object/CurveObject.hx
Normal file
1339
leenkx/Sources/iron/object/CurveObject.hx
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user