even more raycasted lights tests

This commit is contained in:
DBotThePony 2022-09-16 19:54:41 +07:00
parent 5947252dc7
commit 08c2b5a685
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 211 additions and 63 deletions

View File

@ -1,8 +1,11 @@
package ru.dbotthepony.kstarbound.client
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.gl.BlendFunc
import ru.dbotthepony.kstarbound.client.gl.GLFrameBuffer
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quad
import ru.dbotthepony.kstarbound.client.gl.vertex.quadZ
@ -18,6 +21,8 @@ import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.roundToInt
@ -192,17 +197,92 @@ class ClientWorld(
}
*/
client.gl.quadWireframe {
for ((intensity, tpos) in rayLightCircleNaive(pos, 24.0, falloffByTravel = 1.5, falloffByTile = 4.0)) {
it.quad(
tpos.x.toFloat(),
tpos.y.toFloat(),
tpos.x + 1f,
tpos.y + 1f
)
val result = rayLightCircleNaive(pos, 48.0, falloffByTravel = 1.0, falloffByTile = 3.0)
val result2 = rayLightCircleNaive(pos + Vector2d(-8.0), 24.0, falloffByTravel = 1.0, falloffByTile = 3.0)
val frame = GLFrameBuffer(client.gl)
frame.attachTexture(client.viewportWidth, client.viewportHeight)
frame.bind()
client.gl.clearColor = Color.BLACK
glClear(GL_COLOR_BUFFER_BIT)
client.gl.blendFunc = BlendFunc.ADDITIVE
/*client.gl.quadColor {
for (row in 0 until result.rows) {
for (column in 0 until result.columns) {
if (result[column, row] > 0.05) {
val color = result[column, row].toFloat() * 1.5f
it.quad(
pos.x.roundToInt() - result.rows.toFloat() / 2f + row.toFloat(),
pos.y.roundToInt() - result.columns.toFloat() / 2f + column.toFloat(),
pos.x.roundToInt() - result.rows.toFloat() / 2f + row + 1f,
pos.y.roundToInt() - result.columns.toFloat() / 2f + column + 1f
) { a, b -> a.pushVec4f(color, color, color, 1f) }
}
}
}
}
client.gl.quadColor {
for (row in 0 until result2.rows) {
for (column in 0 until result2.columns) {
if (result2[column, row] > 0.05) {
val color = result2[column, row].toFloat() * 1.5f
it.quad(
pos.x.roundToInt() - 8f - result2.rows.toFloat() / 2f + row.toFloat(),
pos.y.roundToInt() - result2.columns.toFloat() / 2f + column.toFloat(),
pos.x.roundToInt() - 8f - result2.rows.toFloat() / 2f + row + 1f,
pos.y.roundToInt() - result2.columns.toFloat() / 2f + column + 1f
) { a, b -> a.pushVec4f(color, 0f, 0f, 1f) }
}
}
}
}*/
val lightTextureWidth = (client.viewportWidth / PIXELS_IN_STARBOUND_UNIT).roundToInt()
val lightTextureHeight = (client.viewportHeight / PIXELS_IN_STARBOUND_UNIT).roundToInt()
val textureBuffer = ByteBuffer.allocateDirect(lightTextureWidth * lightTextureHeight * 3)
textureBuffer.order(ByteOrder.LITTLE_ENDIAN)
for (x in 0 until result.columns.coerceAtMost(lightTextureWidth)) {
for (y in 0 until result.rows.coerceAtMost(lightTextureHeight)) {
textureBuffer.position(x * 3 + y * lightTextureWidth * 3)
if (result[x, y] > 0.05) {
val color = result[x, y].toFloat() * 1.5f
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
textureBuffer.put((color * 255).toInt().coerceAtMost(255).toByte())
}
}
}
frame.unbind()
val texture = GLTexture2D(client.gl)
textureBuffer.position(0)
texture.upload(GL_RGB, lightTextureWidth, lightTextureHeight, GL_RGB, GL_UNSIGNED_BYTE, textureBuffer)
texture.textureMinFilter = GL_LINEAR
texture.textureMagFilter = GL_LINEAR
client.gl.blendFunc = BlendFunc.MULTIPLY_BY_SRC
client.gl.activeTexture = 0
client.gl.texture2D = texture
client.gl.programs.viewTextureQuad.run()
client.gl.blendFunc = old
frame.close()
texture.close()
physics.debugDraw()

View File

@ -362,6 +362,7 @@ class StarboundClient : AutoCloseable {
if (frameRenderTime != 0.0 && Starbound.initialized)
world?.think(frameRenderTime)
gl.clearColor = Color.SLATE_GREY
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
gl.matrixStack.clear(viewportMatrixWorld.toMutableMatrix4f())

View File

@ -82,12 +82,11 @@ class GLFrameBuffer(val state: GLStateTracker) : AutoCloseable {
private set
override fun close() {
state.ensureSameThread()
if (!isValid)
return
cleanable.cleanManual()
texture?.close()
isValid = false
}
}

View File

@ -234,10 +234,15 @@ class GLStateTracker {
if (!cleanManual && LOGGER.isTraceEnabled)
LOGGER.trace("{} with ID {} was GC'd by JVM, without manually calling close()", name, nativeRef)
synchronized(cleanerHits) {
cleanerHits.add {
fn(nativeRef)
checkForGLError()
if (isSameThread()) {
fn(nativeRef)
checkForGLError()
} else {
synchronized(cleanerHits) {
cleanerHits.add {
fn(nativeRef)
checkForGLError()
}
}
}
}
@ -570,30 +575,12 @@ class GLStateTracker {
}
}
val flat2DQuads = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS, 1024)
}
}
val flat2DTexturedQuads = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VERTEX_TEXTURE, GeometryType.QUADS, 1024)
}
}
val flat2DQuadLines = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES, 1024)
}
}
val flat2DQuadWireframe = object : GLStreamBuilderList {
override val small by lazy {
return@lazy StreamVertexBuilder(this@GLStateTracker, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 1024)
}
}
val quadWireframe by lazy {
StreamVertexBuilder(this, GLAttributeList.VEC2F, GeometryType.QUADS_AS_LINES_WIREFRAME, 16384)
}
@ -617,6 +604,19 @@ class GLStateTracker {
builder.draw(GL_LINES)
}
inline fun quadColor(lambda: (StreamVertexBuilder) -> Unit) {
val builder = programs.flatColor.builder
builder.begin()
lambda.invoke(builder)
builder.upload()
programs.flatColor.use()
programs.flatColor.transform.set(matrixStack.last)
builder.draw(GL_TRIANGLES)
}
inline fun quadWireframe(value: AABB, color: Color = Color.WHITE, chain: (StreamVertexBuilder) -> Unit = {}) {
quadWireframe(color) {
it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat())

View File

@ -191,9 +191,37 @@ class GLTextureQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "sc
}
}
class GLTextureBlurredQuadProgram(state: GLStateTracker) : GLInternalProgram(state, "screen_quad_tex_blur") {
init {
link()
}
val texture = this["texture0"]!!
private val builder by lazy {
val builder = StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1)
builder.begin()
builder.quad(-1f, -1f, 1f, 1f, QuadTransformers.uv())
builder.upload()
builder
}
fun run(texture: Int = 0) {
use()
this.texture.set(texture)
builder.draw()
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC2F).build()
}
}
class GLFlatProgram(state: GLStateTracker, vararg shaders: GLShader) : GLTransformableColorableProgram(state, *shaders) {
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 1024)
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 2048)
}
companion object {
@ -201,15 +229,33 @@ class GLFlatProgram(state: GLStateTracker, vararg shaders: GLShader) : GLTransfo
}
}
class GLFlatColorProgram(state: GLStateTracker) : GLInternalProgram(state, "flat_color") {
init {
link()
}
val transform = this["transform"]!!
val builder by lazy {
StreamVertexBuilder(state, FORMAT, GeometryType.QUADS, 16384)
}
companion object {
val FORMAT = GLAttributeList.Builder().push(GLType.VEC2F).push(GLType.VEC4F).build()
}
}
class GLPrograms(val state: GLStateTracker) {
val tile by SimpleProgram("tile", ::GLTransformableColorableProgram)
val font by SimpleProgram("font", ::GLTransformableColorableProgram)
val flat by SimpleProgram("flat", ::GLFlatProgram)
val flatColor by lazy { GLFlatColorProgram(state) }
val liquid by lazy { GLLiquidProgram(state) }
val light by lazy { GLLightProgram(state) }
val hardLightGeometry by lazy { GLHardLightGeometryProgram(state) }
val softLightGeometry by lazy { GLSoftLightGeometryProgram(state) }
val colorQuad by lazy { GLColorQuadProgram(state) }
val textureQuad by lazy { GLTextureQuadProgram(state) }
val viewColorQuad by lazy { GLColorQuadProgram(state) }
val viewTextureQuad by lazy { GLTextureQuadProgram(state) }
val viewTextureBlurQuad by lazy { GLTextureBlurredQuadProgram(state) }
}

