Теперь строится worldinfo для будующей симуляции
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
public record FogBlockProperties(
|
||||
boolean solid,
|
||||
int sign,
|
||||
int magnitude
|
||||
) {
|
||||
public static final FogBlockProperties DEFAULT = new FogBlockProperties(false, 0, 0);
|
||||
|
||||
public FogBlockProperties {
|
||||
if (sign < -1 || sign > 1) {
|
||||
throw new IllegalArgumentException("sign must be -1, 0, or 1");
|
||||
}
|
||||
if (magnitude < 0 || magnitude > 63) {
|
||||
throw new IllegalArgumentException("magnitude must be 0-63");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Упаковывает свойства в один байт:
|
||||
* Бит 7: solid (1 = solid)
|
||||
* Бит 6: sign (0 = source/neutral, 1 = absorber)
|
||||
* Биты 0-5: magnitude (0-63)
|
||||
*/
|
||||
public byte pack() {
|
||||
int packed = 0;
|
||||
|
||||
if (solid) {
|
||||
packed |= 0b10000000;
|
||||
}
|
||||
|
||||
if (sign < 0) {
|
||||
packed |= 0b01000000;
|
||||
}
|
||||
|
||||
packed |= (magnitude & 0b00111111);
|
||||
|
||||
return (byte) packed;
|
||||
}
|
||||
|
||||
public static FogBlockProperties unpack(byte packed) {
|
||||
boolean solid = (packed & 0b10000000) != 0;
|
||||
boolean isAbsorber = (packed & 0b01000000) != 0;
|
||||
int magnitude = packed & 0b00111111;
|
||||
int sign = isAbsorber ? -1 : (magnitude > 0 ? 1 : 0);
|
||||
|
||||
return new FogBlockProperties(solid, sign, magnitude);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.tag.BlockTags;
|
||||
|
||||
public final class FogBlockRegistry {
|
||||
|
||||
private static final byte[] BLOCK_CACHE;
|
||||
private static final boolean[] BLOCK_CACHE_INITIALIZED;
|
||||
|
||||
// Предвычисленное значение для waterlogged блоков (вода)
|
||||
private static final byte WATER_VALUE;
|
||||
|
||||
static {
|
||||
int blockCount = Registries.BLOCK.size();
|
||||
BLOCK_CACHE = new byte[blockCount];
|
||||
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
|
||||
|
||||
// Waterlogged блок всегда содержит воду
|
||||
WATER_VALUE = new FogBlockProperties(true, 1, 60).pack();
|
||||
}
|
||||
|
||||
private FogBlockRegistry() {
|
||||
}
|
||||
|
||||
public static byte getPackedProperties(BlockState state) {
|
||||
// СНАЧАЛА проверяем конкретно воду (waterlogged или обычный блок воды)
|
||||
if (state.getFluidState().getFluid() == Fluids.WATER) {
|
||||
return WATER_VALUE;
|
||||
}
|
||||
|
||||
Block block = state.getBlock();
|
||||
int id = Registries.BLOCK.getRawId(block);
|
||||
|
||||
if (BLOCK_CACHE_INITIALIZED[id]) {
|
||||
return BLOCK_CACHE[id];
|
||||
}
|
||||
|
||||
byte packed = computePackedProperties(state);
|
||||
BLOCK_CACHE[id] = packed;
|
||||
BLOCK_CACHE_INITIALIZED[id] = true;
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
private static byte computePackedProperties(BlockState state) {
|
||||
Block block = state.getBlock();
|
||||
boolean solid = computeSolid(state);
|
||||
|
||||
// Источники тумана (sign = +1)
|
||||
// WATER уже обработан в getPackedProperties
|
||||
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
|
||||
return new FogBlockProperties(solid, 1, 10).pack();
|
||||
}
|
||||
if (block == Blocks.VINE) {
|
||||
return new FogBlockProperties(solid, 1, 5).pack();
|
||||
}
|
||||
|
||||
// Поглотители тумана (sign = -1)
|
||||
if (block == Blocks.LAVA) {
|
||||
return new FogBlockProperties(solid, -1, 50).pack();
|
||||
}
|
||||
if (block == Blocks.MAGMA_BLOCK) {
|
||||
return new FogBlockProperties(solid, -1, 40).pack();
|
||||
}
|
||||
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
|
||||
return new FogBlockProperties(solid, -1, 30).pack();
|
||||
}
|
||||
if (block == Blocks.CAMPFIRE || block == Blocks.SOUL_CAMPFIRE) {
|
||||
return new FogBlockProperties(solid, -1, 25).pack();
|
||||
}
|
||||
if (block == Blocks.TORCH || block == Blocks.WALL_TORCH ||
|
||||
block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
|
||||
return new FogBlockProperties(solid, -1, 15).pack();
|
||||
}
|
||||
if (block == Blocks.LANTERN || block == Blocks.SOUL_LANTERN) {
|
||||
return new FogBlockProperties(solid, -1, 10).pack();
|
||||
}
|
||||
|
||||
// Нейтральные блоки (sign = 0)
|
||||
return new FogBlockProperties(solid, 0, 0).pack();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static boolean computeSolid(BlockState state) {
|
||||
if (state.isAir()) return false;
|
||||
// Waterlogged уже обработан в getPackedProperties
|
||||
if (!state.getFluidState().isEmpty()) return true; // лава и другие жидкости
|
||||
if (state.isIn(BlockTags.LEAVES)) return false;
|
||||
return state.blocksMovement();
|
||||
}
|
||||
}
|
||||
@@ -20,17 +20,7 @@ public final class FogChunkProvider {
|
||||
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
|
||||
}
|
||||
|
||||
public static float computeVoxelValue(BlockState state) {
|
||||
if (state.isOf(Blocks.POPPY)) {
|
||||
return 0.2f;
|
||||
} else if (state.isOf(Blocks.GRASS)) {
|
||||
return 0.4f;
|
||||
} else {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, float[] array) {
|
||||
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, byte[] array) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
if (!isChunkLoaded(chunkX, chunkZ)) return false;
|
||||
@@ -42,10 +32,9 @@ public final class FogChunkProvider {
|
||||
int topYExclusive = bottomY + sections.length * 16;
|
||||
|
||||
int index = 0;
|
||||
byte airValue = FogBlockRegistry.getPackedProperties(Blocks.AIR.getDefaultState());
|
||||
|
||||
// Порядок циклов Z -> Y -> X критичен для glTexSubImage3D
|
||||
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
|
||||
// Кэшируем секции и их состояние
|
||||
int currentSectionIndex = -1;
|
||||
ChunkSection currentSection = null;
|
||||
PalettedContainer<BlockState> currentContainer = null;
|
||||
@@ -54,9 +43,8 @@ public final class FogChunkProvider {
|
||||
for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) {
|
||||
int worldY = FogWorldVolume.MIN_Y + y;
|
||||
|
||||
// За пределами мира (например, ниже -64)
|
||||
if (worldY < bottomY || worldY >= topYExclusive) {
|
||||
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
|
||||
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
|
||||
index += FogWorldVolume.CHUNK_SIZE;
|
||||
continue;
|
||||
}
|
||||
@@ -76,25 +64,25 @@ public final class FogChunkProvider {
|
||||
|
||||
int localY = worldY & 15;
|
||||
|
||||
// ОПТИМИЗАЦИЯ: Если текущая секция полностью пустая (только воздух)
|
||||
if (currentIsEmpty) {
|
||||
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
|
||||
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
|
||||
index += FogWorldVolume.CHUNK_SIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Предварительное вычисление базового индекса для строки Y и Z
|
||||
// Формула локального индекса в секции 16x16x16: (y << 8) | (z << 4) | x
|
||||
int baseIndex = (localY << 8) | (z << 4);
|
||||
|
||||
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
|
||||
BlockState currentBlock = currentContainer.get(baseIndex | x);
|
||||
|
||||
array[index++] = computeVoxelValue(currentBlock);
|
||||
array[index++] = FogBlockRegistry.getPackedProperties(currentBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static byte computePackedVoxelValue(BlockState state) {
|
||||
return FogBlockRegistry.getPackedProperties(state);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public final class FogChunkStreamer {
|
||||
private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS];
|
||||
private static final boolean[] readyToUpload = new boolean[TOTAL_CHUNKS];
|
||||
|
||||
private static final float[][] preparedData = new float[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
||||
private static final byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
||||
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
|
||||
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
|
||||
|
||||
@@ -175,7 +175,7 @@ public final class FogChunkStreamer {
|
||||
}
|
||||
|
||||
// Вычисляем значение для этого вокселя
|
||||
float value = FogChunkProvider.computeVoxelValue(event.newState);
|
||||
byte value = FogChunkProvider.computePackedVoxelValue(event.newState());
|
||||
|
||||
// Обновляем один воксель в текстуре
|
||||
FogWorldVolume.updateSingleVoxel(
|
||||
|
||||
@@ -3,18 +3,12 @@ package su.divan2000.veila.client.render.fog;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogWorldVolume {
|
||||
|
||||
/*
|
||||
* Размер всего объёма
|
||||
*/
|
||||
|
||||
public static final int SIZE_X = 256;
|
||||
public static final int SIZE_Z = 256;
|
||||
|
||||
@@ -22,156 +16,81 @@ public final class FogWorldVolume {
|
||||
public static final int MAX_Y = 320;
|
||||
public static final int SIZE_Y = MAX_Y - MIN_Y;
|
||||
|
||||
/*
|
||||
* Размер одного чанка
|
||||
*/
|
||||
|
||||
public static final int CHUNK_SIZE = 16;
|
||||
|
||||
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
|
||||
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
|
||||
|
||||
/*
|
||||
* OpenGL
|
||||
*/
|
||||
|
||||
private static int texture = -1;
|
||||
|
||||
/*
|
||||
* Буфер для одного чанка
|
||||
*
|
||||
* 16 × 384 × 16
|
||||
*/
|
||||
|
||||
private static final float[] ZEROS_ARRAY = new float[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||
private static final byte[] ZEROS_ARRAY;
|
||||
static {
|
||||
java.util.Arrays.fill(ZEROS_ARRAY, 0.0f);
|
||||
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||
byte airValue = new FogBlockProperties(false, 0, 0).pack();
|
||||
java.util.Arrays.fill(ZEROS_ARRAY, airValue);
|
||||
}
|
||||
|
||||
private static final FloatBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createFloatBuffer(
|
||||
CHUNK_SIZE *
|
||||
SIZE_Y *
|
||||
CHUNK_SIZE
|
||||
);
|
||||
private static final ByteBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
|
||||
|
||||
|
||||
private static final FloatBuffer SINGLE_VOXEL_BUFFER =
|
||||
BufferUtils.createFloatBuffer(1);
|
||||
private static final ByteBuffer SINGLE_VOXEL_BUFFER =
|
||||
BufferUtils.createByteBuffer(1);
|
||||
|
||||
private FogWorldVolume() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
||||
if (texture != -1)
|
||||
return;
|
||||
if (texture != -1) return;
|
||||
|
||||
texture = GL11.glGenTextures();
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
texture
|
||||
);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
GL30.GL_R16F,
|
||||
GL30.GL_R8UI,
|
||||
SIZE_X,
|
||||
SIZE_Y,
|
||||
SIZE_Z,
|
||||
0,
|
||||
GL11.GL_RED,
|
||||
GL11.GL_FLOAT,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
(ByteBuffer) null
|
||||
);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL11.GL_TEXTURE_MIN_FILTER,
|
||||
GL11.GL_LINEAR
|
||||
);
|
||||
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL11.GL_TEXTURE_MAG_FILTER,
|
||||
GL11.GL_LINEAR
|
||||
);
|
||||
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT);
|
||||
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL12.GL_TEXTURE_WRAP_S,
|
||||
GL12.GL_REPEAT
|
||||
);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL12.GL_TEXTURE_WRAP_T,
|
||||
GL12.GL_CLAMP_TO_EDGE
|
||||
);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL12.GL_TEXTURE_WRAP_R,
|
||||
GL12.GL_REPEAT
|
||||
);
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0
|
||||
);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
|
||||
CHUNK_BUFFER.clear();
|
||||
|
||||
int size =
|
||||
CHUNK_SIZE *
|
||||
SIZE_Y *
|
||||
CHUNK_SIZE;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
CHUNK_BUFFER.put(0.0f);
|
||||
|
||||
CHUNK_BUFFER.put(ZEROS_ARRAY);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
texture
|
||||
);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||
|
||||
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||
|
||||
uploadChunk(
|
||||
chunkX,
|
||||
chunkZ,
|
||||
CHUNK_BUFFER
|
||||
);
|
||||
|
||||
uploadChunk(chunkX, chunkZ, CHUNK_BUFFER);
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0
|
||||
);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static void uploadChunk(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
FloatBuffer data
|
||||
) {
|
||||
public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
// Сбрасываем ВСЕ pixel store параметры, которые мог оставить Minecraft
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||
@@ -180,17 +99,14 @@ public final class FogWorldVolume {
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
|
||||
textureChunkX * CHUNK_SIZE,
|
||||
0,
|
||||
textureChunkZ * CHUNK_SIZE,
|
||||
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
|
||||
GL11.GL_RED,
|
||||
GL11.GL_FLOAT,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
|
||||
@@ -203,7 +119,7 @@ public final class FogWorldVolume {
|
||||
int localX,
|
||||
int localY,
|
||||
int localZ,
|
||||
float value
|
||||
byte value
|
||||
) {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
@@ -223,8 +139,8 @@ public final class FogWorldVolume {
|
||||
localY,
|
||||
textureChunkZ * CHUNK_SIZE + localZ,
|
||||
1, 1, 1,
|
||||
GL11.GL_RED,
|
||||
GL11.GL_FLOAT,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
SINGLE_VOXEL_BUFFER
|
||||
);
|
||||
|
||||
@@ -232,7 +148,7 @@ public final class FogWorldVolume {
|
||||
}
|
||||
|
||||
public static void uploadZeros(int textureChunkX, int textureChunkZ) {
|
||||
FloatBuffer buffer = chunkBuffer();
|
||||
ByteBuffer buffer = chunkBuffer();
|
||||
buffer.put(ZEROS_ARRAY);
|
||||
buffer.flip();
|
||||
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||
@@ -241,23 +157,20 @@ public final class FogWorldVolume {
|
||||
public static void uploadChunkFromArray(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
float[] data
|
||||
byte[] data
|
||||
) {
|
||||
FloatBuffer buffer = chunkBuffer();
|
||||
ByteBuffer buffer = chunkBuffer();
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||
}
|
||||
|
||||
public static FloatBuffer chunkBuffer() {
|
||||
|
||||
public static ByteBuffer chunkBuffer() {
|
||||
CHUNK_BUFFER.clear();
|
||||
|
||||
return CHUNK_BUFFER;
|
||||
}
|
||||
|
||||
public static int getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
uniform sampler2D DiffuseSampler;
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform sampler3D FogVolume;
|
||||
uniform usampler3D FogVolume;
|
||||
|
||||
uniform mat4 InverseProjection;
|
||||
uniform mat4 InverseView;
|
||||
@@ -23,7 +23,7 @@ out vec4 FragColor;
|
||||
const float FOG_SIZE_X = 256.0;
|
||||
const float FOG_SIZE_Z = FOG_SIZE_X;
|
||||
|
||||
const int MAX_STEPS = 256; // Уменьшили количество шагов благодаря адаптивности
|
||||
const int MAX_STEPS = 256;
|
||||
|
||||
// Высота мира
|
||||
const float WORLD_BOTTOM = -64.0;
|
||||
@@ -39,7 +39,7 @@ const float MAX_DISTANCE = (FOG_SIZE_X / 2.0) - 18.0;
|
||||
const float GAMMA = 2.0;
|
||||
|
||||
// Включение jittering для устранения banding артефактов
|
||||
const bool USE_JITTERING = true;
|
||||
const bool USE_JITTERING = false;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
@@ -103,7 +103,9 @@ float density(vec3 worldPos)
|
||||
// Псевдослучайное значение для jittering (детерминированное по UV)
|
||||
float random(vec2 st)
|
||||
{
|
||||
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
|
||||
vec3 p3 = fract(vec3(st.xyx) * 0.1031);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
// Функция нелинейного маппинга параметра t [0,1] в расстояние
|
||||
|
||||
Reference in New Issue
Block a user