Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: atmosphere shader #594

Merged
merged 34 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0d271c9
Atmosphere Shader: init commit, still not functional
smlpt Aug 14, 2023
dc63bf0
Atmosphere Shader: fix shader code; works now
smlpt Aug 14, 2023
6642cf6
Atmosphere Shader: added AtmosphereExample
smlpt Aug 14, 2023
462c24a
Atmosphere Shader: starting to use ShaderProperty
smlpt Aug 17, 2023
041f10b
Atmosphere: outsourced the code from the example into a class.
smlpt Aug 17, 2023
e4ddc94
Atmosphere Example: add click behavior to set sun position
smlpt Aug 18, 2023
0eafddf
Deferred Lighting: trying to implement emission material properties (…
smlpt Aug 21, 2023
da2e0f3
Atmosphere, MouseDragSphere: add emissive pointlight shaderproperty, …
smlpt Aug 25, 2023
e8bc1b7
Atmosphere: fix drag behavior, introduce getSunPosFromTime function
smlpt Aug 28, 2023
ccd3897
Atmosphere: working time-based sun elevation and optional VR mode
smlpt Aug 29, 2023
318d203
Atmosphere, DeferredLighting: create Emission framebuffer and materia…
smlpt Sep 5, 2023
2a1ea42
Atmosphere, DeferredLighting: fix emission framebuffer, introduce sky…
smlpt Sep 7, 2023
a0156d0
Material: include emissiveStrength in vec4 emissive attribute
smlpt Sep 8, 2023
9d6209d
PointLight: Fix culling mode visual bug
smlpt Sep 11, 2023
c020842
Atmosphere: minor cleanup
smlpt Sep 11, 2023
4f7d336
Revert "PointLight: Fix culling mode visual bug"
smlpt Sep 12, 2023
0e1d5ed
PointLight: fix light overlap artifact
smlpt Sep 12, 2023
4f2c443
Atmosphere: introduce sunProxy vertex shader to remove its translatio…
smlpt Sep 12, 2023
2d5e9bb
AtmosphereExample: attempts to parent sunproxy to cam
smlpt Sep 13, 2023
691a54c
Atmosphere: add arrow keybind for sun movement (still broken)
smlpt Sep 14, 2023
5337da3
Atmosphere: implement arrow keybinds and sun azimuth calculation
smlpt Sep 15, 2023
f58932a
Atmosphere: fix sun direction calculation, add coroutine
smlpt Sep 18, 2023
8667dde
Atmosphere: introduce directional sunlight
smlpt Sep 18, 2023
6d33840
GLFWMouseAndKeyHandler: Fix cursor key mapping from GLFW to AWT
smlpt Sep 20, 2023
1672de3
DSSDO: fix emission descriptor sets, Atmosphere: add fine-grained sun…
smlpt Sep 20, 2023
e24d198
Atmosphere: code cleanup
smlpt Oct 4, 2023
a5b6ec8
DeferredShadingStereo.yml: Fix attachment order
smlpt Nov 7, 2023
c601ddd
SSAO.frag: add InputEmission attachment
smlpt Nov 7, 2023
0fd7d29
VulkanRenderer: fix error on empty settings entry
smlpt Nov 7, 2023
ea66f65
DeferredShadingGlitchy: add Emission attachment
smlpt Nov 13, 2023
3705a8a
AtmosphereExample: add T1 head object
smlpt Dec 5, 2023
26f3a9f
Code cleanup for PR, deleted sunProxy.vert, add documentation
smlpt Feb 6, 2024
ce59813
DeferredLighting.frag: remove commented code
smlpt Feb 6, 2024
c55bfb0
DeferredLighting.frag: remove another commented code
smlpt Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/main/kotlin/graphics/scenery/PointLight.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import org.joml.Vector4f
* @author Ulrik Günther <[email protected]>
* @constructor Creates a PointLight with default settings, e.g. white emission color.
*/
class PointLight(val radius: Float = 5.0f) : Light("PointLight") {
private var proxySphere = Sphere(radius * 1.1f, 10)
open class PointLight(val radius: Float = 5.0f) : Light("PointLight") {
private var proxySphere = Sphere(radius * 2.0f, 10)
/** The intensity of the point light. Bound to [0.0, 1.0] if using non-HDR rendering. */
@ShaderProperty
override var intensity: Float = 1.0f
Expand All @@ -28,6 +28,12 @@ class PointLight(val radius: Float = 5.0f) : Light("PointLight") {
@ShaderProperty
override val lightType: LightType = LightType.PointLight

/** Introduces self-lighting emission for the parent objects material.
* This attribute should only be used when the [PointLight] acts as a proxy for the corresponding @ShaderProperty. */
//@ShaderProperty
//var emissive: Float = 1f
//TODO Remove this part
smlpt marked this conversation as resolved.
Show resolved Hide resolved

/** Maximum radius in world units */
@Suppress("unused") // will be serialised into ShaderProperty buffer
@ShaderProperty var lightRadius: Float = radius
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import graphics.scenery.net.Networkable
import graphics.scenery.textures.Texture
import graphics.scenery.utils.TimestampedConcurrentHashMap
import org.joml.Vector3f
import org.joml.Vector4f
import kotlin.reflect.KClass

open class DefaultMaterial : Material, Networkable {
Expand Down Expand Up @@ -35,6 +36,11 @@ open class DefaultMaterial : Material, Networkable {
field = value
updateModifiedAt()
}
override var emissive: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 0.0f)
set(value) {
field = value
updateModifiedAt()
}
override var blending: Blending = Blending()
@Volatile
@Transient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import graphics.scenery.attribute.material.Material.CullingMode.*
import graphics.scenery.textures.Texture
import graphics.scenery.utils.TimestampedConcurrentHashMap
import org.joml.Vector3f
import org.joml.Vector4f

/**
* Material interface, storing material colors, textures, opacity properties, etc.
Expand Down Expand Up @@ -37,6 +38,8 @@ interface Material {
var roughness: Float
/** Metallicity, 0.0 is non-metal, 1.0 is full metal */
var metallic: Float
/** Emission of the material and corresponding strength */
var emissive: Vector4f

/** Blending settings for this material. See [Blending]. */
var blending: Blending
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class RenderConfigReader {
*/
data class RendertargetConfig(
@JsonDeserialize(using = JsonDeserialisers.FloatPairDeserializer::class) var size: Pair<Float, Float> = Pair(1.0f, 1.0f),
val attachments: Map<String, TargetFormat> = emptyMap()
val attachments: LinkedHashMap<String, TargetFormat> = LinkedHashMap()
)

data class RendertargetBinding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,7 @@ open class VulkanRenderer(hub: Hub,
add("Roughness", { node.materialOrNull()!!.roughness})
add("Metallic", { node.materialOrNull()!!.metallic})
add("Opacity", { node.materialOrNull()!!.blending.opacity })
add("Emissive", { node.materialOrNull()!!.emissive })

createUniformBuffer()
s.UBOs.put("MaterialProperties", materialPropertiesDescriptorSet.contents to this)
Expand Down Expand Up @@ -2378,7 +2379,7 @@ open class VulkanRenderer(hub: Hub,
fun setConfigSetting(key: String, value: Any) {
val setting = "Renderer.$key"

logger.debug("Setting $setting: ${settings.get<Any>(setting)} -> $value")
logger.debug("Setting {}: {} -> {}", setting, settings.getOrNull<Any>(setting), value)
settings.set(setting, value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,21 @@ open class GLFWMouseAndKeyHandler(var hub: Hub?) : MouseAndKeyHandlerBase(), Aut
else -> KeyEvent.KEY_PRESSED
}

// Fix cursor key mapping
val mappedKey = when(key) {
GLFW_KEY_UP -> KeyEvent.VK_UP
GLFW_KEY_DOWN -> KeyEvent.VK_DOWN
GLFW_KEY_LEFT -> KeyEvent.VK_LEFT
GLFW_KEY_RIGHT -> KeyEvent.VK_RIGHT
else -> key
}

val event = KeyEvent(
fakeComponent,
type,
System.nanoTime(),
mods.glfwToSwingMods(),
key,
mappedKey,
KeyEvent.CHAR_UNDEFINED
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import graphics.scenery.utils.lazyLogger
import graphics.scenery.utils.extensions.minus
import graphics.scenery.utils.extensions.plus
import graphics.scenery.utils.extensions.times
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector3f
import org.scijava.ui.behaviour.DragBehaviour
import kotlin.math.atan2

/**
* Drag nodes roughly along a sphere around the camera by mouse.
Expand All @@ -20,7 +23,8 @@ open class MouseDragSphere(
protected val name: String,
smlpt marked this conversation as resolved.
Show resolved Hide resolved
camera: () -> Camera?,
protected var debugRaycast: Boolean = false,
var filter: (Node) -> Boolean
var filter: (Node) -> Boolean,
private var rotateAroundCenter: Boolean = false,
) : DragBehaviour, WithCameraDelegateBase(camera) {

protected val logger by lazyLogger()
Expand All @@ -33,9 +37,10 @@ open class MouseDragSphere(
name: String,
camera: () -> Camera?,
debugRaycast: Boolean = false,
ignoredObjects: List<Class<*>> = listOf<Class<*>>(BoundingGrid::class.java)
ignoredObjects: List<Class<*>> = listOf<Class<*>>(BoundingGrid::class.java),
rotateAroundCenter: Boolean = false
) : this(name, camera, debugRaycast, { n: Node ->
!ignoredObjects.any { it.isAssignableFrom(n.javaClass) }})
!ignoredObjects.any { it.isAssignableFrom(n.javaClass) }}, rotateAroundCenter)


override fun init(x: Int, y: Int) {
smlpt marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -60,12 +65,21 @@ open class MouseDragSphere(
val (rayStart, rayDir) = cam.screenPointToRay(x, y)
rayDir.normalize()
val newHit = rayStart + rayDir * distance

val movement = newHit - currentHit

it.ifSpatial {
val newPos = position + movement / worldScale()
val newPos = if (rotateAroundCenter) {
// Calculate the rotation around (0, 0, 0)
val currentPos = position / worldScale()
val axis = currentPos.cross(movement, Vector3f()).normalize()
val angle = atan2(movement.length(), currentPos.length())//currentPos.angle(center)
val rotationQuaternion = Quaternionf().identity().rotateAxis(angle, axis)

rotationQuaternion.transform(currentPos, Vector3f())
} else {
// Rotation around camera's center
position + movement / worldScale()
}
currentNode?.spatialOrNull()?.position = newPos
currentHit = newHit
}
Expand Down
135 changes: 135 additions & 0 deletions src/main/kotlin/graphics/scenery/primitives/Atmosphere.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package graphics.scenery.primitives

import graphics.scenery.*
import graphics.scenery.attribute.material.Material
import graphics.scenery.controls.InputHandler
import kotlinx.coroutines.*
import org.joml.Quaternionf
import org.joml.Vector3f
import org.joml.Vector4f
import org.scijava.ui.behaviour.ClickBehaviour
import java.lang.Math.toRadians
import java.time.LocalDateTime
import kotlin.collections.HashMap
import kotlin.math.*

/**
* Implementation of a Nishita sky shader, applied to an [Icosphere] that wraps around the scene as a skybox.
* The shader code is ported from Rye Terrells [repository](https://github.com/wwwtyro/glsl-atmosphere).
* To move the sun with arrow keybinds, attach the behaviours using the [attachRotateBehaviours] function.
* @param initSunDir [Vector3f] of the sun position. Defaults to sun elevation of the current local time.
* @param emissionStrength Emission strength of the atmosphere shader. Defaults to 0.3f.
* @param latitude Latitude of the user; needed to calculate the local sun position. Defaults to 50.0, which is central Germany.
*/
open class Atmosphere(initSunDir: Vector3f? = null, emissionStrength: Float = 0.3f, var latitude: Double = 50.0) :
Icosphere(10f, 2, insideNormals = true) {

@ShaderProperty
var sunDir: Vector3f

private var sunDirectionManual: Boolean = false

init {
this.name = "Atmosphere"
setMaterial(ShaderMaterial.fromClass(this::class.java))
material {
cullingMode = Material.CullingMode.Front
depthTest = Material.DepthTest.LessEqual
emissive = Vector4f(0f, 0f, 0f, emissionStrength)
}

// Only use time-based elevation when the formal parameter is empty
if (initSunDir == null) {
sunDir = getSunDirFromTime()
}
else {
sunDir = initSunDir
sunDirectionManual = true
}

// Spawn a coroutine to update the sun direction
val job = CoroutineScope(Dispatchers.Default).launch {
while (!sunDirectionManual) {
sunDir = getSunDirFromTime()
// Wait 30 seconds
delay(30 * 1000)
}
}
}

/** Turn the current local time into a sun elevation angle, encoded as cartesian [Vector3f].
* @param localTime local time parameter, defaults to [LocalDateTime.now].
*/
private fun getSunDirFromTime(localTime: LocalDateTime = LocalDateTime.now()): Vector3f {
val latitudeRad = toRadians(latitude)
val dayOfYear = localTime.dayOfYear.toDouble()
val declination = toRadians(-23.45 * cos(360.0 / 365.0 * (dayOfYear + 10)))
val hourAngle = toRadians((localTime.hour + localTime.minute / 60.0 - 12) * 15)

val elevation = asin(
sin(toRadians(declination))
* sin(latitudeRad)
+ cos(declination)
* cos(latitudeRad)
* cos(hourAngle)
)

val azimuth = atan2(
sin(hourAngle),
cos(hourAngle) * sin(latitudeRad) - tan(declination) * cos(latitudeRad)
) - PI / 2

val result = Vector3f(
cos(azimuth).toFloat(),
sin(elevation).toFloat(),
sin(azimuth).toFloat()
)
logger.debug("Updated sun direction to {}.", result)
return result
}

/** Move the shader sun in increments by passing a direction and optionally an increment value.
* @param arrowKey The direction to be passed as [String].
* */
private fun moveSun(arrowKey: String, increment: Float) {
// Indicate that the user switched to manual sun direction controls
if (!sunDirectionManual) {
sunDirectionManual = true
logger.info("Switched to manual sun direction.")
}
// Define a HashMap to map arrow key dimension strings to rotation angles and axes
val arrowKeyMappings = HashMap<String, Pair<Float, Vector3f>>()
arrowKeyMappings["UP"] = Pair(increment, Vector3f(1f, 0f, 0f))
arrowKeyMappings["DOWN"] = Pair(-increment, Vector3f(1f, 0f, 0f))
arrowKeyMappings["LEFT"] = Pair(increment, Vector3f(0f, 1f, 0f))
arrowKeyMappings["RIGHT"] = Pair(-increment, Vector3f(0f, 1f, 0f))

val mapping = arrowKeyMappings[arrowKey]
if (mapping != null) {
val (angle, axis) = mapping
val rotation = Quaternionf().rotationAxis(toRadians(angle.toDouble()).toFloat(), axis.x, axis.y, axis.z)
sunDir.rotate(rotation)
}
}

/** Attach Up, Down, Left, Right key mappings to the inputhandler to rotate the sun in increments.
* Keybinds are Ctrl + cursor keys for fast movement and Ctrl + Shift + cursor keys for slow movement.
* @param increment Increment value for the rotation in degrees, defaults to 20°. Slow movement is always 10% of [increment]. */
fun attachRotateBehaviours(inputHandler: InputHandler, increment: Float = 20f) {
val incMap = mapOf(
"fast" to increment,
"slow" to increment / 10
)
for (speed in listOf("fast", "slow")) {
for (direction in listOf("UP", "DOWN", "LEFT", "RIGHT")) {
val clickBehaviour = ClickBehaviour { _, _ -> incMap[speed]?.let { moveSun(direction, it) } }
val bindingName = "move_sun_${direction}_$speed"
val bindingKey = if (speed == "slow") "ctrl shift $direction" else "ctrl $direction"
logger.debug("Attaching behaviour $bindingName to key $direction")
inputHandler.addBehaviour(bindingName, clickBehaviour)
inputHandler.addKeyBinding(bindingName, bindingKey)
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ rendertargets:
attachments:
NormalsMaterial: RGBA_Float16
DiffuseAlbedo: RGBA_UInt8
Emission: RGBA_Float16
ZBuffer: Depth32
ForwardBuffer:
attachments:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ rendertargets:
attachments:
NormalsMaterial: RGBA_Float16
DiffuseAlbedo: RGBA_UInt8
Emission: RGBA_Float16
ZBuffer: Depth32
ForwardBuffer:
attachments:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rendertargets:
attachments:
NormalsMaterial: RGBA_Float16
DiffuseAlbedo: RGBA_UInt8
Emission: RGBA_Float16
ZBuffer: Depth32
ForwardBuffer:
attachments:
Expand Down
Loading