View File

@ -98,7 +98,7 @@ class GPULightRenderer(val state: GLStateTracker) {
state.activeTexture = 0
state.texture2D = outputTexture
state.blendFunc = BlendFunc.ADDITIVE
state.programs.textureQuad.run(0)
state.programs.viewTextureQuad.run(0)
} finally {
state.blendFunc = oldFunc
}
@ -114,7 +114,7 @@ class GPULightRenderer(val state: GLStateTracker) {
state.activeTexture = 0
state.texture2D = outputTexture
state.blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
state.programs.textureQuad.run(0)
state.programs.viewTextureQuad.run(0)
} finally {
state.blendFunc = oldFunc
}
@ -129,7 +129,7 @@ class GPULightRenderer(val state: GLStateTracker) {
state.blendFunc = BlendFunc.ADDITIVE
state.activeTexture = 0
state.texture2D = framebufferRender.texture
state.programs.textureQuad.run(0)
state.programs.viewTextureQuad.run(0)
} finally {
state.blendFunc = oldFunc
framebufferAccumulator.unbind()

View File

@ -17,11 +17,14 @@ import ru.dbotthepony.kstarbound.util.Timer
import ru.dbotthepony.kstarbound.world.entities.CollisionResolution
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController
import ru.dbotthepony.kvector.narray.Double2Dimensional
import ru.dbotthepony.kvector.narray.Int2Dimensional
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.util2d.AABBi
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.LinkedList
import java.util.function.DoubleBinaryOperator
import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.cos
@ -67,10 +70,10 @@ private val veryPreciseFan by lazy { makeDirFan(0.25) }
private fun chooseLightRayFan(size: Double): List<Vector2d> {
return when (size) {
in 0.0 .. 8.0 -> potatoDirFan
in 8.0 .. 12.0 -> veryRoughDirFan
in 12.0 .. 18.0 -> roughDirFan
in 18.0 .. 24.0 -> dirFan
in 24.0 .. 32.0 -> preciseFan
in 8.0 .. 16.0 -> veryRoughDirFan
in 16.0 .. 24.0 -> roughDirFan
in 24.0 .. 48.0 -> dirFan
in 48.0 .. 96.0 -> preciseFan
// in 32.0 .. 48.0 -> veryPreciseFan
else -> veryPreciseFan
}
@ -399,7 +402,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
val dir = rayEnd - rayStart
val inc = 0.5 / dir.length
val tiles = ArrayList<Pair<Vector2i, ITileState>>()
val tiles = LinkedList<Pair<Vector2i, ITileState>>()
var prev = Vector2i(Int.MIN_VALUE, Int.MAX_VALUE)
var hitTile: Pair<Vector2i, ITileState>? = null
@ -482,37 +485,33 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
return result
}
/**
* Трассирует лучи света вокруг себя с заданной позицией, интенсивностью,
* падением интенсивности за проход сквозь тайл [falloffByTile] и
* падением интенсивности за проход по пустому месту [falloffByTravel].
*/
fun rayLightCircleNaive(
position: Vector2d,
intensity: Double,
falloffByTile: Double = 2.0,
falloffByTravel: Double = 2.0,
): List<Pair<Double, Vector2i>> {
val result = Object2DoubleAVLTreeMap<Vector2i> { a, b ->
val cmp = a.x.compareTo(b.x)
falloffByTravel: Double = 1.0,
): Double2Dimensional {
val combinedResult = Double2Dimensional(intensity.roundToInt() * 2, intensity.roundToInt() * 2)
val baselineX = position.x.roundToInt() - intensity.roundToInt()
val baselineY = position.y.roundToInt() - intensity.roundToInt()
if (cmp != 0) {
return@Object2DoubleAVLTreeMap cmp
}
val dirs = chooseLightRayFan(intensity)
val mul = 1.0 / dirs.size
return@Object2DoubleAVLTreeMap a.y.compareTo(b.y)
}
result.defaultReturnValue(-1.0)
for (dir in chooseLightRayFan(intensity)) {
for (dir in dirs) {
val result2 = rayLightNaive(position, dir, intensity, falloffByTile, falloffByTravel)
for (pair in result2) {
val existing = result.getDouble(pair.second)
if (existing < pair.first) {
result.put(pair.second, pair.first)
}
combinedResult[pair.second.y - baselineY, pair.second.x - baselineX] += pair.first * mul
}
}
return result.map { it.value to it.key }
return combinedResult
}
}
@ -560,7 +559,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
intensity: Double,
falloffByTile: Double = 2.0,
falloffByTravel: Double = 2.0,
): List<Pair<Double, Vector2i>> {
): Double2Dimensional {
return CachedGetter().rayLightCircleNaive(position, intensity, falloffByTile, falloffByTravel)
}

View File

@ -0,0 +1,10 @@
#version 460
in vec4 finalVertexColor;
out vec4 color;
void main() {
color = finalVertexColor;
}

View File

@ -0,0 +1,13 @@
#version 460
layout (location = 0) in vec2 vertexPos;
layout (location = 1) in vec4 vertexColor;
uniform mat4 transform;
out vec4 finalVertexColor;
void main() {
gl_Position = transform * vec4(vertexPos, 0.5, 1.0);
finalVertexColor = vertexColor;
}